4.1分组模式及其对象
如,依据性别分组,统计全国人口寿命的平均值
要想实现分组操作,必须明确3个要素:分组依据、数据来源、操作及其返回结果
import pandas as pd
import numpy as np
df = pd.read_csv('learn_pandas.csv')
# 按照性别统计身高中位数
df.groupby('Gender')['Height'].median()
Gender
Female 159.6
Male 173.4
Name: Height, dtype: float64
4.1.2 分组依据的本质
genuine多个维度进行分组,只需在groupby中传入响应列名构成列表即可
df.groupby(['School','Gender'])['Height'].mean()
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
# 根据学生体重是否超过总体均值来分组
# 先在变量中定义条件,然后传到()中进行条件筛选
condition = df.Weight > df.Weight.mean()
df.groupby(condition)['Height'].mean()
Weight
False 159.034646
True 172.705357
Name: Height, dtype: float64
练一练,根据上下四位数分割,将体重分为high、normal、low三组,统计身高的平均值
df.groupby(condition)['Height'].mean()
low = df['Weight']<df['Weight'].quantile(1/3)
df.groupby(low)['Height'].mean()
Weight
False 167.085484
True 155.089831
Name: Height, dtype: float64
high = df['Weight'] > df['Weight'].quantile(2/3)
df.groupby(high)['Height'].mean()
Weight
False 159.034646
True 172.705357
Name: Height, dtype: float64
norm = df['Weight'] < (df['Weight'].quantile(2/3))
df.groupby(norm)['Height'].mean()
Weight
False 171.245714
True 158.245133
Name: Height, dtype: float64
从索引可以看出,最后产生的结果是按照条件列表中元素的值(True or False)来分组
item = np.random.choice(list('abc'), df.shape[0])
df.groupby(item)['Height'].mean()
a 164.115517
b 162.449180
c 163.137500
Name: Height, dtype: float64
# 传入多个序列,分组的依据就是这两个序列对应行的唯一组合
# 因为condition是一个判断条件,所以返回的只有True和False两个值
# 相当于传入的是一个或多个列,最后分组的依据来源于组合的unique值
# 用drop_duplicates就能知道具体的组类别
df.groupby([condition, item])['Height'].mean()
Weight
False a 159.323684
b 159.144444
c 158.672727
True a 173.220000
b 171.743750
c 172.960000
Name: Height, dtype: float64
4.1.3 groupby对象
gb = df.groupby(['School', 'Grade'])
gb
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000227FAC8E2E8>
# ngroups可以访问分为了多少组
gb.ngroups
16
# groups 可以返回从组名映射到组索引列表的字典
# 使用groups之后,可以和python的字典一样的用法
res = gb.groups
res.keys()
res.values()
res
{('Fudan University', 'Freshman'): [15, 28, 63, 70, 73, 105, 108, 157, 186], ('Fudan University', 'Junior'): [26, 41, 82, 84, 90, 107, 145, 152, 173, 187, 189, 195], ('Fudan University', 'Senior'): [39, 46, 49, 52, 66, 77, 112, 129, 131, 138, 144], ('Fudan University', 'Sophomore'): [3, 4, 37, 48, 68, 98, 135, 170], ('Peking University', 'Freshman'): [1, 32, 35, 36, 38, 45, 54, 57, 88, 96, 99, 140, 185], ('Peking University', 'Junior'): [9, 20, 59, 72, 75, 102, 159, 183], ('Peking University', 'Senior'): [30, 86, 116, 127, 130, 132, 147, 194], ('Peking University', 'Sophomore'): [29, 61, 83, 101, 120], ('Shanghai Jiao Tong University', 'Freshman'): [0, 6, 10, 60, 114, 117, 119, 121, 141, 148, 149, 153, 184], ('Shanghai Jiao Tong University', 'Junior'): [31, 42, 50, 56, 58, 64, 85, 93, 115, 122, 143, 155, 164, 172, 174, 188, 190], ('Shanghai Jiao Tong University', 'Senior'): [2, 12, 19, 21, 22, 23, 79, 87, 89, 103, 104, 109, 123, 134, 156, 161, 165, 166, 171, 192, 197, 198], ('Shanghai Jiao Tong University', 'Sophomore'): [13, 65, 71, 124, 167], ('Tsinghua University', 'Freshman'): [5, 8, 33, 34, 43, 44, 47, 51, 62, 67, 81, 111, 125, 133, 136, 142, 146], ('Tsinghua University', 'Junior'): [7, 11, 16, 17, 27, 69, 94, 95, 113, 118, 128, 137, 150, 154, 158, 160, 162, 163, 169, 176, 177, 191], ('Tsinghua University', 'Senior'): [14, 18, 24, 25, 78, 92, 100, 126, 168, 175, 179, 180, 193, 196], ('Tsinghua University', 'Sophomore'): [40, 53, 55, 74, 76, 80, 91, 97, 106, 110, 139, 151, 178, 181, 182, 199]}
上一节提到过可以用drop_duplicates就能知道具体的组类别,可以用groups属性完成类似的功能
df.groupby(['School','Gender']).groups.keys()
dict_keys([('Fudan University', 'Female'), ('Fudan University', 'Male'), ('Peking University', 'Female'), ('Peking University', 'Male'), ('Shanghai Jiao Tong University', 'Female'), ('Shanghai Jiao Tong University', 'Male'), ('Tsinghua University', 'Female'), ('Tsinghua University', 'Male')])
当size作为DataFrame的属性时,返回的是表长乘以表宽的大小,但在groupby对象上表示统计每个组的元素个数
# 最后一列表示每个组的元素个数
gb.size()
School Grade
Fudan University Freshman 9
Junior 12
Senior 11
Sophomore 8
Peking University Freshman 13
Junior 8
Senior 8
Sophomore 5
Shanghai Jiao Tong University Freshman 13
Junior 17
Senior 22
Sophomore 5
Tsinghua University Freshman 17
Junior 22
Senior 14
Sophomore 16
dtype: int64
# get_group 方法可以直接获取所在组对应的行,此时需要知道组的具体名字
# 只取前3列,和前3行
gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3]
School | Grade | Name | |
---|---|---|---|
15 | Fudan University | Freshman | Changqiang Yang |
28 | Fudan University | Freshman | Gaoqiang Qin |
63 | Fudan University | Freshman | Gaofeng Zhao |
4.1.4 分组的三大操作
- 依据性别分组,统计全国人口寿命的平均值 —> 每一个组返回一个标量值,可以是平均值、中位数、组容量size等
- 依据季节分组,对每一个季节的温度进行组内标准化 —> 做了原序列的标准化处理,也就是说每组返回的是一个Series类型
- 依据班级分组,筛选出组内数学分数的平均值超过80分的班级 —> 既不是标量也不是序列,返回整个组所在行的本身,即返回了DataFrame类型4.2 聚合函数
4.2.1 内置聚合函数
一些定义在groupby对象的聚合函数,它的速度基本都会经过内部的优化,使用功能应当优先考虑
根据返回标量值的原则,包括:max/min/mean/median/count/ all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod
gb = df.groupby('Gender')['Height']
gb.idxmin()
Gender
Female 143
Male 199
Name: Height, dtype: int64
gb.quantile(0.95)
Gender
Female 166.8
Male 185.9
Name: Height, dtype: float64
# 聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算
gb = df.groupby('Gender')[['Height','Weight']]
gb.max()
Height | Weight | |
---|---|---|
Gender | ||
Female | 170.2 | 63.0 |
Male | 193.9 | 89.0 |
4.2.2 agg方法
可以解决如下4个问题:
1).无法同时使用多个函数
2).无法对特定的列使用特定的聚合函数
3).无法使用自定义的聚合函数
4).无法直接对结果的列名在聚合前进行自定义命名
# 为解决问题1)
# 列索引是多级索引,第一层是数据源,第二层为使用的聚合方法,分别逐一对列使用聚合,因此结果有6列
gb.agg(['sum','idxmax','skew'])
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 |
# 为解决问题2)
# 对于方法和列的特殊对应,可以通过构造字典传入agg中实现,其中字典以列名为键,以聚合字符串或字符串列表为值
gb.agg({'Height':['mean','max'],'Weight':'count'})
Height | Weight | ||
---|---|---|---|
mean | max | count | |
Gender | |||
Female | 159.19697 | 170.2 | 135 |
Male | 173.62549 | 193.9 | 54 |
请使用2)中传入字典的方法,完成1)中等价的聚合任务
gb.agg({'Height':['sum','idxmax','skew'],'Weight':['sum','idxmax','skew']})
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 |
3)使用自定义函数
在agg中可以使用具体的自定义函数,需要注意传入函数的参数是之前数据源中的列,逐列进行计算
# 分组计算身高和体重的极差
gb.agg(lambda x: x.mean()-x.min())
Height | Weight | |
---|---|---|
Gender | ||
Female | 13.79697 | 13.918519 |
Male | 17.92549 | 21.759259 |
在groupby对象中可以使用describe方法进行统计信息汇总,请同时使用多个聚合函数,完成与该方法相同的功能
gb.describe()
Height | Weight | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | mean | std | min | 25% | 50% | 75% | max | count | mean | std | min | 25% | 50% | 75% | max | |
Gender | ||||||||||||||||
Female | 132.0 | 159.19697 | 5.053982 | 145.4 | 155.675 | 159.6 | 162.825 | 170.2 | 135.0 | 47.918519 | 5.405983 | 34.0 | 44.0 | 48.0 | 52.00 | 63.0 |
Male | 51.0 | 173.62549 | 7.048485 | 155.7 | 168.900 | 173.4 | 177.150 | 193.9 | 54.0 | 72.759259 | 7.772557 | 51.0 | 69.0 | 73.0 | 78.75 | 89.0 |
gb.agg(['count','mean','std','min',('25%', lambda x: x.quantile(0.25)), ('50%', lambda x: x.quantile(0.5)),('75%', lambda x:x.quantile(0.75)),'max'])
Height | Weight | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | mean | std | min | 25% | 50% | 75% | max | count | mean | std | min | 25% | 50% | 75% | max | |
Gender | ||||||||||||||||
Female | 132 | 159.19697 | 5.053982 | 145.4 | 155.675 | 159.6 | 162.825 | 170.2 | 135 | 47.918519 | 5.405983 | 34.0 | 44.0 | 48.0 | 52.00 | 63.0 |
Male | 51 | 173.62549 | 7.048485 | 155.7 | 168.900 | 173.4 | 177.150 | 193.9 | 54 | 72.759259 | 7.772557 | 51.0 | 69.0 | 73.0 | 78.75 | 89.0 |
由于传入的是序列,因此序列上的方法和属性都是可以在函数中使用的,只需保证返回值是标量即可
如果组的指标均值超过该指标的总体均值,返回high,否则返回low
def my_func(s):
res = 'Hight'
if s.mean() <= df[s.name].mean():
res = 'Low'
return res
gb.agg(my_func)
Height | Weight | |
---|---|---|
Gender | ||
Female | Low | Low |
Male | Hight | Hight |
- 聚合结果重命名
需要对上述函数的位置改写为元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数
gb.agg([('range', lambda x:x.max()-x.min()), ('my_sum','sum')])
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 |
# 使用对一个或多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字函数手误输错的内置函数字符串
gb.agg([('my_sum', 'sum')])
Height | Weight | |
---|---|---|
my_sum | my_sum | |
Gender | ||
Female | 21014.0 | 6469.0 |
Male | 8854.9 | 3929.0 |
gb.agg({'Height':[('my_func', my_func),'sum'],'Weight':[('range', lambda x:x.max())]})
Height | Weight | ||
---|---|---|---|
my_func | sum | range | |
Gender | |||
Female | Low | 21014.0 | 63.0 |
Male | Hight | 8854.9 | 89.0 |