对应书本第三部分第6章Pandas分组聚合第2节
分组对象
前文讲到groupby方法最终输出的是一个分组对象,DataFrameGroupBy和SeriesGroupBy都是分组对象,它们分别由DataFrame和Series应用groupby方法而产生。分组对象是聚合统计操作的基础。
# 生成了一个DataFrameGroupBy:
df.groupby('team')
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x7ff5
# 下生成了一个SeriesGroupBy:
df.Q1.groupby(df.team)
# <pandas.core.groupby.generic.SeriesGroupBy object at 0x7ff597fda160
按标签分组
最简单的分组方法是指定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')
'''
name team Q1 Q2 Q3 Q4
4 Oah D 65 49 61 86
8 Reddie D 64 93 57 72
21 Ethan D 79 45 89 88
23 Mason D 80 96 26 49
27 Finley D 62 73 84 68
44 Benjamin D 15 88 52 25
48 Louie D 24 84 54 11
49 Carter7 D 57 52 77 50
52 Bobby1 D 50 55 60 59
57 Albie1 D 79 82 56 96
59 Luca D 5 40 91 83
...
'''
表达式
通过行和列的表达式,生成一个布尔数据的序列,从而将数据分为True和False两组。
# 索引值是否为偶数,分成两组
df.groupby(lambda x:x%2==0).sum()
df.groupby(df.index%2==0).sum() # 同上
'''
Q1 Q2 Q3 Q4
False 2322 2449 2823 2699
True 2598 2806 2444 2579
'''
以下为按索引值是否大于等于50为标准分为两组:
# 按索引是否大于或等于50分为True和False两组
df.groupby(lambda x:x>=50)
df.groupby(df.index>=50).sum() # 同上
'''
Q1 Q2 Q3 Q4
False 2766 2628 2509 2613
True 2154 2627 2758 2665
'''
以下为按列名是否包含字母Q分成两列:
df.groupby(lambda x:'Q' in x, axis=1).sum()
'''
False True
0 LiverE 198
1 ArryC 167
2 AckA 219
3 EorgeC 338
4 OahD 261
.. ... ...
95 GabrielC 268
96 Austin7C 125
97 Lincoln4C 212
98 EliE 234
99 BenE 179
[100 rows x 2 columns]
'''
此外,我们还可以使用之前介绍的在查询中用到的筛选条件函数对数据进行分组:
# 按索引奇偶行分为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])
函数分组
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()
'''
Q1 Q2 Q3 Q4
元音 1462 1440 1410 1574
辅音 3458 3815 3857 3704
'''
多种方法混合
由于分组可以按多个依据,在同一次分组中可以混合使用不同的分组方法。下例中,我们先按team分组,接着调用函数按是否元音字母分组。
# 按team、姓名首字母是否为元音分组
df.groupby(['team', df.name.apply(get_letter_type)]).sum()
'''
Q1 Q2 Q3 Q4
team name
A 元音 274 197 141 199
辅音 792 442 734 584
B 元音 309 291 269 218
辅音 666 927 933 918
C 元音 473 488 453 464
辅音 583 706 615 663
D 元音 273 333 409 486
辅音 587 858 832 713
E 元音 133 131 138 207
辅音 830 882 743 826
'''
用pipe调用分组方法
可以调用一个函数对DataFrame进行处理,我们发现Pandas的groupby是一个函数——pd.DataFrame.groupby:
pd.DataFrame.groupby
# <function pandas.core.frame.DataFrame.groupby(self, by=None, axis=0, level=None,
as_index: bool = True, sort: bool = True, group_keys: bool = True, squeeze:
bool = <object object at 0x7ff5934290b0>, observed: bool = False, dropna: bool
= True) -> 'DataFrameGroupBy'>
# 使用pipe调用分组函数
df.pipe(pd.DataFrame.groupby, 'team').sum()
'''
Q1 Q2 Q3 Q4
team
A 1066 639 875 783
B 975 1218 1202 1136
C 1056 1194 1068 1127
D 860 1191 1241 1199
E 963 1013 881 1033
'''
我们将参数传入了pd.DataFrame.groupby,依此类推,可以传入更多参数,也能完成数据的分组聚合操作。
分组器Grouper
Pandas提供了一个分组器pd.Grouper(),它也能帮助我们完成数据分组的工作。有了分组器,我们可以复用分组工作。
# 分组器语法
pandas.Grouper(key=None, level=None, freq=None, axis=0, sort=False)
df.groupby(pd.Grouper('team'))
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x7ff5980a
以下为一些使用案例:
# df.groupby('team')
df.groupby(pd.Grouper('team')).sum()
# 如果是时间,可以60秒一分组
df.groupby(Grouper(key='date', freq='60s'))
# 轴方向
df.groupby(Grouper(level='date', freq='60s', axis=1))
# 按索引
df.groupby(pd.Grouper(level=1)).sum()
# 多列
df.groupby([pd.Grouper(freq='1M', key='Date'), 'Buyer']).sum()
df.groupby([pd.Grouper('dt', freq='D'),
pd.Grouper('other_column')
])
# 按轴层级
df.groupby([pd.Grouper(level='second'), 'A']).sum()
df.groupby([pd.Grouper(level=1), 'A']).sum()
# 按时间周期分组
df['column_name'] = pd.to_datetime(df['column_name'])
df.groupby(pd.Grouper(key='column_name', freq="M")).mean()
# 10年一个周期
df.groupby(pd.cut(df.date,
pd.date_range('1970', '2020', freq='10YS'),
right=False)
).mean()
索引
groupby操作后分组字段会成为索引,如果不想让它成为索引,可以使用as_index=False进行设置:
df.groupby('team', as_index=False).sum()
'''
team Q1 Q2 Q3 Q4
0 A 1066 639 875 783
1 B 975 1218 1202 1136
2 C 1056 1194 1068 1127
3 D 860 1191 1241 1199
4 E 963 1013 881 1033
# 索引变为0开始的了
排序
groupby操作后分组字段会成为索引,数据会对索引进行排序,如果不想排序,可以使用sort=False进行设置。不排序的情况下会按索引出现的顺序排列:
df.groupby('team', sort=False).sum()
'''
Q1 Q2 Q3 Q4
team
E 963 1013 881 1033
C 1056 1194 1068 1127
A 1066 639 875 783
D 860 1191 1241 1199
B 975 1218 1202 1136
'''