Data Whale第20期组队学习 Pandas学习—分组
任何分组(groupby)操作都涉及原始对象的以下操作:分割对象、应用一个函数和结合的结果。
在许多情况下,任何分组(groupby)可以执行以下操作 :聚合 (计算汇总统计)、转换 (执行一些特定于组的操作)和过滤 (在某些情况下丢弃数据)。实现分组操作,必须明确分组依据 、 数据来源 、 操作及其返回结果等三个要素。
一、groupby的基础操作
import pandas as pd
import numpy as np
df=pd.DataFrame({'A': ['w', 'e', 'l', 'c', 'o', 'm', 'e', 'c'],
'B': [3, 8, 7, 4, 6, 2, 5, 1],
'C': [112, 198, 147, 154, 125, 187, 92, 123]})
print("df=",df)
# df= A B C
# 0 w 3 112
# 1 e 8 198
# 2 l 7 147
# 3 c 4 154
# 4 o 6 125
# 5 m 2 187
# 6 e 5 92
# 7 c 1 123
print("df.groupby('A').mean()=",df.groupby('A').mean()) # 按A列分组(groupby),获取其他列的均值
# df.groupby('A').mean()= B C
# A
# c 2.5 138.5
# e 6.5 145.0
# l 7.0 147.0
# m 2.0 187.0
# o 6.0 125.0
# w 3.0 112.0
print("df.groupby(['A','B']).mean()=",df.groupby(['A','B']).mean()) # 按多列分组(groupby),获取其他列的均值
# df.groupby(['A','B']).mean()= C
# A B
# c 1 123
# 4 154
# e 5 92
# 8 198
# l 7 147
# m 2 187
# o 6 125
# w 3 112
# 分组后,可以选取单列数据,或者多个列组成的列表(list)进行运算
df1=pd.DataFrame([[1,9,5,6,1],[2,7,8,7,3],[1,5,2,4,6],[3,6,9,4,1],[5,6,4,8,2]]
,columns=['C','H','I','N','A'])
print("df1=",df1)
# df1= C H I N A
# 0 1 9 5 6 1
# 1 2 7 8 7 3
# 2 1 5 2 4 6
# 3 3 6 9 4 1
# 4 5 6 4 8 2
x=df1.groupby("I")
print("x['H'].mean()=",x['H'].mean())
# x['H'].mean()= I
# 2 5
# 4 6
# 5 9
# 8 7
# 9 6
# Name: H, dtype: int64
print("x[['C','N']].mean()=",x[['C','N']].mean())
# x[['C','N']].mean()= C N
# I
# 2 1 4
# 4 5 8
# 5 1 6
# 8 2 7
# 9 3 4
print("x.agg({'C':'mean','A':'sum'})=",x.agg({'C':'mean','A':'sum'}))
# x.agg({'C':'mean','A':'sum'})= C A
# I
# 2 1 6
# 4 5 2
# 5 1 1
# 8 2 3
# 9 3 1
1.1 聚合方法size()和count()
size与count的区别: size计数包含Nan值,而count计数不包含Nan值。
df2 = pd.DataFrame({"Name":["Alice", "Bob", "Mallory", "Mallory", "Bob" , "Mallory"],
"City":["Seattle", "Seattle", "Portland", "Seattle", "Seattle", "Portland"],
"Val": [5, 7, 2, np.nan, np.nan, 9]})
print("df2=",df2)
# df2= Name City Val
# 0 Alice Seattle 5.0
# 1 Bob Seattle 7.0
# 2 Mallory Portland 2.0
# 3 Mallory Seattle NaN
# 4 Bob Seattle NaN
# 5 Mallory Portland 9.0
print("df2.groupby(['Name', 'City'],as_index=False)['Val'].count()=",df2.groupby(['Name', 'City'], as_index=False)['Val'].count())
# df2.groupby(['Name', 'City'],as_index=False)['Val'].count()= Name City Val
# 0 Alice Seattle 1
# 1 Bob Seattle 1
# 2 Mallory Portland 2
# 3 Mallory Seattle 0
print('df2.groupby(["Name", "City"])["Val"].size().reset_index(name="Size")=',
df2.groupby(["Name", "City"])['Val'].size().reset_index(name='Size'))
# df2.groupby(["Name", "City"])["Val"].size().reset_index(name="Size")= Name City Size
# 0 Alice Seattle 1
# 1 Bob Seattle 2
# 2 Mallory Portland 2
# 3 Mallory Seattle 1
二、聚合函数
2.1 内置聚合函数
直接定义在groupby对象的聚合函数有如下函数:
max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod。
df3 = pd.read_csv('D:/binchen/txzq/data/learn_pandas.csv')
print("df3.groupby('Gender')['Height'].median()=",
df3.groupby('Gender')['Height'].median())
# df3.groupby('Gender')['Height'].median()= Gender
# Female 159.6
# Male 173.4
# Name: Height, dtype: float64
condition = df3.Weight > df3.Weight.mean()
print("df3.groupby(condition)['Height'].mean()=",df3.groupby(condition)['Height'].mean())
# df3.groupby(condition)['Height'].mean()= Weight
# False 159.034646
# True 172.705357
# Name: Height, dtype: float64
item = np.random.choice(list('abc'), df3.shape[0])
print("df3.groupby(item)['Height'].mean()=",
df3.groupby(item)['Height'].mean())
# df3.groupby(item)['Height'].mean()= a 163.128571
# b 162.652000
# c 163.702857
# Name: Height, dtype: float64
gb = df3.groupby('Gender')['Height']
print("gb.idxmin()=",gb.idxmin())
# gb.idxmin()= Gender
# Female 143
# Male 199
# Name: Height, dtype: int64
print("gb.quantile(0.85)=",gb.quantile(0.85))
# gb.quantile(0.85)= Gender
# Female 164.57
# Male 179.55
# Name: Height, dtype: float64
print("gb.sum()=",gb.sum())
# gb.sum()= Gender
# Female 21014.0
# Male 8854.9
# Name: Height, dtype: float64
print("gb.prod()=",gb.prod())
# gb.prod()= Gender
# Female 4.232080e+290
# Male 1.594210e+114
# Name: Height, dtype: float64
print("gb.count()=",gb.count())
# gb.count()= Gender
# Female 132
# Male 51
# Name: Height, dtype: int64
# 这些聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算
gb1 = df3.groupby('Gender')[['Height', 'Weight']]
print("gb1.max()=",gb1.max())
# gb1.max()= Height Weight
# Gender
# Female 170.2 63.0
# Male 193.9 89.0
print("gb1.min()=",gb1.min())
# gb1.min()= Height Weight
# Gender
# Female 145.4 34.0
# Male 155.7 51.0
2.2 agg方法
即使在 groupby 对象上定义了许多方便的函数,但仍然有以下不便之处:
1)无法同时使用多个函数
2)无法对特定的列使用特定的聚合函数
3) 无法使用自定义的聚合函数
4) 无法直接对结果的列名在聚合前进行自定义命名
使用 agg 函数解决以上四类问题。
1、使用多个聚合函数
当使用多个聚合函数时,需要用列表的形式把内置聚合函数的对应的字符串传入,先前提到的所有字符串都是合法的。
print("gb1.agg(['sum','idxmax','skew','min','max','prod'])=",
gb1.agg(['sum','idxmax','skew','min','max','prod']))
# gb1.agg(['sum','idxmax','skew','min','max','prod'])= Height ... Weight
# sum idxmax skew min ... skew min max prod
# Gender ...
# Female 21014.0 28 -0.219253 145.4 ... -0.268482 34.0 63.0 3.039202e+226
# Male 8854.9 193 0.437535 155.7 ... -0.332393 51.0 89.0 2.540227e+100
#
# [2 rows x 12 columns]
# 从运行结果上看,列索引为多级索引,第一层为数据源,第二层为使用的聚合方法,分别逐一对列使用聚合,因此结果为12列。
2、对特定的列使用特定的聚合函数
对于方法和列的特殊对应,可以通过构造字典传入 agg 中实现,其中字典以列名为键,以聚合字符串或字符串列表为值。
print("gb1.agg({'Height':['mean','max'],'Weight':'count'})=",
gb1.agg({'Height':['mean','max'],'Weight':'count'}))
# gb1.agg({'Height':['mean','max'],'Weight':'count'})= Height Weight
# mean max count
# Gender
# Female 159.19697 170.2 135
# Male 173.62549 193.9 54
print("gb1.agg({'Height':['mean','max','sum','idxmax','skew','count'],'Weight':['mean','max','sum','idxmax','skew','count']})=",
gb1.agg({'Height':['mean','max','sum','idxmax','skew','count'],'Weight':['mean','max','sum','idxmax','skew','count']}))
# gb1.agg({'Height':['mean','max','sum','idxmax','skew','count'],'Weight':['mean','max','sum','idxmax','skew','count']})= Height ... Weight
# mean max sum idxmax ... sum idxmax skew count
# Gender ...
# Female 159.19697 170.2 21014.0 28 ... 6469.0 28 -0.268482 135
# Male 173.62549 193.9 8854.9 193 ... 3929.0 2 -0.332393 54
#
# [2 rows x 12 columns]
3、使用自定义函数
在 agg 中可以使用具体的自定义函数, 但是需要注意传入函数的参数是之前数据源中的列,逐列进行计算 。下面分组计算身高和体重的极差的代码如下:
print("gb1.agg(lambda x: x.mean()-x.min())=",gb1.agg(lambda x: x.mean()-x.min()))
# gb1.agg(lambda x: x.mean()-x.min())= Height Weight
# Gender
# Female 13.79697 13.918519
# Male 17.92549 21.759259
def my_function(x):
res = 'High'
if x.mean()<= df3[x.name].mean():
res='Low'
return res
print("gb1.agg(my_function)=",gb1.agg(my_function))
# gb1.agg(my_function())= Height Weight
# Gender
# Female Low Low
# Male High High
4、聚合结果重命名
如果想要对结果进行重命名,只需要将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数。
print("gb1.agg([('range', lambda x: x.max()-x.min()), ('my_count', 'count')])=",
gb1.agg([('range', lambda x: x.max()-x.min()), ('my_count', 'count')]))
# gb1.agg([('range', lambda x: x.max()-x.min()), ('my_count', 'count')])= Height Weight
# range my_count range my_count
# Gender
# Female 24.8 132 29.0 135
# Male 38.2 51 38.0 54
print("gb1.agg({'Height': [('my_function', my_function), 'sum'],'Weight': lambda x: x.min()})=",
gb1.agg({'Height': [('my_function', my_function), 'sum'],'Weight': lambda x: x.min()}))
# gb1.agg({'Height': [('my_function', my_function), 'sum'],'Weight': lambda x: x.min()})= Height Weight
# my_function sum <lambda>
# Gender
# Female Low 21014.0 34.0
# Male High 8854.9 51.0
# 需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,
# 否则就不知道是新的名字还是手误输错的内置函数字符串
print("gb1.agg([('my_idxmax', 'idxmax')])=",
gb1.agg([('my_idxmax', 'idxmax')]))
# gb1.agg([('my_idxmax', 'idxmax')])= Height Weight
# my_idxmax my_idxmax
# Gender
# Female 28 28
# Male 193 2
print("gb1.agg({'Height': [('my_function', my_function), 'prod'],'Weight': [('range', lambda x:x.min())]})=",
gb1.agg({'Height': [('my_function', my_function), 'prod'],'Weight': [('range', lambda x:x.min())]}))
# gb1.agg({'Height': [('my_function', my_function), 'prod'],'Weight': [('range', lambda x:x.min())]})= Height Weight
# my_function prod range
# Gender
# Female Low 4.232080e+290 34.0
# Male High 1.594210e+114 51.0
三、变换和过滤
3.1 变换函数与transform方法
变换函数的返回值为同长度的序列,最常用的内置变换函数是累计函数: cumcount/cumsum/cumprod/cummax/cummin ,它们的使用方式和聚合函数类似,只不过完成的是组内累计操作。
print("gb1.cummax().head()=",gb1.cummax().head())
# gb1.cummax().head()= Height Weight
# 0 158.9 46.0
# 1 166.5 70.0
# 2 188.9 89.0
# 3 NaN 46.0
# 4 188.9 89.0
当用自定义变换时需要使用 transform 方法,被调用的自定义函数, 其传入值为数据源的序列 ,与 agg 的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的 DataFrame .现对身高和体重进行分组标准化,即减去组均值后除以组的标准差,代码如下:
print("gb1.transform(lambda x: (x-x.mean())/x.std()).head()=",
gb1.transform(lambda x: (x-x.mean())/x.std()).head())
# gb1.transform(lambda x: (x-x.mean())/x.std()).head()= Height Weight
# 0 -0.058760 -0.354888
# 1 -1.010925 -0.355000
# 2 2.167063 2.089498
# 3 NaN -1.279789
# 4 0.053133 0.159631
transform 只能返回同长度的序列,但事实上还可以返回一个标量,这会使得结果被广播到其所在的整个组,这种标量广播的技巧在特征工程中是非常常见的。例如,构造两列新特征来分别表示样本所在性别组的身高均值和体重均值。代码如下:
print("gb1.transform('mean').head()=", gb1.transform('mean').head())
# gb1.transform('mean').head()= Height Weight
# 0 159.19697 47.918519
# 1 173.62549 72.759259
# 2 173.62549 72.759259
# 3 159.19697 47.918519
# 4 173.62549 72.759259
3.2 组索引与过滤
过滤在分组中是对于组的过滤,而索引是对于行的过滤,在第二章中的返回值,无论是布尔列表还是元素列表或者位置列表,本质上都是对于行的筛选,即如果筛选条件的则选入结果的表,否则不选入。
组过滤作为行过滤的推广,指的是如果对一个组的全体所在行进行统计的结果返回 True 则会被保留, False 则该组会被过滤,最后把所有未被过滤的组其对应的所在行拼接起来作为 DataFrame 返回。
在 groupby 对象中,定义了 filter 方法进行组的筛选,其中自定义函数的输入参数为数据源构成的 DataFrame 本身,在之前例子中定义的 groupby 对象中,传入的就是 df[[‘Height’, ‘Weight’]] ,因此所有表方法和属性都可以在自定义函数中相应地使用,同时只需保证自定义函数的返回为布尔值即可。
例如,在原表中通过过滤得到所有容量大于100的组,代码如下:
print("gb1.filter(lambda x: x.shape[0] > 120).head()=",
gb1.filter(lambda x: x.shape[0] > 120).head())
# gb1.filter(lambda x: x.shape[0] > 120).head()= Height Weight
# 0 158.9 46.0
# 3 NaN 41.0
# 5 158.0 51.0
# 6 162.5 52.0
# 7 161.9 50.0
四、 跨列分组
4.1 apply的引入
定义身体质量指数BMI: B M I = W e i g h t / H e i g h t 2 BMI=Weight/Height2 BMI=Weight/Height2 其中体重和身高的单位分别为千克和米,需要分组计算组BMI的均值。
首先,这不是过滤操作,因此 filter 不符合要求;其次,返回的均值是标量而不是序列,因此 transform 不符合要求;最后,似乎使用 agg 函数能够处理,但是之前强调过聚合函数是逐列处理的,而不能够 多列数据同时处理 。由此,引出了 apply 函数来解决这一问题。
4.2 apply的使用
在设计上, apply 的自定义函数传入参数与 filter 完全一致,但是后者只允许返回布尔值。现如下解决上述计算问题:
def BMI(b):
Height = b['Height'] / 100
Weight = b['Weight']
BMI_value = Weight / Height ** 2
return BMI_value.mean()
print("gb1.apply(BMI)=",gb1.apply(BMI))
# gb1.apply(BMI)= Gender
# Female 18.860930
# Male 24.318654
# dtype: float64
"""除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame ,"""
1、标量情况:结果得到的是 Series ,索引与 agg 的结果一致
gb2 = df3.groupby(['Gender','Test_Number'])[['Height','Weight']]
print("gb2.apply(lambda x: 0)=",gb2.apply(lambda x: 0))
# gb2.apply(lambda x: 0)= Gender Test_Number
# Female 1 0
# 2 0
# 3 0
# Male 1 0
# 2 0
# 3 0
# dtype: int64
print("gb2.apply(lambda x: [0, 0,1])=",gb2.apply(lambda x: [0, 0,1]))
# gb2.apply(lambda x: [0, 0,1])= Gender Test_Number
# Female 1 [0, 0, 1]
# 2 [0, 0, 1]
# 3 [0, 0, 1]
# Male 1 [0, 0, 1]
# 2 [0, 0, 1]
# 3 [0, 0, 1]
# dtype: object
2、Series 情况:得到的是 DataFrame ,行索引与标量情况一致,列索引为 Series 的索引
print("gb2.apply(lambda x: pd.Series([0,0,1,6,2],index=['w','o','r','l','d']))=",
gb2.apply(lambda x: pd.Series([0,0,1,6,2],index=['w','o','r','l','d'])))
# gb2.apply(lambda x: pd.Series([0,0,1,6,2],index=['w','o','r','l','d']))= w o r l d
# Gender Test_Number
# Female 1 0 0 1 6 2
# 2 0 0 1 6 2
# 3 0 0 1 6 2
# Male 1 0 0 1 6 2
# 2 0 0 1 6 2
# 3 0 0 1 6 2
3、DataFrame 情况:得到的是 DataFrame ,行索引最内层在每个组原先 agg 的结果索引上,再加一层返回的 DataFrame 行索引,同时分组结果 DataFrame 的列索引和返回的 DataFrame 列索引一致。
print("gb.apply(lambda x: pd.DataFrame(np.ones((3,3)),index = ['w','q','x'],columns=pd.Index([('d','x','s'),('o','y','z'),('e','r','u')])))=",
gb.apply(lambda x: pd.DataFrame(np.ones((3,3)),index = ['w','q','x'],columns=pd.Index([('d','x','s'),('o','y','z'),('e','r','u')]))))
# gb.apply(lambda x: pd.DataFrame(np.ones((3,3)),index = ['w','q','x'],columns=pd.Index([('d','x','s'),('o','y','z'),('e','r','u')])))= d o e
# x y r
# s z u
# Gender
# Female w 1.0 1.0 1.0
# q 1.0 1.0 1.0
# x 1.0 1.0 1.0
# Male w 1.0 1.0 1.0
# q 1.0 1.0 1.0
# x 1.0 1.0 1.0
参考文献
1、https://www.yiibai.com/pandas/python_pandas_groupby.html
2、https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch4.html#id2