Pandas分组
分组模式及其对象
分组的一般模式
df.groupby(分组依据)[数据来源].使用操作
// A code block
df.groupby('Gender')['Height'].median()
// An highlighted block
Gender
Female 159.6
Male 173.4
Name: Height, dtype: float64
分组依据的本质
根据学校和性别进行分组,统计身高的均值就可以如下写出:
// A code block
df.groupby(['School', 'Gender'])['Height'].mean()
// An highlighted block
School Gender
Fudan University Female 158.776923
Male 174.212500
Peking University Female 158.666667
Male 172.030000
Shanghai Jiao Tong University Female 159.122500
Male 176.760000
Tsinghua University Female 159.753333
Male 171.638889
Name: Height, dtype: float64
Groupby对象
通过ngroups属性,可以访问分为了多少组。
通过groups属性,可以返回从 组名 映射到 组索引列表 的字典。
分组的三大操作------聚合、变换和过滤
聚合函数
内置聚合函数
直接定义在groupby对象的聚合函数,max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod
// A code block
gb.quantile(0.95)
// An highlighted block
Gender
Female 166.8
Male 185.9
Name: Height, dtype: float64
agg方法
虽然在groupby对象上定义了许多方便的函数,但仍然有以下不便之处:
无法同时使用多个函数
无法对特定的列使用特定的聚合函数
无法使用自定义的聚合函数
无法直接对结果的列名在聚合前进行自定义命名
下面说明如何通过agg函数解决这四类问题:
【a】使用多个函数
当使用多个聚合函数时,需要用列表的形式把内置聚合函数的对应的字符串传入,先前提到的所有字符串都是合法的。
// A code block
gb.agg(['sum', 'idxmax', 'skew'])
// An highlighted block
Height Weight
sum idxmax skew sum idxmax skew
Gender
Female 21014.0 28 -0.219253 6469.0 28 -0.268482
Male 8854.9 193 0.437535 3929.0 2 -0.332393
【b】对特定的列使用特定的聚合函数
对于方法和列的特殊对应,可以通过构造字典传入agg中实现,其中字典以列名为键,以聚合字符串或字符串列表为值.
// A code block
gb.agg({'Height':['mean','max'], 'Weight':'count'})
// An highlighted block
Height Weight
mean max count
Gender
Female 159.19697 170.2 135
Male 173.62549 193.9 54
【c】使用自定义函数
在agg中可以使用具体的自定义函数, 需要注意传入函数的参数是之前数据源中的列,逐列进行计算 。下面分组计算身高和体重的极差:
// A code block
gb.agg(lambda x: x.mean()-x.min())
// An highlighted block
Height Weight
Gender
Female 13.79697 13.918519
Male 17.92549 21.759259
【d】聚合结果重命名
如果想要对结果进行重命名,只需要将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数,现举若干例子说明:
// A code block
gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])
// An highlighted block
Height Weight
range my_sum range my_sum
Gender
Female 24.8 21014.0 29.0 6469.0
Male 38.2 8854.9 38.0 3929.0
变换和过滤
变换函数与transform方法
变换函数的返回值为同长度的序列,最常用的内置变换函数是累计函数:cumcount/cumsum/cumprod/cummax/cummin,它们的使用方式和聚合函数类似,只不过完成的是组内累计操作。
// A code block
gb.cummax().head()
// An highlighted block
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。
// A code block
gb.transform(lambda x: (x-x.mean())/x.std()).head()
// An highlighted block
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
组索引与过滤
在groupby对象中,定义了filter方法进行组的筛选,其中自定义函数的输入参数为数据源构成的DataFrame本身,在之前例子中定义的groupby对象中,传入的就是df[[‘Height’, ‘Weight’]],因此所有表方法和属性都可以在自定义函数中相应地使用,同时只需保证自定义函数的返回为布尔值即可。
// A code block
gb.filter(lambda x: x.shape[0] > 100).head()
// An highlighted block
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
跨列分组—apply
在设计上,apply的自定义函数传入参数与filter完全一致,只不过后者只允许返回布尔值。除了返回标量之外,apply方法还可以返回一维Series和二维DataFrame,但它们产生的数据框维数和多级索引的层数应当如何变化?下面举三组例子就非常容易明白结果是如何生成的:
【a】标量情况:结果得到的是 Series ,索引与 agg 的结果一致
【b】Series情况:得到的是DataFrame,行索引与标量情况一致,列索引为Series的索引
【c】DataFrame情况:得到的是DataFrame,行索引最内层在每个组原先agg的结果索引上,再加一层返回的DataFrame行索引,同时分组结果DataFrame的列索引和返回的DataFrame列索引一致。
练习
EX1:汽车数据集
// A code block
def normalize(s):
s_min, s_max = s.min(), s.max()
res = (s - s_min)/(s_max - s_min)
return res
df.groupby('Type')['HP'].transform(normalize).head()
// An highlighted block
0 1.00
1 0.54
2 0.00
3 0.58
4 0.80
Name: HP, dtype: float64
// A code block
df.groupby('Type')[['HP', 'Disp.']].apply(lambda x:np.corrcoef(x['HP'].values, x['Disp.'].values)[0,1])
// An highlighted block
Type
Compact 0.586087
Large -0.242765
Medium 0.370491
Small 0.603916
Sporty 0.871426
Van 0.819881
dtype: float64
EX2:实现transform函数
// A code block
class my_groupby:
def __init__(self, my_df, group_cols):
self.my_df = my_df.copy()
self.groups = my_df[group_cols].drop_duplicates()
if isinstance(self.groups, pd.Series):
self.groups = self.groups.to_frame()
self.group_cols = self.groups.columns.tolist()
self.groups = {i: self.groups[i].values.tolist() for i in self.groups.columns}
self.transform_col = None
def __getitem__(self, col):
self.pr_col = [col] if isinstance(col, str) else list(col)
return self
def transform(self, my_func):
self.num = len(self.groups[self.group_cols[0]])
L_order, L_value = np.array([]), np.array([])
for i in range(self.num):
group_df = self.my_df.reset_index().copy()
for col in self.group_cols:
group_df = group_df[group_df[col]==self.groups[col][i]]
group_df = group_df[self.pr_col]
if group_df.shape[1] == 1:
group_df = group_df.iloc[:, 0]
group_res = my_func(group_df)
if not isinstance(group_res, pd.Series):
group_res = pd.Series(group_res,index=group_df.index,name=group_df.name)
L_order = np.r_[L_order, group_res.index]
L_value = np.r_[L_value, group_res.values]
self.res = pd.Series(pd.Series(L_value, index=L_order).sort_index().values,index=self.my_df.reset_index().index, name=my_func.__name__)
return self.res
my_groupby(df, 'Type')
// An highlighted block
<__main__.my_groupby at 0x1c4f9c0c208>
// A code block
def f(s):
res = (s-s.min())/(s.max()-s.min())
return res
my_groupby(df, 'Type')['Price'].transform(f).head()
// An highlighted block
0 0.733592
1 0.372003
2 0.109712
3 0.186244
4 0.177525
Name: f, dtype: float64
总结
总体感觉这一章的内容相比第三章简单。内容也相对较少。
加油!