跳到主要内容

高级索引

Pandas 的索引系统是其强大功能的基础。本章介绍 MultiIndex(多级索引)、索引操作和高级索引技巧。

Index 对象

Index 基础

Index 是 Pandas 中用于轴标签的不可变序列。

import pandas as pd
import numpy as np

# 创建 Index
idx = pd.Index([1, 2, 3, 4, 5])
print(idx) # Index([1, 2, 3, 4, 5], dtype='int64')

# 从列表创建
idx = pd.Index(['a', 'b', 'c', 'd'])

# 从范围创建
idx = pd.RangeIndex(start=0, stop=10, step=2)
# RangeIndex(start=0, stop=10, step=2)

# Index 属性
print(idx.name) # 索引名称
print(idx.dtype) # 数据类型
print(idx.size) # 元素数量
print(idx.shape) # 形状
print(idx.nbytes) # 内存占用
print(idx.is_unique) # 是否唯一

Index 类型

# 整数索引
int_idx = pd.Index([1, 2, 3])

# 浮点索引
float_idx = pd.Index([1.0, 2.0, 3.0])

# 字符串索引
str_idx = pd.Index(['a', 'b', 'c'])

# 时间索引
date_idx = pd.DatetimeIndex(['2024-01-01', '2024-01-02'])

# 时间段索引
period_idx = pd.PeriodIndex(['2024-01', '2024-02'], freq='M')

# 分类索引
cat_idx = pd.CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c', 'd'])

Index 操作

idx = pd.Index(['a', 'b', 'c', 'd'])

# 成员检测
print('a' in idx) # True
print('e' in idx) # False

# 集合操作
idx1 = pd.Index([1, 2, 3, 4])
idx2 = pd.Index([3, 4, 5, 6])

print(idx1.intersection(idx2)) # 交集: Index([3, 4])
print(idx1.union(idx2)) # 并集: Index([1, 2, 3, 4, 5, 6])
print(idx1.difference(idx2)) # 差集: Index([1, 2])
print(idx1.symmetric_difference(idx2)) # 对称差: Index([1, 2, 5, 6])

# 删除和插入
idx_new = idx.delete(0) # 删除第一个
idx_new = idx.insert(0, 'z') # 在开头插入
idx_new = idx.append(pd.Index(['e', 'f'])) # 追加

# 去重
idx_dup = pd.Index(['a', 'b', 'a', 'c'])
idx_unique = idx_dup.unique()

MultiIndex 多级索引

MultiIndex 允许在单个轴上使用多个索引级别,适用于层次化数据。

创建 MultiIndex

# 从数组创建
arrays = [
['北京', '北京', '上海', '上海'],
['2023', '2024', '2023', '2024']
]
index = pd.MultiIndex.from_arrays(arrays, names=['城市', '年份'])
print(index)
# MultiIndex([('北京', '2023'),
# ('北京', '2024'),
# ('上海', '2023'),
# ('上海', '2024')],
# names=['城市', '年份'])

# 从元组创建
tuples = [('北京', '2023'), ('北京', '2024'), ('上海', '2023'), ('上海', '2024')]
index = pd.MultiIndex.from_tuples(tuples, names=['城市', '年份'])

# 从笛卡尔积创建
cities = ['北京', '上海']
years = ['2023', '2024']
index = pd.MultiIndex.from_product([cities, years], names=['城市', '年份'])

# 从 DataFrame 创建
df = pd.DataFrame({
'城市': ['北京', '北京', '上海', '上海'],
'年份': ['2023', '2024', '2023', '2024']
})
index = pd.MultiIndex.from_frame(df)

使用 MultiIndex 创建 DataFrame

# 创建多级索引 DataFrame
index = pd.MultiIndex.from_product(
[['北京', '上海'], ['2023', '2024']],
names=['城市', '年份']
)
columns = pd.MultiIndex.from_product(
[['销售额', '利润'], ['Q1', 'Q2']],
names=['指标', '季度']
)
data = np.random.randint(100, 1000, (4, 4))

df = pd.DataFrame(data, index=index, columns=columns)
print(df)
# 指标 销售额 利润
# 季度 Q1 Q2 Q1 Q2
# 城市 年份
# 北京 2023 456 789 234 567
# 2024 321 654 123 456
# 上海 2023 567 890 345 678
# 2024 432 765 234 567

多级索引选择

# 选择第一级索引
print(df.loc['北京'])
# 指标 销售额 利润
# 季度 Q1 Q2 Q1 Q2
# 年份
# 2023 456 789 234 567
# 2024 321 654 123 456

# 选择多级索引
print(df.loc[('北京', '2023')])
print(df.loc[('北京', '2023'), ('销售额', 'Q1')])

# 使用切片
print(df.loc['北京':'上海'])
print(df.loc[('北京', '2023'):('上海', '2023')])

# 使用 xs 方法(cross-section)
print(df.xs('北京', level='城市')) # 按城市选择
print(df.xs('2023', level='年份')) # 按年份选择
print(df.xs(('北京', '2023'))) # 多级选择

# 选择列的多级索引
print(df.xs('Q1', level='季度', axis=1))
print(df.xs('销售额', level='指标', axis=1))

MultiIndex 操作

# 创建带多级索引的 Series
index = pd.MultiIndex.from_product(
[['北京', '上海', '广州'], ['Q1', 'Q2', 'Q3', 'Q4']],
names=['城市', '季度']
)
s = pd.Series(np.random.randint(100, 500, 12), index=index)

# 重置索引(转为列)
df = s.reset_index(name='销售额')

# 设置多级索引
df_indexed = df.set_index(['城市', '季度'])

# 交换索引级别
s_swapped = s.swaplevel('城市', '季度')
print(s_swapped.index)
# MultiIndex([('Q1', '北京'), ('Q2', '北京'), ...], names=['季度', '城市'])

# 排序索引
s_sorted = s.sort_index()
s_sorted = s.sort_index(level='城市')
s_sorted = s.sort_index(level=['城市', '季度'])

# 重命名级别
s.index = s.index.set_names(['区域', '季度'])

# 删除级别
s_dropped = s.droplevel('城市') # 删除城市级别

多级索引聚合

# 按级别分组
result = s.groupby(level='城市').sum()
print(result)
# 城市
# 北京 1234
# 上海 1456
# 广州 1678

# 多级别分组
result = s.groupby(level=['城市', '季度']).mean()

# 使用 unstack
df = s.unstack(level='季度')
print(df)
# 季度 Q1 Q2 Q3 Q4
# 城市
# 北京 234 345 456 567
# 上海 345 456 567 678
# 广州 456 567 678 789

索引操作方法

set_index 设置索引

# 创建示例数据
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'department': ['技术', '销售', '技术'],
'salary': [10000, 15000, 20000]
})

# 设置单列为索引
df_indexed = df.set_index('name')

# 设置多列为索引
df_indexed = df.set_index(['department', 'name'])

# 保留原列
df_indexed = df.set_index('name', drop=False)

# 原地修改
df.set_index('name', inplace=True)

# 追加索引
df_indexed = df.set_index('department', append=True)

reset_index 重置索引

# 重置索引为默认整数索引
df_reset = df_indexed.reset_index()

# 不保留原索引
df_reset = df_indexed.reset_index(drop=True)

# 重置特定级别(多级索引)
df_reset = df_indexed.reset_index(level='name')

# 原地修改
df_indexed.reset_index(inplace=True)

reindex 重新索引

# 创建示例数据
df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6]
}, index=['a', 'b', 'c'])

# 重新索引
new_index = ['a', 'b', 'c', 'd', 'e']
df_reindexed = df.reindex(new_index)
print(df_reindexed)
# A B
# a 1.0 4.0
# b 2.0 5.0
# c 3.0 6.0
# d NaN NaN
# e NaN NaN

# 填充缺失值
df_reindexed = df.reindex(new_index, fill_value=0)

# 前向填充
df_reindexed = df.reindex(new_index, method='ffill')

# 后向填充
df_reindexed = df.reindex(new_index, method='bfill')

# 重新索引列
new_columns = ['A', 'B', 'C']
df_reindexed = df.reindex(columns=new_columns)

# 同时重新索引行和列
df_reindexed = df.reindex(index=new_index, columns=new_columns)

reindex_like 对齐索引

# 创建两个 DataFrame
df1 = pd.DataFrame({'A': [1, 2, 3]}, index=['a', 'b', 'c'])
df2 = pd.DataFrame({'A': [4, 5]}, index=['a', 'c'])

# 使 df2 的索引与 df1 一致
df2_aligned = df2.reindex_like(df1)
print(df2_aligned)
# A
# a 4.0
# b NaN
# c 5.0

高级选择技巧

使用 get_indexer

# 获取位置索引
idx = pd.Index(['a', 'b', 'c', 'd', 'e'])
positions = idx.get_indexer(['b', 'd', 'f'])
print(positions) # [ 1 3 -1] (-1 表示未找到)

# 获取索引器(匹配模式)
idx = pd.Index(['a', 'b', 'c', 'd', 'e'])
positions = idx.get_indexer(['b', 'd'], method='ffill') # 前向填充
positions = idx.get_indexer(['b', 'd'], method='bfill') # 后向填充
positions = idx.get_indexer(['b', 'd'], method='nearest') # 最近匹配

使用 IndexSlice

# 创建多级索引数据
index = pd.MultiIndex.from_product(
[['北京', '上海'], ['Q1', 'Q2', 'Q3', 'Q4']],
names=['城市', '季度']
)
df = pd.DataFrame({
'sales': np.random.randint(100, 500, 8),
'profit': np.random.randint(10, 50, 8)
}, index=index)

# 使用 IndexSlice 进行复杂切片
idx = pd.IndexSlice

# 选择特定城市的所有季度
print(df.loc[idx['北京', :], :])

# 选择特定季度的所有城市
print(df.loc[idx[:, 'Q1'], :])

# 选择特定城市的特定季度
print(df.loc[idx['北京', ['Q1', 'Q2']], :])

# 选择列
print(df.loc[idx[:, :], idx['sales']])

条件索引选择

# 创建数据
df = pd.DataFrame({
'A': [1, 2, 3, 4, 5],
'B': [10, 20, 30, 40, 50]
}, index=['a', 'b', 'c', 'd', 'e'])

# 使用 where
result = df.where(df > 20)
print(result)
# A B
# a NaN NaN
# b NaN NaN
# c 3.0 30.0
# d 4.0 40.0
# e 5.0 50.0

# 使用 mask(与 where 相反)
result = df.mask(df > 20, -1)

# 使用 query
result = df.query('A > 2 and B < 50')

# 使用 eval(表达式计算)
result = df.eval('C = A + B')

索引性能优化

排序索引

# 排序后的索引查询更快
df_sorted = df.sort_index()

# 检查是否已排序
print(df.index.is_monotonic_increasing)
print(df.index.is_monotonic_decreasing)

唯一索引

# 唯一索引查询更快
idx_unique = pd.Index([1, 2, 3])
print(idx_unique.is_unique) # True

# 检查重复
idx_dup = pd.Index([1, 2, 2, 3])
print(idx_dup.has_duplicates) # True
print(idx_dup.duplicated()) # [False, False, True, False]

分类索引优化

# 对于重复值多的索引,使用分类类型
df['category'] = df['category'].astype('category')
df = df.set_index('category')

实战示例

示例 1:多维度数据分析

# 创建销售数据
np.random.seed(42)
index = pd.MultiIndex.from_product(
[
['华东', '华北', '华南', '西部'],
['2023', '2024'],
['Q1', 'Q2', 'Q3', 'Q4']
],
names=['区域', '年份', '季度']
)
df = pd.DataFrame({
'sales': np.random.randint(1000, 5000, 64),
'profit': np.random.randint(100, 500, 64)
}, index=index)

# 查看特定区域的数据
print(df.loc['华东'])

# 查看特定年份的数据
print(df.xs('2023', level='年份'))

# 查看特定区域特定年份
print(df.loc[('华东', '2023')])

# 计算各区域年度汇总
annual = df.groupby(level=['区域', '年份']).sum()
print(annual)

# 使用 unstack 转换格式
wide_format = annual.unstack(level='年份')
print(wide_format)

示例 2:时间序列多级索引

# 创建时间序列多级索引
dates = pd.date_range('2024-01-01', periods=90, freq='D')
symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN']
index = pd.MultiIndex.from_product(
[dates, symbols],
names=['date', 'symbol']
)

df = pd.DataFrame({
'open': np.random.uniform(100, 200, 360),
'close': np.random.uniform(100, 200, 360),
'volume': np.random.randint(1000000, 5000000, 360)
}, index=index)

# 选择特定日期的所有股票
print(df.xs('2024-01-15', level='date'))

# 选择特定股票的时间序列
print(df.xs('AAPL', level='symbol'))

# 计算每只股票的平均收盘价
avg_close = df.groupby(level='symbol')['close'].mean()
print(avg_close)

# 计算每日总成交量
daily_volume = df.groupby(level='date')['volume'].sum()
print(daily_volume)

小结

Index 类型

  • Index:基本索引
  • MultiIndex:多级索引
  • DatetimeIndex:时间索引
  • CategoricalIndex:分类索引

索引操作

  • set_index():设置索引
  • reset_index():重置索引
  • reindex():重新索引
  • swaplevel():交换级别
  • sort_index():排序索引

选择方法

  • loc[]:标签选择
  • xs():跨级别选择
  • IndexSlice:复杂切片

练习

  1. 创建一个包含城市-年份-季度三级索引的 DataFrame
  2. 使用 xs()loc[] 选择不同级别的数据
  3. 使用 unstack() 将多级索引转换为宽格式
  4. 使用 groupby() 对多级索引数据进行聚合
  5. 比较排序和未排序索引的查询性能

下一步

掌握了高级索引后,让我们学习 性能优化