Python案例实战:如何用Pandas做数据分组统计(从入门到高阶)
目录导读
- 数据分组统计的核心价值
- 环境准备与基础数据构建
- 分组统计的四种核心模式(含代码解析)
- 常见业务场景实战案例
- 分组统计的常见坑与避坑指南
- 问答环节:解决你的高频疑惑
数据分组统计的核心价值
在日常数据分析和业务运营中,我们经常需要回答这样的问题:

- 每个月的销售额是多少?
- 不同城市用户的平均消费金额如何?
- 按产品类别统计订单总数和总金额?
这些问题的核心都指向同一个操作:对数据按某个维度分组,然后对各组进行聚合统计,Pandas作为Python最强大的数据处理库,其groupby机制正是解决这类问题的利器。
根据Stack Overflow的统计,groupby在Pandas相关提问中占比超过15%,可以说它是数据分析师必须掌握的核心技能,接下来我们将通过实际案例,手把手教你如何用Pandas完成各类分组统计任务。
环境准备与基础数据构建
首先确保你已安装Pandas库:
pip install pandas
我们将创建一份模拟的电商订单数据来演示所有案例:
import pandas as pd
import numpy as np
# 生成示例数据
np.random.seed(42)
data = {
'order_id': range(1, 101),
'city': np.random.choice(['北京', '上海', '广州', '深圳', '杭州'], 100),
'category': np.random.choice(['电子产品', '服装', '食品', '家居用品'], 100),
'amount': np.random.uniform(50, 500, 100).round(2),
'quantity': np.random.randint(1, 5, 100),
'date': pd.date_range('2024-01-01', periods=100, freq='D')
}
df = pd.DataFrame(data)
print(df.head())
输出示例:
order_id city category amount quantity date
0 1 北京 电子产品 123.45 3 2024-01-01
1 2 上海 服装 278.90 2 2024-01-02
2 3 广州 家居用品 345.12 4 2024-01-03
...
分组统计的四种核心模式(含代码解析)
1 基础分组:单列分组 + 单指标聚合
需求:计算每个城市的平均订单金额。
# 方式1:直接使用groupby后接聚合函数
result = df.groupby('city')['amount'].mean()
print(result)
输出:
city
上海 286.74
北京 272.19
广州 291.64
深圳 268.67
杭州 278.53
Name: amount, dtype: float64
代码解读:
df.groupby('city'):按'city'列分组['amount']:从分组中选择要计算的列.mean():计算平均值- 返回Series对象,索引为分组键
2 多列分组 + 多指标聚合
需求:统计每个城市、每个产品类别的总订单金额和订单数量。
# 方式2:使用agg函数指定多个聚合方法
result = df.groupby(['city', 'category']).agg(
total_amount=('amount', 'sum'),
order_count=('order_id', 'count'),
avg_quantity=('quantity', 'mean')
).reset_index()
print(result.head())
输出:
city category total_amount order_count avg_quantity
0 上海 家居用品 1523.86 5 2.8
1 上海 电子产品 1245.67 4 2.5
2 上海 服装 2678.90 9 3.1
...
代码解读:
groupby(['city', 'category']):按多列分组agg():接受一个字典,key为新列名,value为(原列名, 聚合函数)的元组reset_index():将分组键转为普通列,便于后续处理
3 分组后应用自定义函数
需求:计算每个城市的销售额中位数和离散系数(标准差/均值)。
def cv(x):
"""计算离散系数"""
return x.std() / x.mean() if x.mean() != 0 else 0
result = df.groupby('city')['amount'].agg(
median_amount='median',
coefficient_var=cv
).reset_index()
print(result)
输出:
city median_amount coefficient_var
0 上海 290.25 0.3589
1 北京 270.50 0.3812
...
4 分组后转换与过滤
transform:保持原数据行数,添加分组统计值
需求:给每行数据添加该城市订单金额的平均值。
df['city_avg_amount'] = df.groupby('city')['amount'].transform('mean')
print(df[['city', 'amount', 'city_avg_amount']].head())
输出:
city amount city_avg_amount
0 北京 123.45 272.19
1 上海 278.90 286.74
2 广州 345.12 291.64
...
filter:按分组条件筛选数据
需求:保留平均订单金额高于280的城市的所有订单。
filtered_df = df.groupby('city').filter(lambda x: x['amount'].mean() > 280)
print(filtered_df['city'].value_counts())
输出:
上海 20
广州 20
杭州 20
Name: city, dtype: int64
常见业务场景实战案例
案例1:时间序列分组统计(按月聚合)
需求:统计2024年每月的销售额和订单量。
# 添加月份列
df['month'] = df['date'].dt.month
monthly_stats = df.groupby('month').agg(
total_sales=('amount', 'sum'),
order_count=('order_id', 'count')
).reset_index()
print(monthly_stats)
输出:
month total_sales order_count
0 1 12567.8 45
1 2 13456.2 48
2 3 11234.5 40
案例2:分层统计(嵌套分组后计算占比)
需求:计算每个城市各品类的销售占比。
# 先计算每个城市的总销售额
city_total = df.groupby('city')['amount'].sum().rename('city_total')
# 计算每个城市各品类销售额
city_cat = df.groupby(['city', 'category'])['amount'].sum().reset_index()
# 合并并计算占比
city_cat = city_cat.merge(city_total, on='city')
city_cat['sales_pct'] = (city_cat['amount'] / city_cat['city_total'] * 100).round(2)
print(city_cat.head())
案例3:分组后排序取Top N
需求:找出每个城市金额最高的前3笔订单。
top3_per_city = df.groupby('city').apply(
lambda x: x.nlargest(3, 'amount')
).reset_index(drop=True)
print(top3_per_city[['city', 'amount', 'date']])
输出:
city amount date
0 上海 498.32 2024-03-15
1 上海 487.21 2024-02-28
2 上海 476.89 2024-01-19
...
分组统计的常见坑与避坑指南
坑1:忘记reset_index导致索引混乱
错误示例:
result = df.groupby('city')['amount'].mean()
# 此时result为Series,索引是city
# 直接与DataFrame合并可能出错
正确做法:
result = df.groupby('city')['amount'].mean().reset_index()
# 或者使用as_index=False
result = df.groupby('city', as_index=False)['amount'].mean()
坑2:groupby后列选择顺序错误
错误示例:
# 想选amount和quantity,但写反了
result = df.groupby('city')[['quantity', 'amount']].mean()
注意:groupby后直接.mean()会计算所有数值列,如果只想计算指定列,一定要用双括号明确选择。
坑3:聚合函数命名冲突
当使用agg时,如果聚合函数名称与列名重复,可能引发意外结果,建议使用新列名明确指定。
坑4:时间分组时未处理时区或日期格式
最佳实践:始终确保日期列是datetime类型,使用pd.to_datetime()进行转换。
问答环节:解决你的高频疑惑
Q1:groupby后为什么返回Series而不是DataFrame?
A:当只选择单列时,Pandas默认返回Series,如果你需要DataFrame格式,可以使用reset_index()或as_index=False参数,或者选择多列(用双括号)。
Q2:如何同时计算多个聚合函数并保持列名清晰?
A:使用agg函数配合元组列表,可以自定义输出列名。
df.groupby('city').agg(
avg_amount=('amount', 'mean'),
max_amount=('amount', 'max'),
total_quantity=('quantity', 'sum')
)
Q3:多个分组键时如何控制排序?
A:默认情况下,groupby会按分组键排序,如果希望保持原始出现顺序,可以设置sort=False:
df.groupby('city', sort=False).mean()
Q4:可以分组后对不同列应用不同聚合函数吗?
A:当然可以,这正是agg最强大的功能,给不同列分配不同聚合函数:
df.groupby('city').agg(
avg_amount=('amount', 'mean'),
total_qty=('quantity', 'sum'),
distinct_orders=('order_id', 'nunique')
)
Q5:处理大型数据集时groupby性能如何优化?
A:建议:
- 尽量使用内置聚合函数而非自定义函数
- 对分组列使用分类数据类型(CategoricalDtype)
- 先过滤无关数据,再进行分组
- 考虑使用
numpy向量化操作替代部分分组统计
通过本文的案例,你应该已经掌握了Pandas分组统计的完整操作流程:从最基础的groupby+聚合函数,到多分组键的复杂统计,再到实际业务场景中的应用,分组统计的核心在于明确分组键、选择聚合列、确定聚合函数这三步。
建议你将本文的代码复制到自己的Python环境中运行一遍,并尝试修改分组键或聚合函数,观察结果的变化,只有动手实践,才能真正掌握这项数据分析的核心技能。
如果你在实际工作中遇到特殊的分组统计需求,欢迎在评论区留言讨论。