首页 / Pandas 入门教程 / 长宽表转换:melt 与 wide_to_long

Pandas 入门教程

长宽表转换:melt 与 wide_to_long

本教程共 54 篇 · 第 29 篇 · 更新于 2026-08-11 · 约 6 分钟阅读

PandasPandas 入门教程meltwide_to_long长宽表数据重塑

本节目标:搞懂长表和宽表的区别,能用 melt 把宽表变成长表,用 wide_to_long 批量处理带后缀的列。

先分清长表和宽表

同一份数据,有两种常见摆法。

宽表(wide format)像 Excel 报表:一行是一个主体,各个指标摊开成一列一列。

import pandas as pd

sales = pd.DataFrame({
    "城市": ["北京", "上海"],
    "一季度": [1200, 900],
    "二季度": [1500, 1100],
    "三季度": [1300, 950],
})
print(sales)

长表(long format)反过来:指标名收进一列,指标值收进另一列,同一个城市占多行。

long_sales = sales.melt(id_vars="城市", var_name="季度", value_name="销售额")
print(long_sales)

对比两张表能看到本质区别:宽表里”季度”是列名的一部分,长表里”季度”是一列的值。专业说法叫”指标在列上”和”指标在行上”。长表也叫记录格式(record format),每一行就是一条完整的”某个主体在某时点的某个指标”记录。

两种格式各有主场:

  • 宽表适合人看:报表、邮件、画多系列折线图,列少信息密;
  • 长表适合机器算:筛选、分组统计(如 §37 的 groupby)、机器学习输入,逻辑统一。

你在 Excel 里攒下的数据大多是宽表,而很多统计函数更爱长表,所以”宽转长”是数据分析里的高频操作。pandas 的 melt 就是干这个的。

melt:把宽表融成长表

melt 是”宽转长”的主力函数。操作分三步:

  1. 用 id_vars 指定保持不变的身份列;
  2. 决定拆哪些列(默认除 id_vars 外全拆,或用 value_vars 点名);
  3. 给拆出来的两列命名:var_name(列名去向)和 value_name(数值去向)。
long_sales = sales.melt(
    id_vars="城市",
    var_name="季度",
    value_name="销售额",
)
print(long_sales)

拆完后四列:“城市”原样保留,三个季度列被叠成两列——列名进了”季度”,数值进了”销售额”。每行变成”某个城市在某个季度的销售额”,正是长表的形态。

id_vars 支持多列,身份信息想留几列都行:id_vars=[“城市”, “省份”]。

Tip

默认的 variable / value 太笼统,建议像上面一样显式改名,下游处理时一眼能看懂。

只拆指定列:value_vars

不想全拆时,用 value_vars 点名:

part = sales.melt(
    id_vars="城市",
    value_vars=["一季度", "二季度"],
    var_name="季度",
    value_name="销售额",
)
print(part)

结果只有一季度、二季度两段,“三季度”被留在门外。variable 列里的取值和顺序,都跟着 value_vars 走——列表里写什么顺序,结果就按什么顺序排。

注意一个细节:value_vars 没点到的列,既不会保留也不会出现在结果里,是直接丢弃。想保留它们,就把列名加进 id_vars。

melt 的索引处理

默认情况下,melt 会丢弃原索引、重新编号成 0..n-1。想保留原索引,用 ignore_index=False:

sales2 = sales.copy()
sales2.index = ["北区", "南区"]

long2 = sales2.melt(
    id_vars="城市",
    var_name="季度",
    value_name="销售额",
    ignore_index=False,
)
print(long2)

设置 ignore_index=False 后,索引被保留,但一行拆成多行,同一个索引会重复出现。什么时候需要保留?当你还要拿原索引和其他表对齐时。多数场景用默认值就行。

wide_to_long:批量处理带后缀的列

melt 适合列名各不相同的表。但现实里更常见这种:列名带年份后缀,比如 A1970、A1980、B1970、B1980。用 melt 拆出来,指标列里会混着”A1970”、“B1980”这种脏值,还得二次清洗。

wide_to_long 就是为这种表设计的:按前缀(stubname)归类,把后缀拆成单独一列。

df = pd.DataFrame({
    "A1970": ["a", "b", "c"],
    "A1980": ["d", "e", "f"],
    "B1970": [2.5, 1.2, 0.7],
    "B1980": [3.2, 1.3, 0.1],
    "X": [1.5, -0.4, 0.6],
})
df["id"] = df.index

long_df = pd.wide_to_long(df, stubnames=["A", "B"], i="id", j="year")
print(long_df)
  • stubnames:要拆的前缀,A1970/A1980 都归到 A 名下;
  • i:身份列,相当于 melt 的 id_vars;
  • j:后缀去向的新列名,这里得到 year。

结果行索引变成 (id, year) 两层,X 这种不带后缀的列原样保留。后缀默认匹配数字;后缀是 “Q1” 这种字母时,要加参数 suffix=r”\D+“。列名中间有分隔符(比如 A_1970)时,用 sep 指定:pd.wide_to_long(df, stubnames=“A”, i=“id”, j=“year”, sep=”_”)。

melt 与 pivot 是互逆操作

melt 宽转长,pivot 长转宽(详见下一章),两个方向正好相反。把 melt 的结果再 pivot 回去,能还原原表:

restored = long_sales.pivot(index="城市", columns="季度", values="销售额")
print(restored)
Warning

pivot 要求每个 (index, columns) 组合在表里只能出现一次。melt 出来的表有重复组合时,pivot 会报 “Index contains duplicate entries”,得先聚合(pivot_table)。

Note

melt 是 pivot 的逆操作,但不是 pivot_table 的逆操作。pivot_table 聚合过(比如取了平均),信息已经损失,再 melt 也还原不出原始明细。变形工具能保住信息的前提,是转换过程没有做任何计算。

选型小结:列名无规律、纯宽转长用 melt;列名有共同前缀加后缀用 wide_to_long;宽表要再叠几层用 stack(见 §31)。判断要不要转长表也简单:下一步要按指标分组统计、画分面图、喂模型,就转;要出报表、画多序列折线,宽表更顺手。

小结

长表和宽表是同一份数据的两种摆法:宽表指标在列上、适合人看,长表指标在行上、适合机器算。melt 用 id_vars 保身份、value_vars 点名拆列,拆出的两列用 var_name 和 value_name 命名;wide_to_long 专治带共同前缀的列名。melt 和 pivot 是互逆操作,前提是转换过程没做任何计算。