查看全课大纲(第 61 / 101 节)
- 1.数据分析基本概念
- 2.学习数据分析的一般路线
- 3.数据分析的流程
- 4.数据类型
- 5.环境部署(1)
- 6.环境部署(2)
- 7.课程介绍
- 8.TXT文件操作
- 9.JSON文件操作
- 10.CSV文件操作
- 11.Excel文件操作
- 12.数据库及SQL常用语法
- 13.数据库基本操作
- 14.数据库多表连接
- 15.实战:欧洲职业足球数据库分析
- 16.爬虫简介
- 17.URL管理模块
- 18.网页下载模块
- 19.网页解析模块(1)
- 20.网页解析模块(2)
- 21.Scrapy简介
- 22.Scrapy使用步骤(1)
- 23.Scrapy使用步骤(2)
- 24.Scrapy使用步骤(3)
- 25.Scrapy使用步骤(4)
- 26.实战:获取国内城市空气质量指数数据
- 27.NumPy和SciPy介绍
- 28.多维数组
- 29.多维数组操作
- 30.NumPy的常用方法
- 31.向量化介绍
- 32.向量化及通用函数
- 33.实战:2016美国大选分析
- 34.数据结构-Series
- 35.数据结构-DataFrame
- 36.数据结构-Index
- 37.Series的索引操作
- 38.DataFrame的索引操作
- 39.索引操作总结
- 40.运算与对齐
- 41.函数应用操作(1) -- map
- 42.函数应用操作 (2) -- apply applymap
- 43.文件读写操作
- 44.排序操作
- 45.数据清洗--处理缺失数据
- 46.数据清洗--处理重复数据
- 47.数据清洗--替换数据
- 48.常用统计方法(1) -- describe quantile
- 49.常用统计方法(2) -- sum mean median count
- 50.常用统计方法(3) -- max min idxmax idxmin
- 51.常用统计方法(4) -- mad var std cumsum
- 52.实战:全球食品数据分析
- 53.层级索引
- 54.分组与聚合介绍
- 55.分组操作(1) -- GroupBy对象及常用聚合操作
- 56.分组操作(2) -- 自定义分组及聚合操作
- 57.透视表介绍
- 58.透视表操作
- 59.数据规整(1) -- 数据合并concat
- 60.数据规整(2) -- 数据连接merge
- 61.数据重构(3) -- 数据重构stack unstack
- 62.实战:互联网电影资料库分析
- 63.探索性数据分析EDA介绍
- 64.EDA的目的
- 65.EDA常用工具
- 66.Matplotlib绘图基本介绍
- 67.Matplotlib画布
- 68.散点图和柱状图的绘制
- 69.直方图的绘制
- 70.矩阵绘图
- 71.子图的使用
- 72.Matplotlib颜色、标记、线型
- 73.Matplotlib坐标刻度、标签、图例、标题
- 74.Seaborn介绍
- 75.数据集分布可视化(1) -- 单变量分布、双变量分布
- 76.数据集分布可视化(2) -- 变量关系可视化
- 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
- 78.交互式数据可视化工具Bokeh介绍
- 79.Bokeh绘制散点图、柱状图、盒子图、弦图
- 80.Bokeh绘制常用图形元素
- 81.3D绘图 -- mplot3d
- 82.3D曲线可视化
- 83.3D散点图可视化
- 84.3D柱状图可视化
- 85.Pandas绘图
- 86.实战:Lending Club借贷数据探索性分析及可视化
- 87.机器学习介绍及应用场景
- 88.机器学习建模介绍 (1) -- 分类
- 89.机器学习建模介绍 (2) -- 回归
- 90.机器学习建模介绍 (3) -- 聚类
- 91.机器学习分类
- 92.机器学习工具scikit-learn
- 93.使用scikit-learn的流程
- 94.数据集准备及划分
- 95.模型选择
- 96.数据预处理及特征工程
- 97.过拟合与欠拟合
- 98.模型调参介绍
- 99.模型调参方法
- 100.模型测试及评价
- 101.实战:通过移动设备行为数据预测性别和年龄
数据重构(3) -- 数据重构stack unstack
约 10 分钟
数据重构:stack 与 unstack
小象实战讲义 · Python数据分析实战
在数据分析中,我们常常需要将数据从一种“宽”格式转换为“长”格式,或者反之,以适应不同的分析需求。stack 和 unstack 是 Pandas 中用于实现这种数据重构的两个核心方法,它们专门用于处理具有层级索引(MultiIndex) 的数据结构。本节我们将深入理解这两个操作,掌握如何灵活地重塑数据视图。
💡 核心导读
- 适用对象:
stack和unstack专为具有层级索引(MultiIndex)的 Series 或 DataFrame 设计。 - 操作本质:
stack将数据的列旋转为行,而unstack将数据的行旋转为列。它们不进行聚合计算,仅改变数据的呈现结构。 - 关键参数
level:用于指定在哪个索引层级上进行旋转。层级从外向内编号为0, 1, 2...,从内向外则为-1, -2...,默认值为-1(最内层)。 - 与透视表的区别:两者都重塑数据,但透视表(
pivot_table)通常伴随聚合操作(如求和、求平均),而stack/unstack仅重构,不聚合。
理解 stack:将列旋转为行
stack 操作直观上可以理解为“堆叠”,它将列索引的某一层级“压缩”到行索引中,从而将数据从较宽的格式转换为较长的格式。
核心机制:对于一个具有多层列索引的 DataFrame,stack 会选取列索引的指定层级(默认为最内层),将其转换为行索引的新层级。结果通常是一个具有更多行、更少列的 Series 或 DataFrame。
为了更好地演示,我们先构造一个具有多层列索引的示例数据。
import pandas as pd
# 构造一个具有多层列索引的 DataFrame
# 外层列索引:学期(Semester1, Semester2)
# 内层列索引:科目(Maths, Science)
header = pd.MultiIndex.from_product([['Semester1', 'Semester2'], ['Maths', 'Science']])
# 数据:四名学生,两个学期,两门科目的成绩
data = [
[12, 45, 67, 56], # Alisa
[78, 89, 45, 67], # Bobby
[45, 67, 89, 90], # Cathrine
[67, 44, 56, 55] # Jack
]
df = pd.DataFrame(data,
index=['Alisa', 'Bobby', 'Cathrine', 'Jack'],
columns=header)
print("原始 DataFrame (多层列索引):")
print(df)原始 DataFrame (多层列索引):
Semester1 Semester2
Maths Science Maths Science
Alisa 12 45 67 56
Bobby 78 89 45 67
Cathrine 45 67 89 90
Jack 67 44 56 55现在,我们对这个 DataFrame 进行 stack 操作。默认情况下 (level=-1),它会旋转最内层的列索引(即“科目”:Maths 和 Science)。
import pandas as pd
# 重新构造数据,确保代码块自包含
header = pd.MultiIndex.from_product([['Semester1', 'Semester2'], ['Maths', 'Science']])
data = [[12, 45, 67, 56], [78, 89, 45, 67], [45, 67, 89, 90], [67, 44, 56, 55]]
df = pd.DataFrame(data, index=['Alisa', 'Bobby', 'Cathrine', 'Jack'], columns=header)
# 默认 stack 操作 (level=-1,旋转最内层索引)
stacked_df = df.stack()
print("\n默认 stack() 后的结果 (旋转科目到行):")
print(stacked_df)默认 stack() 后的结果 (旋转科目到行):
Semester1 Semester2
Alisa Maths 12 67
Science 45 56
Bobby Maths 78 45
Science 89 67
Cathrine Maths 45 89
Science 67 90
Jack Maths 67 56
Science 44 55观察结果:原来的列索引 (Semester1, Maths)、(Semester1, Science) 等被“堆叠”了。科目(Maths, Science) 这一层从列索引中移出,成为了行索引的新一层。现在,行索引变成了两层:学生姓名和科目;列索引则只剩学期(Semester1/Semester2)。数据从 4 行 × 4 列,变成了 8 行 × 2 列。
关键参数:level 指定旋转层级
level 参数决定了具体旋转列索引的哪一层。层级从外向内编号(0, 1, 2…)。
level=0:旋转最外层列索引(本例中是“学期”:Semester1, Semester2)。level=1或level=-1:旋转最内层列索引(本例中是“科目”:Maths, Science),这也是默认行为。
import pandas as pd
# 重新构造数据
header = pd.MultiIndex.from_product([['Semester1', 'Semester2'], ['Maths', 'Science']])
data = [[12, 45, 67, 56], [78, 89, 45, 67], [45, 67, 89, 90], [67, 44, 56, 55]]
df = pd.DataFrame(data, index=['Alisa', 'Bobby', 'Cathrine', 'Jack'], columns=header)
# 指定 level=0,旋转最外层索引(学期)
stacked_semester = df.stack(level=0)
print("\nstack(level=0) 后的结果 (旋转学期到行):")
print(stacked_semester)
# 指定 level=1 或 level=-1,效果与默认相同
print("\nstack(level=1) 后的结果:")
print(df.stack(level=1))
print("\nstack(level=-1) 后的结果:")
print(df.stack(level=-1))stack(level=0) 后的结果 (旋转学期到行):
Maths Science
Alisa Semester1 12 45
Semester2 67 56
Bobby Semester1 78 89
Semester2 45 67
Cathrine Semester1 45 67
Semester2 89 90
Jack Semester1 67 44
Semester2 56 55
stack(level=1) 后的结果:
Semester1 Semester2
Alisa Maths 12 67
Science 45 56
Bobby Maths 78 45
Science 89 67
Cathrine Maths 45 89
Science 67 90
Jack Maths 67 56
Science 44 55
stack(level=-1) 后的结果:
Semester1 Semester2
Alisa Maths 12 67
Science 45 56
Bobby Maths 78 45
Science 89 67
Cathrine Maths 45 89
Science 67 90
Jack Maths 67 56
Science 44 55可以看到,stack(level=0) 将“学期”旋转到了行索引,而列只剩下“科目”。stack(level=1) 和 stack(level=-1) 的结果与默认情况完全一致。
理解 unstack:将行旋转为列
unstack 是 stack 的逆操作,可以理解为“展开”或“铺平”。它将行索引的某一层级“展开”到列索引中,从而将数据从较长的格式转换为较宽的格式。
核心机制:对于一个具有多层行索引的 Series 或 DataFrame,unstack 会选取行索引的指定层级(默认为最内层),将其转换为列索引的新层级。
我们使用 stack(level=0) 操作的结果来演示 unstack(此时行索引为「学生姓名+学期」两层——注意默认 stack 的结果是「姓名+科目」两层,这里取 level=0 的版本,便于观察 unstack 如何把「学期」展开回列)。
import pandas as pd
# 重新构造 stacked_df 的等价数据
index_tuples = [
('Alisa', 'Semester1'), ('Alisa', 'Semester2'),
('Bobby', 'Semester1'), ('Bobby', 'Semester2'),
('Cathrine', 'Semester1'), ('Cathrine', 'Semester2'),
('Jack', 'Semester1'), ('Jack', 'Semester2')
]
index = pd.MultiIndex.from_tuples(index_tuples, names=[None, None])
stacked_df = pd.DataFrame({'Maths': [12, 67, 78, 45, 45, 89, 67, 56],
'Science': [45, 56, 89, 67, 67, 90, 44, 55]},
index=index)
print("当前 stacked_df (两层行索引):")
print(stacked_df)
# 对上一步 stack 的结果进行 unstack
unstacked_df = stacked_df.unstack()
print("\n对 stacked_df 进行默认 unstack() 后的结果:")
print(unstacked_df)当前 stacked_df (两层行索引):
Maths Science
Alisa Semester1 12 45
Semester2 67 56
Bobby Semester1 78 89
Semester2 45 67
Cathrine Semester1 45 67
Semester2 89 90
Jack Semester1 67 44
Semester2 56 55
对 stacked_df 进行默认 unstack() 后的结果:
Maths Science
Semester1 Semester2 Semester1 Semester2
Alisa 12 67 45 56
Bobby 78 45 89 67
Cathrine 45 89 67 90
Jack 67 56 44 55执行默认的 unstack()(level=-1)后,最内层的行索引(“学期”)被展开到了列索引,数据回到了宽格式。注意一个细节:unstack 后的列层级是「科目在外、学期在内」,与最初宽表「学期在外、科目在内」的层级顺序恰好相反——stack/unstack 只保证数值排布对应,并不严格互逆;若要完全还原最初的列层级顺序,还需再对列轴做一次层级交换(如 swaplevel/reorder_levels)。
同样,我们可以通过 level 参数指定旋转行索引的哪一层。
import pandas as pd
# 重新构造 stacked_df 的等价数据
index_tuples = [
('Alisa', 'Semester1'), ('Alisa', 'Semester2'),
('Bobby', 'Semester1'), ('Bobby', 'Semester2'),
('Cathrine', 'Semester1'), ('Cathrine', 'Semester2'),
('Jack', 'Semester1'), ('Jack', 'Semester2')
]
index = pd.MultiIndex.from_tuples(index_tuples, names=[None, None])
stacked_df = pd.DataFrame({'Maths': [12, 67, 78, 45, 45, 89, 67, 56],
'Science': [45, 56, 89, 67, 67, 90, 44, 55]},
index=index)
# 指定 level=0,旋转最外层行索引(学生姓名)
unstacked_name = stacked_df.unstack(level=0)
print("stacked_df.unstack(level=0) 后的结果 (旋转学生姓名到列):")
print(unstacked_name)
# 指定 level=1 或 level=-1,效果与默认相同
print("\nstacked_df.unstack(level=1) 后的结果:")
print(stacked_df.unstack(level=1))
print("\nstacked_df.unstack(level=-1) 后的结果:")
print(stacked_df.unstack(level=-1))stacked_df.unstack(level=0) 后的结果 (旋转学生姓名到列):
Maths Science
Alisa Bobby Cathrine Jack Alisa Bobby Cathrine Jack
Semester1 12 78 45 67 45 89 67 44
Semester2 67 45 89 56 56 67 90 55
stacked_df.unstack(level=1) 后的结果:
Maths Science
Semester1 Semester2 Semester1 Semester2
Alisa 12 67 45 56
Bobby 78 45 89 67
Cathrine 45 89 67 90
Jack 67 56 44 55
stacked_df.unstack(level=-1) 后的结果:
Maths Science
Semester1 Semester2 Semester1 Semester2
Alisa 12 67 45 56
Bobby 78 45 89 67
Cathrine 45 89 67 90
Jack 67 56 44 55unstack(level=0) 将最外层的行索引(学生姓名)展开到了列,形成了以学期为行、以(学生,科目)组合为列的视图。
与透视表的关联与区别
stack/unstack 的操作结果,在形态上很像我们之前学过的透视表(pivot table)。它们都能将数据在“长格式”和“宽格式”之间转换。
核心区别在于:
- 透视表 (
pivot_table):在重塑数据的同时,通常会对重合的值进行聚合操作(如求和sum、求平均mean、计数count等)。它用于汇总和聚合数据。 stack/unstack:仅对数据进行重构,不进行任何聚合计算。原始数据值被重新排列,但每个值本身保持不变。它用于改变数据的视图结构。
简单来说,如果你需要汇总统计,用透视表;如果只是需要换一种方式排列现有数据,用 stack/unstack。
📝 动手练一练
- 概念辨析:假设你有一个记录每日不同城市销售金额的 DataFrame,列是城市,行是日期。如果你使用
df.stack(),得到的新数据结构中,行索引会是什么?这个操作相当于把数据转换成了哪种更利于时间序列分析或模型输入的格式?
👉 点击查看参考答案
df.stack() 会将城市这一列索引旋转为行索引的新层级。新的行索引将是一个两层 MultiIndex:第一层是日期,第二层是城市。数据将从“宽格式”(每个日期一行,各城市销售额排成一列)转换为“长格式”(每个日期和城市的组合占一行,只有一列销售额)。这种“长格式”是许多时间序列分析库和统计模型(如 statsmodels)更偏好的输入格式,因为它明确地将每个观测值(销售额)与两个维度(日期、城市)关联起来。
- 代码实践:使用我们示例中的
stacked_df(学生-学期为行索引,科目为列),编写代码将其转换为一个以“学生”为行、“学期”和“科目”组合为列的 DataFrame(即unstack(level=1)的结果),并计算每位学生在Semester1的Maths和Science两科平均分。
👉 点击查看参考答案
import pandas as pd
# 构造 stacked_df
index_tuples = [('Alisa', 'Semester1'), ('Alisa', 'Semester2'),
('Bobby', 'Semester1'), ('Bobby', 'Semester2'),
('Cathrine', 'Semester1'), ('Cathrine', 'Semester2'),
('Jack', 'Semester1'), ('Jack', 'Semester2')]
index = pd.MultiIndex.from_tuples(index_tuples)
stacked_df = pd.DataFrame({'Maths': [12, 67, 78, 45, 45, 89, 67, 56],
'Science': [45, 56, 89, 67, 67, 90, 44, 55]},
index=index)
# 1. 转换为目标格式
unstacked_semester_subject = stacked_df.unstack(level=1) # 旋转“学期”到列
print("转换后的 DataFrame:")
print(unstacked_semester_subject)
# 2. 计算 Semester1 两科平均分
# 先筛选出 Semester1 的列,然后按行求平均
semester1_avg = unstacked_semester_subject.loc[:, (slice(None), 'Semester1')].mean(axis=1)
print("\n每位学生 Semester1 两科平均分:")
print(semester1_avg)转换后的 DataFrame:
Maths Science
Semester1 Semester2 Semester1 Semester2
Alisa 12 67 45 56
Bobby 78 45 89 67
Cathrine 45 89 67 90
Jack 67 56 44 55
每位学生 Semester1 两科平均分:
Alisa 28.5
Bobby 83.5
Cathrine 56.0
Jack 55.5
dtype: float64本章小结
stack 和 unstack 是 Pandas 数据规整工具箱中不可或缺的工具,它们基于层级索引,高效地在数据的长、宽格式间进行无损转换。
stack():将指定层级的列索引旋转为行索引,使数据变“长”。unstack():将指定层级的行索引旋转为列索引,使数据变“宽”。- 参数
level:精确控制操作发生在哪个索引层级,理解层级编号(从外向内0,1,...,从内向外-1,-2,...)是关键。 - 与透视表的区别:牢记
stack/unstack不聚合数据,仅重构;而透视表的核心功能是聚合汇总。
📋 行动清单
- 在 Jupyter Notebook 中亲手运行本节所有示例代码,观察每一步数据形状和索引的变化。
- 尝试对一个三层索引(如:年份-季度-产品类别)的模拟 DataFrame 进行
stack(level=1)和unstack(level=0)操作,并预测结果。 - 思考你手头或之前练习中的一个数据集,是否可以通过
stack/unstack转换为更利于分析的格式。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载本节课件:数据重构(3) -- 数据重构stack unstackPDF · 297KB
- 下载全套课件打包(第1-5章)ZIP · 12.8MB
- 下载全套课件打包(第6-8章)ZIP · 15MB
- 下载实战数据集:AppleStore 应用商城分析ZIP · 329KB
- 下载实战数据集:女性服装电商分析ZIP · 2.8MB
- 下载Python 数据分析环境搭建指南PDF · 2MB
- 下载Scrapy 安装教程PDF · 12.7MB
- 下载附加实战项目:AppleStore 应用商城数据分析ZIP · 0.3MB · ipynb + CSV 数据
- 下载附加实战项目:银行电话营销数据分析ZIP · 0.4MB · ipynb + CSV 数据
- 下载附加实战项目:女性服装电商评论数据分析ZIP · 2.7MB · ipynb + CSV 数据
- 下载附加实战项目:美国化学学会杂志数据分析ZIP · 34.2MB · ipynb + SQLite 数据库