Pandas数据分析 - 第八章:数据重塑

第八章:数据重塑

📋 章节概述

本章将学习 Pandas 中数据重塑的核心技术。掌握这些方法后,你将能够灵活地在长表和宽表之间转换,满足不同的数据分析和报表展示需求。

🎯 学习目标

  1. 掌握 pivot() 方法进行数据透视(长表转宽表)
  2. 掌握 melt() 方法进行数据融合(宽表转长表)
  3. 掌握 stack() 和 unstack() 进行层次化索引操作
  4. 理解 pivot_table() 进行高级数据透视
  5. 掌握 crosstab() 进行交叉表分析
  6. 学会在实际业务中灵活转换数据格式

📊 知识结构图

数据重塑
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 进行长宽表转换
  • 比较三种方法的结果差异和适用场景
相关推荐
李昊哲小课2 小时前
Pandas数据分析 - 第四章:数据读取与保存
数据挖掘·数据分析·pandas
deepdata_cn3 小时前
数据分析之数据粒度(Granularity)
数据挖掘·数据分析·颗粒度
YangYang9YangYan4 小时前
2026年经管专业学习数据分析的指南
学习·数据挖掘·数据分析
551只玄猫4 小时前
【数学建模 matlab 实验报告9】数据的统计分析与描述
数学建模·matlab·数据分析·课程设计·实验报告
李昊哲小课4 小时前
Pandas数据分析 - 第七章:数据合并与连接
数据挖掘·数据分析·pandas
gushinghsjj4 小时前
元数据管理包含哪些?元数据管理如何支持数据分析?
数据库·oracle·数据分析
编程界一哥5 小时前
2026最新:原神PC启动提示缺失msvcp140.dll,安全修复工具哪家强?
数据挖掘
qyr67895 小时前
全球蜂窝分布式天线系统市场报告2026-2032
大数据·人工智能·数据分析·市场报告·蜂窝分布式天线系统
泰迪智能科技015 小时前
分享|大数据挖掘建模平台赋能企业智能决策与数字化转型
人工智能·数据挖掘