首页 / Pandas 入门教程 / 堆叠与拆堆:stack 与 unstack

Pandas 入门教程

堆叠与拆堆:stack 与 unstack

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

PandasPandas 入门教程stackunstackMultiIndex数据重塑

本节目标:理解 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))
Warning

stack/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 玩索引层级,一句话分清分工。