3.6 层级索引

3.6 层级索引

当遇到多维数据,数据索引超过一两个键,可通过层级索引(也叫多级索引)配合多个有不同等级的一级索引一起使用。

 

import numpy as np
import pandas as pd

3.6.1 多级索引Series

如何用一维的Series对象表示二维数据

笨办法

用元组表示索引

 

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

 

# 通过元组构成的多级索引,直接取值或切片
pop[('California', 2010):('Texas', 2000)]
(California, 2010)    37253956
(New York, 2000)      18976457
(New York, 2010)      19378102
(Texas, 2000)         20851820
dtype: int64

 

# 不方便直接获得元组中的项的所有数据,如2010年的所有数据
pop[[i for i in pop.index if i[1] == 2010]]
(California, 2010)    37253956
(New York, 2010)      19378102
(Texas, 2010)         25145561
dtype: int64

好办法:Pandas多级索引

pd可以用元组创建多级索引: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]])

level属性表示索引的等级,每个数据都有多级的不同的标签

 

# 将pop数据的索引重置,可见层级索引
pop = pop.reindex(index)
pop
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64

 

# 现在用切片的方式查询2010年的全部数据
pop[:, 2010]
California    37253956
New York      19378102
Texas         25145561
dtype: int64

 

pop['California']  # 不用 pop['California', :]
2000    33871648
2010    37253956
dtype: int64

高维数据的多级索引

二维多级索引的Series对象可以和DF对象相互转化,有专用的方法

 

# 二维Series转DF
pop_df = pop.unstack()
pop_df
  2000 2010
California 33871648 37253956
New York 18976457 19378102
Texas 20851820 25145561

 

# DF转二维Series
pop_df.stack()
California  2000    33871648
            2010    37253956
New York    2000    18976457
            2010    19378102
Texas       2000    20851820
            2010    25145561
dtype: int64

通过添加数据,可以提升数据的维数,而这很轻松方便

 

# 添加18岁以下人口的数据
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

3.6.2 多级索引的创建方法

为Series或DF创建多级索引最直接的办法就是将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.987919 0.985122
2 0.115032 0.677066
b 1 0.433856 0.914717
2 0.859339 0.461447

MultiIndex的创建工作将在后台完成。 同理,如果把将元组作为键的字典传递给pd,也会默认转换为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

但有时候显式地创建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]])

用元组列表——包含多个索引值的元组构成的列表:

 

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]])

用两个索引的笛卡尔积创建:

 

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参数创建:

  • levels 列表的列表,包含每个等级的索引值
  • labels 列表的列表,包含每个索引值标签

 

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]])

MultiIndex对象可作为创建Series或DF对象的index参数,或用reindex方法更新。

多级索引的等级名称

给MI对象的等级加上名称,会为某些操作提供方便。可在MI对象创建时用names参数添加名称,也可直接为MI对象的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

 

pop_df.index.names = ['state', 'year']
pop_df
    total under18
state year    
California 2000 33871648 9267089
2010 37253956 9284094
New York 2000 18976457 4687374
2010 19378102 4318033
Texas 2000 20851820 5906301
2010 25145561 6879014

多级列索引

DF对象的行列是对称的,可以有多级行索引,也可以有多级列索引:

 

# 多级行索引和列索引的创建
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 24.0 38.4 58.0 36.0 28.0 36.9
2 57.0 36.0 32.0 37.4 42.0 36.9
2014 1 50.0 37.5 43.0 37.9 56.0 38.7
2 30.0 36.4 37.0 38.8 22.0 38.5

多级行列索引的创建非常简单,上边创建了一个四维数据。可在列索引的第一级查询姓名,从而获得一人的全部检查信息的DF。

 

health_data['Guido']
  type HR Temp
year visit    
2013 1 58.0 36.0
2 32.0 37.4
2014 1 43.0 37.9
2 37.0 38.8

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

3.6.3 多级索引的取值与切片

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

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

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

 

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

类似的还有局部切片,但要求切片参数是按照MI顺序排列的:

 

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

DF多级索引

 

health_data
  subject Bob Guido Sue
  type HR Temp HR Temp HR Temp
year visit            
2013 1 24.0 38.4 58.0 36.0 28.0 36.9
2 57.0 36.0 32.0 37.4 42.0 36.9
2014 1 50.0 37.5 43.0 37.9 56.0 38.7
2 30.0 36.4 37.0 38.8 22.0 38.5

由于DF的基本索引是列索引,多级索引用在列上,如:

 

health_data['Guido', 'HR']
year  visit
2013  1        58.0
      2        32.0
2014  1        43.0
      2        37.0
Name: (Guido, HR), dtype: float64

 

health_data['Guido']
  type HR Temp
year visit    
2013 1 58.0 36.0
2 32.0 37.4
2014 1 43.0 37.9
2 37.0 38.8

loc、iloc等索引器都可以使用,如:

 

health_data.iloc[:2, :2]
  subject Bob
  type HR Temp
year visit    
2013 1 24.0 38.4
2 57.0 36.0

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

 

health_data.loc[:, ('Bob', 'HR')]
year  visit
2013  1        24.0
      2        57.0
2014  1        50.0
      2        30.0
Name: (Bob, HR), dtype: float64

在元组中使用切片语法会导致错误,这时可以使用IndexSlice对象,如:

 

idx = pd.IndexSlice
health_data.loc[idx[:, 1], idx[:, 'HR']]
# 获得行索引的每年第一次访问、和列索引的每人的HR数据
  subject Bob Guido Sue
  type HR HR HR
year visit      
2013 1 24.0 58.0 28.0
2014 1 50.0 43.0 56.0

3.6.4 多级索引行列转换

使用多级索引的关键是掌握有效数据转换的方法。pd提供了许多操作,可以让数据在内容不变的同时,按照需要进行行列转换。除了前述的stack和unstack方法之外,还有许多合理控制层级行列索引的方法

有序的索引和无序的索引

如果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.991095
      2      0.843417
c     1      0.227163
      2      0.755347
b     1      0.276651
      2      0.702083
dtype: float64

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

 

try:
    data['a':'b']
except KeyError as e:
    print(type(e))
    print(e)

'Key length (1) was greater than MultiIndex lexsort depth (0)'

问题就在MI对象的顺序无序上,而切片等操作要求字典序。解决这问题可用pd的方法sort_index()和sortlevel()来先给索引排序。之后就可切片。

 

data = data.sort_index()
data
char  int
a     1      0.991095
      2      0.843417
b     1      0.276651
      2      0.702083
c     1      0.227163
      2      0.755347
dtype: float64

 

data['a':'b']
char  int
a     1      0.991095
      2      0.843417
b     1      0.276651
      2      0.702083
dtype: float64

索引的stack和unstack转换

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

 

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

 

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

 

pop.unstack()  # 默认不动0级而把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

索引的设置与重置

层级数据维度转换的另一种方法是行列标签转换,可通过reset_index方法实现。如在上面的人口数据Series中使用该方法,则会生成一个列标签中包含之前行索引标签的state和year的DF。也可以用数据的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,通常将大有裨益。实际上可通过DF的set_index方法实现,返回结果就是一个带多级索引的DF:

 

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

实践中,用这种重建索引的方法处理数据集非常好用。

3.6.5 多级索引的数据累计方法

pd自带的数据累计方法有mean,sum,max等,而对于层级索引数据,可以设置参数level实现对数据子集的累计操作。

 

health_data
  subject Bob Guido Sue
  type HR Temp HR Temp HR Temp
year visit            
2013 1 24.0 38.4 58.0 36.0 28.0 36.9
2 57.0 36.0 32.0 37.4 42.0 36.9
2014 1 50.0 37.5 43.0 37.9 56.0 38.7
2 30.0 36.4 37.0 38.8 22.0 38.5

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

 

data_mean = health_data.mean(level='year')
data_mean
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year            
2013 40.5 37.20 45.0 36.70 35.0 36.9
2014 40.0 36.95 40.0 38.35 39.0 38.6

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

 

data_mean.mean(axis=1, level='type')
# 必须设定axis=1才能把level设为列中的type参数,因为行中无此索引
type HR Temp
year    
2013 40.166667 36.933333
2014 39.666667 37.966667

Panel数据

pd还有Panel对象和Panel4D对象,分别可看成一维数组Series和二维数组DF的三维与四维形式。但大多数情况多级索引已经够用。

你可能感兴趣的:(python,数据科学)