游乐游手机版
首页/编程语言/文章详情

Pandas数据重塑技巧:长格式与宽格式转换完全指南

时间:2026-08-05 22:13
pandas数据重塑的核心是实现宽格式与长格式的转换。pivot用于无聚合的长转宽,pivot_table支持聚合操作,melt实现宽转长,stack unstack处理多层索引行列转换,crosstab用于分组频数统计。掌握这些函数即可应对日常数据重塑需求。

数据重塑听起来挺专业,其实核心就一件事:让数据在“宽格式”和“长格式”之间来回切换。宽格式看着直观,适合直接展示;长格式则更适合做分析,比如分组统计、画图。所以,什么时候该用哪种,心里得有数。

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

pivot() — 旋转(无聚合)

这个函数用来把长表直接转成宽表,前提是索引+列的组合必须唯一,也就是说,不能有重复的行。如果数据里有重复,那就得用pivot_table了。

import pandas as pd
import numpy as np

df = 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: 对剩余所有列做 pivot
result = df.pivot(index='date', columns='city')

# ⚠️ 如果 index + columns 组合不唯一会报错!
# 如果有多余列,pivot 会用多层级列来容纳

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

当 index + columns 组合不唯一(也就是需要聚合)时,就得用pivot_table了。它跟Excel里的数据透视表是一个道理,默认按均值聚合,但你可以指定求和、计数等函数。

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的逆操作。当数据是以宽表形式存放(比如每个科目单独一列),而你希望按行整理时,melt就派上用场了。

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和unstack能帮你快速调整层级。

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

# 创建有多层列的 DataFrame
cols = 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 的 Series
idx = 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   12

wide = 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')的快捷方式,但写起来更简洁。如果只是想看两个分类变量之间的频数分布,用crosstab最顺手。

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()

总结

掌握这几个函数,日常数据重塑基本就够用了。关键是要先搞清楚数据是长还是宽,需不需要聚合,以及有没有多层索引。另外,多试几次自然就熟练了,毕竟代码这东西,动手比光看管用得多。

来源:https://www.jb51.net/python/368660lx2.htm
上一篇Windows下Gitee与GitHub共用SSH密钥从零开始详细配置步骤指南 下一篇Pandas数据合并连接操作指南
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
35岁转行网络安全:从经验复用到实战落地的可行性评估
编程语言 · 2026-10-10

35岁转行网络安全:从经验复用到实战落地的可行性评估

35岁转行网络安全并非不可行,但核心在于将过往经验转化为安全领域的差异化优势。本文从岗位匹配度、技能学习顺序、实战验证闭环、求职策略及常见误区五个维度,提供一套可执行的转行评估框架与行动指南,帮助读者理性判断投入产出比,避开无效学习陷阱。

网络安全行业前景分析:技术演进与市场机遇
编程语言 · 2026-10-10

网络安全行业前景分析:技术演进与市场机遇

围绕2026年网络安全行业的发展变化,从市场需求、技术演进、细分赛道和企业落地四个层面展开,帮助读者理解行业增长逻辑、识别重点技术方向,并建立评估市场机遇与风险的基本框架。 OWASP China +2 IDC +2

2026网络安全求职全景:从岗位拆解到实战作品集构建
编程语言 · 2026-10-10

2026网络安全求职全景:从岗位拆解到实战作品集构建

本文基于2026年网络安全行业招聘趋势,深入剖析安全运维、攻防渗透、云安全等核心岗位的技术栈差异与能力侧重。文章不仅梳理了从基础网络知识到高级攻防演练的学习路径,更提供了“以终为始”的求职策略:通过拆解JD反向验证技能缺口,并指导如何将CTF经历、HomeLab实验转化为具有说服力的项目作品集,帮助

2024安全攻防实战:从勒索软件到AI治理的破局与重构
编程语言 · 2026-10-10

2024安全攻防实战:从勒索软件到AI治理的破局与重构

2024年的网络安全已从单纯的技术对抗演变为业务连续性的生死博弈。本文基于ENISA、微软及世界经济论坛的最新报告,深入剖析勒索软件的“双重勒索”演变、身份凭证成为首要攻击面的现状,以及生成式AI带来的攻防不对称性。文章进一步拆解企业如何从被动防御转向“发现-保护-检测-响应-恢复”的闭环体系,重点

网站编程AI工具测评:提升开发效率的辅助软件推荐
编程语言 · 2026-10-10

网站编程AI工具测评:提升开发效率的辅助软件推荐

围绕网站开发中的实际需求,对AI编程辅助工具进行分类、操作体验与效果验证,帮助读者快速判断哪些工具真正能提升开发效率,并避开代码质量、隐私、安全与过度依赖等常见问题。