pin_drop当前位置:知识文库 ❯ 图文
pandas groupby分组聚合教程 - 数据分析核心技巧
groupby是pandas中最强大的数据分析工具之一,它遵循"拆分-应用-合并"(Split-Apply-Combine)模式。通过groupby,我们可以按特定条件将数据分组,然后对每组进行聚合、转换或过滤操作。
一、groupby基本原理
groupby的核心思想是"Split-Apply-Combine":
-
Split(拆分):根据条件将数据分成若干组
-
Apply(应用):对每组独立应用函数
-
Combine(合并):将各组结果合并返回
代码示例
import pandas as pd
import numpy as np
# 创建示例数据
df = pd.DataFrame({
'department': ['技术', '销售', '技术', '人事', '销售', '技术'],
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'],
'salary': [12000, 8000, 15000, 7000, 9000, 13000],
'age': [28, 32, 35, 25, 29, 30]
})
# 按部门分组
grouped = df.groupby('department')
# 查看分组
for name, group in grouped:
print(f"部门: {name}")
print(group)
print("-" * 40)二、常用聚合操作
分组后可以直接调用聚合函数,对每组数据进行统计。
代码示例
# 单列分组,单列聚合
dept_salary = df.groupby('department')['salary'].mean()
print("各部门平均薪资:")
print(dept_salary)
# 常用聚合函数
dept_stats = df.groupby('department')['salary'].agg([
'count', # 数量
'mean', # 平均值
'sum', # 总和
'min', # 最小值
'max', # 最大值
'std' # 标准差
])
print("各部门薪资统计:")
print(dept_stats)
# 使用 describe 获取完整描述性统计
dept_desc = df.groupby('department')['salary'].describe()
print(dept_desc)三、多列分组与agg函数
1. 多列分组
代码示例
# 按部门和年龄分组
grouped = df.groupby(['department', 'age'])
# 多列分组的聚合结果
result = df.groupby(['department', 'age'])['salary'].mean()
print(result)
# 使用 reset_index 展平多级索引
result_flat = df.groupby(['department', 'age'])['salary'].mean().reset_index()
print(result_flat)2. 命名聚合(Named Aggregation)
代码示例
# pandas 0.25+ 支持的命名聚合
result = df.groupby('department').agg(
avg_salary=('salary', 'mean'),
max_salary=('salary', 'max'),
total_salary=('salary', 'sum'),
emp_count=('name', 'count'),
avg_age=('age', 'mean')
)
print("命名聚合结果:")
print(result)3. 对不同列应用不同聚合函数
代码示例
# 使用字典指定每列的聚合函数
result = df.groupby('department').agg({
'salary': ['mean', 'max', 'sum'],
'age': ['mean', 'min'],
'name': 'count'
})
print(result)四、transform与apply
1. transform:保持原索引长度
transform返回与原DataFrame相同长度的结果,常用于添加分组统计列。
代码示例
# 计算每个员工薪资与部门平均薪资的差值
df['dept_avg_salary'] = df.groupby('department')['salary'].transform('mean')
df['salary_diff'] = df['salary'] - df['dept_avg_salary']
print(df[['name', 'department', 'salary', 'dept_avg_salary', 'salary_diff']])
# 分组排名
df['salary_rank'] = df.groupby('department')['salary'].rank(
method='dense', ascending=False
)
print(df[['name', 'department', 'salary', 'salary_rank']])
# 分组内标准化
df['salary_zscore'] = df.groupby('department')['salary'].transform(
lambda x: (x - x.mean()) / x.std()
)2. apply:灵活应用自定义函数
代码示例
# 自定义聚合函数
def salary_stats(group):
return pd.Series({
'avg_salary': group['salary'].mean(),
'salary_range': group['salary'].max() - group['salary'].min(),
'top_earner': group.loc[group['salary'].idxmax(), 'name']
})
result = df.groupby('department').apply(salary_stats)
print(result)
# 返回多行结果的apply
def top2_earners(group):
return group.nlargest(2, 'salary')[['name', 'salary']]
result = df.groupby('department').apply(top2_earners)
print(result)提示:
agg用于聚合(结果行数≤分组数),transform用于转换(结果行数=原数据行数),apply更灵活但性能较低。优先使用agg和transform,需要复杂逻辑时再用apply。
五、filter过滤分组
filter用于过滤整个分组,而不是过滤组内行。
代码示例
# 筛选员工数量大于等于2的部门
dept_filtered = df.groupby('department').filter(lambda x: len(x) >= 2)
print("员工数≥2的部门:")
print(dept_filtered)
# 筛选平均薪资大于10000的部门
dept_filtered = df.groupby('department').filter(
lambda x: x['salary'].mean() > 10000
)
print("平均薪资>10000的部门:")
print(dept_filtered)六、实际应用场景
场景一:销售数据统计
代码示例
# 销售数据
sales = pd.DataFrame({
'date': pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-01',
'2024-01-03', '2024-01-02', '2024-01-01']),
'region': ['华东', '华北', '华东', '华南', '华东', '华北'],
'product': ['A', 'A', 'B', 'A', 'A', 'B'],
'amount': [5000, 3000, 8000, 6000, 4000, 2000],
'salesperson': ['张三', '李四', '王五', '赵六', '张三', '李四']
})
# 按区域和产品统计
result = sales.groupby(['region', 'product']).agg(
total_sales=('amount', 'sum'),
avg_sales=('amount', 'mean'),
order_count=('amount', 'count'),
top_salesperson=('salesperson', lambda x: x.value_counts().index[0])
).reset_index()
print(result)场景二:计算占比与累计
代码示例
# 计算每个部门薪资占总薪资的比例
df['salary_pct'] = df['salary'] / df['salary'].sum() * 100
# 计算每个部门薪资占该部门总薪资的比例
df['dept_salary_pct'] = (
df['salary'] / df.groupby('department')['salary'].transform('sum') * 100
)
# 部门内累计薪资
df['cum_salary'] = df.sort_values(['department', 'salary']).groupby('department')['salary'].cumsum()
print(df[['name', 'department', 'salary', 'dept_salary_pct', 'cum_salary']])场景三:透视表与交叉分析
代码示例
# 使用 pivot_table 进行交叉分析
pivot = df.pivot_table(
values='salary',
index='department',
columns='age',
aggfunc='mean',
fill_value=0,
margins=True, # 添加行列总计
margins_name='总计'
)
print(pivot)
# 等价于groupby的写法
grouped = df.groupby(['department', 'age'])['salary'].mean().unstack(fill_value=0)
print(grouped)小贴士
性能优化:避免在groupby中使用自定义Python函数(apply),尽量使用内置的聚合函数(mean、sum、count等)。对于大数据集,可以先使用sort=False参数跳过排序步骤提升性能:df.groupby('col', sort=False)。更多详情参考pandas groupby官方文档。
常见问题
agg、transform和apply应该如何选择?
如果需要将每组聚合成单个值(如求和、平均),使用agg;如果需要对每组计算后将结果广播回原始数据长度(如计算组内均值填充新列),使用transform;如果需要执行复杂的自定义逻辑且无法用agg/transform实现,使用apply。优先使用agg和transform,因为它们性能更好。
groupby 后如何保留所有原始列?
默认情况下聚合后只保留分组列和聚合列。如果需要保留原始列,可以使用transform将聚合结果添加为新列,或者使用first()/last()等聚合函数:df.groupby('col').agg({'col1': 'mean', 'col2': 'first', 'col3': 'last'})。
分组后为什么结果索引是多级的?如何展平?
当按多列分组或使用多个聚合函数时,结果会有多级索引。可以使用reset_index()展平:df.groupby(['a','b']).mean().reset_index()。对于多级列名,可以用df.columns = ['_'.join(col).strip() for col in df.columns.values]展平列名。
groupby 处理大数据集时很慢怎么办?
可以尝试以下优化:1) 设置sort=False跳过排序;2) 使用内置聚合函数代替apply;3) 考虑使用polars或duckdb等更快的替代库;4) 对分组列使用Categorical类型减少内存。
练习1
创建一份学生成绩数据(学生姓名、科目、成绩),计算每个学生的平均分、最高分、最低分,并找出每个科目成绩最高的学生。
练习2
使用transform计算每个部门内员工的薪资与部门平均薪资的差值,并标记出高于部门平均薪资的员工。
练习3
有一份订单数据,使用groupby分析:各地区的销售总额、订单数、平均订单金额,并使用filter筛选出订单数超过5的地区。
本文涉及AI创作
内容由AI创作,请仔细甄别