深入浅出Pandas读书笔记
C6 Pandas分组聚合
6.1 概述
6.1.1 原理
6.1.2 groupby语法
df.groupby()可以按指定字段对DataFrame进行分组, 生成一个分组器对象
各个参数的意义
- by: 代表分组的依据和方法. 如果by是一个函数, 则会在数据的索引的每个值去调用它, 从而产生值, 按这些值进行分组. 如果传递dict或Series, 则将使用dict或Series的值来确定组, 如果传递ndarray, 则按原样使用这些值来确定组. 传入字典, 键为原索引名, 值为分组名.
- axis
- level: 如果轴是多层索引, 则按一个或多个特定的层级进行拆分, 支持数字, 层名及序列
- as_index: 默认返回带有组标签的对象作为索引
- sort: 是否对分组进行排序, 传False会让分组数据中第一个出现的值在前, 同时会提高分组性能
- group_keys
- obserbed:
- dropna: 默认会删除NA值
DataFrame返回DataFrameGroupBy对象, Series返回SeriesGroupBy对象
6.1.3 DataFrame应用分组
df.groupby('team').sum()
# 对不同列采用不同的聚合方式
df.groupby('team').agg({'Q1': sum, 'Q2': 'count', 'Q3': 'mean', 'Q4': max})
# 用一列调用多个聚合方法
df.groupby('team').agg({'Q1': [sum, max, 'std']})
6.1.4 Series应用分组
对Series也可以使用分组聚合, 但先对来说场景叫少. 在下列中, df.Q1是一个Series, 他的分组依据是df.team. 根据groupby语法, 如果传入一个Series, 此Series与被分组数据的索引对齐后, 按Series的值进行分组
# 对Series df.Q1按team分组, 求和
df.Q1.groupby(df.team).sum()
6.2 分组
6.2.1 分组对象
df.groupby('team') # <pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000222810C9220>
df.Q1.groupby(df.team) # <pandas.core.groupby.generic.SeriesGroupBy object at 0x00000222810C9DF0>
6.2.2 按标签分组
grouped = df.groupby('col1')
grouped = df.groupby('col1', axis=1)
grouped = df.groupby(['col1', 'col2'])
# 可以使用get_group()查看分组对象单个分组内容
grouped.get_group('D')
6.2.3 表达式
通过行或列的表达式, 生成一个布尔数据的序列, 从而将数据分为True和False两组
df.groupby(lambda x: x%2==0).sum() # 传入表达式, 表达式会对index进行操作
df.groupby(df.index%2==0).sum() # 同上
# 按索引值是否>50分为两组
df.groupby(lambda x: x>50).sum()
df.groupby(df.index>50).sum()
# 按列名中是否含有Q分为两列
df.groupby(lambda x: 'Q' in x, axis=1).sum()
# 按姓名首字母
df.groupby(df.name.str[0]).sum()
# 按AB和其他分组
df.groupby(df.team.isin(['A', 'B'])).sum()
# 按姓名第一个字母和第二个字母
df.groupby([df.name.str[0], df.name.str[1]]).sum()
# 按日期和小时分组
df.groupby([df.time.date, df.time.hour]).sum()
6.2.4 函数分组
by参数可以调用一个函数来通过计算返回一个分组依据
# 从时间列time中提取年份来分组
df.groupby(df.time.apply(lambda x: x.year)).count()
# 按姓名首字母为元音, 辅音分组
(
df.groupby(df.name.str.lower().str[0].isin(list('aeiou'))).sum()
.rename(index={True: '元音', False: '辅音'})
)
# 方法二, 先定义方法, 再将df的name列设为index, 调用方法, 方法会针对index进行聚合
def get_letter_type(letter):
if letter[0].lower() in 'aeiou':
return '元音'
else:
return '辅音'
df.set_index('name').groupby(get_letter_type).sum()
6.2.5 多种方法混合
# 先按照name, 在按照name的首字母聚合
df.groupby(['team', df.name.apply(get_letter_type)]).sum()
6.2.6 用pipe调用分组方法
df.pipe(pd.DataFrame.groupby, 'team').sum()
6.2.7 分组器Grouper
6.2.8 索引
如果不想让分组字段作为索引, as_index=False
6.2.9 排序
groupby操作后分组字段会成为索引, 数据对索引进行排序, sort=False不想排序
6.3 分组对象的操作
6.3.1 选择分组
分组对象的groups方法会生成一个字典(Pandas定义的PrettyDict), 这个字典包含分组的名称和分组的内容索引列表, 可以使用点的.keys()提取分组名称
df.groupby('team').groups
df.groupby('team').groups.keys() # 查看分组名
# MultiIndex
df.groupby(['team', df.name.str[0]]).get_group(('B', 'A')) # 找到team是B, 首字母是A的记录
# groupby对象.indices
df.groupby('team').indices # 返回与predictDict相似的字典, key是分组条件, values是索引组成的array
df.groupby('team').indices['A'] # 选择A
6.3.2 迭代分组
grouped = df.groupby('team')
for i in grouped:
print(type(i))
for name, group in grouped:
print(name, group)
6.3.3 选择列
# 选择分组中的某一列
grouped.Q1
grouped['Q1']
grouped[['Q1', 'Q2']]
grouped[['Q1', 'Q2']].sum() # 对groupby后的小分组按0轴, 进行sum
6.3.4 应用函数apply()
分组对象使用apply()调用一个函数, 传入的是DataFrame, 返回一个经过函数计算后的DataFrame, Series或标量, 然后再把数组组合
df.groupby('team').apply(lambda x: x*2)
# 实现Hive SQL 中的collect_list函数功能, 即将分组中的一列输出为列表
df.groupby('team').apply(lambda x: x['name'].to_list()) # 将分组好的group对象选择name列, 将name这个series调用to_list(), 返回一个Series, 值是list
# 查看某个组
df.groupby('team').apply(lambda x: x['name'].to_list()).A # 相当于查看Series的某个值
# 查看每组成绩最高的三个
df.set_index('name').groupby('team').apply(lambda x: x.Q1.nlargest(3)) # 希望看到名字, 将name设为index, 对Q1进行nlargest取最大的三个值
df.set_index('name').groupby('team', group_keys=False).apply(lambda x: x.Q1.nlargest(3)) # 通过group_keys=False 不显示分组索引
# 传入一个Series, 映射系列不同的聚合统计方法
(
df.groupby('team')
.apply(lambda x: pd.Series({
'Q1_SUM': x['Q1'].sum(),
'Q2_min': x['Q2'].min()
}))
)
# 等同于, 相对来说agg看起来更方便
df.groupby('team').agg(Q1_sum=('Q1', 'sum'),
Q2_min=('Q2', 'min')
)
6.3.5 管道方法pipe()
类似于DataFrame的管道方法, 分组对象的管道方法是接收之前的分组对象, 将同组的所有数据应用在方法中, 最后返回的是经过函数处理的数据格式
df.groupby('team').pipe(lambda x: x.max() + x.min())
6.3.6 转换方法transform()
transform()类似于agg(), 但与agg()不同的是它返回的是和原始数据相同形状的DataFrame, 会将每个数据原来的值一一替换成统计后的值
df.groupby('team').transform(np.mean)
# Q1平均成绩大于60的组的所有成员
df[df.groupby('team').transform('mean').Q1 > 60]
6.3.7 筛选方法filter()
对组作为整体进行筛选, 如果满足条件, 则整个组会被显示, 经过计算返回一个布尔值, 不是布尔序列, 为真的dataFrame会被显示
# 分组后, 所有分组中有一个Q1>97
df.groupby('team').filter(lambda x: (x.Q1>97).any())
# 分组后, 所有分组按0轴聚合计算mean, 每列mean值都>=48
df.groupby('team').filter(lambda x: (x.mean()>=48).all())
# 分组中Q1的和>1060
df.groupby('team').filter(lambda x: x.Q1.sum()>1060)
6.3.8 其他功能
df.groupby('team').first() # 组内第一个值
df.groupby('team').last() # 组内最后一个值
df.groupby('team').ngroups # 有5个分组
df.groupby('team').ngroup() # 每个元素的分组序号
6.4 聚合统计
6.4.1 描述统计 describe()
6.4.2 统计函数
对分组对象直接使用统计函数, 对分组内的所有数据进行此计算, 最终以DataFrame形式显示数据
df.groupby('team').describe()
df.groupby('team').sum()
df.groupby('team').count()
df.groupby('team').max()
df.groupby('team').min()
df.groupby('team').size()
df.groupby('team').mean()
df.groupby('team').median() # 中位数
df.groupby('team').corr() # 分组后的相关性系数
# groupby没有求众数的方法
6.4.3 聚合方法agg()
df.groupby('team').agg(sum)
# 使用agg为了实现一个字段使用多种统计方法, 不同字段使用不同方法
df.groupby().agg([np.sum, np.mean, np.std])
df.groupby().agg('Q1': [min, max], 'Q2': sum)
# agg 可以指定新列的名字
df.groupby('team').agg(Q1_Mean=('Q1', 'mean'), Q2_Sum=('Q2', 'sum'))
# agg调用自定义函数
def maxmin(s):
return s.max() - s.min()
df.groupby('team').agg(maxmin=('Q1', lambda x: maxmin(x)))
6.4.4 时序重采样方法 resample()
# data
idx = pd.date_range('2020-1-1', periods=100, freq='T')
df2 = pd.DataFrame(data={'a': [0, 1]*50, 'b': 1, }, index=idx)
# 每20分钟聚合一次, 按照a
df2.groupby('a').resample('20T').sum()
6.4.5 组内头尾值 first(), last()
6.4.6 组内分位数 quantile()
df.groupby('team').quantile() # 同median()
6.4.7 组内差值 diff()
和DataFrame的diff()一样, 分组对象的diff()方法会在组内进行前后数据的差值计算, 并以原DataFrame形状返回数据
6.5 数据分箱 pd.cut(), pd.qcut()
pd.cut(): 根据指定分界点对连续数据进行分箱处理
pd.qcut(): 根据指定区间数量对连续数据进行等宽分箱处理, 每个区间中的数据量是相同的
6.5.1 定界分箱 pd.cut()
pd.cut(df.Q1, bins=[0, 60, 100]) # 返回分箱后的结果Series
pd.cut(df.Q1, bins=[0, 60, 100]).value_counts()
pd.cut(df.Q1, bins=[0, 60, 100], labels=['不及格', '及格'])
pd.cut(df.Q1, bins=[0, 60, 100], right=False) # 右闭
6.5.2 等宽分箱 pd.qcut()
pd.qcut(df.Q1, q=3)