Pandas 多层索引(MultiIndex)
多层索引(MultiIndex,也叫多级索引)是 Pandas 用来表达高维层次数据的核心能力,适合二维表承载多维度分组结果(地区-商品、日期-标的、渠道-品类),透视表、groupby、滚动分组、多维度报表都会高频遇到。本文独立成文,聚焦多层索引的创建、取值、切片、排序、层级交换、展平、过滤、运算、性能和坑点,和前面 Index 教程互补。
前置导入
import pandas as pd
import numpy as np
一、什么是多层索引
普通 Index 只有一层标签;MultiIndex 每一行索引是一个元组 (level0, level1, ...),可以有2层、3层甚至更多层级。
- 行多层索引:
df.index是 MultiIndex - 列多层索引:
df.columns是 MultiIndex(透视表非常常见) - 层级有名字:
names,强烈建议给层级命名,可读性大幅提升
示例基础数据
df_raw = pd.DataFrame({
"渠道": ["线上","线上","线下","线下","线上","线上"],
"商品": ["键盘","鼠标","键盘","鼠标","键盘","鼠标"],
"数量": [2,1,3,2,4,5],
"金额": [178,45,267,90,356,225]
})
# groupby 多列自动生成多层索引(最常见来源)
df_multi = df_raw.groupby(["渠道","商品"])["金额"].sum()
print(df_multi.index)
输出:
MultiIndex([('线上', '键盘'),
('线上', '鼠标'),
('线下', '键盘'),
('线下', '鼠标')],
names=['渠道', '商品'])
二、创建 MultiIndex 的 4 种方式
2.1 from_tuples 元组列表(最常用)
tuples = [("线上","键盘"),("线上","鼠标"),("线下","键盘"),("线下","鼠标")]
midx = pd.MultiIndex.from_tuples(tuples, names=["渠道","商品"])
s = pd.Series([178,45,267,90], index=midx)
2.2 from_product 笛卡尔积(维度组合全覆盖)
适合维度枚举:渠道 × 商品全部组合
midx = pd.MultiIndex.from_product(
[["线上","线下"], ["键盘","鼠标","显示器"]],
names=["渠道","商品"]
)
2.3 from_arrays 分层数组
每一层单独给一个数组
level1 = ["线上","线上","线下","线下"]
level2 = ["键盘","鼠标","键盘","鼠标"]
midx = pd.MultiIndex.from_arrays([level1, level2], names=["渠道","商品"])
2.4 从 DataFrame 多列 set_index
df = df_raw.set_index(["渠道","商品"])
列多层索引:columns 也可以是 MultiIndex,透视表典型场景
df_pivot = df_raw.pivot_table(index="渠道", columns="商品", values="金额", aggfunc="sum")
print(type(df_pivot.columns))
三、多层索引基础属性
midx = df_multi.index
midx.names # 层级名称列表
midx.levels # 每一层的唯一标签
midx.codes # 编码映射(内部存储)
midx.nlevels # 层级数量
midx.has_duplicates # 是否存在重复索引
midx.is_monotonic_increasing # 是否有序
四、取值与切片 loc(重点)
⚠️ 重要前提:多层索引切片前,建议先 sort_index(),无序索引不能做范围切片!
df_multi = df_multi.sort_index()
4.1 取第一层全部
df_multi.loc["线上"]
4.2 精确两层取值(元组)
df_multi.loc[("线上", "键盘")]
4.3 第一层固定,第二层切片
df_multi.loc["线上", :]
4.4 范围切片(有序索引才支持)
df_multi.loc["线上":"线下"]
4.5 第二层筛选(IndexSlice 复杂切片,高阶)
当需要跨层级灵活切片,推荐 pd.IndexSlice
idx = pd.IndexSlice
df_multi.loc[idx[:, "键盘"]] # 所有渠道,只取键盘
五、层级操作:swaplevel、reorder_levels
5.1 交换两层
# 交换渠道、商品两层
df_multi.swaplevel("渠道", "商品")
# 也可以用层级序号
df_multi.swaplevel(0, 1)
5.2 多层重排顺序(3层及以上用)
# 顺序:商品 → 渠道
df_multi.reorder_levels(["商品","渠道"])
六、层级排序 sort_index
# 按全部层级排序
df_multi = df_multi.sort_index()
# 指定层级排序
df_multi = df_multi.sort_index(level=["渠道","商品"], ascending=[True, False])
坑:未排序的 MultiIndex,
loc范围切片直接抛异常,单点取值不受影响。
七、多层索引展平 reset_index(报表导出必用)
多层索引导出Excel不方便,用 reset_index 把索引变回普通列
df_flat = df_multi.reset_index()
列多层索引展平(透视表头最常用)
列是MultiIndex时,拼接层级名称
df_pivot = df_raw.pivot_table(index="渠道", columns="商品", values="金额", aggfunc="sum")
df_pivot.columns = ["_".join(col) for col in df_pivot.columns]
八、多层索引分组、聚合
已经是多层索引的表,依然可以继续 groupby,按指定层级聚合
# 按第一层【渠道】聚合
df_multi.groupby(level="渠道").sum()
# 按第二层【商品】聚合
df_multi.groupby(level="商品").sum()
对比:普通groupby是按列分组;level 是按索引层级分组。
九、多层索引过滤、布尔筛选
两种思路:
- 提取索引层级的值,用
index.get_level_values()
# 取出商品这一层
df_multi.index.get_level_values("商品")
# 筛选商品=键盘
cond = df_multi.index.get_level_values("商品") == "键盘"
df_multi[cond]
- IndexSlice 切片(适合简单标签筛选)
十、多层索引对齐、运算
多层索引参与加减运算时,会按全部层级自动对齐,元组完全匹配才合并,匹配不到为NaN,和单层索引对齐逻辑一致。
s1 = df_multi
s2 = pd.Series([200,50], index=pd.MultiIndex.from_tuples([("线上","键盘"),("线下","鼠标")], names=["渠道","商品"]))
s1 + s2
十一、多层索引 droplevel 删除层级
# 删除渠道这一层
df_multi.droplevel("渠道")
# 删除第0层
df_multi.droplevel(0)
十二、多层索引 + 窗口 rolling(时序多维高频)
按分组做滚动统计的经典写法:分组后滚动,注意索引对齐
df = df_raw.set_index(["渠道","商品"])
df = df.sort_index()
df["滚动2笔金额"] = df.groupby(level=["渠道","商品"])["金额"].rolling(2).sum().reset_index(drop=True)
重点:
reset_index(drop=True)防止多级索引冲突,是很多人踩坑的地方。
十三、性能要点
- MultiIndex 有序(sort_index)后,loc、groupby、join 速度明显更快
- 层级标签重复不影响读取,但 join、asof、某些算法要求索引唯一
- 层级越多,内存开销越大;报表输出前尽量展平 reset_index
- 做join时,两边都是同结构MultiIndex,索引对索引join速度远快于merge
十四、高频坑清单
- 忘记 sort_index,使用
loc["A":"B"]范围切片报错 - 取值混用
iloc:iloc 是位置,不感知层级标签;loc 才是多层标签 - 多层索引rolling后索引不一致,忘记
reset_index(drop=True),赋值出现NaN - 列MultiIndex直接取列名报错,需要先展平列名
- 层级名称为空(None),调试、筛选时容易混乱,建议始终给 names
- 两个MultiIndex层级顺序不一致,运算无法对齐,产生大量NaN
十五、速查表
| 操作 | 作用 |
|---|---|
| pd.MultiIndex.from_tuples | 元组构造多层索引 |
| pd.MultiIndex.from_product | 笛卡尔积构造 |
| df.set_index([col1,col2]) | 多列转为多层行索引 |
| df.sort_index() | 多层索引排序,支持切片 |
| df.swaplevel(a,b) | 交换两个层级 |
| df.reorder_levels() | 重新调整层级顺序 |
| df.droplevel() | 删除某一层索引 |
| df.reset_index() | 索引展平为普通列 |
| df.groupby(level=name) | 按索引层级分组 |
| df.index.get_level_values(name) | 取出某一层标签数组 |
| pd.IndexSlice | 多层索引灵活切片 |
十六、综合实战模板
def create_multi_report(df):
# 1. 设置多层索引并排序
df = df.set_index(["渠道","商品"]).sort_index()
# 2. 层级分组滚动指标
df["滚动2期金额"] = df.groupby(level=["渠道","商品"])["金额"].rolling(2).sum().reset_index(drop=True)
# 3. 筛选:只保留键盘
cond = df.index.get_level_values("商品") == "键盘"
df_filter = df[cond]
# 4. 导出前展平
df_out = df_filter.reset_index()
return df_out
0 条笔记