pandas数据重塑实用技巧:长格式和宽格式如何转换?看这篇就够了

作者:袖梨 2026-08-06

数据重塑是将数据在"宽格式"和"长格式"之间转换。宽格式适合呈现,长格式适合分析。

pandas数据重塑技巧:长格式和宽格式如何转换?看这篇就够了

pivot()— 旋转(无聚合)

将长表不经过聚合直接变成宽表。要求索引+列的组合唯一。

import pandas as pdimport numpy as npdf = pd.DataFrame({    'date': ['2024-01', '2024-01', '2024-02', '2024-02'],    'city': ['北京', '上海', '北京', '上海'],    'temp': [5, 8, 10, 12]})print(df)#       date city  temp# 2024-01   北京     5# 2024-01   上海     8# 2024-02   北京    10# 2024-02   上海    12# pivot: 长 -> 宽wide = df.pivot(index='date', columns='city', values='temp')print(wide)# city      北京  上海# date# 2024-01     5    8# 2024-02    10   12# 不指定 values: 对剩余所有列做 pivotresult = df.pivot(index='date', columns='city')# ⚠️ 如果 index + columns 组合不唯一会报错!# 如果有多余列,pivot 会用多层级列来容纳

pivot_table()— 数据透视表(带聚合)

index + columns 组合不唯一(即需要聚合)时使用。

df = pd.DataFrame({    'date': ['2024-01', '2024-01', '2024-01', '2024-02', '2024-02', '2024-02'],    'city': ['北京', '北京', '上海', '北京', '北京', '上海'],    'product': ['A', 'B', 'A', 'A', 'B', 'B'],    'sales': [100, 150, 200, 110, 160, 220]})# 基本用法result = df.pivot_table(    values='sales',      # 要聚合的值    index='city',        # 行索引    columns='product',   # 列索引    aggfunc='sum',       # 聚合函数(默认 'mean')    fill_value=0         # 用 0 填充 NaN)print(result)# product    A    B# city# 北京     210  310# 上海     200  220# 多个聚合函数result = df.pivot_table(    values='sales',    index='city',    columns='product',    aggfunc=['sum', 'mean', 'count'])# 对多个值列使用不同聚合result = df.pivot_table(    index='city',    aggfunc={'sales': 'sum', 'quantity': 'mean'})# 多层索引result = df.pivot_table(    values='sales',    index=['city', 'date'],    # 多级行索引    columns='product',    aggfunc='sum')

pivot_table 完整参数

pd.pivot_table(    data,    values=None,          # 要聚合的列    index=None,           # 行分组键    columns=None,         # 列分组键    aggfunc='mean',       # 聚合函数: str / list / dict    fill_value=None,      # 填充缺失值    margins=False,        # 是否添加 'All' 汇总行/列    margins_name='All',   # 汇总标签名    dropna=True,          # 是否丢弃全为 NaN 的列    observed=False,       # 分类数据:是否只显示数据中出现的组    sort=True             # 是否对结果排序)

margins— 汇总行列

result = df.pivot_table(    values='sales',    index='city',    columns='product',    aggfunc='sum',    margins=True,    margins_name='合计')# 添加了"合计"行和列

melt()— 宽 -> 长(逆透视)

将列名"融化"成行值,是 pivot 的逆操作。

df_wide = pd.DataFrame({    'name': ['张三', '李四', '王五'],    'math': [85, 90, 78],    'english': [88, 82, 95],    'chinese': [92, 88, 85]})# 基本用法: 指定 id_vars(保持不变的列)long = pd.melt(df_wide, id_vars=['name'])#       name variable  value# 0      张三     math     85# 1      李四     math     90# ...# 7      李四  chinese     88# 8      王五  chinese     85# 自定义列名long = pd.melt(    df_wide,    id_vars=['name'],    value_vars=['math', 'english', 'chinese'],  # 指定要融化的列    var_name='科目',        # 原列名变成的新列名    value_name='分数'       # 原值变成的新列名)print(long)#   name    科目  分数# 0  张三  math    85# ...

melt 常见场景

# 只融化部分列pd.melt(df, id_vars=['id', 'date'], value_vars=['sale', 'cost'])# 不指定 value_vars: 自动使用所有非 id_vars 列pd.melt(df, id_vars=['id'])# 配合 groupby 做聚合long = pd.melt(df, id_vars=['region'], value_vars=['Q1', 'Q2', 'Q3', 'Q4'])result = long.groupby(['region', 'variable'])['value'].sum()

stack()/unstack()— 层级旋转

操作的是 MultiIndex 的行/列层级。

stack()— 列层级 -> 行层级(变长)

# 创建有多层列的 DataFramecols = pd.MultiIndex.from_product([['温度', '湿度'], ['北京', '上海']],                                   names=['指标', '城市'])df = pd.DataFrame(np.arange(12).reshape(3, 4),                  index=['1月', '2月', '3月'],                  columns=cols)print(df)# 指标   温度      湿度# 城市   北京  上海  北京  上海# 1月     0    1    2    3# 2月     4    5    6    7# 3月     8    9   10   11# stack: 列层级 -> 行层级stacked = df.stack()         # 压缩最内层列到行print(stacked)# 指标     温度  湿度# 1月 北京   0    2#     上海   1    3# 2月 北京   4    6# ...stacked = df.stack(level=0)  # 压缩第 0 层列到行stacked = df.stack(level='指标')  # 按名称压缩

unstack()— 行层级 -> 列层级(变宽)

# 创建有 MultiIndex 的 Seriesidx = pd.MultiIndex.from_product([['1月', '2月'], ['北京', '上海']],                                  names=['月份', '城市'])s = pd.Series([5, 8, 10, 12], index=idx, name='温度')print(s)# 月份  城市# 1月   北京     5#       上海     8# 2月   北京    10#       上海    12# unstack: 行层级 -> 列层级wide = s.unstack()           # 压缩最内层行到列print(wide)# 城市  北京  上海# 月份# 1月     5    8# 2月    10   12wide = s.unstack(level=0)    # 压缩第 0 层wide = s.unstack(level='月份')  # 按名称压缩wide = s.unstack(fill_value=0)  # NaN 填充为 0

使用场景对比

操作方向效果
stack()列 → 行数据变长、变窄
unstack()行 → 列数据变宽
melt()列 → 行将列名变为一列的值
pivot()行 → 列将一列的值变成列名

crosstab()— 交叉表/列联表

计算分组频率,类似 pivot_table(aggfunc='count'),但更简洁。

df = pd.DataFrame({    '性别': ['男', '女', '女', '男', '女', '男', '男'],    '学历': ['本科', '硕士', '本科', '博士', '硕士', '本科', '硕士'],    '部门': ['技术', '销售', '技术', '技术', '销售', '销售', '技术']})# 基本交叉表result = pd.crosstab(df['性别'], df['学历'])print(result)# 学历  博士  本科  硕士# 性别# 女     0    1    2# 男     1    2    1# 多列分组result = pd.crosstab(df['性别'], [df['学历'], df['部门']])# 归一化:百分比pd.crosstab(df['性别'], df['学历'], normalize='all')     # 整体占比pd.crosstab(df['性别'], df['学历'], normalize='index')   # 行占比pd.crosstab(df['性别'], df['学历'], normalize='columns') # 列占比# 带汇总pd.crosstab(df['性别'], df['学历'], margins=True, margins_name='合计')# 带值(而非计数)df2 = pd.DataFrame({    '性别': ['男', '女', '女', '男'],    '产品': ['A', 'A', 'B', 'B'],    '金额': [100, 200, 150, 180]})pd.crosstab(df2['性别'], df2['产品'],            values=df2['金额'], aggfunc='sum')

crosstab 完整参数

pd.crosstab(    index,          # 行分组    columns,        # 列分组    values=None,    # 要聚合的值    aggfunc=None,   # 聚合函数    rownames=None,  # 行名称    colnames=None,  # 列名称    margins=False,  # 汇总    margins_name='All',    dropna=True,    # 丢弃 NaN 列    normalize=False # 归一化: 'all' / 'index' / 'columns')

格式转换速查

需求方法
长→宽(无聚合,唯一键)df.pivot(index, columns, values)
长→宽(需要聚合)df.pivot_table(values, index, columns, aggfunc)
宽→长pd.melt(df, id_vars, var_name, value_name)
列层级→行层级df.stack()
行层级→列层级df.unstack()
分组计数pd.crosstab(index, columns)
行/列互换df.T 或 df.transpose()

总结

相关文章

精彩推荐