多级索引 MultiIndex
本教程共 54 篇 · 第 22 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:学会创建和使用 MultiIndex,能用多层标签组织数据,并用 loc、xs 快速定位任意层级的数据。
一张表装不下两个维度
到目前为止,DataFrame 的索引都是一层:一行一个标签。但真实数据常常有好几个维度。比如考试成绩:年级(高一、高二)、班级(A班、B班),再往下才是每个人。如果把”年级 + 班级”都收进索引,一行标签就能完整描述一条数据的归属。
MultiIndex(多级索引)就是干这个的:把多层标签叠成一个”元组数组”。你可以把它理解成书的目录——先分章,再分节,层层往下。
NoteMultiIndex 本质上是一组元组,每个元组唯一。它可以把三维甚至更高维的数据,装进一维的 Series 或二维的 DataFrame 里,这也是旧版 Panel 三维容器被移除后的推荐替代方案。
创建 MultiIndex 的四种方式
from_arrays:按”列”给
最直观的方式:每一层标签各放一个数组,逐行配对成元组。
import pandas as pd
import numpy as np
arrays = [
["高一", "高一", "高二", "高二", "高三", "高三"],
["A班", "B班", "A班", "B班", "A班", "B班"],
]
idx = pd.MultiIndex.from_arrays(arrays, names=["年级", "班级"])
print(idx)
第一个数组是外层(年级),第二个是内层(班级),位置一一对应。
from_tuples:按”行”给
数据本身就是一组元组时用这个,比如从数据库取回来的就是 (年级, 班级) 形式:
tuples = [("高一", "A班"), ("高一", "B班"), ("高二", "A班"), ("高二", "B班")]
idx2 = pd.MultiIndex.from_tuples(tuples, names=["年级", "班级"])
print(idx2)
from_product:笛卡尔积
想要”每个外层 × 每个内层”的全组合,用 from_product:
idx3 = pd.MultiIndex.from_product(
[["2024", "2025"], ["Q1", "Q2", "Q3"]],
names=["年份", "季度"],
)
print(idx3)
2024 和 2025 各配 Q1、Q2、Q3,一共 6 个组合,外层先走完再换内层。做”全组合”的统计表时,这个方法最省事。
from_frame:从 DataFrame 生成
索引信息已经躺在几列数据里时,可以直接提取:
df_labels = pd.DataFrame({"年份": ["2024", "2025"], "季度": ["Q1", "Q2"]})
print(pd.MultiIndex.from_frame(df_labels))
直接构造带 MultiIndex 的数据
创建 Series 或 DataFrame 时,把”数组的列表”传给 index 参数,pandas 自动生成 MultiIndex:
idx = pd.MultiIndex.from_product([["2024", "2025"], ["Q1", "Q2"]], names=["年份", "季度"])
s = pd.Series([120, 150, 200, 210], index=idx)
print(s)
打印时 pandas 会把外层相同的标签合并显示,看起来像目录一样错落有致,这个显示开关是 display.multi_sparse。索引的层数没有上限,三层、四层都行,但层数越多越难读,两层是最常见的。
列上同样可以挂 MultiIndex。比如一张表要同时存”语文、数学”两科的”期中、期末”成绩:
cols = pd.MultiIndex.from_tuples(
[("语文", "期中"), ("语文", "期末"), ("数学", "期中"), ("数学", "期末")]
)
df = pd.DataFrame(
np.random.randint(60, 100, (3, 4)),
index=["张三", "李四", "王五"],
columns=cols,
)
print(df)
给层级命名与查看结构
创建时传 names 参数,事后也能改 index.names;get_level_values 按层级取出标签序列,调试数据时很常用:
print(s.index.names) # FrozenList(['年份', '季度'])
print(s.index.get_level_values("季度"))
几个查看结构的常用属性:
- index.nlevels:层级数量
- index.levels:每层有哪些取值
- index.get_level_values(0):按位置取某一层的完整标签
用 set_index 生成多级索引
实际工作中最多的场景是:数据是普通的多列表,想直接按其中几列组织索引。set_index 传一个列名列表即可(§23 有完整讲解):
score = pd.DataFrame({
"年级": ["高一", "高一", "高二", "高二"],
"班级": ["A班", "B班", "A班", "B班"],
"语文": [85, 92, 78, 88],
"数学": [90, 88, 95, 82],
})
score2 = score.set_index(["年级", "班级"])
print(score2)
分组统计(§37)、透视表(§30)、stack/unstack(§31)这些操作也都会自动产生 MultiIndex。所以即使你不手动创建,迟早也会在结果里遇到它——认识它很有必要。
访问多级索引:loc 与元组
行选择用 loc。给一个元组是”完整地址”,给一个字符串是”部分选择”:
print(score2.loc["高一"]) # 整个高一年级
print(score2.loc[("高一", "A班")]) # 高一 A 班这一行
print(score2.loc[("高二", "B班"), "数学"]) # 精确定位到单元格
Warningloc 里元组和列表的含义不同:元组是”穿层级”,代表一个完整键;列表是”选多个”,代表多个键。想一次选多行要用
score2.loc[[("高一", "A班"), ("高二", "B班")]],写成score2.loc[("高一", "A班")]只取一行。
范围选择(部分切片)也支持,比如取”高一”到”高二”的所有班级:
score3 = score2.sort_index()
print(score3.loc["高一":"高二"])
注意:多层切片要求索引已排序,所以先 sort_index() 再切,否则会报错或结果不对。
xs:横截面
xs 专门用于”在某一层上挑一个值,并把这一层从结果中去掉”。比如想同时看所有年级 A 班的成绩:
print(score2.xs("A班", level="班级"))
level 可以传层级名,也可以传位置数字。xs 同样支持在列上操作(axis=1),以及用 drop_level=False 保留被选中的层级:
print(df.xs("期中", level=1, axis=1))
print(df.xs("期中", level=1, axis=1, drop_level=False))
层级调整与转回普通表
swaplevel 交换两层顺序,reorder_levels 重排多层:
print(score2.swaplevel(0, 1))
想恢复成普通的多列表,reset_index 一键搞定(§23 细讲):
print(score2.reset_index())
Tip切片、筛选后,MultiIndex 里可能残留”未使用的层级”(levels 里还留着旧取值)。对结果调用 remove_unused_levels() 可以清掉,避免后续操作被多余层级干扰。
MultiIndex 和 stack/unstack(§31)、透视表(§30)、按层级分组(§37)配合最紧密,这几章都会回来用它。先把”怎么建、怎么取”练熟,后面全是顺水推舟。