github star gitee star atomgit star PyPI Downloads AI 编程 AI 交流群

大家好,我是正在实战各种AI项目的程序员晚枫。

今天学习Pandas最强大的功能——groupby分组聚合

无论是做销售报表、用户分析还是运营统计,groupby都是必不可少的工具。掌握它,你就能像写SQL一样灵活地分析数据。


基础语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import numpy as np

# 示例数据
df = pd.DataFrame({
'部门': ['技术', '销售', '技术', '销售', '人事', '技术'],
'姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八'],
'销售额': [150000, 120000, 180000, 200000, 80000, 160000],
'订单数': [30, 25, 35, 40, 15, 32]
})

# 基础分组聚合
dept_sales = df.groupby('部门')['销售额'].sum()
print(dept_sales)

常用聚合函数

单函数聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 求和
df.groupby('部门')['销售额'].sum()

# 平均值
df.groupby('部门')['销售额'].mean()

# 计数
df.groupby('部门').size() # 包括NaN
df.groupby('部门')['姓名'].count() # 不包括NaN

# 最值
df.groupby('部门')['销售额'].max()
df.groupby('部门')['销售额'].min()

# 标准差、方差
df.groupby('部门')['销售额'].std()
df.groupby('部门')['销售额'].var()

多函数agg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 对单列应用多个函数
df.groupby('部门')['销售额'].agg(['sum', 'mean', 'count', 'max'])

# 对不同列应用不同函数
df.groupby('部门').agg({
'销售额': ['sum', 'mean'],
'订单数': ['sum', 'count']
})

# 自定义列名
df.groupby('部门').agg(
总销售额=('销售额', 'sum'),
平均销售额=('销售额', 'mean'),
总订单=('订单数', 'sum')
)

多列分组

1
2
3
4
5
6
7
8
9
10
11
# 按部门和年份分组(假设有年份列)
df.groupby(['部门', '年份'])['销售额'].sum()

# 多级索引结果
result = df.groupby(['部门', '年份']).agg({
'销售额': 'sum',
'订单数': 'sum'
})

# 重置索引变成普通DataFrame
result = result.reset_index()

transform:保持原数据结构

1
2
3
4
5
6
7
8
# 计算每个部门的平均薪资
df['部门平均'] = df.groupby('部门')['销售额'].transform('mean')

# 计算个人业绩占部门比例
df['占比'] = df['销售额'] / df['部门平均']

# 排名(组内)
df['部门排名'] = df.groupby('部门')['销售额'].rank(ascending=False)

apply:自定义复杂逻辑

1
2
3
4
5
6
7
8
9
10
# 定义自定义函数
def top_n(group, n=2):
"""返回每组前N名"""
return group.nlargest(n, '销售额')

# 应用
top_performers = df.groupby('部门').apply(top_n, n=2)

# lambda简化
df.groupby('部门').apply(lambda x: x['销售额'].sum() / x['订单数'].sum())

过滤筛选

1
2
3
4
5
6
7
8
# filter:筛选满足条件的组
# 只保留总销售额超过30万的部门
big_depts = df.groupby('部门').filter(
lambda x: x['销售额'].sum() > 300000
)

# 只保留人数大于2的部门
df.groupby('部门').filter(lambda x: len(x) > 2)

数据透视表pivot_table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 创建更复杂的示例数据
df = pd.DataFrame({
'日期': ['2024-01', '2024-01', '2024-02', '2024-02'] * 3,
'地区': ['北', '南', '北', '南'] * 3,
'产品': ['A']*4 + ['B']*4 + ['C']*4,
'销售额': [100, 150, 120, 180, 80, 120, 90, 140, 60, 90, 70, 110]
})

# 简单透视表
pivot = df.pivot_table(
values='销售额',
index='日期',
columns='产品',
aggfunc='sum'
)

# 复杂透视表
pivot = df.pivot_table(
values='销售额',
index=['日期', '地区'],
columns='产品',
aggfunc={'销售额': ['sum', 'mean']},
fill_value=0,
margins=True, # 添加总计
margins_name='合计'
)

实战:销售数据分析报表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import pandas as pd

# 模拟销售数据
sales = pd.DataFrame({
'订单ID': range(1, 101),
'日期': pd.date_range('2024-01-01', periods=100, freq='D'),
'销售员': np.random.choice(['张三', '李四', '王五', '赵六'], 100),
'地区': np.random.choice(['华北', '华东', '华南'], 100),
'产品': np.random.choice(['手机', '电脑', '平板'], 100),
'数量': np.random.randint(1, 10, 100),
'单价': np.random.choice([2999, 5999, 3999], 100)
})

sales['金额'] = sales['数量'] * sales['单价']

# 生成多维度报表
print("=== 销售分析报表 ===\n")

# 1. 按销售员统计
print("【销售员业绩TOP5】")
salesman_stats = sales.groupby('销售员').agg({
'金额': 'sum',
'订单ID': 'count'
}).rename(columns={'订单ID': '订单数'}).sort_values('金额', ascending=False)
print(salesman_stats)

# 2. 按地区统计
print("\n【地区销售对比】")
region_stats = sales.groupby('地区')['金额'].agg(['sum', 'mean', 'count'])
region_stats['占比'] = region_stats['sum'] / region_stats['sum'].sum()
print(region_stats)

# 3. 按月趋势
print("\n【月度销售趋势】")
sales['月份'] = sales['日期'].dt.to_period('M')
monthly = sales.groupby('月份')['金额'].sum()
print(monthly)

# 4. 产品交叉分析
print("\n【地区-产品交叉分析】")
cross = pd.pivot_table(sales, values='金额', index='地区',
columns='产品', aggfunc='sum')
print(cross)

性能优化技巧

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 分类类型加速
df['部门'] = df['部门'].astype('category')

# 2. 只选择需要的列
df[['部门', '销售额']].groupby('部门').sum()

# 3. 使用numba加速(复杂计算)
# import numba

# 4. 分块处理大数据
for chunk in pd.read_csv('big.csv', chunksize=10000):
result = chunk.groupby('key').sum()

性能对比:groupby vs 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import numpy as np

df = pd.DataFrame({
'group': np.random.choice(['A', 'B', 'C', 'D'], 100000),
'value': np.random.randn(100000)
})

# 循环方式
def groupby_loop(df):
result = {}
for g in df['group'].unique():
result[g] = df[df['group'] == g]['value'].mean()
return result

%timeit groupby_loop(df) # 约100ms

# groupby方式
%timeit df.groupby('group')['value'].mean() # 约1ms,快100倍!

进阶用法

多级分组与聚合

1
2
3
4
5
6
7
8
9
# 多级分组
result = df.groupby(['city', 'category']).agg({
'sales': ['sum', 'mean', 'count'],
'profit': ['sum', 'mean']
})

# 展平多层列名
result.columns = ['_'.join(col) for col in result.columns]
result = result.reset_index()

transform vs agg

1
2
3
4
5
6
7
8
9
# agg:每组返回一个值(压缩行数)
df.groupby('city')['salary'].mean() # 每城市一个均值

# transform:每组返回与原数据等长的结果
df['city_avg'] = df.groupby('city')['salary'].transform('mean')
# 每行的城市均值都算出来了,行数不变!

# 实战:计算每个人薪资在城市的排名百分位
df['salary_pct'] = df.groupby('city')['salary'].rank(pct=True)

apply自定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 自定义聚合函数
def top_n_sum(x, n=3):
return x.nlargest(n).sum()

df.groupby('city')['salary'].apply(top_n_sum, n=5)

# 每组返回Series
def describe_group(x):
return pd.Series({
'mean': x.mean(),
'std': x.std(),
'cv': x.std() / x.mean() if x.mean() != 0 else 0 # 变异系数
})

result = df.groupby('city')['salary'].apply(describe_group)

避坑指南

❌ 坑1:groupby后忘记reset_index

1
2
3
4
5
6
result = df.groupby('city')['salary'].mean()
print(type(result)) # Series,不是DataFrame

# 转回DataFrame
result = result.reset_index() # 或
result = df.groupby('city')['salary'].mean().to_frame().reset_index()

❌ 坑2:agg函数返回多值

1
2
3
4
5
6
7
8
9
10
11
# 错误方式
df.groupby('city').agg({'salary': ['mean', 'std']})
# 列名变成多层索引,不好用

# 更好的方式:命名聚合
result = df.groupby('city').agg(
avg_salary=('salary', 'mean'),
std_salary=('salary', 'std'),
max_salary=('salary', 'max'),
count=('salary', 'count')
)

实战案例:电商多维度销售分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import pandas as pd
import numpy as np

np.random.seed(42)
n = 20000
df = pd.DataFrame({
'order_id': range(n),
'date': pd.date_range('2025-01-01', periods=n, freq='30min'),
'city': np.random.choice(['北京', '上海', '广州', '深圳', '成都', '杭州'], n),
'category': np.random.choice(['电子产品', '服装', '食品', '家居'], n, p=[0.3, 0.3, 0.2, 0.2]),
'amount': np.random.exponential(500, n).round(2),
'quantity': np.random.randint(1, 10, n)
})

# 1. 城市维度分析
city_analysis = df.groupby('city').agg(
total_sales=('amount', 'sum'),
avg_order=('amount', 'mean'),
order_count=('order_id', 'count')
).round(2).sort_values('total_sales', ascending=False)
print("=== 城市销售排行 ===")
print(city_analysis)

# 2. 品类维度分析
cat_analysis = df.groupby('category').agg(
total_sales=('amount', 'sum'),
avg_quantity=('quantity', 'mean'),
order_count=('order_id', 'count')
).round(2)
print("\n=== 品类分析 ===")
print(cat_analysis)

# 3. 城市×品类交叉分析
cross = df.pivot_table(
values='amount', index='city', columns='category',
aggfunc='sum', margins=True, margins_name='合计'
).round(0)
print("\n=== 城市×品类交叉分析 ===")
print(cross)

# 4. 月度趋势
monthly = df.set_index('date').resample('M')['amount'].agg(['sum', 'mean', 'count'])
monthly.index = monthly.index.strftime('%Y-%m')
print("\n=== 月度趋势 ===")
print(monthly)

groupby的3个核心概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# groupby过程 = 拆分(Split) → 应用(Apply) → 合并(Combine)

# 1. Split:按key拆分
grouped = df.groupby('city')
for name, group in grouped:
print(f"{name}: {len(group)}条")

# 2. Apply:对每组应用函数
# agg: 每组一个值
# transform: 每组返回与原数据等长
# apply: 任意操作

# 3. Combine:合并结果
result = grouped['salary'].mean()

# 理解这3步,groupby就没问题了

分组后筛选

1
2
3
4
5
6
7
8
# 筛选符合条件的组
# 例如:只保留订单数>100的城市
city_counts = df.groupby('city').size()
big_cities = city_counts[city_counts > 100].index
result = df[df['city'].isin(big_cities)]

# 更简洁的写法:filter
result = df.groupby('city').filter(lambda x: len(x) > 100)

下节预告

下一课我们将学习时间序列处理,掌握日期时间数据的分析方法。

👉 继续阅读:Pandas数据变换-时间序列处理


💬 加入学习交流群

扫码加入Python学习交流群,和数千名同学一起进步:

👉 点击加入交流群

群里不定期分享:

  • 数据分析实战案例
  • Python学习资料
  • 求职面试经验
  • 行业最新动态

推荐:AI Python数据分析实战营

🎁 限时福利:送《利用Python进行数据分析》实体书

👉 点击了解详情


课程导航

上一篇: Pandas数据变换-合并与连接

下一篇: Pandas数据变换-时间序列处理


PS:groupby是Pandas的灵魂。熟练掌握它,你就能应对90%的数据统计需求。



📚 推荐教材

主教材《Excel+Python 飞速搞定数据分析与处理(图灵出品)》

💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!