从需求出发,循序渐进,由浅入深掌握pandas好用的 Group
在上一期中,我们介绍了分组的基本概念,并从需求出发引申出分组后的三种操作:聚合,变换和过滤。
上一期我们重点介绍了聚合的用法,(电梯直达:https://www.jianshu.com/p/aba2761f471b)
本期我们来看看后面的两个方法,变换和过滤。
-
Transform/变换方法
上一篇文章中提到分组之后使用agg进行聚合时,其过程遵循SAC原则,即Split-Apply-Combine(分割-应用-在拼接)。聚合后返回的DataFrame的行索引由分组依据的Unique值来决定。但是如果出现下面这种需求,我们还能用分组聚合来完成吗?
需求:假设下表为一个快餐连锁机构的华东区的按省份的月份销售额统计,现在要求增加一列计算每个店铺销售额在该省份的占比,即江苏的店在江苏省内比,不能用华东区的总量作为分母。
image.png
思考一下,这个需求有两个关键点:
第一,需要按省份分组统计总销售额用于计算每个店的组内销售额占比。
第二,计算之后的返回值需要和原DataFrame的长度一致,也就是要看到每个店铺的占比。
第一个需求,按照我们之前学习的分组聚合很容易得到,但是其返回值是一个按照组别聚合后的DataFrame,那么如何完成呢?
我们可以先聚合,将聚合后的DataFrame 和原数据合并在计算每个店的组内销售额占比:代码如下,
import pandas as pd
import numpy as np
df = pd.read_clipboard()
df_total = df.groupby('地区')['销售额/万'].sum().rename('地区总销售额').reset_index()
df_total
image.png
之后在将上面返回的DataFrame和原来的数据merger到一起,目的是获取该地区的总销售额。merger之后就简单了,直接增加一列计算组内销售额占比即可:
df = df.merge(df_total)
df['percent'] = df['销售额/万']/df['地区总销售额']
df.head(7)
image.png
虽然完成了任务,但是有没有更好的方法呢?当然有了,pandas之所以强大,就是因为里面内置很多种方法。因为组内统计比较这种需求属于高频需求,pandas里面用transform方法来搞定!
和聚合agg方法比起来,transform方法既能完成组内统计,又可以返回一个行列索引和数据源一致的Dataframe,代码如下:
df_new = pd.read_clipboard()
df_new['percent'] = df_new['销售额/万'] / df_new.groupby('地区')['销售额/万'].transform('sum')
df_new
image.png
上面的代码中利用了transform既能做组内统计,又可以返回一个数据源等长度的序列特性,将一开始介绍的方法中的merge步骤完美的避过了。
transform方法另一个常用的功能是组内特征值填充和使用组内特征值创建新的特征。这些功能在机器学习特征工程中经常使用。即通过原始数据创建新的特征(均值/最大值/计数等),但是这些特征需要按组别来填充,不可以使用全局的特征,这样是模型更加准确合理。下面分别说明。
假如有一张小学生年龄/身高/体重的表格,但是有缺省值,如下:
image.png
我们要按照组别完成两件事:
- 数据预处理任务,将缺失值按照年龄分组,使用组内均值填充
- 创建新的特征。使用组内的身高体重均值创建新的特征
1) 首先按照年龄分组,使用组内平均值填充缺失数据:
df['weight'] = df.groupby('age')['weight'].transform(lambda x: x.fillna(x.mean()))
df['height'] = df.groupby('age')['height'].transform(lambda x: x.fillna(x.mean()))
df
image.png
对比之前的数据,可以看到缺失值被组内均值填充了,这种填充方式要比使用全局的均值填充要合理。
2)按照年龄分组,创建组别的均值作为新的字段(特征)
for col in ['weight', 'height']:
df[col + '_mean'] = df.groupby('age')[col].transform('mean')
df
上面的代码中使用一个for循环,这个在需要处理的字段较多时,比较省力。
创建新的特征后结果如下:
image.png
以上两个方法在机器学习建模中经常使用(数据预处理和特征工程)
- 过滤/filter方法
在groupby对象中,定义了filter方法进行组级别的筛选。和之前我们说过索引类似。只不过索引是对行进行操作,将满足索引条件的行留下,不满足的丢弃
组过滤本质上是行过滤的推广。其操作时是对一个组的全体进行聚合统计,若其结果满足条件(返回True)则整个组会被保留,反之则会被过滤掉。最后将未被过滤的组中的元素其对应的行进行拼接形成一个Dataframe返回。
还是用上面的年龄身高数据举例,我们先按照年龄分组,查看每个组的组容量:
df.groupby('age').size()
>>>
age
8 6
9 6
10 4
dtype: int64
可以看到8岁和9岁有6位同学,而10岁只有4个同学。我们将组容量小于5的去掉
df.groupby('age').filter(lambda x: x.shape[0] > 5)
结果如下:
image.png好了,至此我们将groupby分组之后的三个常用操作过了一遍,您是或否都学会了呢?
参考: Joyful pandas, Datawhale 耿远昊