堆叠与拆堆:stack 与 unstack
本教程共 54 篇 · 第 31 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:理解 stack 和 unstack 怎么在行、列索引之间移动层级,能用 level 参数指定要搬的层。
stack 和 unstack 是干什么的
melt/pivot 玩的是”普通列”,stack(堆叠)和 unstack(拆堆)专门处理”索引层级”。
- unstack:把行索引的最内层搬到列上,表变宽;
- stack:把列索引的最内层搬回行上,表变长。
两者互为逆操作。凡是涉及 MultiIndex 的变形,基本靠这对兄弟。
unstack:行索引变列
先造一个带两层行索引的表:
import pandas as pd
index = pd.MultiIndex.from_tuples(
[("一班", "张三"), ("一班", "李四"), ("二班", "王五")],
names=["班级", "姓名"],
)
df = pd.DataFrame({"语文": [80, 75, 90], "数学": [88, 92, 85]}, index=index)
print(df)
unstack 默认把最内层(姓名)搬到列上:
print(df.unstack())
结果变成”科目 × 姓名”的四列(一班张三语文、一班张三数学……),行只剩”班级”。想搬”班级”这层,用 level 参数:
print(df.unstack(level=0))
level 可以传位置数字(0 是最外层),也可以传层级名字:df.unstack(level=“班级”)。
单层索引也能 unstack:把唯一的索引层变成列,结果常常是一个 Series。反过来,Series 的索引带层级时也能 unstack 成 DataFrame。也就是说,unstack 的作用对象是”索引层”,和索引是单层还是多层无关。
stack:列索引变行
把列上的层级压回行索引,用 stack。上一步的结果再 stack 回去,能还原原表:
wide = df.unstack()
back = wide.stack()
print(back)
stack 默认压最内层列。wide 的两层列(科目、姓名)中,“姓名”被压回行索引,回到两层行索引的形态,和原表一致。
单层列的 DataFrame 也能 stack,结果是一个 Series:
simple = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
print(simple.stack())
Series 的索引变成 (原索引, 列名) 两层,这就是”列被压到索引”的直观体现。
Tip记方向:想让表变宽、列变多用 unstack;想让表变长、行变多用 stack。拿不准时想想”列上的东西被压下来”就是 stack。
用 level 指定任意层
层级多时,要明确告诉它搬哪层。stack 的 level 参数和 unstack 一样,数字或名字都行:
import numpy as np
df2 = pd.DataFrame(
np.random.randn(4, 4),
columns=pd.MultiIndex.from_tuples(
[("A", "cat"), ("B", "dog"), ("A", "dog"), ("B", "cat")],
names=["实验", "动物"],
),
)
print(df2.stack(level="动物"))
“动物”层从列压到行索引,剩下”实验”层还在列上。也可以一次搬多层:df2.stack(level=[“实验”, “动物”]),效果等于逐层搬。注意 level 列表里数字和名字不能混着写,要么全是数字,要么全是名字。
想在同一根轴上交换两层的位置(比如把”姓名”挪到”班级”前面),stack/unstack 帮不上忙,用 swaplevel 更直接:df.swaplevel()。stack/unstack 是把层级搬到另一根轴,swaplevel 是同轴换位置,两者别混。
还有一个容易踩的细节:unstack 默认搬最内层(level=-1),stack 默认也压最内层列。层级顺序一变,搬哪层的含义就完全不同,动手前先想清楚目标层是哪一个,别靠默认值猜。
拆堆产生的缺失值
unstack 时,某些行没有对应组合,位置会空出来变成 NaN:
df3 = pd.DataFrame(
{"成绩": [80, 75, 90]},
index=pd.MultiIndex.from_tuples(
[("一班", "张三"), ("一班", "李四"), ("二班", "张三")],
names=["班级", "姓名"],
),
)
print(df3.unstack())
为什么会出现 NaN?因为拆开后行、列组合出了”空格子”——原表里根本没有这个组合的数据。反过来,stack 也可能产生 NaN:当某些行缺少部分列层级时。处理方式一样,fill_value 参数 stack 也有。
二班没有李四的成绩,对应格子就是 NaN。想用指定值填空,用 fill_value 参数:
print(df3.unstack(fill_value=0))
Warningstack/unstack 结果会被自动排序,和原表顺序不一定一致。要精确还原时,先 sort_index 再比较,别直接拿两个结果比 ==。
和 MultiIndex、melt/pivot 的关系
stack/unstack 的价值建立在 MultiIndex 上。§22 讲过 set_index 可以把几列变成索引层级——set_index 之后想展开层级就用 unstack,想收拢就用 stack。典型工作流:数据进来,set_index([“班级”, “姓名”]),按科目展开用 unstack,统计完再 stack 回去。
一句话总结分工:melt/pivot 玩普通列,stack/unstack 玩索引层级。但两者可以互相转化——多层索引 reset_index 成普通列后,melt 就能处理;pivot 的结果自带 MultiIndex,正好用 stack 继续压。stack/unstack 只做变形、不改数据内容,可以放心在管道里来回用。
选型看起点:列名不规则用 melt/pivot;数据本身是多级索引、或刚透视完想继续变形,用 stack/unstack。
小结
stack 把列压进行索引、unstack 把行索引展开成列,两者互为逆操作,level 参数指定搬哪一层。拆堆会凭空多出 NaN(原表里没有的组合),用 fill_value 填空。melt/pivot 玩普通列,stack/unstack 玩索引层级,一句话分清分工。