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    40

2. 完整元组选择

# 精确匹配完整层级
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    40

7. 布尔索引 + 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() 再执行切片