第八章:数据重塑
📋 章节概述
本章将学习 Pandas 中数据重塑的核心技术。掌握这些方法后,你将能够灵活地在长表和宽表之间转换,满足不同的数据分析和报表展示需求。
🎯 学习目标
- 掌握 pivot() 方法进行数据透视(长表转宽表)
- 掌握 melt() 方法进行数据融合(宽表转长表)
- 掌握 stack() 和 unstack() 进行层次化索引操作
- 理解 pivot_table() 进行高级数据透视
- 掌握 crosstab() 进行交叉表分析
- 学会在实际业务中灵活转换数据格式
📊 知识结构图
数据重塑
pivot
长表转宽表
melt
宽表转长表
pivot_table
高级透视
stack/unstack
层次化索引
crosstab
交叉表
index行索引
columns列索引
values值
id_vars标识列
value_vars值列
var_name变量名
value_name值名
aggfunc聚合
margins总计
fill_value填充
stack行转列
unstack列转行
🏢 实战场景
本章使用"销售数据分析"案例:
- 销售记录表:长格式销售数据(日期、地区、产品、销售额)
- 员工绩效表:宽格式月度绩效(员工×月份矩阵)
- 库存表:多维度库存数据(仓库×类别×商品)
- 问卷调查表:长格式问卷数据(受访者×问题×评分)
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_sales_long = pd.DataFrame({
'日期': ['2026-01-01', '2026-01-01', '2026-01-01', '2026-01-01',
'2026-01-02', '2026-01-02', '2026-01-02', '2026-01-02',
'2026-01-03', '2026-01-03', '2026-01-03', '2026-01-03'],
'地区': ['北京', '北京', '上海', '上海',
'北京', '北京', '上海', '上海',
'北京', '北京', '上海', '上海'],
'产品': ['手机', '电脑', '手机', '电脑',
'手机', '电脑', '手机', '电脑',
'手机', '电脑', '手机', '电脑'],
'销售额': [12000, 25000, 15000, 28000,
11000, 26000, 14000, 27000,
13000, 24000, 16000, 29000],
'销量': [12, 5, 15, 6, 11, 5, 14, 6, 13, 5, 16, 7]
})
# 员工绩效表(宽格式)
df_performance_wide = pd.DataFrame({
'员工ID': ['E001', 'E002', 'E003', 'E004', 'E005'],
'姓名': ['张伟', '李娜', '王强', '刘洋', '陈静'],
'部门': ['销售部', '技术部', '销售部', '技术部', '人事部'],
'1月业绩': [85000, 92000, 78000, 88000, 65000],
'2月业绩': [90000, 95000, 82000, 91000, 68000],
'3月业绩': [88000, 98000, 85000, 93000, 70000],
'1月考勤': [22, 20, 21, 22, 22],
'2月考勤': [20, 21, 20, 20, 21],
'3月考勤': [23, 22, 22, 23, 23]
})
# 库存表
df_inventory = pd.DataFrame({
'仓库': ['北京仓', '北京仓', '北京仓', '北京仓',
'上海仓', '上海仓', '上海仓', '上海仓'],
'类别': ['电子产品', '电子产品', '服装', '服装',
'电子产品', '电子产品', '服装', '服装'],
'商品': ['手机', '电脑', 'T恤', '裤子',
'手机', '电脑', 'T恤', '裤子'],
'期初库存': [100, 50, 200, 150, 80, 40, 180, 140],
'入库数量': [50, 20, 100, 80, 40, 15, 90, 70],
'出库数量': [30, 15, 80, 60, 25, 10, 70, 55],
})
df_inventory['期末库存'] = (df_inventory['期初库存'] +
df_inventory['入库数量'] -
df_inventory['出库数量'])
# 问卷调查表(长格式)
df_survey = pd.DataFrame({
'问卷ID': ['Q001', 'Q001', 'Q001', 'Q002', 'Q002', 'Q002',
'Q003', 'Q003', 'Q003', 'Q004', 'Q004', 'Q004'],
'受访者': ['张三', '张三', '张三', '李四', '李四', '李四',
'王五', '王五', '王五', '赵六', '赵六', '赵六'],
'问题': ['满意度', '推荐度', '价格合理性',
'满意度', '推荐度', '价格合理性',
'满意度', '推荐度', '价格合理性',
'满意度', '推荐度', '价格合理性'],
'评分': [8, 7, 6, 9, 8, 7, 7, 6, 8, 8, 9, 7],
'权重': [0.4, 0.3, 0.3, 0.4, 0.3, 0.3, 0.4, 0.3, 0.3, 0.4, 0.3, 0.3]
})
print("数据表预览:")
print(f"\n1. 销售记录表(长格式): {df_sales_long.shape}")
print(f"2. 员工绩效表(宽格式): {df_performance_wide.shape}")
print(f"3. 库存表: {df_inventory.shape}")
print(f"4. 问卷调查表(长格式): {df_survey.shape}")
数据表预览:
1. 销售记录表(长格式): (12, 5)
2. 员工绩效表(宽格式): (5, 9)
3. 库存表: (8, 7)
4. 问卷调查表(长格式): (12, 5)
python
# 查看销售记录表
df_sales_long
| | 日期 | 地区 | 产品 | 销售额 | 销量 |
| 0 | 2026-01-01 | 北京 | 手机 | 12000 | 12 |
| 1 | 2026-01-01 | 北京 | 电脑 | 25000 | 5 |
| 2 | 2026-01-01 | 上海 | 手机 | 15000 | 15 |
| 3 | 2026-01-01 | 上海 | 电脑 | 28000 | 6 |
| 4 | 2026-01-02 | 北京 | 手机 | 11000 | 11 |
| 5 | 2026-01-02 | 北京 | 电脑 | 26000 | 5 |
| 6 | 2026-01-02 | 上海 | 手机 | 14000 | 14 |
| 7 | 2026-01-02 | 上海 | 电脑 | 27000 | 6 |
| 8 | 2026-01-03 | 北京 | 手机 | 13000 | 13 |
| 9 | 2026-01-03 | 北京 | 电脑 | 24000 | 5 |
| 10 | 2026-01-03 | 上海 | 手机 | 16000 | 16 |
| 11 | 2026-01-03 | 上海 | 电脑 | 29000 | 7 |
|---|
python
# 查看员工绩效表
df_performance_wide
| | 员工ID | 姓名 | 部门 | 1月业绩 | 2月业绩 | 3月业绩 | 1月考勤 | 2月考勤 | 3月考勤 |
| 0 | E001 | 张伟 | 销售部 | 85000 | 90000 | 88000 | 22 | 20 | 23 |
| 1 | E002 | 李娜 | 技术部 | 92000 | 95000 | 98000 | 20 | 21 | 22 |
| 2 | E003 | 王强 | 销售部 | 78000 | 82000 | 85000 | 21 | 20 | 22 |
| 3 | E004 | 刘洋 | 技术部 | 88000 | 91000 | 93000 | 22 | 20 | 23 |
| 4 | E005 | 陈静 | 人事部 | 65000 | 68000 | 70000 | 22 | 21 | 23 |
|---|
8.1 pivot() - 数据透视(长表转宽表)
pivot() 将长格式数据转换为宽格式,便于对比分析和报表展示。
pivot
长表
宽表
日期
地区
产品
销售额
日期为行
产品为列
销售额为值
8.1.1 基础 pivot 操作
python
# 将销售记录透视为地区×产品矩阵
print("将销售记录透视为地区×产品矩阵:")
print("行=日期, 列=(地区,产品), 值=销售额")
pivot_basic = df_sales_long.pivot(
index='日期',
columns=['地区', '产品'],
values='销售额'
)
print(pivot_basic)
print(f"\n透视后形状: {pivot_basic.shape}")
print(f"列名变为多级索引: {pivot_basic.columns.tolist()}")
将销售记录透视为地区×产品矩阵:
行=日期, 列=(地区,产品), 值=销售额
地区 北京 上海
产品 手机 电脑 手机 电脑
日期
2026-01-01 12000 25000 15000 28000
2026-01-02 11000 26000 14000 27000
2026-01-03 13000 24000 16000 29000
透视后形状: (3, 4)
列名变为多级索引: [('北京', '手机'), ('北京', '电脑'), ('上海', '手机'), ('上海', '电脑')]
python
# 单层列索引的 pivot
print("单层列索引的 pivot:")
print("只透视'产品'作为列,地区作为行的一部分")
pivot_single = df_sales_long.pivot(
index=['日期', '地区'],
columns='产品',
values='销售额'
)
pivot_single
单层列索引的 pivot:
只透视'产品'作为列,地区作为行的一部分
| | 产品 | 手机 | 电脑 |
| 日期 | 地区 | | |
| 2026-01-01 | 上海 | 15000 | 28000 |
| 2026-01-01 | 北京 | 12000 | 25000 |
| 2026-01-02 | 上海 | 14000 | 27000 |
| 2026-01-02 | 北京 | 11000 | 26000 |
| 2026-01-03 | 上海 | 16000 | 29000 |
| 2026-01-03 | 北京 | 13000 | 24000 |
|---|
python
# 透视多个值列(使用 pivot_table)
print("透视多个值列(使用 pivot_table):")
print("注意:pivot() 不支持多个 values 列,改用 pivot_table:")
pivot_multi = df_sales_long.pivot_table(
index='日期',
columns='地区',
values=['销售额', '销量'],
aggfunc='sum'
)
print(pivot_multi)
print(f"\n多级列索引: {pivot_multi.columns.tolist()}")
透视多个值列(使用 pivot_table):
注意:pivot() 不支持多个 values 列,改用 pivot_table:
销售额 销量
地区 上海 北京 上海 北京
日期
2026-01-01 43000 37000 21 17
2026-01-02 41000 37000 20 16
2026-01-03 45000 37000 23 18
多级列索引: [('销售额', '上海'), ('销售额', '北京'), ('销量', '上海'), ('销量', '北京')]
8.1.2 pivot 的限制与注意事项
| 限制 | 说明 | 解决方案 |
|---|---|---|
| 唯一性约束 | 索引+列的组合必须是唯一的 | 使用 pivot_table() 并指定聚合函数 |
| 聚合操作 | 不支持聚合 | 使用 pivot_table() |
| 列存在性 | 只能使用已存在的列 | 先创建需要的列 |
pivot限制
重复组合报错
不支持聚合
使用pivot_table
使用pivot_table
python
# 演示重复组合会导致错误
print("演示重复组合会导致错误:")
print("创建有重复组合的数据:")
df_duplicate = pd.DataFrame({
'日期': ['2026-01-01', '2026-01-01'],
'地区': ['北京', '北京'],
'销售额': [10000, 12000]
})
print(df_duplicate)
try:
pivot_error = df_duplicate.pivot(index='日期', columns='地区', values='销售额')
except ValueError as e:
print(f"\n错误信息: {e}")
print("解决方案: 使用 pivot_table() 并指定 aggfunc")
演示重复组合会导致错误:
创建有重复组合的数据:
日期 地区 销售额
0 2026-01-01 北京 10000
1 2026-01-01 北京 12000
错误信息: Index contains duplicate entries, cannot reshape
解决方案: 使用 pivot_table() 并指定 aggfunc
8.2 melt() - 数据融合(宽表转长表)
melt() 将宽格式数据转换为长格式,便于数据存储和规范化处理。
melt
宽表
长表
员工ID
姓名
1月业绩
2月业绩
3月业绩
id_vars保留
value_vars融合
变量名列
值列
8.2.1 基础 melt 操作
python
# 将员工绩效表从宽格式转为长格式
print("将员工绩效表从宽格式转为长格式:")
print("原始宽表(保留标识列):")
print(df_performance_wide[['员工ID', '姓名', '部门', '1月业绩', '2月业绩', '3月业绩']])
melted_basic = df_performance_wide.melt(
id_vars=['员工ID', '姓名', '部门'],
value_vars=['1月业绩', '2月业绩', '3月业绩'],
var_name='月份',
value_name='业绩'
)
print("\nmelt 后的长表:")
print(melted_basic.head(8))
print(f"\n形状从 {df_performance_wide.shape} 变为 {melted_basic.shape}")
将员工绩效表从宽格式转为长格式:
原始宽表(保留标识列):
员工ID 姓名 部门 1月业绩 2月业绩 3月业绩
0 E001 张伟 销售部 85000 90000 88000
1 E002 李娜 技术部 92000 95000 98000
2 E003 王强 销售部 78000 82000 85000
3 E004 刘洋 技术部 88000 91000 93000
4 E005 陈静 人事部 65000 68000 70000
melt 后的长表:
员工ID 姓名 部门 月份 业绩
0 E001 张伟 销售部 1月业绩 85000
1 E002 李娜 技术部 1月业绩 92000
2 E003 王强 销售部 1月业绩 78000
3 E004 刘洋 技术部 1月业绩 88000
4 E005 陈静 人事部 1月业绩 65000
5 E001 张伟 销售部 2月业绩 90000
6 E002 李娜 技术部 2月业绩 95000
7 E003 王强 销售部 2月业绩 82000
形状从 (5, 9) 变为 (15, 5)
python
# melt 所有数值列(不指定 value_vars)
print("melt 所有数值列(不指定 value_vars):")
melted_all = df_performance_wide.melt(
id_vars=['员工ID', '姓名', '部门'],
var_name='指标',
value_name='数值'
)
print(melted_all.head(10))
print(f"\n所有非id列都被融合,形状: {melted_all.shape}")
melt 所有数值列(不指定 value_vars):
员工ID 姓名 部门 指标 数值
0 E001 张伟 销售部 1月业绩 85000
1 E002 李娜 技术部 1月业绩 92000
2 E003 王强 销售部 1月业绩 78000
3 E004 刘洋 技术部 1月业绩 88000
4 E005 陈静 人事部 1月业绩 65000
5 E001 张伟 销售部 2月业绩 90000
6 E002 李娜 技术部 2月业绩 95000
7 E003 王强 销售部 2月业绩 82000
8 E004 刘洋 技术部 2月业绩 91000
9 E005 陈静 人事部 2月业绩 68000
所有非id列都被融合,形状: (30, 5)
8.2.2 选择性 melt
python
# 只融化业绩相关列
print("只融化业绩相关列:")
melted_sales = df_performance_wide.melt(
id_vars=['员工ID', '姓名', '部门'],
value_vars=['1月业绩', '2月业绩', '3月业绩'],
var_name='月份',
value_name='业绩'
)
print("业绩数据长表:")
melted_sales.head(8)
只融化业绩相关列:
业绩数据长表:
| | 员工ID | 姓名 | 部门 | 月份 | 业绩 |
| 0 | E001 | 张伟 | 销售部 | 1月业绩 | 85000 |
| 1 | E002 | 李娜 | 技术部 | 1月业绩 | 92000 |
| 2 | E003 | 王强 | 销售部 | 1月业绩 | 78000 |
| 3 | E004 | 刘洋 | 技术部 | 1月业绩 | 88000 |
| 4 | E005 | 陈静 | 人事部 | 1月业绩 | 65000 |
| 5 | E001 | 张伟 | 销售部 | 2月业绩 | 90000 |
| 6 | E002 | 李娜 | 技术部 | 2月业绩 | 95000 |
| 7 | E003 | 王强 | 销售部 | 2月业绩 | 82000 |
|---|
python
# 分别 melt 后再合并
print("分别 melt 后再合并:")
melted_sales['月份'] = melted_sales['月份'].str.replace('月业绩', '')
melted_attendance = df_performance_wide.melt(
id_vars=['员工ID', '姓名', '部门'],
value_vars=['1月考勤', '2月考勤', '3月考勤'],
var_name='月份',
value_name='出勤天数'
)
melted_attendance['月份'] = melted_attendance['月份'].str.replace('月考勤', '')
combined = pd.merge(
melted_sales,
melted_attendance[['员工ID', '月份', '出勤天数']],
on=['员工ID', '月份']
)
print("合并业绩和考勤数据:")
combined.head(8)
分别 melt 后再合并:
合并业绩和考勤数据:
| | 员工ID | 姓名 | 部门 | 月份 | 业绩 | 出勤天数 |
| 0 | E001 | 张伟 | 销售部 | 1 | 85000 | 22 |
| 1 | E002 | 李娜 | 技术部 | 1 | 92000 | 20 |
| 2 | E003 | 王强 | 销售部 | 1 | 78000 | 21 |
| 3 | E004 | 刘洋 | 技术部 | 1 | 88000 | 22 |
| 4 | E005 | 陈静 | 人事部 | 1 | 65000 | 22 |
| 5 | E001 | 张伟 | 销售部 | 2 | 90000 | 20 |
| 6 | E002 | 李娜 | 技术部 | 2 | 95000 | 21 |
| 7 | E003 | 王强 | 销售部 | 2 | 82000 | 20 |
|---|
8.3 pivot_table() - 高级数据透视
pivot_table() 是 pivot() 的增强版,支持聚合操作处理重复值。
pivot_table
aggfunc聚合
margins总计
fill_value填充
sum求和
mean平均
count计数
max/min最值
8.3.1 基础 pivot_table 操作
python
# 处理重复值的 pivot_table
print("处理重复值的 pivot_table:")
df_sales_dup = pd.DataFrame({
'日期': ['2026-01-01', '2026-01-01', '2026-01-02', '2026-01-02'],
'地区': ['北京', '北京', '上海', '上海'],
'产品': ['手机', '手机', '电脑', '电脑'],
'销售额': [10000, 12000, 25000, 28000]
})
print(df_sales_dup)
print("\n使用 pivot_table 求和聚合:")
pivot_sum = df_sales_dup.pivot_table(
index='日期',
columns='地区',
values='销售额',
aggfunc='sum'
)
pivot_sum
处理重复值的 pivot_table:
日期 地区 产品 销售额
0 2026-01-01 北京 手机 10000
1 2026-01-01 北京 手机 12000
2 2026-01-02 上海 电脑 25000
3 2026-01-02 上海 电脑 28000
使用 pivot_table 求和聚合:
| 地区 | 上海 | 北京 |
| 日期 | | |
| 2026-01-01 | NaN | 22000.0 |
| 2026-01-02 | 53000.0 | NaN |
|---|
python
# 多种聚合函数
print("多种聚合函数:")
pivot_multi_agg = df_sales_long.pivot_table(
index='地区',
columns='产品',
values='销售额',
aggfunc=['sum', 'mean', 'count']
)
pivot_multi_agg
多种聚合函数:
| | sum || mean || count ||
| 产品 | 手机 | 电脑 | 手机 | 电脑 | 手机 | 电脑 |
| 地区 | | | | | | |
| 上海 | 45000 | 84000 | 15000.0 | 28000.0 | 3 | 3 |
| 北京 | 36000 | 75000 | 12000.0 | 25000.0 | 3 | 3 |
|---|
8.3.2 高级 pivot_table 功能
python
# 多级索引透视
print("多级索引透视:")
pivot_multi_index = df_sales_long.pivot_table(
index=['地区', '日期'],
columns='产品',
values='销售额',
aggfunc='sum'
)
pivot_multi_index
多级索引透视:
| | 产品 | 手机 | 电脑 |
| 地区 | 日期 | | |
| 上海 | 2026-01-01 | 15000 | 28000 |
| 上海 | 2026-01-02 | 14000 | 27000 |
| 上海 | 2026-01-03 | 16000 | 29000 |
| 北京 | 2026-01-01 | 12000 | 25000 |
| 北京 | 2026-01-02 | 11000 | 26000 |
| 北京 | 2026-01-03 | 13000 | 24000 |
|---|
python
# 使用 margins 添加总计
print("使用 margins 添加总计:")
pivot_with_total = df_sales_long.pivot_table(
index='地区',
columns='产品',
values='销售额',
aggfunc='sum',
margins=True,
margins_name='总计'
)
pivot_with_total
使用 margins 添加总计:
| 产品 | 手机 | 电脑 | 总计 |
| 地区 | | | |
| 上海 | 45000 | 84000 | 129000 |
| 北京 | 36000 | 75000 | 111000 |
| 总计 | 81000 | 159000 | 240000 |
|---|
python
# 使用 fill_value 填充缺失值
print("使用 fill_value 填充缺失值:")
pivot_fill = df_sales_long.pivot_table(
index='日期',
columns='地区',
values='销售额',
aggfunc='sum',
fill_value=0
)
pivot_fill
使用 fill_value 填充缺失值:
| 地区 | 上海 | 北京 |
| 日期 | | |
| 2026-01-01 | 43000 | 37000 |
| 2026-01-02 | 41000 | 37000 |
| 2026-01-03 | 45000 | 37000 |
|---|
8.4 stack() 和 unstack() - 层次化索引操作
用于在 DataFrame 和 Series 之间转换,操作层次化索引。
stack
unstack
unstack
stack
DataFrame
Series
行转列
宽表
长表
列转行
8.4.1 unstack() - 将列索引转为行索引(宽表转长表)
python
# 将透视表 unstack
print("将透视表 unstack:")
pivot_for_unstack = df_sales_long.pivot_table(
index='日期',
columns='地区',
values='销售额',
aggfunc='sum'
)
print("原始透视表:")
print(pivot_for_unstack)
print("\nunstack() 将列索引转为行索引:")
unstacked = pivot_for_unstack.unstack(level=-1)
print(unstacked)
print(f"\n形状从 {pivot_for_unstack.shape} 变为 {unstacked.shape}")
将透视表 unstack:
原始透视表:
地区 上海 北京
日期
2026-01-01 43000 37000
2026-01-02 41000 37000
2026-01-03 45000 37000
unstack() 将列索引转为行索引:
地区 日期
上海 2026-01-01 43000
2026-01-02 41000
2026-01-03 45000
北京 2026-01-01 37000
2026-01-02 37000
2026-01-03 37000
dtype: int64
形状从 (3, 2) 变为 (6,)
python
# 指定 unstack 的层级
print("指定 unstack 的层级:")
print("unstack(level=0) 将第一级列索引转为行:")
unstack_level0 = pivot_for_unstack.unstack(level=0)
unstack_level0
指定 unstack 的层级:
unstack(level=0) 将第一级列索引转为行:
地区 日期
上海 2026-01-01 43000
2026-01-02 41000
2026-01-03 45000
北京 2026-01-01 37000
2026-01-02 37000
2026-01-03 37000
dtype: int64
8.4.2 stack() - 将行索引转为列索引(长表转宽表)
python
# 将数据 stack
print("将数据 stack:")
print("创建带多级索引的数据:")
multi_index = df_sales_long.groupby(['日期', '地区', '产品'])['销售额'].sum().reset_index()
multi_index = multi_index.set_index(['日期', '地区', '产品'])
print("多级索引数据(前5行):")
print(multi_index.head())
print("\nstack() 将行索引转为列:")
stacked = multi_index.stack()
print(stacked.head(10))
print(f"\n类型变为 Series: {type(stacked)}")
将数据 stack:
创建带多级索引的数据:
多级索引数据(前5行):
销售额
日期 地区 产品
2026-01-01 上海 手机 15000
电脑 28000
北京 手机 12000
电脑 25000
2026-01-02 上海 手机 14000
stack() 将行索引转为列:
日期 地区 产品
2026-01-01 上海 手机 销售额 15000
电脑 销售额 28000
北京 手机 销售额 12000
电脑 销售额 25000
2026-01-02 上海 手机 销售额 14000
电脑 销售额 27000
北京 手机 销售额 11000
电脑 销售额 26000
2026-01-03 上海 手机 销售额 16000
电脑 销售额 29000
dtype: int64
类型变为 Series: <class 'pandas.Series'>
8.4.3 stack/unstack 完整流程演示
python
print("stack/unstack 完整流程演示:")
print("原始长表 -> pivot_table(宽表) -> unstack(长表) -> stack(宽表)")
print("\nStep 1: 原始长表")
print(df_sales_long[['日期', '地区', '产品', '销售额']].head(6))
print("\nStep 2: pivot_table 转为宽表(按日期×地区汇总)")
step2 = df_sales_long.pivot_table(
index='日期',
columns='地区',
values='销售额',
aggfunc='sum'
)
print(step2)
print("\nStep 3: unstack 转为长表")
step3 = step2.unstack().reset_index(name='销售额')
print(step3.head())
print("\nStep 4: pivot_table 转为宽表(还原)")
step4 = step3.pivot_table(
index='日期',
columns='地区',
values='销售额',
aggfunc='sum'
)
print(step4)
stack/unstack 完整流程演示:
原始长表 -> pivot_table(宽表) -> unstack(长表) -> stack(宽表)
Step 1: 原始长表
日期 地区 产品 销售额
0 2026-01-01 北京 手机 12000
1 2026-01-01 北京 电脑 25000
2 2026-01-01 上海 手机 15000
3 2026-01-01 上海 电脑 28000
4 2026-01-02 北京 手机 11000
5 2026-01-02 北京 电脑 26000
Step 2: pivot_table 转为宽表(按日期×地区汇总)
地区 上海 北京
日期
2026-01-01 43000 37000
2026-01-02 41000 37000
2026-01-03 45000 37000
Step 3: unstack 转为长表
地区 日期 销售额
0 上海 2026-01-01 43000
1 上海 2026-01-02 41000
2 上海 2026-01-03 45000
3 北京 2026-01-01 37000
4 北京 2026-01-02 37000
Step 4: pivot_table 转为宽表(还原)
地区 上海 北京
日期
2026-01-01 43000 37000
2026-01-02 41000 37000
2026-01-03 45000 37000
8.5 crosstab() - 交叉表分析
快速创建频数统计表,适用于分类数据的交叉分析。
crosstab
分类数据
交叉表
计数统计
聚合计算
行列总计
8.5.1 基础 crosstab
python
# 创建交叉表
print("创建交叉表:")
print("统计各地区×产品的销售次数:")
crosstab_basic = pd.crosstab(
index=df_sales_long['地区'],
columns=df_sales_long['产品'],
values=df_sales_long['销售额'],
aggfunc='count'
)
crosstab_basic
创建交叉表:
统计各地区×产品的销售次数:
| 产品 | 手机 | 电脑 |
| 地区 | | |
| 上海 | 3 | 3 |
| 北京 | 3 | 3 |
|---|
python
# crosstab 默认计数
print("crosstab 默认计数:")
crosstab_count = pd.crosstab(
index=df_sales_long['地区'],
columns=df_sales_long['产品']
)
crosstab_count
crosstab 默认计数:
| 产品 | 手机 | 电脑 |
| 地区 | | |
| 上海 | 3 | 3 |
| 北京 | 3 | 3 |
|---|
8.5.2 高级 crosstab 功能
python
# 添加行列总计
print("添加行列总计:")
crosstab_with_margins = pd.crosstab(
index=df_sales_long['地区'],
columns=df_sales_long['产品'],
margins=True,
margins_name='总计'
)
crosstab_with_margins
添加行列总计:
| 产品 | 手机 | 电脑 | 总计 |
| 地区 | | | |
| 上海 | 3 | 3 | 6 |
| 北京 | 3 | 3 | 6 |
| 总计 | 6 | 6 | 12 |
|---|
python
# 多级行/列索引
print("多级行/列索引:")
print("按地区和日期统计产品销量:")
crosstab_multi = pd.crosstab(
index=[df_sales_long['日期'], df_sales_long['地区']],
columns=df_sales_long['产品'],
margins=True
)
crosstab_multi
多级行/列索引:
按地区和日期统计产品销量:
| | 产品 | 手机 | 电脑 | All |
| 日期 | 地区 | | | |
| 2026-01-01 | 上海 | 1 | 1 | 2 |
| 2026-01-01 | 北京 | 1 | 1 | 2 |
| 2026-01-02 | 上海 | 1 | 1 | 2 |
| 2026-01-02 | 北京 | 1 | 1 | 2 |
| 2026-01-03 | 上海 | 1 | 1 | 2 |
| 2026-01-03 | 北京 | 1 | 1 | 2 |
| All | 6 | 6 | 12 |
|---|
8.6 实战场景:问卷调查数据的长宽表转换
pivot
计算
问卷长表
问卷宽表
加权得分
python
# 问卷调查数据的长宽表转换
print("问卷调查数据的长宽表转换:")
print("原始问卷数据(长表):")
print(df_survey)
print("\n转为宽表便于分析:")
survey_wide = df_survey.pivot(
index='问卷ID',
columns='问题',
values='评分'
)
print(survey_wide)
print("\n计算加权满意度得分:")
survey_scores = df_survey.copy()
survey_scores['加权得分'] = survey_scores['评分'] * survey_scores['权重']
weighted_scores = survey_scores.groupby('受访者')['加权得分'].sum()
print("各受访者加权满意度得分:")
print(weighted_scores)
问卷调查数据的长宽表转换:
原始问卷数据(长表):
问卷ID 受访者 问题 评分 权重
0 Q001 张三 满意度 8 0.4
1 Q001 张三 推荐度 7 0.3
2 Q001 张三 价格合理性 6 0.3
3 Q002 李四 满意度 9 0.4
4 Q002 李四 推荐度 8 0.3
5 Q002 李四 价格合理性 7 0.3
6 Q003 王五 满意度 7 0.4
7 Q003 王五 推荐度 6 0.3
8 Q003 王五 价格合理性 8 0.3
9 Q004 赵六 满意度 8 0.4
10 Q004 赵六 推荐度 9 0.3
11 Q004 赵六 价格合理性 7 0.3
转为宽表便于分析:
问题 价格合理性 推荐度 满意度
问卷ID
Q001 6 7 8
Q002 7 8 9
Q003 8 6 7
Q004 7 9 8
计算加权满意度得分:
各受访者加权满意度得分:
受访者
张三 7.1
李四 8.1
王五 7.0
赵六 8.0
Name: 加权得分, dtype: float64
8.7 长宽表选择指南
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 数据库存储 | 长表 | 规范化,易于扩展 |
| Excel 报表 | 宽表 | 便于阅读和对比 |
| 数据可视化 | 宽表 | 大多数图表库要求 |
| 统计分析 | 长表 | 便于分组和聚合 |
| 数据合并 | 长表 | 便于关联操作 |
| 时间序列分析 | 长表 | 便于时序建模 |
方法选择建议
| 需求 | 推荐方法 |
|---|---|
| 简单长宽转换 | pivot() / melt() |
| 需要聚合处理重复值 | pivot_table() |
| 层次化索引转换 | stack() / unstack() |
| 快速频数统计 | crosstab() |
| 多级行列索引 | pivot_table() |
| 添加行列总计 | pivot_table() + margins |
本章小结
学习内容回顾
1. pivot() - 长表转宽表
| 参数 | 说明 | 示例 |
|---|---|---|
index |
行索引列 | index='日期' |
columns |
列索引列 | columns='产品' |
values |
值列 | values='销售额' |
2. melt() - 宽表转长表
| 参数 | 说明 | 示例 |
|---|---|---|
id_vars |
标识列 | id_vars=['ID', 'Name'] |
value_vars |
值列 | value_vars=['1月', '2月'] |
var_name |
变量名列 | var_name='月份' |
value_name |
值名列 | value_name='业绩' |
3. pivot_table() - 高级数据透视
| 参数 | 说明 | 示例 |
|---|---|---|
aggfunc |
聚合函数 | aggfunc='sum' |
margins |
添加总计 | margins=True |
fill_value |
填充缺失值 | fill_value=0 |
4. stack() 和 unstack()
stack():行索引转列索引(长表转宽表)unstack():列索引转行索引(宽表转长表)level参数指定操作层级
5. crosstab() - 交叉表
- 快速创建计数交叉表
- 支持聚合函数和总计
下章预告
第九章:分组聚合操作
将学习分组聚合的核心操作,包括:
- groupby 分组机制
- 聚合函数应用
- transform 转换
- apply 自定义函数
- 分组筛选与过滤
课后练习
练习1:使用 pivot() 完成以下操作:
- 将销售记录表透视为日期×地区的销售额矩阵
- 尝试创建多级列索引(地区+产品)
- 观察并理解透视后的数据结构变化
练习2:使用 melt() 完成以下操作:
- 将员工绩效表从宽格式转为长格式
- 分别融化业绩列和考勤列,然后合并
- 计算每个员工每季度的平均业绩
练习3:使用 pivot_table() 完成以下操作:
- 创建按地区和产品汇总的销售额透视表
- 使用多种聚合函数(sum、mean、count)
- 添加 margins 显示行列总计
练习4:使用 stack/unstack 完成以下操作:
- 创建一个带多级索引的 DataFrame
- 使用 stack() 将行索引转为列索引
- 使用 unstack() 将列索引转为行索引
- 观察数据结构的变化
练习5:使用 crosstab() 完成以下操作:
- 创建地区×产品的频数交叉表
- 添加 margins 显示总计
- 尝试多级行/列索引的交叉表
练习6:综合练习:
- 创建一个包含销售数据的 DataFrame
- 分别使用 pivot、melt、pivot_table 进行长宽表转换
- 比较三种方法的结果差异和适用场景