长宽表转换:melt 与 wide_to_long
本教程共 54 篇 · 第 29 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:搞懂长表和宽表的区别,能用 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 是”宽转长”的主力函数。操作分三步:
- 用 id_vars 指定保持不变的身份列;
- 决定拆哪些列(默认除 id_vars 外全拆,或用 value_vars 点名);
- 给拆出来的两列命名: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)
Warningpivot 要求每个 (index, columns) 组合在表里只能出现一次。melt 出来的表有重复组合时,pivot 会报 “Index contains duplicate entries”,得先聚合(pivot_table)。
Notemelt 是 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 是互逆操作,前提是转换过程没做任何计算。