Pandas: 多层索引
最后更新:2026-08-26
当数据有层次结构——按城市再按产品分组、按年份再按季度统计——单层索引不够用。MultiIndex(多层索引)让层次数据有层次索引:先选一级,再选二级,像文件夹层级一样导航。本节覆盖 MultiIndex 的创建、选取、重排和与 stack/unstack 的配合。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
1. 你将学到
- ❶ MultiIndex 概念
- ❷ 创建方式(from_tuples / from_product / from_frame)
- ❸ 层级选取(loc / xs)
- ❹ swaplevel / reorder_levels
- ❺ sort_index 与展平
2. Bob 的多城市多品类销售数据
(1) 痛点:两层分组结果难选取
Bob 按城市+品类分组后,结果有双层索引,不知道怎么选特定城市:
PYTHON
import pandas as pd
df = pd.DataFrame({
'city': ['NYC', 'NYC', 'LA', 'LA', 'Chicago', 'Chicago'],
'category': ['Electronics', 'Clothing', 'Electronics', 'Clothing', 'Electronics', 'Clothing'],
'sales': [5000, 800, 3000, 600, 2000, 400]
})
result = df.groupby(['city', 'category'])['sales'].sum()
print(result)
# city category
# Chicago Clothing 400
# Electronics 2000
# LA Clothing 600
# Electronics 3000
# NYC Clothing 800
# Electronics 5000
# How to select just NYC?
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) 解法:MultiIndex 选取
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:MultiIndex 选取(难度⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'city': ['NYC', 'NYC', 'LA', 'LA', 'Chicago', 'Chicago'],
'category': ['Electronics', 'Clothing', 'Electronics', 'Clothing', 'Electronics', 'Clothing'],
'sales': [5000, 800, 3000, 600, 2000, 400]
})
result = df.groupby(['city', 'category'])['sales'].sum()
# Select by MultiIndex label
print(result.loc['NYC'])
# category
# Clothing 800
# Electronics 5000
# Select specific (city, category)
print(result.loc[('NYC', 'Electronics')]) # 5000
# xs: cross-section selection
print(result.xs('Electronics', level='category'))
# city
# Chicago 2000
# LA 3000
# NYC 5000
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
3. MultiIndex 创建
(1) 三种创建方式
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:MultiIndex 创建方式(难度⭐⭐)
PYTHON
import pandas as pd
# 1. from_tuples — explicit pairs
index = pd.MultiIndex.from_tuples([
('NYC', 'Electronics'), ('NYC', 'Clothing'),
('LA', 'Electronics'), ('LA', 'Clothing')
], names=['city', 'category'])
df1 = pd.DataFrame({'sales': [5000, 800, 3000, 600]}, index=index)
print("from_tuples:")
print(df1)
# 2. from_product — cartesian product (most common)
index2 = pd.MultiIndex.from_product(
[['NYC', 'LA', 'Chicago'], ['Electronics', 'Clothing']],
names=['city', 'category']
)
df2 = pd.DataFrame({
'sales': [5000, 800, 3000, 600, 2000, 400]
}, index=index2)
print("\nfrom_product:")
print(df2)
# 3. from_frame — from DataFrame
df_index = pd.DataFrame({
'city': ['NYC', 'NYC', 'LA', 'LA'],
'category': ['Electronics', 'Clothing', 'Electronics', 'Clothing']
})
index3 = pd.MultiIndex.from_frame(df_index)
df3 = pd.DataFrame({'sales': [5000, 800, 3000, 600]}, index=index3)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
4. 层级选取
(1) loc 与 xs
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:层级选取方法(难度⭐⭐)
PYTHON
import pandas as pd
index = pd.MultiIndex.from_product(
[['NYC', 'LA'], ['Electronics', 'Clothing', 'Home']],
names=['city', 'category']
)
df = pd.DataFrame({
'sales': [5000, 800, 300, 3000, 600, 200],
'profit': [1500, 200, 50, 800, 100, 30]
}, index=index)
# loc: select first level
print(df.loc['NYC'])
# loc: select both levels
print(df.loc[('NYC', 'Electronics')])
# xs: cross-section (select at ANY level)
print(df.xs('Electronics', level='category'))
# Returns all cities' Electronics data
# xs with drop_level=False
print(df.xs('NYC', level='city', drop_level=False))
# Partial selection with slice
print(df.loc[('NYC', slice(None)), :]) # all categories in NYC
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) loc vs xs 对比
| 特性 | loc | xs |
|---|---|---|
| 选一级 | ✅ loc['NYC'] |
✅ xs('NYC', level=0) |
| 选二级 | 复杂 loc[(slice(None),'Elec')] |
简单 xs('Elec', level=1) |
| 多级同时 | ✅ loc[('NYC','Elec')] |
❌ 只能选一级 |
| 保留层级 | 默认保留 | drop_level 控制 |
5. swaplevel / sort_index
(1) 交换层级
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:swaplevel 重排层级(难度⭐⭐)
PYTHON
import pandas as pd
index = pd.MultiIndex.from_product(
[['NYC', 'LA'], ['Q1', 'Q2', 'Q3', 'Q4']],
names=['city', 'quarter']
)
df = pd.DataFrame({
'sales': [1200, 1300, 1100, 1400, 800, 900, 700, 1000]
}, index=index)
# Swap levels
df_swapped = df.swaplevel()
print(df_swapped.head(4))
# city quarter
# Q1 NYC 1200
# Q2 NYC 1300
# Q3 NYC 1100
# Q4 NYC 1400
# sort_index after swap (important!)
df_sorted = df_swapped.sort_index()
print(df_sorted.head(4))
# reorder_levels: arbitrary reordering
df_reordered = df.reorder_levels(['quarter', 'city'])
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
⚠️ 注意: swaplevel 或 reorder_levels 后必须 sort_index(),否则后续选取操作可能出错或很慢。
6. MultiIndex 与 stack/unstack
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:stack/unstack 与 MultiIndex(难度⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'city': ['NYC', 'NYC', 'LA', 'LA'],
'category': ['Electronics', 'Clothing', 'Electronics', 'Clothing'],
'Q1': [1200, 400, 800, 200],
'Q2': [1300, 500, 900, 300]
})
# set_index → MultiIndex DataFrame
multi_df = df.set_index(['city', 'category'])
print(multi_df)
# unstack: category level → columns
wide = multi_df.unstack(level='category')
print(wide)
# Q1 Q2
# category Clothing Electronics Clothing Electronics
# city
# LA 200 800 300 900
# NYC 400 1200 500 1300
# stack: columns → index level (reverse)
back = wide.stack(level='category')
print(back)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
7. 展平与 reset_index
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:MultiIndex 展平(难度⭐)
PYTHON
import pandas as pd
index = pd.MultiIndex.from_product(
[['NYC', 'LA'], ['Q1', 'Q2']],
names=['city', 'quarter']
)
df = pd.DataFrame({'sales': [1200, 1300, 800, 900]}, index=index)
# reset_index: MultiIndex → regular columns
flat = df.reset_index()
print(flat)
# city quarter sales
# 0 NYC Q1 1200
# 1 NYC Q2 1300
# 2 LA Q1 800
# 3 LA Q2 900
# Flatten column MultiIndex
wide = df.unstack()
flat_cols = wide.copy()
flat_cols.columns = [f'{a}_{b}' for a, b in flat_cols.columns]
print(flat_cols)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
8. 完整示例:多城市多品类销售分析
(6) ▶ MultiIndex 层级结构
graph TB
A[DataFrame] --> B[外层索引: city]
B --> C[NYC]
B --> D[LA]
B --> E[Chicago]
C --> F[内层索引: category]
F --> G[Electronics]
F --> H[Clothing]
F --> I[Home]
D --> J[Electronics]
D --> K[Clothing]
E --> L[Electronics]
E --> M[Clothing]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:MultiIndex 全流程分析(难度⭐⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# ============================================
# Comprehensive example: Multi-city Multi-category
# sales analysis with MultiIndex
# ============================================
# 1. Create hierarchical data
np.random.seed(42)
index = pd.MultiIndex.from_product(
[['NYC', 'LA', 'Chicago', 'Houston'],
['Electronics', 'Clothing', 'Home'],
['Q1', 'Q2', 'Q3', 'Q4']],
names=['city', 'category', 'quarter']
)
df = pd.DataFrame({
'sales': np.random.randint(100, 5000, 48),
'profit': np.random.randint(10, 1500, 48)
}, index=index)
# 2. View structure
print(f"Levels: {df.index.names}")
print(f"Shape: {df.shape}")
# 3. Select by city
print("\n=== NYC Sales ===")
print(df.loc['NYC', 'sales'].unstack(level='quarter'))
# 4. xs: all Electronics across cities
print("\n=== Electronics by City ===")
elec = df.xs('Electronics', level='category')[['sales']]
print(elec.unstack(level='quarter'))
# 5. swaplevel → sort → select
df2 = df.swaplevel('city', 'quarter').sort_index()
print("\n=== Q1 across cities ===")
print(df2.loc['Q1'])
# 6. Unstack for wide report
report = df['sales'].unstack(level=['category', 'quarter'])
print("\n=== Wide Report (first 2 cities) ===")
print(report.head(2))
# 7. Reset for export
flat = df.reset_index()
print(f"\nFlat shape: {flat.shape}")
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
❓ 常见问题
Q MultiIndex 什么时候用?
A 当数据有自然层次结构时——城市+品类、年份+季度、部门+小组。groupby 多列结果自动产生 MultiIndex。set_index 多列也产生 MultiIndex。如果只是简单数据,单层索引更方便,不要刻意用 MultiIndex。
Q xs 和 loc 区别?
A xs 可以在任意层级选取(如直接选 category='Electronics',无需先选 city)。loc 只能从外层向内层选取(先选 city,再选 category)。xs 更灵活但不能同时选多级,loc 可以
loc[('NYC','Electronics')] 但二级选取语法复杂。Q swaplevel 会改变数据吗?
A 不会——swaplevel 只交换索引层级顺序,数据不变。但交换后必须 sort_index(),否则后续选取可能出错或性能差。建议:swaplevel → sort_index → 选取。
Q 怎么展平 MultiIndex?
A 行级 MultiIndex →
df.reset_index() 把层级变普通列。列级 MultiIndex → 重命名列为拼接字符串 ['_'.join(col) for col in df.columns]。groupby 后用 as_index=False 避免产生 MultiIndex(但失去层次选取能力)。Q MultiIndex 性能如何?
A MultiIndex 查找是 O(1)(底层是哈希),性能很好。但 sort_index 后才保证高效——未排序的 MultiIndex 选取可能退化为 O(n)。大数据建议:创建后立即 sort_index(),用 is_unique 检查唯一性。
Q get_level_values 有什么用?
A
df.index.get_level_values('city') 返回某层级的所有值(如 ['NYC','NYC','LA','LA',...])。用于条件选取:df[df.index.get_level_values('city') == 'NYC']。也可用于 groupby:df.groupby(df.index.get_level_values(0)).sum()。Q from_product 和 from_tuples 怎么选?
A from_product 生成笛卡尔积(所有组合)——适合完整网格数据(3城市×4季度=12 行)。from_tuples 指定具体组合——适合不完整数据(某些城市没有某些品类)。from_product 更简洁,from_tuples 更精确。
📖 小节
- MultiIndex 为层次数据提供层次索引,像文件夹层级导航
- 创建:from_product(完整网格)、from_tuples(指定组合)、from_frame(DataFrame 转换)
- 选取:loc 从外到内,xs 任意层级跨选
- swaplevel/reorder_levels 交换层级顺序,操作后必须 sort_index()
- unstack 把行层级转列,stack 把列层级转行,与 MultiIndex 天然配合
- reset_index 展平行级 MultiIndex,重命名列展平列级 MultiIndex
📝 作业
- 基础题(难度⭐):用 from_product 创建 3 城市×2 品类的 MultiIndex DataFrame,用 loc 和 xs 分别选取特定城市和特定品类。
- 进阶题(难度⭐⭐):创建城市×季度的 MultiIndex DataFrame,swaplevel 交换层级→sort_index→unstack 转宽表→stack 转回验证。
- 挑战题(难度⭐⭐⭐):模拟 4 地区×3 品类×4 季度的 48 行数据,完成:xs 跨层选取→swaplevel 重排→unstack 多层展宽→reset_index 导出→groupby 重新聚合。