Pandas 数据分组聚合 GroupBy 操作
Pandas 数据分组聚合 GroupBy 操作
1. 为什么需要分组聚合?
在数据分析中,我们经常需要将数据按照某种类别进行划分,然后计算各组的统计量(如总和、均值、计数等)。Pandas 的 groupby 操作正是为此设计,它遵循分裂-应用-合并(split-apply-combine)的策略:
- 分裂:根据一个或多个键将数据拆分成多个组;
- 应用:对每个组独立执行某种操作(聚合、变换、过滤);
- 合并:将结果组合成一个新的数据结构。
它类似于 SQL 中的 GROUP BY,但功能更强大、语法更灵活。本教程将带你从零掌握 Pandas 的分组聚合操作。
2. 准备示例数据
我们使用经典的泰坦尼克号数据集,其中包含乘客的生存情况、船舱等级、性别、年龄等信息。
import pandas as pd
# 读取在线数据集
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
# 查看前5行
print(df.head())
主要列名及含义:
Survived:0 死亡,1 生还Pclass:乘客等级(1 = 头等舱,2 = 二等舱,3 = 三等舱)Sex:性别Age:年龄Fare:票价
3. GroupBy 对象的基本创建
使用 df.groupby('列名') 即可创建一个 GroupBy 对象。它本身不返回具体数据,只是一个分组映射。
# 按船舱等级分组
grouped = df.groupby('Pclass')
print(grouped) # <pandas.core.groupby.generic.DataFrameGroupBy object at ...>
此时还没有进行任何计算,只有当你调用聚合函数时,才会真正执行运算。
4. 常用内置聚合函数
Pandas 提供了丰富的聚合方法,可以直接应用于 GroupBy 对象。
| 方法 | 说明 |
|---|---|
mean() |
组均值 |
sum() |
组总和 |
size() |
组大小(包含 NaN) |
count() |
组中非空值的数量 |
std() |
组标准差 |
min() / max() |
组最小值 / 最大值 |
first() / last() |
组内第一个 / 最后一个值 |
示例1:计算各等级船舱的平均票价
print(df.groupby('Pclass')['Fare'].mean())
输出:
Pclass
1 84.154687
2 20.662183
3 13.675550
Name: Fare, dtype: float64
示例2:统计各性别的生还人数(按生还列求和)
print(df.groupby('Sex')['Survived'].sum())
输出:
Sex
female 233
male 109
Name: Survived, dtype: int64
注意:
size()返回的是包含缺失值的组大小,而count()只统计非空值。分组计数时优先使用size()。- 如果只选择一列进行聚合,返回的是
Series;若不指定列,聚合会应用到所有数值列,返回DataFrame。
5. 按多列分组
可以向 groupby() 传入一个列表,实现多个键的分组。
# 按等级和性别分组,计算平均年龄
multi_group = df.groupby(['Pclass', 'Sex'])['Age'].mean()
print(multi_group)
输出:
Pclass Sex
1 female 34.611765
male 41.281386
2 female 28.722973
male 30.740707
3 female 21.750000
male 26.507589
Name: Age, dtype: float64
结果拥有多层索引(MultiIndex)。可以通过 .unstack() 将其展开为更易读的表格形式:
print(multi_group.unstack())
6. 使用 agg() 同时应用多个聚合
agg() 方法(别名 aggregate)可以在分组后对不同的列应用不同的聚合函数,或对同一列应用多个函数。
6.1 对单列应用多个聚合
# 对票价列计算均值和标准差
df.groupby('Pclass')['Fare'].agg(['mean', 'std'])
输出:
mean std
Pclass
1 84.154687 78.746457
2 20.662183 13.417399
3 13.675550 11.778142
6.2 对不同列指定不同的聚合
传入字典,键为列名,值为聚合函数(字符串或函数列表)。
df.groupby('Pclass').agg({
'Age': 'median', # 年龄中位数
'Fare': ['min', 'max'], # 票价最小值和最大值
'Survived': 'mean' # 生存率(均值)
})
此方法返回一个多层列索引的 DataFrame。
6.3 自定义聚合函数
可以将自定义的 Python 函数传递给 agg,例如计算极差(最大值-最小值)。
def range_func(x):
return x.max() - x.min()
df.groupby('Pclass')['Age'].agg(range_func)
7. 高级 GroupBy 方法:transform、filter、apply
除了聚合,GroupBy 还支持更灵活的逐组操作。
7.1 transform:组内广播
transform 会将函数的结果按照原索引广播,保持与原数据相同的形状。常用于缺失值填充、标准化等。
# 用各船舱等级的平均年龄填充年龄缺失值
df['Age'] = df.groupby('Pclass')['Age'].transform(lambda x: x.fillna(x.median()))
此处 transform 返回与传入对象同样长度的 Series,可直接用于赋值。
7.2 filter:根据组特征筛选整组数据
filter 根据每个组的布尔条件决定保留或丢弃整个组。
# 只保留组内成员数大于 200 的组(即只保留人数较多的等级)
filtered = df.groupby('Pclass').filter(lambda x: len(x) > 200)
print(filtered['Pclass'].unique()) # 所有等级都大于200?实际上三个组都大于200,可尝试其他阈值
更实际的用法:保留平均票价大于20的组。
# 这一步需要先计算组平均,然后筛选
avg_fare = df.groupby('Pclass')['Fare'].transform('mean')
filtered_df = df[avg_fare > 20] # 但这是对所有行基于分组均值筛选,并非 filter
纯粹的 filter 示例:
# 仅保留组内 Fare 标准差小于 40 的等级
filtered = df.groupby('Pclass').filter(lambda x: x['Fare'].std() < 40)
7.3 apply:最通用的分组操作
apply 可以将任意函数应用到每个组,并灵活返回标量、Series 或 DataFrame。当内置方法无法满足时使用。
# 提取每组票价最高的前2名乘客信息
def top2_fare(group):
return group.nlargest(2, 'Fare')
df.groupby('Pclass').apply(top2_fare)
注意:apply 可能会在结果中添加分组键作为索引,可以通过 .reset_index(drop=True) 清理。
8. 分组后迭代
GroupBy 对象支持迭代,返回元组 (组名, 子DataFrame)。
for name, group in df.groupby('Pclass'):
print(f"等级 {name} 的平均票价为 {group['Fare'].mean():.2f}")
9. 总结与最佳实践
- 首选内置聚合:标准聚合(sum、mean、size 等)性能最高,可直接调用。
- 使用
agg执行复杂聚合:多函数、多列聚合时,agg是最清晰的方式。 transform用于保持形状:需填充组内均值或标准化特征时,避免手动循环。filter进行组级筛选:相比先聚合再合并索引,filter更直接。apply作为万能武器:当其他方法无法实现时使用,但要注意性能,大数据集下可能较慢。
通过本教程,你已经掌握了 Pandas 分组聚合的核心技术。现在可以尝试用真实数据练习,例如分析销售数据中各地区的销售额总和、各产品类别的平均折扣等,逐步提升你的数据分析效率。