自学教程 学编程从这里开始

Pandas 多层索引

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 是按索引层级分组。

九、多层索引过滤、布尔筛选

两种思路:

  1. 提取索引层级的值,用 index.get_level_values()
# 取出商品这一层
df_multi.index.get_level_values("商品")
# 筛选商品=键盘
cond = df_multi.index.get_level_values("商品") == "键盘"
df_multi[cond]
  1. 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) 防止多级索引冲突,是很多人踩坑的地方。

十三、性能要点

  1. MultiIndex 有序(sort_index)后,loc、groupby、join 速度明显更快
  2. 层级标签重复不影响读取,但 join、asof、某些算法要求索引唯一
  3. 层级越多,内存开销越大;报表输出前尽量展平 reset_index
  4. 做join时,两边都是同结构MultiIndex,索引对索引join速度远快于merge

十四、高频坑清单

  1. 忘记 sort_index,使用 loc["A":"B"] 范围切片报错
  2. 取值混用 iloc:iloc 是位置,不感知层级标签;loc 才是多层标签
  3. 多层索引rolling后索引不一致,忘记 reset_index(drop=True),赋值出现NaN
  4. 列MultiIndex直接取列名报错,需要先展平列名
  5. 层级名称为空(None),调试、筛选时容易混乱,建议始终给 names
  6. 两个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 条笔记