1.1 groupby语法
df.groupby()方法可以按指定字段对DataFrame进行分组,生成一个分组器对象。分组操作会按指定的规则对数据进行拆分,groupby完成的就是拆分工作。groupby也能对Series完成分组操作。各个参数的意义如下。
1.2 DataFrame应用分组
在下例中,我们对df按team进行了分组,然后对各分组求和:
# 按team分组对应列并相加
df.groupby('team').sum()
如果想对不同列采用不同的聚合计算方式,可以对分组对象使用agg方法:
# 对不同列使用不同的计算方法
df.groupby('team').agg({'Q1': sum, # 总和
'Q2': 'count', # 总数
'Q3':'mean', # 平均
'Q4': max}) # 最大值
还可以对同一列使用不同的计算方法:
# 对同一列使用不同的计算方法
df.groupby('team').agg({'Q1': [sum, 'std', max], # 使用三个方法
'Q2': 'count', # 总数
'Q3':'mean', # 平均
'Q4': max}) # 最大值
2.1 按标签分组
最简单的分组方法是指定DataFrame中的一列,按这列的去重数据分组。也可以指定多列,按这几列的排列组合去重进行分组。示例如下:
grouped = df.groupby('col') # 单列
grouped = df.groupby('col', axis='columns') # 按行
grouped = df.groupby(['col1', 'col2']) # 多列
可以使用get_group()查看分组对象单个分组的内容:
# 分组
grouped = df.groupby('team')
# 查看D组
grouped.get_group('D')
2.2 表达式
通过行和列的表达式,生成一个布尔数据的序列,从而将数据分为True和False两组。
# 索引值是否为偶数,分成两组
df.groupby(lambda x:x%2==0).sum()
df.groupby(df.index%2==0).sum() # 同上
以下为按索引值是否大于等于50为标准分为两组:
# 按索引是否大于或等于50分为True和False两组
df.groupby(lambda x:x>=50)
df.groupby(df.index>=50).sum() # 同上
用之前介绍的在查询中用到的筛选条件函数对数据进行分组:
# 按索引奇偶行分为True和False两组
df.groupby(df.index%2==0) # 同上例
# 按姓名首字母分组
df.groupby(df.name.str[0])
# 按A及B、其他团队分组
df.groupby(df.team.isin(['A','B']))
# 按姓名第一个字母和第二个字母分组
df.groupby([df.name.str[0], df.name.str[1]])
# 按日期和小时分组
df.groupby([df.time.date, df.time.hour])
2.3 函数分组
by参数可以调用一个函数来通过计算返回一个分组依据。假如我们有一个时间列,如果按年进行分组,可以简单使用lambda提取年份,如:
# 从时间列time中提取年份来分组
df.groupby(df.time.apply(lambda x:x.year)).count()
如果DataFrame和Series函数接收到的参数是数值,想传入其他列的值,可以与上例一样使用列的apply来调用。接下来,我们实现一个按姓名的首字母为元音、辅音分组的案例。
# 按姓名首字母为元音、辅音分组
def get_letter_type(letter):
if letter[0].lower() in 'aeiou':
return '元音'
else:
return '辅音'
# 使用函数
df.set_index('name').groupby(get_letter_type).sum()
2.4 多种方法混合
由于分组可以按多个依据,在同一次分组中可以混合使用不同的分组方法。下例中,我们先按team分组,接着调用函数按是否元音字母分组。
# 按team、姓名首字母是否为元音分组
df.groupby(['team', df.name.apply(get_letter_type)]).sum()
2.5 索引
groupby操作后分组字段会成为索引,如果不想让它成为索引,可以使用as_index=False进行设置:
df.groupby('team', as_index=False).sum()
2.6 排序
groupby操作后分组字段会成为索引,数据会对索引进行排序,如果不想排序,可以使用sort=False进行设置。不排序的情况下会按索引出现的顺序排列:
# 不对索引进行排序
df.groupby('team', sort=False).sum()
3 分组对象的操作
创建一个分组对象:
# 分组,为了方便案例介绍,删去name列,分组后全为数字
grouped = df.drop('name', axis=1).groupby('team')
# 应用聚合函数
grouped.sum()
3.1 选择分组
分组对象的groups方法会生成一个字典(其实是Pandas定义的PrettyDict),这个字典包含分组的名称和分组的内容索引列表,然后我们可以使用字典的.keys()方法取出分组名称:
# 查看分组内容
df.groupby('team').groups
# 查看分组名
df.groupby('team').groups.keys()
grouped.indices返回一个字典,其键为组名,值为本组索引的array格式,可以实现对单分组数据的选取:
# 获取分组字典数据
grouped.indices
# 选择A组
grouped.indices['A']
3.2 选择列
# 选择分组后的某一列
grouped.Q1grouped['Q1'] # 同上
# 选择多列
grouped[['Q1','Q2']]
# 对多列进行聚合计算
grouped[['Q1','Q2']].sum()
3.3 应用函数apply()
分组对象使用apply()调用一个函数,传入的是DataFrame,返回一个经过函数计算后的DataFrame、Series或标量,然后再把数据组合。
# 将所有元素乘以2
df.groupby('team').apply(lambda x: x*2)
调用函数,实现每组Q1成绩最高的前三个:
# 各组Q1(为参数)成绩最高的前三个
def first_3(df_, c):
return df_[c].sort_values(ascending=False).head(3)
# 调用函数
df.set_index('name').groupby('team').apply(first_3, 'Q1')
3.4 管道方法pipe()
类似于DataFrame的管道方法,分组对象的管道方法是接收之前的分组对象,将同组的所有数据应用在方法中,最后返回的是经过函数处理过的返回数据格式。
# 每组最大值和最小值之和
df.groupby('team').pipe(lambda x: x.max() + x.min())
# 定义了A组和B组平均值的差值
def mean_diff(x):
return x.get_group('A').mean()- x.get_group('B').mean()
# 使用函数
df.groupby('team').pipe(mean_diff)
3.5 转换方法transform()
transform()类似于agg(),但与agg()不同的是它返回的是一个与原始数据相同形状的DataFrame,会将每个数据原来的值一一替换成统计后的值。例如按组计算平均成绩,那么返回的新DataFrame中每个学生的成绩就是它所在组的平均成绩。
# 将所有数据替换成分组中的平均成绩
df.groupby('team').transform(np.mean)
使用函数时,分别传入每个分组的子DataFrame的每一列,经过计算后每列返回一个结果,然后再将每组的这列所有值都替换为此计算结果,最后以原DataFrame形式显示所有数据。以下是一些其他的使用方法。
df.groupby('team').transform(max) # 最大值
df.groupby('team').transform(np.std) # 标准差
# 使用函数,和上一个学生的差值(没有处理姓名列)
df.groupby('team').transform(lambda x: x.shift(-1))
# 函数
def score(gb):
return (gb - gb.mean()) / gb.std()*10
# 调用
grouped.transform(score)
可以用它来进行按组筛选:
# Q1成绩大于60的组的所有成员
df[df.groupby('team').transform('mean').Q1 > 60]
3.6 筛选方法filter()
使用filter()对组作为整体进行筛选,如果满足条件,则整个组会被显示。传入它调用函数中的默认变量为每个分组的DataFrame,经过计算,最终返回一个布尔值(不是布尔序列),为真的DataFrame全部显示。
我们来看这样一个需求,按团队分组,然后每组的每个季度成绩为本季度的平均分,全年的成绩又是这个季度平均分的平均分,最终需要筛选出团队中分数高于51的所有成员。
# 每组每个季度的平均分
df.groupby('team').mean()
# 每组4个季度的平均分的平均分为本组的总平均分
df.groupby('team').mean().mean(1)
# 筛选出所在组总平均分大于51的成员
df.groupby('team').filter(lambda x: x.mean(1).mean()>51)
其他的一些案例
# Q1成绩至少有一个大于97的组
df.groupby(['team']).filter(lambda x: (x['Q1'] > 97).any())# 所有成员平均成绩大于60的组df.groupby(['team']).filter(lambda x: (x.mean() >= 60).all())# Q1所有成员成绩之和超过106
0的组
df.groupby('team').filter(lambda g: g.Q1.sum() > 1060)