第九章:分组聚合操作
📋 章节概述
本章将学习 Pandas 中强大的分组聚合功能。掌握这些方法后,你将能够高效地对数据进行分组统计、组内变换和复杂的数据分析。
🎯 学习目标
- 掌握 groupby() 方法进行数据分组
- 掌握常用聚合函数(sum, mean, count, min, max等)
- 理解 agg() 进行多列多聚合操作
- 掌握 transform() 进行组内变换
- 掌握 apply() 进行自定义分组操作
- 掌握 filter() 进行分组过滤
- 学会处理分组后的数据分析和统计
📊 知识结构图
分组聚合
groupby
数据分组
聚合函数
agg
多列聚合
transform
组内变换
apply
自定义操作
filter
分组过滤
单列分组
多列分组
获取分组
多列不同聚合
自定义列名
自定义函数
组内均值
组内排名
标准化
缺失值填充
🏢 实战场景
本章使用"电商销售数据分析"案例:
- 订单表:订单ID、客户ID、商品类别、销售金额、订单日期、地区
- 员工表:员工ID、姓名、部门、入职日期、基本工资、绩效评分
- 库存表:仓库、商品、类别、库存数量、成本价、销售价、利润
python
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
准备示例数据 - 分组聚合案例
python
np.random.seed(2026)
# 创建订单数据
df_orders = pd.DataFrame({
'订单ID': ['O' + str(i).zfill(4) for i in range(1, 21)],
'客户ID': ['C' + str(np.random.randint(1, 6)).zfill(3) for _ in range(20)],
'商品类别': np.random.choice(['电子产品', '服装', '食品', '家居'], 20),
'销售金额': np.random.randint(100, 5000, 20),
'订单日期': pd.date_range('2026-01-01', periods=20, freq='D'),
'地区': np.random.choice(['北京', '上海', '广州', '深圳'], 20)
})
# 创建员工数据
df_employees = pd.DataFrame({
'员工ID': ['E' + str(i).zfill(3) for i in range(1, 11)],
'姓名': ['张伟', '李娜', '王强', '刘洋', '陈静', '杨帆', '赵敏', '孙杰', '周丽', '吴刚'],
'部门': np.random.choice(['销售部', '技术部', '人事部', '财务部'], 10),
'入职日期': pd.date_range('2020-01-01', periods=10, freq='ME'),
'基本工资': np.random.randint(5000, 15000, 10),
'绩效评分': np.random.randint(60, 100, 10)
})
# 创建库存数据
df_inventory = pd.DataFrame({
'仓库': np.random.choice(['北京仓', '上海仓', '广州仓'], 15),
'商品': ['商品' + str(i) for i in range(1, 16)],
'类别': np.random.choice(['电子产品', '服装', '食品'], 15),
'库存数量': np.random.randint(10, 200, 15),
'成本价': np.random.randint(50, 500, 15),
'销售价': np.random.randint(100, 800, 15)
})
df_inventory['利润'] = df_inventory['销售价'] - df_inventory['成本价']
print("数据表预览:")
print(f"\n1. 订单表: {df_orders.shape}")
print(f"2. 员工表: {df_employees.shape}")
print(f"3. 库存表: {df_inventory.shape}")
数据表预览:
1. 订单表: (20, 6)
2. 员工表: (10, 6)
3. 库存表: (15, 7)
python
# 查看订单表
df_orders.head(8)
| | 订单ID | 客户ID | 商品类别 | 销售金额 | 订单日期 | 地区 |
| 0 | O0001 | C002 | 电子产品 | 4488 | 2026-01-01 | 深圳 |
| 1 | O0002 | C003 | 服装 | 2629 | 2026-01-02 | 广州 |
| 2 | O0003 | C001 | 食品 | 2057 | 2026-01-03 | 深圳 |
| 3 | O0004 | C005 | 服装 | 3821 | 2026-01-04 | 北京 |
| 4 | O0005 | C005 | 电子产品 | 1591 | 2026-01-05 | 深圳 |
| 5 | O0006 | C004 | 家居 | 2374 | 2026-01-06 | 广州 |
| 6 | O0007 | C005 | 家居 | 391 | 2026-01-07 | 北京 |
| 7 | O0008 | C005 | 服装 | 3388 | 2026-01-08 | 上海 |
|---|
python
# 查看员工表
df_employees
| | 员工ID | 姓名 | 部门 | 入职日期 | 基本工资 | 绩效评分 |
| 0 | E001 | 张伟 | 人事部 | 2020-01-31 | 13556 | 81 |
| 1 | E002 | 李娜 | 销售部 | 2020-02-29 | 13441 | 95 |
| 2 | E003 | 王强 | 财务部 | 2020-03-31 | 10552 | 79 |
| 3 | E004 | 刘洋 | 财务部 | 2020-04-30 | 5789 | 68 |
| 4 | E005 | 陈静 | 销售部 | 2020-05-31 | 12201 | 95 |
| 5 | E006 | 杨帆 | 销售部 | 2020-06-30 | 9325 | 93 |
| 6 | E007 | 赵敏 | 销售部 | 2020-07-31 | 9829 | 74 |
| 7 | E008 | 孙杰 | 财务部 | 2020-08-31 | 14485 | 91 |
| 8 | E009 | 周丽 | 财务部 | 2020-09-30 | 10846 | 64 |
| 9 | E010 | 吴刚 | 人事部 | 2020-10-31 | 5369 | 98 |
|---|
9.1 groupby() 基础分组
groupby() 是 Pandas 分组操作的核心,遵循 Split-Apply-Combine 模式。
Split
Apply
Combine
原始数据
分组
应用聚合
合并结果
9.1.1 groupby 的基本概念
python
# 基础 groupby 分组
print("基础 groupby 分组:")
print("按商品类别分组,查看分组对象:")
grouped = df_orders.groupby('商品类别')
print(f"分组对象类型: {type(grouped)}")
print(f"分组键: {list(grouped.groups.keys())}")
print(f"\n每个组的记录数:")
for name, group in grouped:
print(f" {name}: {len(group)} 条记录")
基础 groupby 分组:
按商品类别分组,查看分组对象:
分组对象类型: <class 'pandas.api.typing.DataFrameGroupBy'>
分组键: ['家居', '服装', '电子产品', '食品']
每个组的记录数:
家居: 5 条记录
服装: 7 条记录
电子产品: 5 条记录
食品: 3 条记录
python
# 查看分组后的数据
print("查看分组后的数据:")
print("获取'电子产品'组的数据:")
electronics = grouped.get_group('电子产品')
electronics[['订单ID', '客户ID', '销售金额']]
查看分组后的数据:
获取'电子产品'组的数据:
| | 订单ID | 客户ID | 销售金额 |
| 0 | O0001 | C002 | 4488 |
| 4 | O0005 | C005 | 1591 |
| 8 | O0009 | C003 | 1435 |
| 15 | O0016 | C001 | 634 |
| 17 | O0018 | C005 | 4271 |
|---|
python
# 多列分组
print("多列分组:")
print("按地区和商品类别分组:")
multi_grouped = df_orders.groupby(['地区', '商品类别'])
print(f"分组数量: {len(multi_grouped.groups)}")
print("\n各组记录数:")
for name, group in multi_grouped:
print(f" {name}: {len(group)} 条")
多列分组:
按地区和商品类别分组:
分组数量: 12
各组记录数:
('上海', '服装'): 2 条
('上海', '电子产品'): 1 条
('北京', '家居'): 3 条
('北京', '服装'): 3 条
('北京', '电子产品'): 1 条
('广州', '家居'): 2 条
('广州', '服装'): 1 条
('广州', '电子产品'): 1 条
('广州', '食品'): 1 条
('深圳', '服装'): 1 条
('深圳', '电子产品'): 2 条
('深圳', '食品'): 2 条
9.1.2 常用聚合函数
python
# 单列聚合 - 计算各类别销售总额
print("单列聚合 - 计算各类别销售总额:")
category_sum = df_orders.groupby('商品类别')['销售金额'].sum()
print(category_sum)
print(f"\n返回类型: {type(category_sum)}")
单列聚合 - 计算各类别销售总额:
商品类别
家居 7257
服装 16048
电子产品 12419
食品 3934
Name: 销售金额, dtype: int32
返回类型: <class 'pandas.Series'>
python
# 多列聚合 - 同时计算多个统计量
print("多列聚合 - 同时计算多个统计量:")
category_stats = df_orders.groupby('商品类别')['销售金额'].agg(['sum', 'mean', 'count', 'min', 'max'])
category_stats
多列聚合 - 同时计算多个统计量:
| | sum | mean | count | min | max |
| 商品类别 | | | | | |
| 家居 | 7257 | 1451.400000 | 5 | 173 | 4080 |
| 服装 | 16048 | 2292.571429 | 7 | 359 | 3821 |
| 电子产品 | 12419 | 2483.800000 | 5 | 634 | 4488 |
| 食品 | 3934 | 1311.333333 | 3 | 465 | 2057 |
|---|
python
# 多列同时聚合
print("多列同时聚合:")
dept_stats = df_employees.groupby('部门')[['基本工资', '绩效评分']].mean()
dept_stats
多列同时聚合:
| | 基本工资 | 绩效评分 |
| 部门 | | |
| 人事部 | 9462.5 | 89.50 |
| 财务部 | 10418.0 | 75.50 |
| 销售部 | 11199.0 | 89.25 |
|---|
python
# 使用 as_index=False 保持 DataFrame 格式
print("使用 as_index=False 保持 DataFrame 格式:")
category_df = df_orders.groupby('商品类别', as_index=False)['销售金额'].sum()
print(category_df)
print(f"\n返回类型: {type(category_df)}")
使用 as_index=False 保持 DataFrame 格式:
商品类别 销售金额
0 家居 7257
1 服装 16048
2 电子产品 12419
3 食品 3934
返回类型: <class 'pandas.DataFrame'>
9.2 agg() 高级聚合
agg() 允许对不同列应用不同的聚合函数,实现更灵活的统计分析。
agg高级聚合
多列不同聚合
自定义列名
自定义函数
列1: sum
列2: mean
总金额=销售额.sum
lambda函数
自定义统计量
9.2.1 多列多聚合
python
# 对不同列应用不同聚合函数
print("对不同列应用不同聚合函数:")
agg_result = df_employees.groupby('部门').agg({
'基本工资': ['mean', 'min', 'max'],
'绩效评分': ['mean', 'std']
})
agg_result
对不同列应用不同聚合函数:
| | 基本工资 ||| 绩效评分 ||
| | mean | min | max | mean | std |
| 部门 | | | | | |
| 人事部 | 9462.5 | 5369 | 13556 | 89.50 | 12.020815 |
| 财务部 | 10418.0 | 5789 | 14485 | 75.50 | 12.124356 |
| 销售部 | 11199.0 | 9325 | 13441 | 89.25 | 10.210289 |
|---|
python
# 使用自定义列名
print("使用自定义列名:")
agg_custom = df_orders.groupby('商品类别').agg(
总金额=('销售金额', 'sum'),
平均金额=('销售金额', 'mean'),
订单数=('订单ID', 'count'),
最大订单=('销售金额', 'max')
)
agg_custom
使用自定义列名:
| | 总金额 | 平均金额 | 订单数 | 最大订单 |
| 商品类别 | | | | |
| 家居 | 7257 | 1451.400000 | 5 | 4080 |
| 服装 | 16048 | 2292.571429 | 7 | 3821 |
| 电子产品 | 12419 | 2483.800000 | 5 | 4488 |
| 食品 | 3934 | 1311.333333 | 3 | 2057 |
|---|
python
# 多索引分组后的聚合
print("多索引分组后的聚合:")
multi_agg = df_orders.groupby(['地区', '商品类别']).agg({
'销售金额': ['sum', 'mean'],
'订单ID': 'count'
})
multi_agg
多索引分组后的聚合:
| | | 销售金额 || 订单ID |
| | | sum | mean | count |
| 地区 | 商品类别 | | | |
| 上海 | 服装 | 6854 | 3427.000000 | 2 |
| 上海 | 电子产品 | 4271 | 4271.000000 | 1 |
| 北京 | 家居 | 4644 | 1548.000000 | 3 |
| 北京 | 服装 | 5362 | 1787.333333 | 3 |
| 北京 | 电子产品 | 1435 | 1435.000000 | 1 |
| 广州 | 家居 | 2613 | 1306.500000 | 2 |
| 广州 | 服装 | 2629 | 2629.000000 | 1 |
| 广州 | 电子产品 | 634 | 634.000000 | 1 |
| 广州 | 食品 | 1412 | 1412.000000 | 1 |
| 深圳 | 服装 | 1203 | 1203.000000 | 1 |
| 深圳 | 电子产品 | 6079 | 3039.500000 | 2 |
| 深圳 | 食品 | 2522 | 1261.000000 | 2 |
|---|
9.2.2 自定义聚合函数
python
# 使用自定义聚合函数
print("使用自定义聚合函数:")
def range_func(x):
"""计算极差(最大值-最小值)"""
return x.max() - x.min()
def cv_func(x):
"""计算变异系数(标准差/均值)"""
return x.std() / x.mean() if x.mean() != 0 else 0
custom_agg = df_orders.groupby('商品类别')['销售金额'].agg([
('总和', 'sum'),
('平均值', 'mean'),
('极差', range_func),
('变异系数', cv_func)
])
custom_agg
使用自定义聚合函数:
| | 总和 | 平均值 | 极差 | 变异系数 |
| 商品类别 | | | | |
| 家居 | 7257 | 1451.400000 | 3907 | 1.192838 |
| 服装 | 16048 | 2292.571429 | 3462 | 0.595563 |
| 电子产品 | 12419 | 2483.800000 | 3854 | 0.712565 |
| 食品 | 3934 | 1311.333333 | 1592 | 0.610646 |
|---|
python
# 使用 lambda 函数
print("使用 lambda 函数:")
lambda_agg = df_inventory.groupby('类别')['库存数量'].agg([
('总量', 'sum'),
('平均值', 'mean'),
('中位数', lambda x: x.median()),
('第75百分位', lambda x: x.quantile(0.75))
])
lambda_agg
使用 lambda 函数:
| | 总量 | 平均值 | 中位数 | 第75百分位 |
| 类别 | | | | |
| 服装 | 681 | 136.2 | 146.0 | 163.0 |
| 电子产品 | 612 | 122.4 | 133.0 | 176.0 |
| 食品 | 509 | 101.8 | 103.0 | 122.0 |
|---|
9.3 transform() 组内变换
transform() 的特点:保持原始行数,为每条记录添加组统计信息。
返回
返回
普通聚合
组数行
transform
原行数
9.3.1 transform 基础应用
python
# 添加组内平均值列
print("添加组内平均值列:")
df_orders['类别平均销售额'] = df_orders.groupby('商品类别')['销售金额'].transform('mean')
df_orders['与类别平均差'] = df_orders['销售金额'] - df_orders['类别平均销售额']
df_orders[['订单ID', '商品类别', '销售金额', '类别平均销售额', '与类别平均差']].head(8)
添加组内平均值列:
| | 订单ID | 商品类别 | 销售金额 | 类别平均销售额 | 与类别平均差 |
| 0 | O0001 | 电子产品 | 4488 | 2483.800000 | 2004.200000 |
| 1 | O0002 | 服装 | 2629 | 2292.571429 | 336.428571 |
| 2 | O0003 | 食品 | 2057 | 1311.333333 | 745.666667 |
| 3 | O0004 | 服装 | 3821 | 2292.571429 | 1528.428571 |
| 4 | O0005 | 电子产品 | 1591 | 2483.800000 | -892.800000 |
| 5 | O0006 | 家居 | 2374 | 1451.400000 | 922.600000 |
| 6 | O0007 | 家居 | 391 | 1451.400000 | -1060.400000 |
| 7 | O0008 | 服装 | 3388 | 2292.571429 | 1095.428571 |
|---|
python
# 计算组内排名
print("计算组内排名:")
df_orders['类别内排名'] = df_orders.groupby('商品类别')['销售金额'].transform(
lambda x: x.rank(ascending=False)
)
df_orders[['订单ID', '商品类别', '销售金额', '类别内排名']].head(10)
计算组内排名:
| | 订单ID | 商品类别 | 销售金额 | 类别内排名 |
| 0 | O0001 | 电子产品 | 4488 | 1.0 |
| 1 | O0002 | 服装 | 2629 | 4.0 |
| 2 | O0003 | 食品 | 2057 | 1.0 |
| 3 | O0004 | 服装 | 3821 | 1.0 |
| 4 | O0005 | 电子产品 | 1591 | 3.0 |
| 5 | O0006 | 家居 | 2374 | 2.0 |
| 6 | O0007 | 家居 | 391 | 3.0 |
| 7 | O0008 | 服装 | 3388 | 3.0 |
| 8 | O0009 | 电子产品 | 1435 | 4.0 |
| 9 | O0010 | 服装 | 359 | 7.0 |
|---|
python
# 组内标准化(Z-score)
print("组内标准化(Z-score):")
df_employees['工资标准化'] = df_employees.groupby('部门')['基本工资'].transform(
lambda x: (x - x.mean()) / x.std()
)
df_employees[['姓名', '部门', '基本工资', '工资标准化']]
组内标准化(Z-score):
| | 姓名 | 部门 | 基本工资 | 工资标准化 |
| 0 | 张伟 | 人事部 | 13556 | 0.707107 |
| 1 | 李娜 | 销售部 | 13441 | 1.149147 |
| 2 | 王强 | 财务部 | 10552 | 0.037567 |
| 3 | 刘洋 | 财务部 | 5789 | -1.297750 |
| 4 | 陈静 | 销售部 | 12201 | 0.513580 |
| 5 | 杨帆 | 销售部 | 9325 | -0.960527 |
| 6 | 赵敏 | 销售部 | 9829 | -0.702200 |
| 7 | 孙杰 | 财务部 | 14485 | 1.140192 |
| 8 | 周丽 | 财务部 | 10846 | 0.119991 |
| 9 | 吴刚 | 人事部 | 5369 | -0.707107 |
|---|
9.3.2 transform 实际应用
python
# 计算占比
print("计算占比:")
df_inventory['类别库存占比'] = df_inventory.groupby('类别')['库存数量'].transform(
lambda x: x / x.sum() * 100
)
df_inventory[['商品', '类别', '库存数量', '类别库存占比']].round(2)
计算占比:
| | 商品 | 类别 | 库存数量 | 类别库存占比 |
| 0 | 商品1 | 服装 | 146 | 21.44 |
| 1 | 商品2 | 电子产品 | 58 | 9.48 |
| 2 | 商品3 | 食品 | 69 | 13.56 |
| 3 | 商品4 | 服装 | 177 | 25.99 |
| 4 | 商品5 | 服装 | 79 | 11.60 |
| 5 | 商品6 | 电子产品 | 133 | 21.73 |
| 6 | 商品7 | 食品 | 122 | 23.97 |
| 7 | 商品8 | 食品 | 103 | 20.24 |
| 8 | 商品9 | 电子产品 | 188 | 30.72 |
| 9 | 商品10 | 食品 | 197 | 38.70 |
| 10 | 商品11 | 服装 | 116 | 17.03 |
| 11 | 商品12 | 食品 | 18 | 3.54 |
| 12 | 商品13 | 服装 | 163 | 23.94 |
| 13 | 商品14 | 电子产品 | 176 | 28.76 |
| 14 | 商品15 | 电子产品 | 57 | 9.31 |
|---|
python
# 填充缺失值(组内均值填充)
print("填充缺失值(组内均值填充):")
df_with_nan = df_orders.copy()
df_with_nan.loc[0:2, '销售金额'] = np.nan
df_with_nan['销售金额_填充'] = df_with_nan.groupby('商品类别')['销售金额'].transform(
lambda x: x.fillna(x.mean())
)
df_with_nan[['订单ID', '商品类别', '销售金额', '销售金额_填充']].head(8)
填充缺失值(组内均值填充):
| | 订单ID | 商品类别 | 销售金额 | 销售金额_填充 |
| 0 | O0001 | 电子产品 | NaN | 1982.75 |
| 1 | O0002 | 服装 | NaN | 2236.50 |
| 2 | O0003 | 食品 | NaN | 938.50 |
| 3 | O0004 | 服装 | 3821.0 | 3821.00 |
| 4 | O0005 | 电子产品 | 1591.0 | 1591.00 |
| 5 | O0006 | 家居 | 2374.0 | 2374.00 |
| 6 | O0007 | 家居 | 391.0 | 391.00 |
| 7 | O0008 | 服装 | 3388.0 | 3388.00 |
|---|
9.4 apply() 自定义分组操作
apply() 提供最灵活的分组操作方式,可以返回任意形状的结果。
apply自定义操作
返回前N条
统计摘要
复杂分析
nlargest
nsmallest
9.4.1 apply 基础应用
python
# 对每组应用自定义函数
print("对每组应用自定义函数:")
def top_n_sales(group, n=3):
"""返回每组销售金额最高的n条记录"""
return group.nlargest(n, '销售金额')[['订单ID', '销售金额', '地区']]
top_sales = df_orders.groupby('商品类别', group_keys=False).apply(top_n_sales, n=2)
top_sales
对每组应用自定义函数:
| | 订单ID | 销售金额 | 地区 |
| 19 | O0020 | 4080 | 北京 |
| 5 | O0006 | 2374 | 广州 |
| 3 | O0004 | 3821 | 北京 |
| 11 | O0012 | 3466 | 上海 |
| 0 | O0001 | 4488 | 深圳 |
| 17 | O0018 | 4271 | 上海 |
| 2 | O0003 | 2057 | 深圳 |
| 12 | O0013 | 1412 | 广州 |
|---|
python
# 计算每组统计摘要
print("计算每组统计摘要:")
def group_summary(group):
"""返回每组的统计摘要"""
return pd.Series({
'订单数': len(group),
'总销售额': group['销售金额'].sum(),
'平均销售额': group['销售金额'].mean(),
'销售额标准差': group['销售金额'].std(),
'最大单笔': group['销售金额'].max(),
'最小单笔': group['销售金额'].min()
})
summary = df_orders.groupby('商品类别').apply(group_summary, include_groups=False)
summary
计算每组统计摘要:
| | 订单数 | 总销售额 | 平均销售额 | 销售额标准差 | 最大单笔 | 最小单笔 |
| 商品类别 | | | | | | |
| 家居 | 5.0 | 7257.0 | 1451.400000 | 1731.285447 | 4080.0 | 173.0 |
| 服装 | 7.0 | 16048.0 | 2292.571429 | 1365.369896 | 3821.0 | 359.0 |
| 电子产品 | 5.0 | 12419.0 | 2483.800000 | 1769.868272 | 4488.0 | 634.0 |
| 食品 | 3.0 | 3934.0 | 1311.333333 | 800.759847 | 2057.0 | 465.0 |
|---|
python
# 复杂分组操作
print("复杂分组操作:")
def analyze_performance(group):
"""分析部门绩效"""
avg_salary = group['基本工资'].mean()
avg_score = group['绩效评分'].mean()
high_performers = len(group[group['绩效评分'] >= 90])
return pd.Series({
'平均工资': avg_salary,
'平均绩效': avg_score,
'优秀人数': high_performers,
'优秀率': high_performers / len(group) * 100,
'部门评级': 'A' if avg_score >= 85 else 'B' if avg_score >= 75 else 'C'
})
dept_analysis = df_employees.groupby('部门').apply(analyze_performance, include_groups=False)
dept_analysis
复杂分组操作:
| | 平均工资 | 平均绩效 | 优秀人数 | 优秀率 | 部门评级 |
| 部门 | | | | | |
| 人事部 | 9462.5 | 89.50 | 1 | 50.0 | A |
| 财务部 | 10418.0 | 75.50 | 1 | 25.0 | B |
| 销售部 | 11199.0 | 89.25 | 3 | 75.0 | A |
|---|
9.5 filter() 分组过滤
filter() 用于根据条件筛选分组,保留满足条件的组的全部记录。
分组条件
满足
不满足
原始数据
条件判断
保留
过滤
9.5.1 filter 基础应用
python
# 过滤记录数不足的组
print("过滤记录数不足的组:")
print("只保留订单数 >= 5 的商品类别:")
large_categories = df_orders.groupby('商品类别').filter(lambda x: len(x) >= 5)
print(f"原始记录数: {len(df_orders)}")
print(f"过滤后记录数: {len(large_categories)}")
print(f"\n过滤后的类别分布:")
print(large_categories['商品类别'].value_counts())
过滤记录数不足的组:
只保留订单数 >= 5 的商品类别:
原始记录数: 20
过滤后记录数: 17
过滤后的类别分布:
商品类别
服装 7
电子产品 5
家居 5
Name: count, dtype: int64
python
# 过滤平均销售额低的组
print("过滤平均销售额低的组:")
print("只保留平均销售额 >= 1500 的类别:")
high_value_categories = df_orders.groupby('商品类别').filter(
lambda x: x['销售金额'].mean() >= 1500
)
print(f"原始记录数: {len(df_orders)}")
print(f"过滤后记录数: {len(high_value_categories)}")
print(f"\n过滤后的类别平均销售额:")
print(high_value_categories.groupby('商品类别')['销售金额'].mean())
过滤平均销售额低的组:
只保留平均销售额 >= 1500 的类别:
原始记录数: 20
过滤后记录数: 12
过滤后的类别平均销售额:
商品类别
服装 2292.571429
电子产品 2483.800000
Name: 销售金额, dtype: float64
python
# 复杂条件过滤
print("复杂条件过滤:")
print("保留人数 >= 2 且平均绩效 >= 75 的部门:")
qualified_depts = df_employees.groupby('部门').filter(
lambda x: len(x) >= 2 and x['绩效评分'].mean() >= 75
)
print(f"原始记录数: {len(df_employees)}")
print(f"过滤后记录数: {len(qualified_depts)}")
print(f"\n过滤后的部门分布:")
print(qualified_depts['部门'].value_counts())
复杂条件过滤:
保留人数 >= 2 且平均绩效 >= 75 的部门:
原始记录数: 10
过滤后记录数: 10
过滤后的部门分布:
部门
销售部 4
财务部 4
人事部 2
Name: count, dtype: int64
9.6 高级技巧
方法对比
| 方法 | 返回行数 | 主要用途 | 典型场景 |
|---|---|---|---|
agg() |
等于组数 | 汇总统计 | 计算各类别总和、均值 |
transform() |
等于原行数 | 组内变换 | 添加组统计列、标准化 |
apply() |
灵活 | 自定义操作 | 复杂分组处理 |
filter() |
小于等于原行数 | 筛选组 | 过滤小样本组 |
9.6.1 组内排序与排名
python
# 组内排序
print("组内排序 - 获取各类别销售额最高的3个订单:")
top3_per_category = df_orders.groupby('商品类别').apply(
lambda x: x.nlargest(3, '销售金额')[['订单ID', '销售金额', '地区']],
include_groups=False
)
top3_per_category
组内排序 - 获取各类别销售额最高的3个订单:
| | | 订单ID | 销售金额 | 地区 |
| 商品类别 | | | | |
| 家居 | 19 | O0020 | 4080 | 北京 |
| 家居 | 5 | O0006 | 2374 | 广州 |
| 家居 | 6 | O0007 | 391 | 北京 |
| 服装 | 3 | O0004 | 3821 | 北京 |
| 服装 | 11 | O0012 | 3466 | 上海 |
| 服装 | 7 | O0008 | 3388 | 上海 |
| 电子产品 | 0 | O0001 | 4488 | 深圳 |
| 电子产品 | 17 | O0018 | 4271 | 上海 |
| 电子产品 | 4 | O0005 | 1591 | 深圳 |
| 食品 | 2 | O0003 | 2057 | 深圳 |
| 食品 | 12 | O0013 | 1412 | 广州 |
| 食品 | 13 | O0014 | 465 | 深圳 |
|---|
python
# 组内排名
print("组内排名 - 计算各类别销售额排名:")
df_orders['类别销售额排名'] = df_orders.groupby('商品类别')['销售金额'].rank(
method='dense',
ascending=False
)
df_orders[['订单ID', '商品类别', '销售金额', '类别销售额排名']].head(10)
组内排名 - 计算各类别销售额排名:
| | 订单ID | 商品类别 | 销售金额 | 类别销售额排名 |
| 0 | O0001 | 电子产品 | 4488 | 1.0 |
| 1 | O0002 | 服装 | 2629 | 4.0 |
| 2 | O0003 | 食品 | 2057 | 1.0 |
| 3 | O0004 | 服装 | 3821 | 1.0 |
| 4 | O0005 | 电子产品 | 1591 | 3.0 |
| 5 | O0006 | 家居 | 2374 | 2.0 |
| 6 | O0007 | 家居 | 391 | 3.0 |
| 7 | O0008 | 服装 | 3388 | 3.0 |
| 8 | O0009 | 电子产品 | 1435 | 4.0 |
| 9 | O0010 | 服装 | 359 | 7.0 |
|---|
9.6.2 交叉分组与透视
python
# 分组后透视
print("分组后透视:")
pivot_result = df_orders.pivot_table(
index='地区',
columns='商品类别',
values='销售金额',
aggfunc='sum',
fill_value=0
)
print("地区×类别 销售金额透视表:")
pivot_result
分组后透视:
地区×类别 销售金额透视表:
| 商品类别 | 家居 | 服装 | 电子产品 | 食品 |
| 地区 | | | | |
| 上海 | 0 | 6854 | 4271 | 0 |
| 北京 | 4644 | 5362 | 1435 | 0 |
| 广州 | 2613 | 2629 | 634 | 1412 |
| 深圳 | 0 | 1203 | 6079 | 2522 |
|---|
python
# 分组聚合后重置索引
print("分组聚合后重置索引:")
result_reset = df_orders.groupby(['地区', '商品类别'])['销售金额'].sum().reset_index()
result_reset.head(8)
分组聚合后重置索引:
| | 地区 | 商品类别 | 销售金额 |
| 0 | 上海 | 服装 | 6854 |
| 1 | 上海 | 电子产品 | 4271 |
| 2 | 北京 | 家居 | 4644 |
| 3 | 北京 | 服装 | 5362 |
| 4 | 北京 | 电子产品 | 1435 |
| 5 | 广州 | 家居 | 2613 |
| 6 | 广州 | 服装 | 2629 |
| 7 | 广州 | 电子产品 | 634 |
|---|
9.7 实战场景
9.7.1 销售分析场景
python
# 月度销售趋势分析
print("月度销售趋势分析:")
df_orders['月份'] = df_orders['订单日期'].dt.to_period('M')
monthly_sales = df_orders.groupby(['月份', '商品类别'])['销售金额'].sum().unstack(fill_value=0)
print("月度×类别 销售金额:")
monthly_sales
月度销售趋势分析:
月度×类别 销售金额:
| 商品类别 | 家居 | 服装 | 电子产品 | 食品 |
| 月份 | | | | |
| 2026-01 | 7257 | 16048 | 12419 | 3934 |
|---|
python
# 客户价值分析(RFM简化版)
print("客户价值分析(RFM简化版):")
customer_stats = df_orders.groupby('客户ID').agg({
'订单日期': 'max',
'订单ID': 'count',
'销售金额': 'sum'
}).rename(columns={
'订单日期': '最近购买',
'订单ID': '购买次数',
'销售金额': '总消费'
})
customer_stats['客户等级'] = pd.cut(
customer_stats['总消费'],
bins=[0, 2000, 5000, 10000, float('inf')],
labels=['普通', 'Bronze', 'Silver', 'Gold']
)
customer_stats
客户价值分析(RFM简化版):
| | 最近购买 | 购买次数 | 总消费 | 客户等级 |
| 客户ID | | | | |
| C001 | 2026-01-20 | 5 | 8356 | Silver |
| C002 | 2026-01-17 | 4 | 10339 | Gold |
| C003 | 2026-01-09 | 2 | 4064 | Bronze |
| C004 | 2026-01-19 | 3 | 2972 | Bronze |
| C005 | 2026-01-18 | 6 | 13927 | Gold |
|---|
9.7.2 库存分析场景
python
# 库存健康度分析
print("库存健康度分析:")
inventory_health = df_inventory.groupby(['仓库', '类别']).agg({
'库存数量': ['sum', 'mean'],
'利润': 'mean'
}).round(2)
print("各仓库各类别库存情况:")
inventory_health
库存健康度分析:
各仓库各类别库存情况:
| | | 库存数量 || 利润 |
| | | sum | mean | mean |
| 仓库 | 类别 | | | |
| 上海仓 | 服装 | 163 | 163.00 | -19.00 |
| 上海仓 | 电子产品 | 57 | 57.00 | 580.00 |
| 北京仓 | 服装 | 341 | 113.67 | 498.00 |
| 北京仓 | 电子产品 | 188 | 188.00 | -124.00 |
| 北京仓 | 食品 | 387 | 96.75 | 68.50 |
| 广州仓 | 服装 | 177 | 177.00 | 108.00 |
| 广州仓 | 电子产品 | 367 | 122.33 | 18.67 |
| 广州仓 | 食品 | 122 | 122.00 | 151.00 |
|---|
python
# 找出需要补货的商品
print("找出需要补货的商品:")
print("各类别库存最少的2个商品:")
low_stock = df_inventory.groupby('类别').apply(
lambda x: x.nsmallest(2, '库存数量')[['商品', '仓库', '库存数量']],
include_groups=False
)
low_stock
找出需要补货的商品:
各类别库存最少的2个商品:
| | | 商品 | 仓库 | 库存数量 |
| 类别 | | | | |
| 服装 | 4 | 商品5 | 北京仓 | 79 |
| 服装 | 10 | 商品11 | 北京仓 | 116 |
| 电子产品 | 14 | 商品15 | 上海仓 | 57 |
| 电子产品 | 1 | 商品2 | 广州仓 | 58 |
| 食品 | 11 | 商品12 | 北京仓 | 18 |
| 食品 | 2 | 商品3 | 北京仓 | 69 |
|---|
本章小结
学习内容回顾
1. groupby() 基础分组
| 操作 | 语法 | 说明 |
|---|---|---|
| 单列分组 | df.groupby('列名') |
按单列分组 |
| 多列分组 | df.groupby(['列1', '列2']) |
按多列组合分组 |
| 获取分组 | .get_group('组名') |
获取特定组数据 |
| 遍历分组 | for name, group in grouped: |
遍历所有分组 |
2. 常用聚合函数
| 函数 | 说明 |
|---|---|
sum() |
求和 |
mean() |
平均值 |
count() |
计数 |
min() / max() |
最小/最大值 |
std() / var() |
标准差/方差 |
3. agg() 高级聚合
| 用法 | 示例 |
|---|---|
| 多列不同聚合 | agg({'列1': 'sum', '列2': 'mean'}) |
| 自定义列名 | agg(新名=('列', '函数')) |
| 自定义函数 | agg([('名', lambda x: ...)]) |
4. transform() 组内变换
- 添加组统计列:
transform('mean') - 组内排名:
transform(lambda x: x.rank()) - 组内标准化:
transform(lambda x: (x-x.mean())/x.std()) - 缺失值填充:
transform(lambda x: x.fillna(x.mean()))
5. apply() 自定义操作
- 复杂分组处理:
apply(custom_function) - 返回多列结果
- 灵活的数据处理
6. filter() 分组过滤
- 按组大小过滤:
filter(lambda x: len(x) >= n) - 按统计量过滤:
filter(lambda x: x['列'].mean() > v) - 保留原始数据结构
下章预告
第十章:时间序列处理
将学习时间序列数据的核心操作,包括:
- 时间索引和重采样
- 时间偏移和滚动窗口
- 时间差计算
- 季节性分析
课后练习
练习1:使用 groupby() 完成以下操作:
- 按商品类别分组,计算各类别的销售总额和平均销售额
- 按地区分组,统计各地区的订单数量和总销售额
- 尝试多列分组(地区+类别),观察结果变化
练习2:使用 agg() 完成以下操作:
- 对订单数据按类别聚合,计算总金额、平均金额、订单数、最大最小金额
- 使用自定义列名重新命名聚合结果
- 编写自定义聚合函数(如极差、变异系数)并应用到分组数据
练习3:使用 transform() 完成以下操作:
- 为订单表添加类别平均销售额列
- 计算每个订单与其类别平均值的差异
- 计算各类别内的销售额排名
- 对销售额进行组内标准化(Z-score)
练习4:使用 apply() 完成以下操作:
- 编写函数返回每类销售额最高的前3个订单
- 编写函数计算每组的统计摘要(订单数、总额、均值、标准差)
- 对部门数据进行复杂分析(平均工资、优秀率、部门评级)
练习5:使用 filter() 完成以下操作:
- 过滤掉记录数少于3的类别
- 过滤掉平均销售额低于1000的类别
- 使用复杂条件过滤(记录数>=2 且 平均值>=1500)
练习6:综合练习:
- 创建一个包含销售数据的 DataFrame
- 分别使用 agg、transform、apply、filter 进行分组操作
- 比较四种方法的结果差异和适用场景
- 实现一个完整的销售数据分析报告