在处理表格数据时,我们经常需要在宽格式和长格式之间来回切换。比如一个按月份展开的销售明细表,做统计分析时希望它变成一行一条记录的长表,而画透视图时又希望它变回宽表。Pandas提供的stack和unstack这对函数,就是专门解决这类形态转换问题的利器。它们的本质是操作索引层级,理解了这一点,数据重塑就不再是玄学。

stack与unstack的基本原理
stack的中文含义是堆叠,它的作用是把列标签转变成最内层的行索引,相当于把横向排列的数据往纵向压。unstack则相反,把最内层的行索引提升为列标签,把纵向的数据摊开。两者互为逆操作,df.stack().unstack()通常能还原回原始结构。
先看一个最简单的例子。假设有这样一个DataFrame,行索引是城市,列是两个季度的销售额:
import pandas as pd
df = pd.DataFrame({
'Q1': [100, 200, 150],
'Q2': [120, 180, 170]
}, index=['北京', '上海', '广州'])
print(df)
# Q1 Q2
# 北京 100 120
# 上海 200 180
# 广州 150 170
# 堆叠:列标签Q1、Q2变成行索引的第二层
stacked = df.stack()
print(stacked)
# 北京 Q1 100
# Q2 120
# 上海 Q1 200
# Q2 180
# 广州 Q1 150
# Q2 170可以看到,stack之后DataFrame变成了一个带有MultiIndex的Series,原来的列名Q1和Q2现在成了行索引的第二层。这种一行一条数值的形式,就是典型的长格式数据。再执行unstack就能还原:
# 解堆叠:把第二层行索引展开成列 restored = stacked.unstack() print(restored) # Q1 Q2 # 北京 100 120 # 上海 200 180 # 广州 150 170
多级索引下的层级控制
当DataFrame本身带有多级索引时,stack和unstack操作哪一层就需要明确指定了。unstack的level参数默认是-1,也就是操作最内层的索引;stack的level参数默认是-1,操作最内层的列索引。level既可以是整数,也可以是索引的名称。
通过一个两层索引的例子来看层级切换的效果:
index = pd.MultiIndex.from_tuples(
[('华东', '杭州'), ('华东', '苏州'), ('华北', '天津')],
names=['大区', '城市']
)
df2 = pd.DataFrame({
'2023年': [10, 20, 30],
'2024年': [15, 25, 35]
}, index=index)
# 默认unstack最内层(城市)
print(df2.unstack())
# 2023年 2024年
# 城市 杭州 苏州 天津 杭州 苏州 天津
# 大区
# 华东 10 20 NaN 15 25 NaN
# 华北 NaN NaN 30 NaN NaN 35
# unstack外层(大区),传入层名或序号0
print(df2.unstack(level='大区'))
# 城市 2023年 2024年
# 大区 华东 华北 华东 华北
# 杭州 10 NaN 15 NaN
# 苏州 20 NaN 25 NaN
# 天津 NaN 30 NaN 35注意unstack之后产生的缺失值。因为华东大区没有天津,华北大区没有杭州和苏州,展开后这些交叉位置自然就是NaN。这是unstack非常常见的副作用,实际使用时要考虑是否需要用fillna(0)之类的操作补齐。
dropna参数与缺失值的处理技巧
stack有一个容易被忽略的参数dropna,默认值为True,表示堆叠时自动丢弃全为NaN的数据,避免长格式里出现大量无意义的空行。如果原始DataFrame中本来就有NaN列,stack后这些数据会直接消失,需要特别留意。
df3 = pd.DataFrame({
'A': [1, 2],
'B': [3, 4],
'C': [pd.NA, pd.NA] # 全空列
}, index=['x', 'y'])
print(df3.stack())
# x A 1
# B 3
# y A 2
# B 4
# 保留NaN,C列的空值也会出现在结果里
print(df3.stack(dropna=False))
# x A 1
# B 3
# C <NA>
# y A 2
# B 4
# C <NA>反过来,unstack产生的NaN在多数统计场景下没有意义,常见的处理方式是链式调用unstack(fill_value=0),unstack原生支持fill_value参数,比事后fillna效率更高,写法也更简洁。不过要注意,fill_value只能填充一个固定值,如果需要区分处理不同来源的缺失,还是要分步操作。
stack与pivot、melt的关系
Pandas中实现宽转长的函数不止stack一个,melt和wide_to_long也能做类似的事情。它们的核心区别在于:stack基于索引层级操作,要求列标签结构规整;melt基于参数指定,灵活性更强,可以指定哪些列保持不动,哪些列被融化成两列。
df4 = pd.DataFrame({
'城市': ['北京', '上海'],
'Q1': [100, 200],
'Q2': [120, 180]
})
# 用melt实现宽转长,城市列作为标识保留
long_df = df4.melt(id_vars=['城市'], var_name='季度', value_name='销售额')
print(long_df)
# 城市 季度 销售额
# 0 北京 Q1 100
# 1 上海 Q1 200
# 2 北京 Q2 120
# 3 上海 Q2 180
# 如果用stack,需要先把城市列先设为索引
long_by_stack = df4.set_index('城市').stack().reset_index()
long_by_stack.columns = ['城市', '季度', '销售额']选择建议很简单:数据列名有规律、追求链式调用简洁性时用stack;列名不规则、只需要融化部分列时用melt。反向转换中,unstack适合配合MultiIndex使用,而pivot_table适合从普通长表直接重建宽表,并且自带聚合能力,遇到重复索引也不会报错。
掌握stack和unstack之后,配合groupby和agg可以实现很多一气呵成的分析流程。例如先按地区和月份分组求和,再unstack月份做同比对比,几行代码就能得到一张清晰的透视对比表,这也是它们在数据分析实战中最常见的用法。
Pandas stackunstack数据重塑修改时间:2026-09-03 12:16:57