Pandas(4)

《Python数据科学手册》读书笔记

层级索引

当目前为止, 接触的都是一维数据和二维数据, 用 Pandas 的
Series 和 DataFrame 对象就可以存储。 但也经常会遇到存储多维
数据的需求, 数据索引超过一两个键。 因此, Pandas 提供了 Panel 和
Panel4D 对象解决三维数据与四维数据 。 而在实
践中, 更直观的形式是通过层级索引配合多个有不同等级的一级索引一
起使用, 这样就可以将高维数组转换成类似一维 Series 和二维
DataFrame 对象的形式。

import pandas as pd
import numpy as np
## 多级索引Series

用一维的 Series 对象表示二维数据
  1. 笨办法

分析美国各州在两个不同年份的数据,用Python 元组来表示索
引:

index = [('California', 2000), ('California', 2010),
         ('New York', 2000), ('New York', 2010),
         ('Texas', 2000), ('Texas', 2010)]
populations = [33871648, 37253956,
               18976457, 19378102,
               20851820, 25145561]
pop = pd.Series(populations, index=index)
pop
(California, 2000)    33871648
(California, 2010)    37253956
(New York, 2000)      18976457
(New York, 2010)      19378102
(Texas, 2000)         20851820
(Texas, 2010)         25145561
dtype: int64

通过元组构成的多级索引, 可以直接在 Series 上取值或用切片
查询数据

pop[('California', 2010):('Texas', 2000)]
(California, 2010)    37253956
(New York, 2000)      18976457
(New York, 2010)      19378102
(Texas, 2000)         20851820
dtype: int64

但是这么做很不方便。 假如想要选择所有 2000 年的数据, 那么
就得用一些比较复杂的清理方法:

pop[[i for i in pop.index if i[1] == 2010]]
(California, 2010)    37253956
(New York, 2010)      19378102
(Texas, 2010)         25145561
dtype: int64

这种方法不够简洁,在处理较大的数据时也不
够高效 。

  1. 好办法: Pandas多级索引

Pandas 的 MultiIndex 类型提供了更丰富的操作方
法。 先用元组创建一个多级索引, 如下所示:

index = pd.MultiIndex.from_tuples(index)
index
MultiIndex(levels=[['California', 'New York', 'Texas'], [2000, 2010]],
           labels=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])

MultiIndex 里面有一个 levels 属性表示索引的等级
——这样做可以将州名和年份作为每个数据点的不同标签。

如果将前面创建的 pop 的索引重置(reindex) 为 MultiIndex,
就会看到层级索引:

pop = pop.reindex(index)
pop
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64

其中前两列表示 Series 的多级索引值, 第三列是数据。 你会发现
有些行仿佛缺失了第一列数据——这其实是多级索引的表现形式,
每个空格与上面的索引相同。

现在可以直接用第二个索引获取 2010 年的全部数据

pop[:, 2010]
California    37253956
New York      19378102
Texas         25145561
dtype: int64

与之前的元组索引相比,
多级索引的语法更简洁。

  1. 高维数据的多级索引

其实完全可以用一个带行列索引的简单
DataFrame 代替前面的多级索引。
unstack() 方法可以快速将一个多级索引的 Series 转化为
普通索引的 DataFrame:

pop_df = pop.unstack()
pop_df
2000 2010
California 33871648 37253956
New York 18976457 19378102
Texas 20851820 25145561

当然了, 也有 stack() 方法实现相反的效果:

pop_df.stack()
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64

你可能会纠结于为什么要费时间研究层级索引。 其实理由很简单:
如果我们可以用含多级索引的一维 Series 数据表示二维数据, 那
么我们就可以用 Series 或 DataFrame 表示三维甚至更高维度的
数据。 多级索引每增加一级, 就表示数据增加一维, 利用这一特点
就可以轻松表示任意维度的数据了。 假如要增加一列显示每一年各
州的人口统计指标(例如 18 岁以下的人口) , 那么对于这种带有
MultiIndex 的对象, 增加一列就像 DataFrame 的操作一样简单:

pop_df = pd.DataFrame({'total': pop,
                        'under18': [9267089, 9284094,
                                    4687374, 4318033,
                                    5906301, 6879014]})
pop_df
total under18
California 2000 33871648 9267089
2010 37253956 9284094
New York 2000 18976457 4687374
2010 19378102 4318033
Texas 2000 20851820 5906301
2010 25145561 6879014

另外, 通用函数和其他功能函数也同样适用于层
级索引。 我们可以计算上面数据中 18 岁以下的人口占总人口的比
例:

f_u18 = pop_df['under18'] / pop_df['total']
f_u18.unstack()
2000 2010
California 0.273594 0.249211
New York 0.247010 0.222831
Texas 0.283251 0.273568

多级索引的创建方法

为 Series 或 DataFrame 创建多级索引最直接的办法就是将 index 参
数设置为至少二维的索引数组, 如下所示:

df = pd.DataFrame(np.random.rand(4, 2),
                  index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
                  columns=['data1', 'data2'])
df
data1 data2
a 1 0.722323 0.425514
2 0.770060 0.948675
b 1 0.621423 0.603367
2 0.551296 0.462293

MultiIndex 的创建工作将在后台完成。

同理, 如果你把将元组作为键的字典传递给 Pandas, Pandas 也会默认转
换为 MultiIndex:

data = {('California', 2000): 33871648,
        ('California', 2010): 37253956,
        ('Texas', 2000): 20851820,
        ('Texas', 2010): 25145561,
        ('New York', 2000): 18976457,
        ('New York', 2010): 19378102}
pd.Series(data)
California  2000    33871648
            2010    37253956
Texas       2000    20851820
            2010    25145561
New York    2000    18976457
            2010    19378102
dtype: int64
  1. 显式地创建多级索引

可以用 pd.MultiIndex 中的类方法更加灵活地构建多级索引。就像前面介绍的, 可以通过一个有不同等级的若干简单数
组组成的列表来构建 MultiIndex:

pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])
MultiIndex(levels=[['a', 'b'], [1, 2]],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]])

也可以通过包含多个索引值的元组构成的列表创建 MultiIndex:

pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b',2)])
MultiIndex(levels=[['a', 'b'], [1, 2]],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]])

还可以用两个索引的笛卡尔积创建
MultiIndex:

pd.MultiIndex.from_product([['a', 'b'], [1, 2]])
MultiIndex(levels=[['a', 'b'], [1, 2]],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]])

也可以直接提供 levels(包含每个等级的索引值列表的列表) 和
labels(包含每个索引值标签列表的列表) 创建 MultiIndex:

pd.MultiIndex(levels=[['a', 'b'], [1, 2]],
              labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
MultiIndex(levels=[['a', 'b'], [1, 2]],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]])

在创建 Series 或 DataFrame 时, 可以将这些对象作为 index 参
数, 或者通过 reindex 方法更新 Series 或 DataFrame 的索引。

  1. 多级索引的等级名称

可以在
前面任何一个 MultiIndex 构造器中通过 names 参数设置等级名
称, 也可以在创建之后通过索引的 names 属性来修改名称:

pop.index.names = ['state', 'year']
pop
state       year
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64

在处理复杂的数据时, 为等级设置名称是管理多个索引值的好办
法。

  1. 多级列索引

既然有多级行索
引, 那么同样可以有多级列索引。 让我们通过一份医学报告的模拟
数据来演示:

# 多级行列索引
index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]],
names=['year', 'visit'])
columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'], ['HR', 'Temp']],
names=['subject', 'type'])
# 模拟数据
data = np.round(np.random.randn(4, 6), 1)
data[:, ::2] *= 10
data += 37
# 创建DataFrame
health_data = pd.DataFrame(data, index=index, columns=columns)
health_data
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year visit
2013 1 35.0 36.6 45.0 36.2 33.0 37.6
2 29.0 37.9 31.0 38.3 37.0 38.1
2014 1 33.0 37.6 25.0 35.9 47.0 36.6
2 57.0 37.3 45.0 37.0 24.0 37.4

多级行列索引的创建非常简单。 上面创建了一个简易的四维数据,
四个维度分别为被检查人的姓名、 检查项目、 检查年份和检查次
数。 可以在列索引的第一级查询姓名, 从而获取包含一个人(例如
Guido) 全部检查信息的 DataFrame:

 health_data['Guido']
type HR Temp
year visit
2013 1 45.0 36.2
2 31.0 38.3
2014 1 25.0 35.9
2 45.0 37.0

如果想获取包含多种标签的数据, 需要通过对多个维度(姓名、 国
家、 城市等标签) 的多次查询才能实现, 这时使用多级行列索引进
行查询会非常方便。

多级索引的取值与切片

对 MultiIndex 的取值和切片操作很直观, 你可以直接把索引看成额外
增加的维度。

  1. Series多级索引

看看下面由各州历年人口数量创建的多级索引 Series:

pop
state       year
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64

可以通过对多个级别索引值获取单个元素:

pop['California', 2000]
33871648

MultiIndex 也支持局部索引, 即只取索引的某
一个层级。 假如只取最高级的索引, 获得的结果是一个新的
Series, 未被选中的低层索引值会被保留:

pop['California']
year
2000    33871648
2010    37253956
dtype: int64

类似的还有局部切片, 不过要求 MultiIndex 是按顺序排列的

pop.loc['California':'New York']
state       year
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
dtype: int64

如果索引已经排序, 那么可以用较低层级的索引取值, 第一层级的
索引可以用空切片:

pop[:, 2000]
state
California    33871648
New York      18976457
Texas         20851820
dtype: int64

其他取值与数据选择的方法也都起作用。 下
面的例子是通过布尔掩码选择数据:

pop[pop > 22000000]
state       year
California  2000    33871648
            2010    37253956
Texas       2010    25145561
dtype: int64

也可以用花哨的索引选择数据:

pop[['California', 'Texas']]
state       year
California  2000    33871648
            2010    37253956
Texas       2000    20851820
            2010    25145561
dtype: int64
  1. DataFrame多级索引

DataFrame 多级索引的用法与 Series 类似。 还用之前的体检报告
数据来演示:

由于 DataFrame 的基本索引是列索引, 因此 Series 中多级索引
的用法到了 DataFrame 中就应用在列上了。 例如, 可以通过简单
的操作获取 Guido 的心率数据:

 health_data['Guido', 'HR']
year  visit
2013  1        45.0
      2        31.0
2014  1        25.0
      2        45.0
Name: (Guido, HR), dtype: float64

与单索引类似,loc、 iloc 和 ix 索引器都可以使
用, 例如:

health_data.iloc[:2, :2]
subject Bob
type HR Temp
year visit
2013 1 35.0 36.6
2 29.0 37.9

虽然这些索引器将多维数据当作二维数据处理, 但是在 loc 和
iloc 中可以传递多个层级的索引元组, 例如:

这种索引元组的用法不是很方便, 如果在元组中使用切片还会导致
语法错误:

health_data.loc[(:, 1), (:, 'HR')]
  File "<ipython-input-38-6d10fc475b38>", line 1
    health_data.loc[( : , 1), ( : , 'HR')]
                      ^
SyntaxError: invalid syntax

还有一种更好的办法, 就是使用 IndexSlice 对象。 Pandas 专门用
它解决这类问题, 例如:

idx = pd.IndexSlice
health_data.loc[idx[:, 1], idx[:, 'HR']]
subject Bob Guido Sue
type HR HR HR
year visit
2013 1 35.0 45.0 33.0
2014 1 33.0 25.0 47.0

多级索引行列转换

  1. 有序的索引和无序的索引

如果 MultiIndex 不是有序的索引, 那么大多数切片
操作都会失败。

首先创建一个不按字典顺序排列的多级索引
Series:

index = pd.MultiIndex.from_product([['a', 'c', 'b'], [1, 2]])
data = pd.Series(np.random.rand(6), index=index)
data.index.names = ['char', 'int']
data
char  int
a     1      0.029188
      2      0.386739
c     1      0.273822
      2      0.514064
b     1      0.395069
      2      0.930861
dtype: float64

如果想对索引使用局部切片, 那么错误就会出现:

try:
    data['a':'b']
except KeyError as e:
    print(type(e))
    print(e)
<class 'pandas.errors.UnsortedIndexError'>
'Key length (1) was greater than MultiIndex lexsort depth (0)'

尽管从错误信息里面看不出具体的细节, 但问题是出在
MultiIndex 无序排列上。 局部切片和许多其他相似的操作都要求
MultiIndex 的各级索引是有序的(即按照字典顺序由 A 至 Z) 。
为此, Pandas 提供了许多便捷的操作完成排序, 如 sort_index()
和 sortlevel() 方法。 用最简单的 sort_index() 方法来演
示:

data = data.sort_index()
data
char  int
a     1      0.029188
      2      0.386739
b     1      0.395069
      2      0.930861
c     1      0.273822
      2      0.514064
dtype: float64

索引排序之后, 局部切片就可以正常使用了:

data['a':'b']
char  int
a     1      0.029188
      2      0.386739
b     1      0.395069
      2      0.930861
dtype: float64
  1. 索引stack与unstack

可以将一个多级索引数据集转换成简单的二维形
式, 可以通过 level 参数设置转换的索引层级:

pop.unstack(level=0)
state California New York Texas
year
2000 33871648 18976457 20851820
2010 37253956 19378102 25145561
pop.unstack(level=1)
year 2000 2010
state
California 33871648 37253956
New York 18976457 19378102
Texas 20851820 25145561

unstack() 是 stack() 的逆操作, 同时使用这两种方法让数据保
持不变:

pop.unstack().stack()
state       year
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64
  1. 索引的设置与重置

层级数据维度转换的另一种方法是行列标签转换, 可以通过
reset_index 方法实现。 如果在上面的人口数据 Series 中使用该
方法, 则会生成一个列标签中包含之前行索引标签 state 和 year 的
DataFrame。 也可以用数据的 name 属性为列设置名称:

pop_flat = pop.reset_index(name='population')
pop_flat
state year population
0 California 2000 33871648
1 California 2010 37253956
2 New York 2000 18976457
3 New York 2010 19378102
4 Texas 2000 20851820
5 Texas 2010 25145561

在解决实际问题的时候, 如果能将类似这样的原始输入数据的列直
接转换成 MultiIndex, 通常将大有裨益。 其实可以通过
DataFrame 的 set_index 方法实现, 返回结果就会是一个带多级
索引的 DataFrame:

pop_flat.set_index(['state', 'year'])
population
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561

多级索引的数据累计方法

前面我们已经介绍过一些 Pandas 自带的数据累计方法, 比如
mean()、 sum() 和 max()。 而对于层级索引数据, 可以设置参数
level 实现对数据子集的累计操作。

再一次以体检数据为例:

health_data
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year visit
2013 1 35.0 36.6 45.0 36.2 33.0 37.6
2 29.0 37.9 31.0 38.3 37.0 38.1
2014 1 33.0 37.6 25.0 35.9 47.0 36.6
2 57.0 37.3 45.0 37.0 24.0 37.4

如果需要计算每一年各项指标的平均值, 那么可以将参数 level 设
置为索引 year:

data_mean = health_data.mean(level='year')
data_mean
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year
2013 32.0 37.25 38.0 37.25 35.0 37.85
2014 45.0 37.45 35.0 36.45 35.5 37.00

如果再设置 axis 参数, 就可以对列索引进行类似的累计操作了:

data_mean.mean(axis=1, level='type')
type HR Temp
year
2013 35.0 37.450000
2014 38.5 36.966667

通过这两行数据, 就可以获取每一年所有人的平均心率和体温了。

这里还有一些 Pandas 的基本数据结构没有介绍到, 包括 pd.Panel
对象和 pd.Panel4D 对象。 这两种数据结构可以分别看成是(一维
数组) Series 和(二维数组) DataFrame 的三维与四维形式。 如
果熟悉 Series 和 DataFrame 的使用方法, 那么 Panel 和
Panel4D 使用起来也会很简单, ix、 loc 和 iloc 索引器在高维数据结构上的用法更是完全相同。

发布了57 篇原创文章 · 获赞 63 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/weixin_41503009/article/details/104185264