从 MultiIndex 选择数据
使用带有元组和切片的 .loc,以及 pd.IndexSlice 辅助工具,获取外层和内层索引级别的数据。
从 MultiIndex 选择数据 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。
浏览层次化数据
拥有 MultiIndex 后,您需要高效地检索数据子集。Pandas 为此提供了三种工具:使用带元组的 .loc 进行精确标签选择,使用 slice() 和 pd.IndexSlice 跨层级进行范围选择,以及使用 .xs() 在特定层级值处选择横截面。掌握这些访问方式,才能充分发挥层次化索引的能力。
import pandas as pd
import numpy as np
# Setup: GDP data by country and year
df_flat = pd.DataFrame({
'country': ['USA','USA','USA','UK','UK','UK','DE','DE','DE'],
'year': [2021, 2022, 2023, 2021, 2022, 2023, 2021, 2022, 2023],
'gdp_bn': [23000, 25000, 26000, 3000, 3100, 3200, 4100, 4200, 4350]
})
df = df_flat.set_index(['country', 'year']).sort_index()
print(df)使用 .loc 按外层选择
将单个外层标签传递给 .loc[],即可获取该分组的所有行。对于双层 MultiIndex(国家、年份),df.loc['USA'] 会返回所有 USA 行,结果是一个仅保留内层索引(年份)的 DataFrame。这种行为称为层级下移——当您选择外层的特定值时,Pandas 会从返回对象的索引中移除该层级。
import pandas as pd
df_flat = pd.DataFrame({
'country': ['USA','USA','USA','UK','UK','UK'],
'year': [2021, 2022, 2023, 2021, 2022, 2023],
'gdp_bn': [23000, 25000, 26000, 3000, 3100, 3200]
})
df = df_flat.set_index(['country', 'year']).sort_index()
# Select all USA rows
usa = df.loc['USA']
print('All USA rows:')
print(usa)
print('\nRemaining index type:', usa.index.name)使用 .loc 选择特定元组
要获取由两个索引层级共同标识的单行,请将一个元组传递给 .loc[]:df.loc[('USA', 2022)]。这会返回与该(外层、内层)标签组合完全对应的 Series(如果只有一列,则返回标量)。如果希望得到 DataFrame 而不是 Series,必须将元组放在列表中:df.loc[[('USA', 2022)]]。
import pandas as pd
df_flat = pd.DataFrame({
'country': ['USA','USA','USA','UK','UK','UK'],
'year': [2021, 2022, 2023, 2021, 2022, 2023],
'gdp_bn': [23000, 25000, 26000, 3000, 3100, 3200],
'cpi': [3.2, 5.1, 4.8, 2.5, 3.4, 3.0]
})
df = df_flat.set_index(['country', 'year']).sort_index()
# Single row as Series
print('USA 2022 (Series):')
print(df.loc[('USA', 2022)])
# Single row as DataFrame
print('\nUSA 2022 (DataFrame):')
print(df.loc[[('USA', 2022)]])使用元组列表选择多行
要一次选择多个指定行,请将元组列表传递给 .loc[]。当您需要根据复合键选择不连续的行子集时,这种方式非常有用——例如选择 USA 和 UK 的 2022 年数据,但不选择德国的数据。返回的 DataFrame 会保留 MultiIndex。
import pandas as pd
df_flat = pd.DataFrame({
'country': ['USA','USA','UK','UK','DE','DE'],
'year': [2022, 2023, 2022, 2023, 2022, 2023],
'gdp_bn': [25000, 26000, 3100, 3200, 4200, 4350]
})
df = df_flat.set_index(['country', 'year']).sort_index()
# Select specific (country, year) combinations
subset = df.loc[[('USA', 2022), ('UK', 2023), ('DE', 2022)]]
print(subset)使用 pd.IndexSlice 进行切片
pd.IndexSlice(通常别名为 idx)可以让您为 MultiIndex 层级编写范围切片,无需手动构造冗长的元组切片。语法为:df.loc[idx[outer_slice, inner_slice], column_slice]。例如,df.loc[idx['UK':'USA', 2022:2023], :] 会选择外层在 UK 到 USA 之间、内层在 2022 到 2023 之间的所有行。索引必须经过排序,此操作才能正确运行。
import pandas as pd
import numpy as np
years = [2021, 2022, 2023]
countries = ['DE', 'UK', 'USA']
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'gdp': np.random.randint(3000, 26000, 9)}, index=mi)
idx = pd.IndexSlice
# Select UK and USA for years 2022 and 2023
subset = df.loc[idx['UK':'USA', 2022:2023], :]
print(subset)使用 .xs() 访问横截面
df.xs(key, level=) 会选择特定层级等于给定值的所有行,而不受其他层级内容的影响。与要求先指定外层的 .loc 不同,.xs 可以直接按名称访问任意层级。例如,df.xs(2022, level='year') 会返回所有国家的 2022 年行,无需指定 USA、UK 或 DE。
import pandas as pd
import numpy as np
countries = ['DE', 'UK', 'USA']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'gdp': [4100,4200,4350, 3000,3100,3200, 23000,25000,26000]}, index=mi)
# All countries for year 2022 (inner level)
print('All data for year 2022:')
print(df.xs(2022, level='year'))
# All years for UK (outer level)
print('\nAll years for UK:')
print(df.xs('UK', level='country'))访问 MultiIndex 列
当 DataFrame 的列使用 MultiIndex 时,请使用元组访问特定列:df[('revenue', 'Q1')]。要选择外层的所有列(例如所有 revenue 列),请使用 df['revenue'],它会返回该外层键下所有内层列组成的 DataFrame。将 pd.IndexSlice 模式与 .loc 结合使用时,同样适用于列的 MultiIndex。
import pandas as pd
import numpy as np
metrics = ['revenue', 'cost']
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
cols = pd.MultiIndex.from_product([metrics, quarters], names=['metric', 'quarter'])
df = pd.DataFrame(
np.random.randint(50, 200, (3, 8)),
columns=cols,
index=['USA', 'UK', 'DE']
)
# Access all revenue columns
print('Revenue columns:')
print(df['revenue'])
# Access a specific cell
print('\nUSA revenue Q1:', df.loc['USA', ('revenue', 'Q1')])跨所有外层键选择内层
要跨所有外层值选择单个内层值,请将 .loc 与外层的 slice(None) 结合使用:df.loc[(slice(None), 2022), :]。这会选择每个国家的 2022 年行。使用 pd.IndexSlice 的写法更易读:df.loc[idx[:, 2022], :]。当您希望获取特定时间点所有实体的快照时,通常需要使用这种模式。
import pandas as pd
import numpy as np
countries = ['DE', 'UK', 'USA']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'gdp': [4100,4200,4350, 3000,3100,3200, 23000,25000,26000]}, index=mi)
idx = pd.IndexSlice
# All countries for year 2022 (using IndexSlice)
all_2022 = df.loc[idx[:, 2022], :]
print('All countries in 2022:')
print(all_2022)使用 MultiIndex 选择时的常见陷阱
有三个常见陷阱:1)索引未排序:对未排序的 MultiIndex 进行切片会引发 UnsortedIndexError 或返回错误结果——请始终先调用 sort_index()。2)将元组作为键导致 KeyError:如果传入元组时没有将其放入 .loc[],Python 会将其解释为位置索引——请始终使用 .loc 进行基于标签的 MultiIndex 访问。3)层级不匹配:执行 groupby 后,MultiIndex 的层级名称可能与您的预期不一致——切片前请检查 df.index.names。
import pandas as pd
# Unsorted MultiIndex slicing
tuples = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi = pd.MultiIndex.from_tuples(tuples, names=['letter', 'num'])
df = pd.DataFrame({'value': [10, 20, 30, 40]}, index=mi)
print('Is sorted:', df.index.is_monotonic_increasing)
# Sort before slicing
df = df.sort_index()
print('After sort:', df.index.is_monotonic_increasing)
idx = pd.IndexSlice
print('\nSlice A:')
print(df.loc[idx['A', :], :])实际示例:季度报告
一个具体用例是:您有按(地区、季度)建立索引的销售数据,需要提取所有地区的 Q4 数据,用于年终报告。使用 .xs('Q4', level='quarter') 即可通过一次调用获取该横截面。然后使用 .sum() 计算总计。这种模式——groupby 聚合 → MultiIndex 结果 → 横截面提取——在业务报告处理流程中非常常见。
import pandas as pd
import numpy as np
regions = ['North', 'South', 'East', 'West']
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
mi = pd.MultiIndex.from_product([regions, quarters], names=['region', 'quarter'])
np.random.seed(42)
df = pd.DataFrame({
'sales': np.random.randint(200, 500, 16),
'units': np.random.randint(50, 150, 16)
}, index=mi)
# Extract Q4 performance across all regions
q4 = df.xs('Q4', level='quarter')
print('Q4 Results by Region:')
print(q4)
print('\nQ4 Total Sales:', q4['sales'].sum())结合列使用 MultiIndex 选择
您可以使用 .loc[row_indexer, column_list] 同时选择指定的行和列。对于 MultiIndex,行索引器可以是元组、元组列表或 IndexSlice,列索引器可以是列名或列名列表。这样,您就能通过一个表达式,从层次化 DataFrame 中提取精确的矩形子表。
import pandas as pd
import numpy as np
countries = ['DE', 'UK', 'USA']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({
'gdp': [4100,4200,4350, 3000,3100,3200, 23000,25000,26000],
'inflation': [1.5, 2.1, 2.8, 2.0, 3.4, 2.9, 3.2, 5.1, 4.8],
'unemployment': [5.0, 4.8, 4.5, 4.5, 4.2, 4.0, 3.8, 3.5, 3.3]
}, index=mi)
idx = pd.IndexSlice
# UK and USA, years 2022-2023, only gdp and inflation
result = df.loc[idx['UK':'USA', 2022:2023], ['gdp', 'inflation']]
print(result)快速检查
请测试您对本课中 MultiIndex 选择方法的理解。
课程回顾
本课中您学到了:使用带元组的 .loc 选择特定的(外层、内层)标签组合,使用 pd.IndexSlice 跨任意层级进行范围切片,以及使用 .xs() 在任意层级提取横截面,而不受外层键限制。请始终先对索引排序,以避免 UnsortedIndexError。接下来,我们将学习索引对齐和重新索引,即 Pandas 如何将两个 DataFrame 对齐到同一个索引。
常见问题解答
「从 MultiIndex 选择数据」课时是免费的吗?
是的 — 「从 MultiIndex 选择数据」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。
「从 MultiIndex 选择数据」这节课中我会学到什么?
使用带有元组和切片的 .loc,以及 pd.IndexSlice 辅助工具,获取外层和内层索引级别的数据。 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Pandas & NumPy Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「从 MultiIndex 选择数据」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?
能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 创建 MultiIndex
- 从 MultiIndex 选择数据
- 索引对齐与重新索引
- 排序索引的性能优势