一句话总结

melt() 将宽表转为长表(多列折叠为两列),wide_to_long() 将多组相似列名宽表转为长表(适合重复测量数据)。

1. melt() — 宽表 → 长表

将多个列”熔化”为两列:一列是变量名(原列名),一列是变量值。

基本语法

DataFrame.melt(id_vars=None, value_vars=None, var_name=None,
               value_name='value', col_level=None, ignore_index=True)

参数详解

参数说明
id_vars保持不变的标识列(作为每行的唯一标识),可以是列名或列表
value_vars要折叠为”变量-值”的列。默认折叠除 id_vars 外的所有列
var_name变量列的列名,默认 'variable'
value_name值列的列名,默认 'value'
col_level若列为 MultiIndex,指定要熔化的层级
ignore_index是否对结果行重新编号(0,1,2,…),默认 True;False 时保留原索引

示例:基本熔解

import pandas as pd
 
df = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'math': [90, 85],
    'english': [88, 92],
    'physics': [95, 78]
})
 
melted = df.melt(id_vars='name', var_name='subject', value_name='score')
print(melted)
#     name  subject  score
# 0  Alice     math     90
# 1    Bob     math     85
# 2  Alice  english     88
# 3    Bob  english     92
# 4  Alice  physics     95
# 5    Bob  physics     78

示例:指定 value_vars

df.melt(id_vars='name', value_vars=['math', 'physics'],
        var_name='subject', value_name='score')
# 仅折叠 math 和 physics 两列

示例:col_level(多层列索引)

df_cols = pd.DataFrame(np.random.rand(3, 4),
                       columns=pd.MultiIndex.from_product([['math', 'english'], ['期中', '期末']]))
df_cols.melt(col_level=0)  # 只熔最外层

示例:ignore_index

df.melt(id_vars='name', ignore_index=False)
# 保留原 DataFrame 的行索引作为结果索引

2. wide_to_long() — 宽表 → 长表(带 stubnames)

针对列名有共同前缀(stubname)的宽表,一次性将多组列转换为长表。

基本语法

pd.wide_to_long(df, stubnames, i, j, sep='', suffix=r'\d+')

参数详解

参数说明
stubnames列名前缀(stub),字符串或字符串列表
i标识列(id 列),类似于 melt 中的 id_vars
j新生成的”后缀变量”列名(如年份、时间点)
sep前缀与后缀的分隔符,默认 ''(如 'math1');若列名为 'math_1' 则 sep='_'
suffix识别后缀的正则表达式,默认 r'\d+'(数字)

示例:基本用法

df_wide = pd.DataFrame({
    'id':     [1, 2, 3],
    'name':   ['A', 'B', 'C'],
    'math_1': [90, 85, 88],
    'math_2': [92, 80, 90],
    'eng_1':  [88, 92, 85],
    'eng_2':  [95, 89, 82]
})
 
long = pd.wide_to_long(df_wide, stubnames=['math', 'eng'],
                       i='id', j='year', sep='_', suffix=r'\d+')
print(long)
#       name  math  eng
# id year
# 1  1      A    90   88
#    2      A    92   95
# 2  1      B    85   92
#    2      B    80   89
# 3  1      C    88   85
#    2      C    90   82

示例:多列 stubnames

df_panel = pd.DataFrame({
    'subject': [1, 2, 3],
    'pre_test':  [10, 12, 11],
    'post_test': [15, 18, 17],
    'pre_score': [5.5, 6.0, 5.8],
    'post_score':[8.0, 8.5, 7.9]
})
 
result = pd.wide_to_long(df_panel,
                         stubnames=['pre', 'post'],  # 两个前缀
                         i='subject', j='phase', sep='_')

3. 长宽转换的互逆关系

# melt 的"逆操作" ≈ pivot
restored = melted.pivot(index='name', columns='subject', values='score')
 
# wide_to_long 的"逆操作" ≈ pivot + 列名拼接
restored2 = long.reset_index().pivot(index='id', columns='year', values='math')
restored2.columns = [f'math_{c}' for c in restored2.columns]

4. 实战:宽表转 tidy data

标准数据整理流程

# 原始宽表:每个学生一行,多门课多学期
raw = pd.DataFrame({
    'student': ['Alice', 'Bob'],
    'math_2023': [85, 90],
    'math_2024': [92, 88],
    'eng_2023': [80, 78],
    'eng_2024': [85, 82]
})
 
# 方法 1:melt + 拆分列
step1 = raw.melt(id_vars='student', var_name='variable', value_name='score')
step1[['subject', 'year']] = step1['variable'].str.split('_', expand=True)
tidy = step1.drop(columns='variable')
 
# 方法 2:wide_to_long 一步到位
tidy2 = pd.wide_to_long(raw, stubnames=['math', 'eng'],
                        i='student', j='year', sep='_')

注意事项

  • melt() 后原来的各列数据类型可能不一致,折叠后变为同一列,可能会被提升为 object 类型,必要时使用 pd.to_numeric() 转换。
  • wide_to_long() 要求列名遵循 stubname + sep + suffix 的固定模式,不满足时需先 rename。

相关笔记

  • 15.2 透视:pivot / pivot_table 是 melt 的逆操作
  • index:可通过 pd.set_option('display.max_rows', None) 查看完整长表
  • index:返回章节总览