分组
一.分组模式及其对象
分组的一般模式:
df.groupby(分组依据)[数据来源].使用操作
来看下面这列代码:
df = pd.read_csv('data/learn_pandas.csv')
df.groupby('Gender')['Height'].median()
根据df.groupby(分组依据)[数据来源].使用操作
可以很好的理解这段代码。
通过性别来统计身高的中位数。
二.分组依据的本质
上面的分组依据仅仅是单个,但如果需要面对多个分组依据的时候,仅仅需要再group by中传入相应列名构成的列表即可。
看这个例子:
df.groupby(['School', 'Gender'])['Height'].mean()
通过学校和性别来统计身高的平均数。
但是如果我们想要有选择性的(自定义)分组依据时,我们可以先写出分组条件,
condition = df.Weight > df.Weight.mean()
然后将分组条件传入group by中。
df.groupby(condition)['Height'].mean()
表示通过自定义的分组条件去统计身高的平均数。
group by对象
通过上面的代码,我们发现分组是使用Pandas中的group by对象进行操作,group by对象上定义了很多方法,也提供了一些方便的属性。
gb = df.groupby(['School', 'Grade'])
gb
通过ngroups属性,可以得到分组个数。
gb.ngroups
通过groups属性,可以返回从组名映射到组索引列表的字典
res = gb.groups
res.keys()
通过 get_group 方法可以直接获取所在组对应的行
当 size 作为 DataFrame 的属性时,返回的是表长乘以表宽的大小。
分组三大操作
分组的三大操作:聚合、变换和过滤
聚合函数
一.内置聚合函数
一些直接定义再group by对象中的聚合函数,因为它们的速度都经过内部的优化,建议优先考虑
max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod
当这些聚合函数传入的数据来源包含多个列时,将按照列进行迭代计算
gb = df.groupby('Gender')[['Height', 'Weight']]
gb.max()
agg方法
尽管group by对象中定义了很多简便的函数,但是仍有一些不足:
- 无法同时使用多个函数
- 无法对特定的列使用特定的聚合函数
- 无法使用自定义的聚合函数
- 无法直接对结果的列名在聚合前进行自定义命名
所以agg方法的诞生用来解决这些问题。
- 使用多个函数
当使用多个聚合函数时,把需要使用的内置聚合函数以列表的形式写成字符串传入
gb.agg(['sum', 'idxmax', 'skew'])
- 对特定的列使用特定的聚合函数
对于方法和列的特殊对应,可以通过构造字典传入 agg 中实现,其中字典以列名为键,以聚合字符串或字符串列表为值。
gb.agg({'Height':['mean','max'], 'Weight':'count'})
- 使用自定义函数
如果要使用自定义函数,需要注意传入函数的参数是之前数据源中的列,逐列进行计算
gb.agg(lambda x: x.mean()-x.min())
表示分组计算身高和体重的极差。
4. 聚合结果重命名
如果想要对聚合结果的列名进行重命名,只需要将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数。
gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])
另外需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字还是手误输错的内置函数字符串.
变换和过滤
一. 变换函数和transform方法
变换函数的返回值为同长度的序列,常用的内置变换函数是累计函数:
cumcount/cumsum/cumprod/cummax/cummin
,使用操作与聚合函数类似,不过结果仅仅是组内的累计操作。
gb.cummax().head()
如果需要自定义变换函数就要使用transfrom方法,被调用的自定义函数,其传入值为数据源的序列,与agg的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的DataFrame 。
对身高和体重进行分组标准化,减去组均值后除以组的标准差:
gb.transform(lambda x: (x-x.mean())/x.std()).head()
二.组索引和过滤
索引和过滤的区别:
groupby对象中定义了 filter 方法进行组的筛选,其中自定义函数的输入参数为数据源构成的DataFrame本身.
通过过滤得到表中容量大于100的组:
gb.filter(lambda x: x.shape[0] > 100).head()
跨列分组
1.apply的引入
事实上还有一种常见的分组场景,无法用前面介绍的任何一种方法处理,例如现在如下定义身体质量指数BMI:
其中体重和身高的单位分别为千克和米,需要分组计算组BMI的均值。
首先,这显然不是过滤操作,因此 filter 不符合要求;其次,返回的均值是标量而不是序列,因此 transform 不符合要求;最后,似乎使用 agg 函数能够处理,但是之前强调过聚合函数是逐列处理的,而不能够 多列数据同时处理 。由此,引出了 apply 函数来解决这一问题。
apply的使用
apply 的自定义函数传入参数与 filter 完全一致,不过filter允许返回布尔值。
除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame
但它们产生的数据框维数和多级索引的层数应当如何变化?下面举三组例子理解:
- 标量情况:结果得到的是 Series ,索引与 agg 的结果一致
gb = df.groupby(['Gender','Test_Number'])[['Height','Weight']]
gb.apply(lambda x: 0)
gb.apply(lambda x: [0, 0]) # 虽然是列表,但是作为返回值仍然看作标量
2.Series 情况:得到的是 DataFrame ,行索引与标量情况一致,列索引为 Series 的索引
gb.apply(lambda x: pd.Series([0,0],index=['a','b']))
- DataFrame 情况:得到的是 DataFrame ,行索引最内层在每个组原先 agg 的结果索引上,再加一层返回的 DataFrame 行索引,同时分组结果 DataFrame 的列索引和返回的 DataFrame 列索引一致。
gb.apply(lambda x: pd.DataFrame(np.ones((2,2)),
index = ['a','b'],
columns=pd.Index([('w','x'),('y','z')])))