MultiIndex(多层索引)使 DataFrame 可以按多个维度组织数据。本节介绍多层索引的定位、切片与交叉选择。
1. 创建 MultiIndex 示例
import pandas as pd
idx = pd.MultiIndex.from_product(
[['华东', '华北'], [2023, 2024]],
names=['区域', '年份']
)
df = pd.DataFrame(
{'销售额': [100, 150, 120, 180], '利润': [20, 30, 25, 40]},
index=idx
)
print(df)
# 销售额 利润
# 区域 年份
# 华东 2023 100 20
# 2024 150 30
# 华北 2023 120 25
# 2024 180 402. 完整元组选择
# 精确匹配完整层级
df.loc[('华东', 2023)]
# 销售额 100
# 利润 20
# Name: (华东, 2023), dtype: int64
# 精确行 + 指定列
df.loc[('华北', 2024), '销售额'] # 180
# 行列同时多选
df.loc[('华东', 2023), ['销售额', '利润']]
# 多个元组列表
df.loc[[('华东', 2023), ('华北', 2024)]]完整元组 vs 部分选择
df.loc[('华东', 2023)]返回标量行(Series)df.loc['华东']返回多行(DataFrame)- 元组在
.loc中必须加括号:df.loc[('华东', 2023)]而不是df.loc['华东', 2023](后者在两级索引时被解读为行列传入)。
3. 部分层级选择
# 只指定第一层标签
df.loc['华东'] # 返回华东全部行(剩余层级的索引)
# 只指定第一层 + 第二层
df.loc['华东', 2023] # 注意这里有两个层级,不会误解为行列
# 部分层级切片(第一层切片)
df.loc['华东':'华北'] # 包含两个区域
# 第二层切片(需配合第一层)
df.loc['华东', 2023:2024]4. 使用 IndexSlice 进行复杂切片
pd.IndexSlice 允许在多层索引中使用 : 占位符,实现任意层级的部分匹配。
idx = pd.IndexSlice
# 选择所有区域、所有年份
df.loc[idx[:, :], :]
# 选择 '华东'、所有年份
df.loc[idx['华东', :], :]
# 选择 '华东' 和 '华北'、2023 到 2024
df.loc[idx[['华东', '华北'], 2023:2024], :]
# 选择所有区域、仅 2024
df.loc[idx[:, 2024], :]
# 行和列同时使用
df.loc[idx['华东', :], ['销售额']]IndexSlice 核心用法
| 用法 | 说明 |
|---|---|
idx[:, 'a'] | 第一层全部,第二层取 'a' |
idx['a', :] | 第一层取 'a',第二层全部 |
idx['a':'b', 2023:2024] | 两层均为切片(含端点) |
idx[['a', 'b'], :] | 第一层为列表 |
idx[:, :] | 全部选择 |
与
.loc的区别
df.loc['华东']只适用于第一层索引的部分选择- 当需要选择第二层或更深的层级时,必须使用
IndexSlice
5. 交叉选择 .xs()
.xs() 返回移除某一层级的交叉切片,操作后维度减少一层。
# 获取 '华东' 的所有行(level 默认取第一个层级)
df.xs('华东')
# 销售额 利润
# 年份
# 2023 100 20
# 2024 150 30
# 用 level 参数指定层级
df.xs(2023, level='年份') # 年份作为选择层级
# 销售额 利润
# 区域
# 华东 100 20
# 华北 120 25
# 多层级组合(元组)
df.xs(('华东', 2023))
# 按列做交叉选择
df.xs('销售额', axis=1) # 返回所有行 + 销售额列xs() 参数详解
| 参数 | 说明 |
|---|---|
key | 要选择的行/列标签(字符串、整数或元组) |
axis | 沿行(0)或列(1)选择 |
level | 指定在哪个层级执行选择(名称或整数) |
drop_level | 是否移除被选层级(默认 True) |
# drop_level=False 保留原层级结构
df.xs('华东', drop_level=False)6. 使用 get_level_values()
# 获取特定层级的标签数组
df.index.get_level_values('区域')
# Index(['华东', '华东', '华北', '华北'], name='区域')
# 布尔筛选(基于某个层级)
mask = df.index.get_level_values('年份') == 2024
df[mask]
# 销售额 利润
# 区域 年份
# 华东 2024 150 30
# 华北 2024 180 407. 布尔索引 + MultiIndex
# 方法一:索引获取后布尔
mask = (
(df.index.get_level_values('区域') == '华东') &
(df['销售额'] > 120)
)
df.loc[mask]
# 方法二:reset_index 后常规布尔(直观但性能略低)
df_reset = df.reset_index()
df_reset[(df_reset['区域'] == '华东') & (df_reset['销售额'] > 120)]
# 方法三:query 语法(推荐,可直接引用层级名称)
df.query('区域 == "华东" and 销售额 > 120')
df.query('年份 in [2023, 2024]')8. MultiIndex 排序与选择的关系
# 多层索引排序
df.sort_index(inplace=True)
# 按指定层级排序
df.sort_index(level='年份')切片匹配基于排序
- 多层索引切片(尤其是
IndexSlice与连续标签切片)要求索引已排序- 未排序时可能抛出
UnsortedIndexError或返回错误结果- 建议在创建 MultiIndex 后立即
sort_index()再执行切片