Pandas `MultiIndex` 高级操作:复杂数据层次化处理与切片

Pandas MultiIndex 高级操作:复杂数据层次化处理与切片

大家好!欢迎来到今天的“Pandas MultiIndex 高级操作”讲座。今天我们要聊聊Pandas的MultiIndex,这家伙就像数据界的俄罗斯套娃,一层套一层,专门用来处理复杂、层次化的数据。

你可能觉得,单层索引挺好的,简单直接。但是,当你的数据维度增加,比如你想同时根据年份、地区、产品类别来分析销售额,单层索引就显得力不从心了。这时候,MultiIndex就闪亮登场了,它可以让你轻松管理和操作这种多维度的数据。

今天,我们就来深入探讨MultiIndex的创建、操作、切片以及一些高级应用。准备好了吗?让我们开始吧!

1. 什么是 MultiIndex?为什么要用它?

简单来说,MultiIndex 就是具有多个层次的索引。每个层次都可以有自己的标签,共同组成一个唯一的索引值。

为什么要用 MultiIndex?

  • 组织复杂数据: 它可以自然地表示层次化数据,让数据结构更清晰。
  • 更强大的切片和选择: 可以方便地根据多个层次的标签进行数据选择和切片。
  • 简化数据分析: 在分组、聚合等数据分析操作中,MultiIndex 可以提供更灵活的控制。

举个例子,假设我们有一个关于不同城市不同年份的销售数据:

import pandas as pd

data = {
    ('北京', 2022): 1000,
    ('北京', 2023): 1200,
    ('上海', 2022): 1500,
    ('上海', 2023): 1800,
    ('广州', 2022): 800,
    ('广州', 2023): 900
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['城市', '年份'])
sales = pd.Series(data.values(), index=index)

print(sales)

输出:

城市  年份
北京  2022    1000
    2023    1200
上海  2022    1500
    2023    1800
广州  2022     800
    2023     900
dtype: int64

在这个例子中,城市年份构成了 MultiIndex,这样我们就可以方便地查询特定城市特定年份的销售额。

2. MultiIndex 的创建方式

创建 MultiIndex 的方式有很多,我们来逐一介绍:

2.1. pd.MultiIndex.from_tuples()

这是最常见的方式,通过一个元组列表来创建 MultiIndex。每个元组代表一个索引值,元组的长度决定了 MultiIndex 的层数。就像我们上面例子中使用的方法。

index_tuples = [('A', 1), ('A', 2), ('B', 1), ('B', 2)]
multi_index = pd.MultiIndex.from_tuples(index_tuples, names=['字母', '数字'])

print(multi_index)

输出:

MultiIndex([('A', 1),
            ('A', 2),
            ('B', 1),
            ('B', 2)],
           names=['字母', '数字'])

2.2. pd.MultiIndex.from_arrays()

这种方式通过一个数组列表来创建 MultiIndex。每个数组代表一个层次的标签。

levels = [['A', 'B'], [1, 2]]
labels = [[0, 0, 1, 1], [0, 1, 0, 1]]
multi_index = pd.MultiIndex.from_arrays(levels, names=['字母', '数字'])

print(multi_index)

输出:

MultiIndex([('A', 1),
            ('A', 2),
            ('B', 1),
            ('B', 2)],
           names=['字母', '数字'])

levels 定义了每个层次的标签取值范围,labels 定义了每个索引值在对应层次的标签位置。

2.3. pd.MultiIndex.from_product()

这种方式通过多个可迭代对象的笛卡尔积来创建 MultiIndex。

letters = ['A', 'B']
numbers = [1, 2]
multi_index = pd.MultiIndex.from_product([letters, numbers], names=['字母', '数字'])

print(multi_index)

输出:

MultiIndex([('A', 1),
            ('A', 2),
            ('B', 1),
            ('B', 2)],
           names=['字母', '数字'])

这种方式最适合创建所有可能的组合索引。

2.4. pd.MultiIndex.from_frame()

这种方式直接从DataFrame创建 MultiIndex。

data = {'字母': ['A', 'A', 'B', 'B'], '数字': [1, 2, 1, 2]}
df = pd.DataFrame(data)
multi_index = pd.MultiIndex.from_frame(df, names=['字母', '数字'])

print(multi_index)

输出:

MultiIndex([('A', 1),
            ('A', 2),
            ('B', 1),
            ('B', 2)],
           names=['字母', '数字'])

选择哪种方式?

  • 如果你的索引数据已经存在于元组列表中,from_tuples() 最方便。
  • 如果你的数据是分层存储的,from_arrays() 更有优势。
  • 如果你需要创建所有可能的组合,from_product() 是最佳选择。
  • 如果你已经有了一个DataFrame,用from_frame() 可以一步到位。

3. MultiIndex 的基本操作

创建好 MultiIndex 之后,我们需要掌握一些基本操作,才能更好地使用它。

3.1. 获取和设置索引名称

使用 names 属性可以获取和设置 MultiIndex 的名称。

index = pd.MultiIndex.from_product([['A', 'B'], [1, 2]], names=['字母', '数字'])
print(index.names)

index.names = ['一级', '二级']
print(index.names)

输出:

FrozenList(['字母', '数字'])
FrozenList(['一级', '二级'])

3.2. 获取索引层级

使用 levels 属性可以获取 MultiIndex 的层级信息。

index = pd.MultiIndex.from_product([['A', 'B'], [1, 2]], names=['字母', '数字'])
print(index.levels)

输出:

FrozenList([['A', 'B'], [1, 2]])

3.3. 获取索引长度

使用 len() 函数可以获取 MultiIndex 的长度。

index = pd.MultiIndex.from_product([['A', 'B'], [1, 2]], names=['字母', '数字'])
print(len(index))

输出:

4

3.4. 获取索引值

可以通过索引位置来获取索引值。

index = pd.MultiIndex.from_product([['A', 'B'], [1, 2]], names=['字母', '数字'])
print(index[0])

输出:

('A', 1)

4. MultiIndex 的切片与选择

这是 MultiIndex 最强大的地方之一,它可以让你根据多个层次的标签进行灵活的数据选择和切片。

4.1. 基本切片

可以使用 loc[] 进行基本切片。

data = {
    ('北京', 2022): 1000,
    ('北京', 2023): 1200,
    ('上海', 2022): 1500,
    ('上海', 2023): 1800,
    ('广州', 2022): 800,
    ('广州', 2023): 900
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['城市', '年份'])
sales = pd.Series(data.values(), index=index)

print(sales.loc['北京']) # 选择北京所有年份的数据
print(sales.loc[('北京', 2022)]) # 选择北京2022年的数据

输出:

年份
2022    1000
2023    1200
dtype: int64
1000

4.2. 使用 slice()

slice() 函数可以更灵活地定义切片范围。

data = {
    ('北京', 2022): 1000,
    ('北京', 2023): 1200,
    ('上海', 2022): 1500,
    ('上海', 2023): 1800,
    ('广州', 2022): 800,
    ('广州', 2023): 900
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['城市', '年份'])
sales = pd.Series(data.values(), index=index)

print(sales.loc[(slice(None), 2022)]) # 选择所有城市2022年的数据

输出:

城市  年份
北京  2022    1000
上海  2022    1500
广州  2022     800
dtype: int64

slice(None) 表示选择该层次的所有标签。

4.3. 使用 pd.IndexSlice

pd.IndexSlice 是一个更高级的切片工具,可以更方便地进行复杂的多层切片。

idx = pd.IndexSlice
data = {
    ('北京', 2022, 'A'): 100,
    ('北京', 2022, 'B'): 120,
    ('北京', 2023, 'A'): 150,
    ('北京', 2023, 'B'): 180,
    ('上海', 2022, 'A'): 80,
    ('上海', 2022, 'B'): 90,
    ('上海', 2023, 'A'): 110,
    ('上海', 2023, 'B'): 130
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['城市', '年份', '产品'])
sales = pd.Series(data.values(), index=index)

print(sales.loc[idx[:, 2022, :]]) # 选择所有城市2022年所有产品的数据
print(sales.loc[idx['北京':'上海', :, 'A']]) # 选择北京到上海所有年份A产品的数据

输出:

城市  年份  产品
北京  2022  A     100
          B     120
上海  2022  A      80
          B      90
dtype: int64
城市  年份  产品
北京  2022  A    100
          2023  A    150
上海  2022  A     80
          2023  A    110
dtype: int64

pd.IndexSlice 可以让你像写 SQL 一样进行数据选择,非常强大。

4.4. 使用 xs()

xs() 函数可以方便地选择特定层次的标签。

data = {
    ('北京', 2022): 1000,
    ('北京', 2023): 1200,
    ('上海', 2022): 1500,
    ('上海', 2023): 1800,
    ('广州', 2022): 800,
    ('广州', 2023): 900
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['城市', '年份'])
sales = pd.Series(data.values(), index=index)

print(sales.xs(2022, level='年份')) # 选择2022年的数据

输出:

城市
北京    1000
上海    1500
广州     800
dtype: int64

level 参数指定了要选择的层次名称。

5. MultiIndex 的高级应用

除了基本的切片和选择,MultiIndex 还可以用于更高级的数据分析操作。

5.1. 堆叠 (Stacking) 与取消堆叠 (Unstacking)

stack()unstack() 函数可以改变 DataFrame 的数据结构,将列转换为行索引,或者将行索引转换为列。

data = {'A': [1, 2], 'B': [3, 4], 'C': [5, 6]}
index = pd.MultiIndex.from_product([['X', 'Y'], [1, 2]], names=['字母', '数字'])
df = pd.DataFrame(data, index=index)

print(df)
stacked = df.stack()
print(stacked)
unstacked = stacked.unstack()
print(unstacked)

输出:

      A  B  C
字母 数字         
X  1   1  3  5
   2   2  4  6
Y  1   1  3  5
   2   2  4  6
字母  数字   
X   1  A    1
      B    3
      C    5
   2  A    2
      B    4
      C    6
Y   1  A    1
      B    3
      C    5
   2  A    2
      B    4
      C    6
dtype: int64
      A  B  C
字母 数字         
X  1   1  3  5
   2   2  4  6
Y  1   1  3  5
   2   2  4  6

stack() 将列标签 A, B, C 转换为行索引的第三层,unstack() 则将行索引的第三层还原为列标签。

5.2. 排序 (Sorting)

可以使用 sort_index() 函数对 MultiIndex 进行排序。

data = {
    ('B', 2023): 1200,
    ('A', 2022): 1000,
    ('C', 2022): 800,
    ('B', 2022): 1500,
    ('A', 2023): 1800,
    ('C', 2023): 900
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['字母', '年份'])
sales = pd.Series(data.values(), index=index)

print(sales.sort_index()) # 默认按字母升序排序,再按年份升序排序
print(sales.sort_index(level=['年份', '字母'])) # 按年份升序排序,再按字母升序排序

输出:

字母  年份
A  2022    1000
   2023    1800
B  2022    1500
   2023    1200
C  2022     800
   2023     900
dtype: int64
字母  年份
A  2022    1000
B  2022    1500
C  2022     800
A  2023    1800
B  2023    1200
C  2023     900
dtype: int64

5.3. 分组 (Grouping) 与聚合 (Aggregation)

MultiIndex 可以方便地进行分组和聚合操作。

data = {
    ('北京', 2022): 1000,
    ('北京', 2023): 1200,
    ('上海', 2022): 1500,
    ('上海', 2023): 1800,
    ('广州', 2022): 800,
    ('广州', 2023): 900
}

index = pd.MultiIndex.from_tuples(data.keys(), names=['城市', '年份'])
sales = pd.Series(data.values(), index=index)

print(sales.groupby(level='城市').sum()) # 按城市分组求和
print(sales.groupby(level='年份').mean()) # 按年份分组求平均值

输出:

城市
北京    2200
上海    3300
广州    1700
dtype: int64
年份
2022    1100.0
2023    1300.0
dtype: float64

groupby() 函数的 level 参数指定了要分组的层次名称。

6. 实际案例:销售数据分析

让我们用一个更真实的案例来演示 MultiIndex 的应用。假设我们有一个销售数据,包含地区、产品类别和月份三个维度。

import numpy as np
# 创建示例数据
regions = ['North', 'South', 'East', 'West']
categories = ['Electronics', 'Clothing', 'Home Goods']
months = ['Jan', 'Feb', 'Mar']

# 创建MultiIndex
index = pd.MultiIndex.from_product([regions, categories, months], names=['Region', 'Category', 'Month'])

# 创建随机销售数据
sales_data = np.random.randint(50, 200, size=len(index))
sales = pd.Series(sales_data, index=index)

print(sales)

# 1. 计算每个地区每个类别的总销售额
regional_category_sales = sales.groupby(level=['Region', 'Category']).sum()
print("n每个地区每个类别的总销售额:n", regional_category_sales)

# 2. 计算每个月所有地区的平均销售额
monthly_average_sales = sales.groupby(level='Month').mean()
print("n每个月所有地区的平均销售额:n", monthly_average_sales)

# 3. 选择北方地区所有月份的电子产品销售数据
north_electronics_sales = sales.loc[('North', 'Electronics', slice(None))]
print("n北方地区所有月份的电子产品销售数据:n", north_electronics_sales)

# 4. 使用 IndexSlice 选择南方和东方地区,所有类别的二月份销售数据
idx = pd.IndexSlice
south_east_feb_sales = sales.loc[idx[['South', 'East'], :, 'Feb']]
print("n南方和东方地区,所有类别的二月份销售数据:n", south_east_feb_sales)

# 5. 将数据取消堆叠,把月份变成列
unstacked_sales = sales.unstack(level='Month')
print("n取消堆叠后的数据:n", unstacked_sales)

这个例子展示了如何使用 MultiIndex 进行各种复杂的数据分析操作,包括分组、聚合、切片和数据重塑。

7. MultiIndex 的注意事项

  • 性能: MultiIndex 的操作可能会比单层索引慢一些,尤其是在数据量很大的情况下。因此,在使用 MultiIndex 时要注意性能优化。
  • 内存占用: MultiIndex 会占用更多的内存,因为它需要存储多个层次的索引信息。
  • 易用性: MultiIndex 的语法可能比较复杂,需要一定的学习成本。

总结

今天,我们深入探讨了 Pandas 的 MultiIndex,从创建方式到基本操作,再到高级应用,相信你已经对 MultiIndex 有了更深刻的理解。

MultiIndex 是一个强大的工具,可以帮助你更好地组织和分析复杂的数据。虽然它有一定的学习成本,但一旦掌握,你就可以像玩俄罗斯套娃一样玩转你的数据了!

希望今天的讲座对你有所帮助。感谢大家的参与!

附录:常用 MultiIndex 操作表格

操作 描述 示例
创建 MultiIndex 将多个层级的数据组合成一个 MultiIndex。 pd.MultiIndex.from_tuples([('A', 1), ('B', 2)])
设置/获取索引名称 使用 names 属性可以获取和设置 MultiIndex 的名称。 index.names = ['一级', '二级']
获取索引层级 使用 levels 属性可以获取 MultiIndex 的层级信息。 index.levels
基本切片 使用 loc[] 进行基本切片。 sales.loc['北京']
使用 slice() slice() 函数可以更灵活地定义切片范围。 sales.loc[(slice(None), 2022)]
使用 pd.IndexSlice pd.IndexSlice 是一个更高级的切片工具。 sales.loc[idx[:, 2022, :]]
使用 xs() xs() 函数可以方便地选择特定层次的标签。 sales.xs(2022, level='年份')
堆叠 (stack()) 将 DataFrame 的列转换为行索引。 df.stack()
取消堆叠 (unstack()) 将 DataFrame 的行索引转换为列。 stacked.unstack()
排序 (sort_index()) 对 MultiIndex 进行排序。 sales.sort_index()
分组 (groupby()) 与聚合 MultiIndex 可以方便地进行分组和聚合操作。 sales.groupby(level='城市').sum()

希望这个表格能够帮助你更快地掌握 MultiIndex 的常用操作。祝你学习愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注