Pandas: 多层索引

最后更新:2026-08-26

当数据有层次结构——按城市再按产品分组、按年份再按季度统计——单层索引不够用。MultiIndex(多层索引)让层次数据有层次索引:先选一级,再选二级,像文件夹层级一样导航。本节覆盖 MultiIndex 的创建、选取、重排和与 stack/unstack 的配合。

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

1. 你将学到


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 层级结构

100%
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 更精确。

📖 小节


📝 作业

  1. 基础题(难度⭐):用 from_product 创建 3 城市×2 品类的 MultiIndex DataFrame,用 loc 和 xs 分别选取特定城市和特定品类。
  2. 进阶题(难度⭐⭐):创建城市×季度的 MultiIndex DataFrame,swaplevel 交换层级→sort_index→unstack 转宽表→stack 转回验证。
  3. 挑战题(难度⭐⭐⭐):模拟 4 地区×3 品类×4 季度的 48 行数据,完成:xs 跨层选取→swaplevel 重排→unstack 多层展宽→reset_index 导出→groupby 重新聚合。

← 上一课:时间序列进阶 · 下一课:窗口计算 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏