
本文介绍如何将具有多行表头(年份+高低值)的宽格式dataframe,通过multiindex和stack操作高效转换为规范的长格式结构。
在实际数据分析中,常会遇到以多行作为复合列名的“非标准”宽格式数据——例如第一行为年份、第二行为统计类型(如 low/high),后续行为指标名称(如 foo/bar)。这种结构虽便于人工阅读,却不利于计算与建模。Pandas 提供了灵活的 MultiIndex 与 stack() 配合 set_axis() 的组合方案,可一步完成语义化重塑。
核心思路是:将前两行提取为列的双层索引 → 将数据主体(第2行起)设为行索引 → 按参数名重命名索引 → 堆叠年份-类型维度 → 重置索引得到规整长表。
以下为完整实现代码(含关键注释):
import pandas as pd
import numpy as np
# 构造原始数据(注意:第0行是列名行,第1行是子列名行)
data = {
0: ['parameter', '', 'foo', 'bar'],
1: [2001, 'low', 1, np.nan],
2: [2001, 'high', 2, np.nan],
3: [2002, 'low', 7, 12],
4: [2002, 'high', 8, 13],
}
df = pd.DataFrame(data)
# 步骤1:用前两行(跳过第0列)构建 MultiIndex 列
# df.iloc[0, 1:] → 年份数组 [2001, 2001, 2002, 2002]
# df.iloc[1, 1:] → 类型数组 ['low', 'high', 'low', 'high']
idx = pd.MultiIndex.from_arrays(
[df.iloc[0, 1:], df.iloc[1, 1:]],
names=('year', 'stat') # 显式命名层级,增强可读性
)
# 步骤2:取数据主体(第2行及以后),设第0列为行索引(即 'foo', 'bar')
df_data = df.iloc[2:].set_index(0)
# 步骤3:将列替换为新 MultiIndex,并重命名行索引为 'parameter'
df_data = df_data.set_axis(idx, axis=1).rename_axis('parameter')
# 步骤4:stack 第一层('year'),自动展开为行;reset_index 得到平面DataFrame
result = df_data.stack(0).reset_index(name='value')
# 可选:分离 'stat' 列为独立列(low/high),便于后续分析
result = result.pivot(index=['parameter', 'year'], columns='stat', values='value') \
.reset_index() \
.rename_axis(None, axis=1)输出结果为:
parameter year high low 0 foo 2001 2.0 1.0 1 foo 2002 8.0 7.0 2 bar 2002 13.0 12.0
⚠️ 注意事项:
- df.iloc[0, 1:] 和 df.iloc[1, 1:] 必须长度一致,否则 from_arrays 报错;建议先校验 len(df.iloc[0,1:]) == len(df.iloc[1,1:]);
- 若原始数据存在缺失列(如某年份缺 high),stack() 会保留 NaN,符合预期;
- stack(0) 中的 0 表示堆叠最外层列索引(即 'year' 层),若需堆叠内层(如 'stat'),应使用 stack(1);
- 最终 pivot() 步骤可省略,若接受 MultiIndex 列(如 result.columns = MultiIndex.from_tuples([('foo', 2001), ...])),则更节省内存。
该方法避免了手动循环或 melt() + pivot() 的多步冗余,兼具简洁性与可扩展性,适用于任意规模的年份×类型二维列结构重塑。








