Pandas第四章分组
一、分组模式及其对象
1.分组的一般模式
分组操作的核心三要素:
- 分组依据:按什么维度分组(如性别、班级、自定义条件)
- 数据来源:要对哪些列进行计算(如身高、寿命、分数)
- 操作:组内要执行的统计 / 变换(如均值、中位数、标准化)
通用代码模板:
df.groupby(分组依据)[数据来源].使用操作示例:按性别统计身高中位数
df = pd.read_csv('../data/learn_pandas.csv') df.groupby('Gender')['Height'].median()2.分组依据的本质
分组依据本质是可对齐的序列 / 列表,groupby会按序列的唯一值对行进行分组。
(1)常见分组依据类型
列名 / 列名列表(最常用)
# 单维度:按性别分组 df.groupby('Gender')['Height'].mean() # 多维度:按学校+性别分组 df.groupby(['School', 'Gender'])['Height'].mean()布尔序列 / 条件序列
# 按体重是否超过均值分组 condition = df.Weight > df.Weight.mean() df.groupby(condition)['Height'].mean()结果会按
True/False分成两组。任意序列 / 列表
import numpy as np item = np.random.choice(list('abc'), df.shape[0]) df.groupby(item)['Height'].mean()结果会按
a/b/c分成三组。多个序列组合
# 按「体重条件 + 随机字母」分组 df.groupby([condition, item])['Height'].mean()结果是两个序列的笛卡尔积分组。
(2)分组依据的等价形式
传入列名等价于传入该列的 Series:
# 以下两种写法完全等价 df.groupby(['School', 'Gender'])['Height'].mean() df.groupby([df['School'], df['Gender']])['Height'].mean()可以通过drop_duplicates()查看所有组的类别:
df[['School', 'Gender']].drop_duplicates()这会列出所有「学校 + 性别」的唯一组合,也就是最终的分组。
注:
groupby的本质是按序列的唯一值分组,列名、条件、列表都是序列的不同形式。- 多维度分组 = 多个序列的组合,组是所有组合的唯一值。
- 通用模式:
df.groupby(by=...)[cols].agg(...),是 pandas 数据分析的核心工具。
3.Groupby 对象
Groupby 对象的核心属性与方法
先创建分组对象:
gb = df.groupby(['School', 'Grade'])| 属性 / 方法 | 作用 | 示例 |
|---|---|---|
ngroups | 返回分组的总个数 | gb.ngroups→ 16 |
groups | 返回组名 → 组内行索引的字典 | gb.groups.keys()可查看所有组名 |
size() | 返回每个组的元素个数 | gb.size()按组统计行数 |
get_group() | 传入组名,直接提取该组的所有行 | gb.get_group(('Fudan University', 'Freshman')) |
注意:
size()在 DataFrame 中是表的总元素数,但在groupby对象中是每组的行数,这是一个易混点。
4.分组的三大操作
分组操作根据返回结果的形态,分为三类:
| 操作类型 | 对应方法 | 返回结果 | 典型场景 |
|---|---|---|---|
| 聚合(Aggregation) | agg()/mean()/median()/size() | 每组返回单个标量值 | 统计组内均值、中位数、组容量等 |
| 变换(Transformation) | transform() | 每组返回与原行等长的 Series | 组内标准化、组内填充缺失值等 |
| 过滤(Filtering) | filter() | 返回符合条件的整组行 | 筛选出组均值 > 80 的班级、组容量 > 10 的组等 |
三大操作的对应场景示例
- 聚合:按性别统计身高的平均值 → 每组返回一个数。
- 变换:按季节对温度做组内标准化 → 每行返回一个变换后的值,长度与原表一致。
- 过滤:筛选出组内数学平均分 > 80 的班级 → 返回符合条件班级的所有学生行。
二、聚合函数
1. 内置聚合函数
groupby对象上直接定义了大量经过优化的内置聚合函数,优先使用(速度快、代码简洁)。
常用函数列表:max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod
基础示例
# 1. 按性别分组,选取身高列 gb = df.groupby('Gender')['Height'] # 2. 示例1:返回每组最小值的索引(idxmin) gb.idxmin() # 输出:Gender Female→143, Male→199 # 3. 示例2:返回每组95%分位数(quantile) gb.quantile(0.95) # 输出:Gender Female→166.8, Male→185.9多列迭代计算
当传入多列时,函数会按列独立计算:
gb = df.groupby('Gender')[['Height', 'Weight']] gb.max()结果:
| Gender | Height | Weight |
|---|---|---|
| Female | 170.2 | 63.0 |
| Male | 193.9 | 89.0 |
2. 万能聚合方法:agg()/aggregate()
agg是aggregate的缩写,它能解决内置函数无法覆盖的四大问题。
(1)同时使用多个函数
传入函数名列表,结果列为多级索引(列名 × 函数名)。
gb.agg(['sum', 'idxmax', 'skew'])结果结构:
- 行:分组键(Female/Male)
- 列:Height/Weight 分别对应 sum/idxmax/skew
(2)为特定列指定特定函数
传入字典(键 = 列名,值 = 函数 / 函数列表)。
gb.agg({ 'Height': ['mean', 'max'], # 身高求均值、最大值 'Weight': 'count' # 体重求计数 })结果:
| Gender | Height_mean | Height_max | Weight_count |
|---|---|---|---|
| Female | 159.19697 | 170.2 | 135 |
| Male | 173.62549 | 193.9 | 54 |
(3)使用自定义函数
lambda或自定义函数,参数为组内列的 Series,必须返回标量。
Lambda 计算极差(最大值 - 最小值):
gb.agg(lambda x: x.mean() - x.min())自定义复杂逻辑(对比总体均值):
def my_func(s): # s.name 是列名(Height/Weight) return 'High' if s.mean() > df[s.name].mean() else 'Low' gb.agg(my_func)结果:根据组均值是否高于总体均值,返回 High/Low。
(4)聚合结果重命名
将函数位置改写为元组(新名字, 原函数),灵活定制列名。
列表形式重命名(多函数):
gb.agg([ ('range', lambda x: x.max()-x.min()), ('my_sum', 'sum') ])字典形式重命名(列映射):
gb.agg({ 'Height': [('my_func', my_func), 'sum'], 'Weight': ('max', lambda x: x.max()) })
注:
- 优先使用:Groupby 原生函数(
mean,sum,idxmax等),速度经过优化。agg是万能钥匙:
- 用列表实现多函数同时计算
- 用字典实现列 - 函数精准匹配
- 用函数 / Lambda 实现自定义逻辑
- 用元组
(别名, 函数)实现结果重命名- 注意点:自定义传入函数必须保证返回标量(单值)。
三、变换和过滤
1. 变换函数与transform方法
(1)变换的核心定义
- 返回规则:变换函数必须返回与原数据 **** 同长度的序列(Series/DataFrame)。
- 机制:“拆分 - 应用 - 合并” 中的 “应用” 阶段,将计算结果广播回原数据的每一行,保持索引和形状不变。
(2)内置变换函数
Groupby 对象支持一系列组内累计变换函数,返回同长度的序列:
| 函数 | 含义 |
|---|---|
cummax() | 组内累计最大值 |
cummin() | 组内累计最小值 |
cumsum() | 组内累计求和 |
cumprod() | 组内累计求积 |
cumcount() | 组内计数(从 0 开始) |
示例:
gb = df.groupby('Gender')[['Height', 'Weight']] gb.cummax().head() # 每组内逐行累计最大值- 输出结果行数与原表一致,每一行展示当前行及之前的最大值。
(3)自定义变换:transform方法
transform是自定义变换的核心方法,传入的函数参数是组内的 Series,必须返回同长度的结构。
示例:分组标准化(Z-Score)
对身高和体重进行组内标准化:(x−μ)/σ
# gb = df.groupby('Gender')[['Height', 'Weight']] gb.transform(lambda x: (x - x.mean()) / x.std()).head()- 输入:每组的 Height/Weight 序列。
- 输出:与原表形状完全一致的标准化结果,索引保持不变。
(4)标量广播机制(特征工程常用)
transform不仅可以返回同长序列,还可以返回标量,结果会自动广播到整个组。这是特征工程中构造组级特征的关键技巧。
示例:构造组均值特征
为每一行添加所在组的身高、体重均值信息:
# gb = df.groupby('Gender')[['Height', 'Weight']] gb.transform('mean').head()结果结构:
- 每行的值是该组(性别)的整体均值。
- 例如 Female 组的每一行 Height 都会填充为 159.19...,Weight 填充为 47.91...。
2.组索引与过滤
(1)过滤 vs 索引(行过滤)
- 索引 / 行过滤:对每一行单独判断,符合条件的行保留,不符合的丢弃。
- 组过滤:对整个组判断,如果组整体满足条件(返回
True),则该组所有行都保留;如果不满足(返回False),则该组所有行都被丢弃。 - 本质:组过滤是行过滤的推广,筛选单位从「行」升级为「组」。
(2)filter方法核心用法
groupby对象提供filter()方法实现组过滤:
- 输入:自定义函数(或 lambda),参数是当前组的完整 DataFrame,可以使用所有 DataFrame 方法 / 属性。
- 输出:函数必须返回一个布尔值(True/False)。
True:保留该组的所有行False:丢弃该组的所有行
- 最终结果:所有保留组的行拼接成新的 DataFrame。
示例:保留样本量 > 100 的组
# gb = df.groupby('Gender')[['Height', 'Weight']] gb.filter(lambda x: x.shape[0] > 100).head()x.shape[0]:获取当前组的行数(样本量)。- 逻辑:只保留样本量超过 100 的组,其余组全部过滤掉。
(3)核心特点
- 函数参数是 DataFrame:可以在函数里做复杂统计(如均值、标准差、样本量等)来决定是否保留该组。
- 结果形状:行数可能减少(因为丢弃了部分组),但列数不变。
- 典型场景:
- 过滤掉样本量过小的组(避免统计偏差)
- 过滤掉组均值 / 方差不符合要求的组
- 只保留满足特定业务条件的组
(4)总结
- 行过滤:
df[condition]→ 逐行判断 - 组过滤:
gb.filter(lambda x: ...)→ 逐组判断 filter是 pandas 分组三大操作(聚合 / 变换 / 过滤)之一,是处理分组数据的重要工具。
四、跨列分组
1. apply的引入
- 跨列计算需求:比如 BMI 计算需要同时用到
Weight和Height两列,agg只能逐列聚合,无法处理多列联动。 - 返回标量 / Series/DataFrame 灵活适配:
filter只能返回布尔值,transform只能返回同长度序列,而apply可以返回任意结构。 - 典型场景:分组后基于多列计算复合指标(如 BMI、评分公式),并返回聚合结果。
2. 基础示例:分组计算 BMI 均值
import pandas as pd def BMI(x): Height = x['Height'] / 100 # 假设身高单位是厘米,转成米 Weight = x['Weight'] BMI_value = Weight / Height**2 return BMI_value.mean() # 按 Gender 分组,传入 BMI 函数 gb = df.groupby('Gender')[['Height', 'Weight']] result = gb.apply(BMI)结果是一个Series,索引为分组键,值为每组 BMI 均值。
3. 不同返回值类型的结果形态
| 返回类型 | 结果类型 | 索引结构 | 示例效果 |
|---|---|---|---|
| 标量 / 列表 | Series | 与agg结果一致(分组键作索引) | 每组返回一个值 / 列表,如lambda x: 0或lambda x: [0,0] |
| Series | DataFrame | 行索引 = 分组键,列索引 = Series 索引 | 如pd.Series([0,0], index=['a','b'])→ 生成列名为a/b的 DataFrame |
| DataFrame | DataFrame | 行索引 = 分组键 + 返回 DF 行索引列索引 = 返回 DF 列索引 | 嵌套层级最多,适合返回多维结果 |
4. 三种返回值的代码示例
① 标量 / 列表返回
gb = df.groupby(['Gender','Test_Number'])[['Height','Weight']] gb.apply(lambda x: 0) # 返回 Series gb.apply(lambda x: [0,0]) # 列表仍被视为标量,返回 Series(元素为列表)② Series 返回
gb.apply(lambda x: pd.Series([0,0], index=['a','b'])) # 结果:行索引=Gender+Test_Number,列索引=a/b③ DataFrame 返回
import numpy as np gb.apply(lambda x: pd.DataFrame( np.ones((2,2)), index=['a','b'], columns=pd.MultiIndex.from_tuples([('w','x'),('y','z')]) )) # 结果:行索引=Gender+Test_Number+a/b,列索引=多级(w/x, y/z)注:
- 返回标量→ 结果是Series,结构最简洁。
- 返回 Series→ 结果是DataFrame,列由 Series 索引决定。
- 返回 DataFrame→ 结果是多级索引 DataFrame,行 / 列都会嵌套。
apply是处理跨列分组计算的核心工具,尤其适合需要多列联动的复杂逻辑
