pandas中的多层索引(MultiIndex)允许在多个层次上组织数据,使得我们可以处理更高维度的数据,同时保持二维DataFrame的形式。多层索引可以用于行索引(index)和列索引(columns)。
这里我们主要介绍多层索引在行索引上的操作,列索引的多层索引操作类似。
- 创建具有多层索引的DataFrame
- 索引和切片
- 重塑数据:stack和unstack
- 聚合操作
下面我们通过例子来详细说明。
pandas DataFrame的多层索引(MultiIndex)操作详解
多层索引(MultiIndex)允许在多个维度上组织数据,是处理高维数据的强大工具。以下通过多个示例详细说明其操作:
1. 创建多层索引DataFrame
(1) 从元组列表创建
import pandas as pd
import numpy as np
# 创建多层索引的行
index_tuples = [
('北京', '2023', '第一季度'),
('北京', '2023', '第二季度'),
('北京', '2024', '第一季度'),
('上海', '2023', '第一季度'),
('上海', '2023', '第二季度'),
('上海', '2024', '第一季度')
]
# 创建多层索引
multi_index = pd.MultiIndex.from_tuples(
index_tuples,
names=['城市', '年份', '季度']
)
# 创建DataFrame
data = {
'销售额': [1000, 1200, 1100, 800, 900, 850],
'利润': [200, 250, 220, 150, 180, 160],
'成本': [800, 950, 880, 650, 720, 690]
}
df = pd.DataFrame(data, index=multi_index)
print("原始DataFrame:")
print(df)
print("\n索引信息:")
print(df.index)
(2) 使用product创建多层索引
# 创建多层索引的另一种方式
cities = ['北京', '上海']
years = [2023, 2024]
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
# 使用笛卡尔积创建所有组合
multi_index = pd.MultiIndex.from_product(
[cities, years, quarters],
names=['城市', '年份', '季度']
)
# 创建随机数据
np.random.seed(42)
data = {
'销售额': np.random.randint(500, 2000, len(multi_index)),
'利润': np.random.randint(50, 500, len(multi_index))
}
df_product = pd.DataFrame(data, index=multi_index)
print("使用product创建的DataFrame:")
print(df_product.head(8))
(3) 从普通DataFrame创建多层索引
# 创建普通DataFrame
df_flat = pd.DataFrame({
'城市': ['北京', '北京', '上海', '上海'] * 2,
'年份': [2023, 2023, 2023, 2023, 2024, 2024, 2024, 2024],
'季度': ['Q1', 'Q2'] * 4,
'销售额': [1000, 1200, 800, 900, 1100, 1300, 850, 950],
'利润': [200, 250, 150, 180, 220, 270, 160, 190]
})
# 设置多层索引
df_multi = df_flat.set_index(['城市', '年份', '季度'])
print("设置多层索引后的DataFrame:")
print(df_multi)
2. 基本索引操作
(1) 选择特定层级的数据
# 选择北京的所有数据
print("北京的所有数据:")
print(df.xs('北京')) # xs方法用于选择特定索引值
# 选择2023年的所有数据(需要指定层级)
print("\n2023年的所有数据:")
print(df.xs('2023', level='年份'))
# 选择第一季度的所有数据
print("\n第一季度的所有数据:")
print(df.xs('第一季度', level='季度'))
(2) 切片操作
# 使用loc进行切片
print("北京2023年的数据:")
print(df.loc['北京', '2023'])
print("\n所有城市2023年第一季度的数据:")
print(df.loc[(slice(None), '2023', '第一季度'), :])
# 更简洁的切片方式(推荐)
print("\n使用IndexSlice进行切片:")
idx = pd.IndexSlice
print(df.loc[idx['北京', :, '第一季度'], :])
print("\n北京和上海2023年Q1和Q2的数据:")
print(df.loc[idx[['北京', '上海'], '2023', ['第一季度', '第二季度']], :])
3. 数据选择和查询
(1) 多条件选择
# 选择销售额大于1000的数据
high_sales = df[df['销售额'] > 1000]
print("销售额大于1000的数据:")
print(high_sales)
# 选择特定城市和年份的数据
mask = (df.index.get_level_values('城市') == '北京') & \
(df.index.get_level_values('年份') == '2023')
print("\n北京2023年的数据:")
print(df[mask])
(2) 使用query方法
# 重置索引以便使用query
df_reset = df.reset_index()
print("使用query查询数据:")
print(df_reset.query('城市 == "北京" and 年份 == "2023"'))
# 或者直接在多层索引上使用
print("\n查询销售额大于1000且利润大于200的数据:")
print(df[df['销售额'] > 1000].query('利润 > 200'))
4. 数据聚合和统计
(1) 按层级聚合
# 按城市聚合
city_summary = df.groupby(level='城市').agg({
'销售额': ['sum', 'mean', 'std'],
'利润': ['sum', 'mean']
})
print("按城市聚合:")
print(city_summary)
# 按城市和年份聚合
city_year_summary = df.groupby(level=['城市', '年份']).sum()
print("\n按城市和年份聚合:")
print(city_year_summary)
(2) 使用pivot_table
# 使用透视表
pivot_df = df_flat.pivot_table(
values='销售额',
index=['城市', '年份'],
columns='季度',
aggfunc='sum',
fill_value=0
)
print("透视表结果:")
print(pivot_df)
5. 数据重塑:stack和unstack
# 创建示例数据
df_example = pd.DataFrame({
'A': {'X': 1, 'Y': 3},
'B': {'X': 2, 'Y': 4}
})
# stack:将列索引转换为行索引
stacked = df_example.stack()
print("stack操作结果:")
print(stacked)
print(f"\nstack后的索引: {stacked.index}")
# unstack:将行索引转换为列索引
unstacked = stacked.unstack()
print("\nunstack操作结果:")
print(unstacked)
# 多层索引的stack/unstack
print("\n多层索引的unstack:")
# 将季度从行索引移到列索引
df_unstacked = df.unstack('季度')
print(df_unstacked)
# 将城市从行索引移到列索引
print("\n将城市移到列索引:")
print(df.unstack('城市'))
6. 排序和重组
# 按索引排序
df_sorted = df.sort_index()
print("按索引排序:")
print(df_sorted)
# 按特定层级排序
df_sorted_city = df.sort_index(level='城市')
print("\n按城市排序:")
print(df_sorted_city)
# 按数值列排序
df_sorted_sales = df.sort_values('销售额', ascending=False)
print("\n按销售额降序排序:")
print(df_sorted_sales)
# 交换索引层级
df_swapped = df.swaplevel('城市', '季度')
print("\n交换城市和季度索引层级:")
print(df_swapped)
7. 多层索引的列操作
# 创建具有多层列索引的DataFrame
arrays = [
['销售额', '销售额', '利润', '利润'],
['实际', '目标', '实际', '目标']
]
columns = pd.MultiIndex.from_arrays(arrays, names=['指标', '类型'])
# 创建数据
data_multi_col = np.random.randint(100, 1000, (6, 4))
df_multi_col = pd.DataFrame(data_multi_col, index=multi_index[:6], columns=columns)
print("具有多层列索引的DataFrame:")
print(df_multi_col)
# 选择特定列
print("\n选择所有销售额列:")
print(df_multi_col['销售额'])
print("\n选择销售额的实际值:")
print(df_multi_col[('销售额', '实际')])
# 列索引的聚合
print("\n按指标聚合:")
print(df_multi_col.groupby(level='指标', axis=1).sum())
8. 实际应用示例:销售数据分析
def analyze_sales_data():
"""销售数据分析示例"""
# 创建更复杂的数据
np.random.seed(42)
# 创建索引
cities = ['北京', '上海', '广州']
years = [2022, 2023, 2024]
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
products = ['产品A', '产品B', '产品C']
# 创建多层索引
index = pd.MultiIndex.from_product(
[cities, years, quarters, products],
names=['城市', '年份', '季度', '产品']
)
# 创建数据
data = {
'销售额': np.random.randint(1000, 10000, len(index)),
'成本': np.random.randint(500, 8000, len(index)),
'销量': np.random.randint(10, 500, len(index))
}
df_full = pd.DataFrame(data, index=index)
df_full['利润'] = df_full['销售额'] - df_full['成本']
df_full['利润率'] = df_full['利润'] / df_full['销售额']
print("完整数据集形状:", df_full.shape)
print("\n前10行数据:")
print(df_full.head(10))
# 1. 分析各城市年度销售额
print("\n各城市年度销售额:")
city_year_sales = df_full.groupby(['城市', '年份'])['销售额'].sum().unstack('年份')
print(city_year_sales)
# 2. 分析各产品季度趋势
print("\n各产品季度销售额趋势:")
product_quarter_sales = df_full.groupby(['产品', '季度'])['销售额'].mean().unstack('产品')
print(product_quarter_sales)
# 3. 查找利润率最高的组合
print("\n利润率最高的前10个组合:")
top_profit = df_full.nlargest(10, '利润率')
print(top_profit[['销售额', '成本', '利润', '利润率']])
# 4. 创建数据透视表
print("\n数据透视表:各城市各产品的平均利润率:")
pivot = pd.pivot_table(
df_full,
values='利润率',
index=['城市', '产品'],
columns='年份',
aggfunc='mean'
)
print(pivot)
return df_full
# 运行示例
df_complete = analyze_sales_data()
9. 性能优化技巧
# 1. 使用sort_index提高查询性能
df_sorted = df.sort_index()
print("排序后的索引:", df_sorted.index.is_monotonic_increasing)
# 2. 使用categorical类型节省内存
df['城市'] = df['城市'].astype('category')
df['年份'] = df['年份'].astype('category')
print("\n内存使用情况:")
print(df.memory_usage(deep=True))
# 3. 避免在多层索引上使用循环
# 不好的做法
bad_results = []
for city in df.index.get_level_values('城市').unique():
for year in df.index.get_level_values('年份').unique():
# 循环操作
# 好的做法 - 使用groupby
good_results = df.groupby(['城市', '年份']).apply(lambda x: x.mean())
10. 常见问题和解决方案
# 问题1:重置索引
print("重置索引(保持所有层级):")
print(df.reset_index())
print("\n重置索引(删除某些层级):")
print(df.reset_index(level='季度'))
# 问题2:处理缺失的组合
# 使用reindex填充缺失的组合
full_index = pd.MultiIndex.from_product(
[['北京', '上海'], ['2023', '2024'], ['第一季度', '第二季度', '第三季度']],
names=['城市', '年份', '季度']
)
df_reindexed = df.reindex(full_index)
print("\n重新索引后的DataFrame(有NaN):")
print(df_reindexed)
# 问题3:扁平化多层索引列
df_flat_cols = df_multi_col.copy()
df_flat_cols.columns = ['_'.join(col).strip() for col in df_flat_cols.columns.values]
print("\n扁平化列名后的DataFrame:")
print(df_flat_cols.head())
总结
多层索引的主要操作要点:
- 创建:使用
MultiIndex.from_tuples、from_product或set_index - 选择:使用
xs、loc配合slice或IndexSlice - 聚合:使用
groupby指定level参数 - 重塑:使用
stack和unstack转换行列 - 排序:使用
sort_index按索引层级排序 - 优化:保持索引有序,使用分类数据类型
多层索引虽然学习曲线较陡,但一旦掌握,可以极大地提高处理复杂结构化数据的效率。它特别适合时间序列分析、面板数据分析和多维数据报表等场景。