分组聚合 #
分组聚合概述 #
分组聚合是数据分析中最常用的操作之一,Pandas 提供了强大的 groupby 功能。
text
┌─────────────────────────────────────────────────────────────┐
│ 分组聚合流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原始数据 │
│ ┌─────┬─────┬─────┐ │
│ │ A │ B │ C │ │
│ ├─────┼─────┼─────┤ │
│ │ 1 │ x │ 10 │ │
│ │ 1 │ y │ 20 │ │
│ │ 2 │ x │ 30 │ │
│ │ 2 │ y │ 40 │ │
│ └─────┴─────┴─────┘ │
│ │ │
│ ▼ │
│ 分组 (groupby) │
│ ┌─────┬─────┐ │
│ │ A=1 │ A=2 │ │
│ └─────┴─────┘ │
│ │ │
│ ▼ │
│ 聚合 (agg) │
│ ┌─────┬──────┐ │
│ │ A=1 │ 15 │ │
│ │ A=2 │ 35 │ │
│ └─────┴──────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
准备数据 #
python
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'department': np.random.choice(['Sales', 'Engineering', 'Marketing'], 100),
'team': np.random.choice(['A', 'B', 'C'], 100),
'employee_id': range(1, 101),
'salary': np.random.randint(40000, 100000, 100),
'performance': np.random.randint(60, 100, 100),
'years_exp': np.random.randint(1, 15, 100)
})
print(df.head())
基本分组 #
创建分组对象 #
python
# 单列分组
grouped = df.groupby('department')
print(grouped)
# 查看分组
print(grouped.groups)
print(grouped.size())
# 遍历分组
for name, group in grouped:
print(f"Department: {name}")
print(group.head())
单列分组聚合 #
python
# 单列聚合
print(df.groupby('department')['salary'].mean())
# 多列聚合
print(df.groupby('department')[['salary', 'performance']].mean())
# 所有数值列聚合
print(df.groupby('department').mean(numeric_only=True))
多列分组 #
python
# 多列分组
print(df.groupby(['department', 'team'])['salary'].mean())
# 分组后选择多列
print(df.groupby(['department', 'team'])[['salary', 'performance']].mean())
聚合函数 #
内置聚合函数 #
python
# 常用聚合函数
print(df.groupby('department')['salary'].sum()) # 求和
print(df.groupby('department')['salary'].mean()) # 平均值
print(df.groupby('department')['salary'].median()) # 中位数
print(df.groupby('department')['salary'].std()) # 标准差
print(df.groupby('department')['salary'].var()) # 方差
print(df.groupby('department')['salary'].min()) # 最小值
print(df.groupby('department')['salary'].max()) # 最大值
print(df.groupby('department')['salary'].count()) # 计数
print(df.groupby('department')['salary'].first()) # 第一个值
print(df.groupby('department')['salary'].last()) # 最后一个值
agg 方法 #
python
# 单列多聚合
print(df.groupby('department')['salary'].agg(['mean', 'std', 'count']))
# 重命名聚合列
print(df.groupby('department')['salary'].agg(
mean_salary='mean',
std_salary='std',
count='count'
))
# 多列多聚合
print(df.groupby('department').agg({
'salary': ['mean', 'std'],
'performance': ['mean', 'max'],
'employee_id': 'count'
}))
# 多列自定义命名
print(df.groupby('department').agg(
mean_salary=('salary', 'mean'),
std_salary=('salary', 'std'),
mean_perf=('performance', 'mean'),
count=('employee_id', 'count')
))
自定义聚合函数 #
python
# 使用 lambda
print(df.groupby('department')['salary'].agg(lambda x: x.max() - x.min()))
# 自定义函数
def range_func(x):
return x.max() - x.min()
def iqr_func(x):
return x.quantile(0.75) - x.quantile(0.25)
print(df.groupby('department')['salary'].agg(['mean', range_func, iqr_func]))
# 多个自定义函数
print(df.groupby('department')['salary'].agg({
'range': range_func,
'iqr': iqr_func,
'cv': lambda x: x.std() / x.mean()
}))
同时应用多个函数 #
python
# 使用列表
print(df.groupby('department')['salary'].agg(['mean', 'std', 'min', 'max']))
# 使用字典
print(df.groupby('department').agg({
'salary': ['mean', 'std'],
'performance': ['mean', 'max'],
'years_exp': 'mean'
}))
# 命名聚合(推荐)
print(df.groupby('department').agg(
avg_salary=('salary', 'mean'),
std_salary=('salary', 'std'),
avg_performance=('performance', 'mean'),
employee_count=('employee_id', 'count')
))
transform 方法 #
transform 返回与原数据相同形状的结果,常用于组内标准化。
python
# 组内标准化
df['salary_std'] = df.groupby('department')['salary'].transform(
lambda x: (x - x.mean()) / x.std()
)
# 组内排名
df['salary_rank'] = df.groupby('department')['salary'].transform(
lambda x: x.rank(ascending=False)
)
# 组内填充缺失值
df['salary_filled'] = df.groupby('department')['salary'].transform(
lambda x: x.fillna(x.mean())
)
# 组内累计求和
df['cumsum_salary'] = df.groupby('department')['salary'].transform('cumsum')
# 组内累计计数
df['cumcount'] = df.groupby('department').cumcount()
apply 方法 #
apply 可以对每个分组应用任意函数,返回值可以是标量、Series 或 DataFrame。
python
# 返回标量
print(df.groupby('department').apply(lambda x: x['salary'].max() - x['salary'].min()))
# 返回 Series
def top_n(df, n=3, column='salary'):
return df.nlargest(n, column)
print(df.groupby('department').apply(top_n))
# 返回 DataFrame
def summary(group):
return pd.DataFrame({
'mean': group.mean(numeric_only=True),
'std': group.std(numeric_only=True)
})
print(df.groupby('department').apply(summary))
filter 方法 #
filter 根据分组条件过滤整个分组。
python
# 过滤小分组
print(df.groupby('department').filter(lambda x: len(x) > 30))
# 过滤满足条件的分组
print(df.groupby('department').filter(lambda x: x['salary'].mean() > 60000))
# 过滤后保留原索引
filtered = df.groupby('department').filter(lambda x: x['performance'].mean() > 75)
print(filtered)
分组操作技巧 #
分组后排序 #
python
# 分组后排序取前 N
print(df.groupby('department').apply(lambda x: x.nlargest(3, 'salary')))
# 分组后排序
print(df.groupby('department')['salary'].rank(ascending=False))
分组后采样 #
python
# 每组随机采样
print(df.groupby('department').sample(2))
# 每组采样比例
print(df.groupby('department').sample(frac=0.5))
分组后累计操作 #
python
# 累计求和
print(df.groupby('department')['salary'].cumsum())
# 累计最大值
print(df.groupby('department')['salary'].cummax())
# 累计最小值
print(df.groupby('department')['salary'].cummin())
# 累计计数
print(df.groupby('department').cumcount())
分组后滚动窗口 #
python
# 分组后滚动平均
print(df.groupby('department')['salary'].rolling(3).mean())
# 分组后扩展窗口
print(df.groupby('department')['salary'].expanding().mean())
透视表 #
基本透视表 #
python
# 简单透视表
print(pd.pivot_table(df, values='salary', index='department', aggfunc='mean'))
# 多值
print(pd.pivot_table(df,
values=['salary', 'performance'],
index='department',
aggfunc='mean'))
# 多索引
print(pd.pivot_table(df,
values='salary',
index=['department', 'team'],
aggfunc='mean'))
# 多列
print(pd.pivot_table(df,
values='salary',
index='department',
columns='team',
aggfunc='mean'))
# 填充缺失值
print(pd.pivot_table(df,
values='salary',
index='department',
columns='team',
aggfunc='mean',
fill_value=0))
# 添加汇总
print(pd.pivot_table(df,
values='salary',
index='department',
columns='team',
aggfunc='mean',
margins=True))
多聚合函数透视表 #
python
print(pd.pivot_table(df,
values=['salary', 'performance'],
index='department',
columns='team',
aggfunc={'salary': 'mean', 'performance': 'max'}))
分组时间序列 #
python
# 创建时间序列数据
dates = pd.date_range('2024-01-01', periods=100, freq='D')
df_ts = pd.DataFrame({
'date': dates,
'category': np.random.choice(['A', 'B'], 100),
'value': np.random.randn(100).cumsum()
})
df_ts.set_index('date', inplace=True)
# 按月分组
print(df_ts.groupby('category').resample('M').mean())
# 按周分组
print(df_ts.groupby('category').resample('W').sum())
性能优化 #
使用内置函数 #
python
# 快:内置函数
df.groupby('department')['salary'].mean()
# 慢:自定义函数
df.groupby('department')['salary'].agg(lambda x: x.mean())
避免重复分组 #
python
# 不推荐:重复分组
df.groupby('department')['salary'].mean()
df.groupby('department')['performance'].mean()
# 推荐:一次分组
grouped = df.groupby('department')
grouped['salary'].mean()
grouped['performance'].mean()
# 或使用 agg
df.groupby('department').agg({'salary': 'mean', 'performance': 'mean'})
使用分类类型 #
python
# 分组列使用 category 类型可以加速
df['department'] = df['department'].astype('category')
df.groupby('department')['salary'].mean()
实用案例 #
分组统计报告 #
python
def group_report(df, group_col, value_col):
return df.groupby(group_col)[value_col].agg([
('count', 'count'),
('mean', 'mean'),
('std', 'std'),
('min', 'min'),
('25%', lambda x: x.quantile(0.25)),
('50%', 'median'),
('75%', lambda x: x.quantile(0.75)),
('max', 'max')
])
print(group_report(df, 'department', 'salary'))
分组 Top N #
python
def top_n_per_group(df, group_col, value_col, n=3):
return df.groupby(group_col).apply(
lambda x: x.nlargest(n, value_col)
).reset_index(drop=True)
print(top_n_per_group(df, 'department', 'salary', n=3))
下一步 #
掌握了分组聚合后,接下来学习 合并连接,了解如何合并多个 DataFrame!
最后更新:2026-04-04