Pandas数据分析 - 第九章:分组聚合操作

第九章:分组聚合操作

📋 章节概述

本章将学习 Pandas 中强大的分组聚合功能。掌握这些方法后,你将能够高效地对数据进行分组统计、组内变换和复杂的数据分析。

🎯 学习目标

  1. 掌握 groupby() 方法进行数据分组
  2. 掌握常用聚合函数(sum, mean, count, min, max等)
  3. 理解 agg() 进行多列多聚合操作
  4. 掌握 transform() 进行组内变换
  5. 掌握 apply() 进行自定义分组操作
  6. 掌握 filter() 进行分组过滤
  7. 学会处理分组后的数据分析和统计

📊 知识结构图

分组聚合
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 进行分组操作
  • 比较四种方法的结果差异和适用场景
  • 实现一个完整的销售数据分析报告
相关推荐
李昊哲小课4 小时前
Pandas数据分析 - 第八章:数据重塑
数据挖掘·数据分析·pandas
李昊哲小课5 小时前
Pandas数据分析 - 第四章:数据读取与保存
数据挖掘·数据分析·pandas
deepdata_cn6 小时前
数据分析之数据粒度(Granularity)
数据挖掘·数据分析·颗粒度
YangYang9YangYan6 小时前
2026年经管专业学习数据分析的指南
学习·数据挖掘·数据分析
551只玄猫6 小时前
【数学建模 matlab 实验报告9】数据的统计分析与描述
数学建模·matlab·数据分析·课程设计·实验报告
李昊哲小课6 小时前
Pandas数据分析 - 第七章:数据合并与连接
数据挖掘·数据分析·pandas
gushinghsjj7 小时前
元数据管理包含哪些?元数据管理如何支持数据分析?
数据库·oracle·数据分析
编程界一哥7 小时前
2026最新:原神PC启动提示缺失msvcp140.dll,安全修复工具哪家强?
数据挖掘
qyr67897 小时前
全球蜂窝分布式天线系统市场报告2026-2032
大数据·人工智能·数据分析·市场报告·蜂窝分布式天线系统