Pandas进阶 | 一步一步跟我学Python(六)
- 1.本系列基于新生大学课程《Python编程&数据科学入门》和公开的参考资料;
- 2.文章例子基于python3.6,使用Windows系统(除了安装,其余基本没有影响);
- 3.我是纯小白,所以,错误在所难免,体系会逐渐成熟,我会经常进行更新;如果您发现了错误,也烦请帮我指出来,在此先谢过了。
这一课拖了很久,但是再怎么困难,还是要补上。这一章将pandas的进阶用法:数据的分组聚合方法——groupby
主要内容有四个部分:
- groupby()内置函数方法
- groupby().agg()方法
- groupby().apply()方法
- 一个具体案例
对DataFrame表格数据进行深入处理和加工,最主要的方法是采用groupby方法。groupby是一种分组计算方法,它的运算过程分为三步:分组-应用-聚合。
如下图所示:
先按照某个或多个键进行分组,而后将这些分组分别进行相应的函数运算,最后将运算后的结果重新聚合为一个DataFrame。
基本用法:groupby()内置函数方法
我们先引入一个DataFrame数据:
import pandas as pd
iris_index = ['sepal_width','sepal_length','petal_width','petal_length','species']
iris = pd.read_csv('iris.csv',names = iris_index )
iris.head()
用info方法查看iris的基本信息:
iris.info
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
sepal_width 150 non-null float64
sepal_length 150 non-null float64
petal_width 150 non-null float64
petal_length 150 non-null float64
species 150 non-null object
dtypes: float64(4), object(1)
memory usage: 5.9+ KB
用value_counts可以按列查看统计信息:
iris.species.value_counts()
Iris-versicolor 50
Iris-setosa 50
Iris-virginica 50
Name: species, dtype: int64
groupby方法既可以按组统计,还能进行相应的计算:
iris.gropby('species).max()
得出按品质分组中,各组的最大值。输出结果为:
groupby之后的内置函数有很多运算函数:mean方法进行平均值运算,用min进行最小值运算,用count进行统计运算,等等。
- 注意,用groupby方法后,它只是进行了分组,是一个groupby对象,要完成分组运算,必须加上其后的函数。
s = iris.groupby('species')
type(s)
得出s的类型是:
pandas.core.groupby.DataFrameGroupBy
这是一个groupby对象。
- 可以通过分组,对其他的列进行运算
iris.groupby('species').sepal_width.mean()
得到花萼的平均值:
species
Iris-setosa 5.006
Iris-versicolor 5.936
Iris-virginica 6.588
Name: sepal_width, dtype: float64
用自定义函数进行分组运算:groupby().agg()
也就是将分组的groupby数据按照自定义的函数进行运算,而后返回一个标量值。
我们定义一个求最大与最小值差值的函数:
def range_iris(arr):
return arr.max() - arr.min()
而后使用groupby函数:
iris.groupby('species').agg(range_iris)
得到按照品种分类的iris表格:
sepal_width sepal_length petal_width petal_length
species
Iris-setosa 1.5 2.1 0.9 0.5
Iris-versicolor 2.1 1.4 2.1 0.8
Iris-virginica 3.0 1.6 2.4 1.1
用自定义函数进行非标量的分组运算:groupby().apply()
这种方法是更为一般的方法,可以将自定义函数的返回值不是标量值的函数。
比如我们要看看各个品种分别有多少花瓣的长度大于4cm。
采取的方法是:
先自定义一个比较的函数,返回这个值;而后根据品种进行分组运算。
def bigger_x(df, var, x=4.0):
return(df[df[var] > x])
iris.groupby('species').apply(bigger_x, var='sepal_width').size
得出的结果是:
305
一个具体的案例
加入我们要分析一个时间记录,我们先导入这个记录,看看这个记录长什么样。
import pandas as pd
import numpy as np
from datetime import datetime
# 导入1735和1736两周的数据
weeks = (1735,1736)
records = []
# 给各列确定索引值
record_index = ['item', 'period', 'start', 'end', 'memo']
for week in weeks:
path = 'record/report%d.csv' %week
record = pd.read_csv(path, names = record_index)
record.drop(record.index[0], inplace = True) #
删除一列的中文索引,便于计算
record['week']= week # 增加一列“周数”
records.append(record)
records = pd.concat(records, ignore_index = True) # 将两个表格拼接起来
records.info()
records.head()
从两个文件导入了两周的时间记录,取得结果如下:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 650 entries, 0 to 649
Data columns (total 6 columns):
item 650 non-null object
period 650 non-null object
start 650 non-null object
end 650 non-null object
memo 34 non-null object
week 650 non-null int64
dtypes: int64(1), object(5)
memory usage: 30.5+ KB
以及这个表格的前5行:
下面是引入时间函数,网上一搜就找到了,不懂的也不要紧,后面会讲到。
from dateutil.parser import parse
我们按项目对时间段period列进行聚合
records.groupby('item').period.sum()
输出这样的结果:
怎么会这样,原来,period列的格式不是数字,而是字符。可以回看之前的info内容。所以,聚合的结果就是这样……
怎样转换表格列的函数呢?
抓紧在网上找答案,网上有一个astype的方法,赶紧拿来用一用。
records['period'] = records['period'].astype('float')
records.dtypes
item object
period float64
start object
end object
memo object
week int64
date object
dtype: object
period列顺利转换成了float浮点数。
在搜索的同时,又找到其他的方法,实验一下。
就是把字符型转换为数字型,因为已经没有需要转换的数字的列了,所以想增加一列,而后转换。
records['start2'] = records['start']
pd.to_numeric(records['start2'], errors='coerce').head()
输出结果如下:
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
Name: start2, dtype: float64
刚刚定义的start2列变成了浮点型数字。
搞定!
现在就可以对period进行聚合运算了。
records.groupby('item').period.sum()
item
交通 14.8
休息扫除 27.7
写作 43.0
听讲座 2.4
坐享 3.0
娱乐 1.0
家庭 199.9
工作 176.7
晚阅读 1.1
杂务处理 55.2
用餐 0.4
电话沟通 0.3
睡眠 289.9
聚会 9.9
记录反思 21.0
财务 8.2
跑步 14.5
运动 8.7
阅读 20.0
Name: period, dtype: float64
另外,我们还可以按照两个列进行分组
records.groupby(['date','item'])['period'].sum()
咦,出现了一个错误?!
翻译过来就是“有一个无效的识别符”。程序不认识,那就是命令敲错了。
找了半天,原来是逗号用了中文,这种情况出现过好几次。一定要注意,抓紧改过来!
records.groupby(['date','item']).period.sum().head(20)
date item
2017-08-01 睡眠 6.3
2017-08-02 休息扫除 0.4
写作 1.2
坐享 0.3
家庭 1.2
工作 6.8
杂务处理 1.5
电话沟通 0.2
睡眠 6.7
记录反思 0.9
运动 1.0
阅读 2.6
2017-08-03 休息扫除 1.0
写作 1.5
坐享 0.2
家庭 0.7
工作 7.3
杂务处理 1.2
睡眠 7.0
记录反思 0.8
Name: period, dtype: float64
这是按日期和项目对时间段进行求和的结果。