5. 层级索引

处理多维数据的时候,虽然Pandas提供了Panel和Panel4D, 但更直观的是使用层级索引(Hierarchical Indeing,也叫多级索引 multi-indexing), 通过层级索引, 可以将高维度数据转换成类似一维Series或者二维DataFrame对象的形式。

5.1. 层级索引的创建

5.1.1. 直接创建

import numpy as np
import pandas as pd
# 通过输入层级索引,直接创建Series

# 我们创建一组翻译数据,分别包含1-5的汉语,英语,德语

# 用元组信息作为index
idx = [("yi", "en"),  ("yi", "de"),
       ("er", "en"),    ("er", "de"), 
         ("san", "en"),   ("san", "de"), 
         ("si", "en"),   ("si", "de"), 
         ("wu", "en"),   ("wu", "de")]
# 内容是元组对应的每一个英语或者德育的翻译
digits = ["ONE", "EINS", "TWO", "ZWEI", "THREE", "DREI", "FOUR", "VIER", 
         "FIVE", "FUNF"]

# 创建一维的数组
trans = pd.Series(digits, index=idx)

# 查看打印的结果
print("trans = \n", trans)

# 这个一维数组可以支持常常的比如取值,切片操作
print("\n trans[('san', 'en')] = ", trans[('san', 'en')] )
print("\n trans[('san', 'en'):('wu','en')] = \n",  trans[('san', 'en'):('wu','en')])
trans = 
 (yi, en)       ONE
(yi, de)      EINS
(er, en)       TWO
(er, de)      ZWEI
(san, en)    THREE
(san, de)     DREI
(si, en)      FOUR
(si, de)      VIER
(wu, en)      FIVE
(wu, de)      FUNF
dtype: object

 trans[('san', 'en')] =  THREE

 trans[('san', 'en'):('wu','en')] = 
 (san, en)    THREE
(san, de)     DREI
(si, en)      FOUR
(si, de)      VIER
(wu, en)      FIVE
dtype: object

5.1.2. 使用多级索引创建

可以先创建多级MultiIndex,然后利用多级索引来创建内容。

MultiIndex是一个包含levels的结构,levels表示的是索引的层级,每个层级分别有哪些内容等等。

# 创建多级索引
# 使用上面的数据idx

midx = pd.MultiIndex.from_tuples(idx)
print( "多级索引midx=\n", midx)
多级索引midx=
 MultiIndex(levels=[['er', 'san', 'si', 'wu', 'yi'], ['de', 'en']],
           labels=[[4, 4, 0, 0, 1, 1, 2, 2, 3, 3], [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]])
# 然后利用创建的多级索引创建数据
trans = pd.Series(digits, index=midx)
print("\n trans = \n", trans)

# 然后可以使用切片等功能获取相应内容
# 注意:因为索引不是按照字典排序,所以不能使用切片,即切片的前提是,索引是有序索引 !!!
print("\n trans['san', 'de'] = ", trans['san', 'de'])
 trans = 
 yi   en      ONE
     de     EINS
er   en      TWO
     de     ZWEI
san  en    THREE
     de     DREI
si   en     FOUR
     de     VIER
wu   en     FIVE
     de     FUNF
dtype: object

 trans['san', 'de'] =  DREI

5.1.3. 利用Series创建二维DataFrame

pandas给我们提供了一对函数,stack和unstack,可以让Series和DataFrame相互转换。

#数据直接使用一维的trans
trans_df = trans.unstack()
print("折叠后的二维数据是:")
print("trans_df = \n", trans_df)
折叠后的二维数据是:
trans_df = 
        de     en
er   ZWEI    TWO
san  DREI  THREE
si   VIER   FOUR
wu   FUNF   FIVE
yi   EINS    ONE

5.1.4. 创建数据的时候直接时候用二维索引

trans_df = pd.DataFrame(digits, index=[["YI", "YI", "ER", "ER", "SAN", "SAN","SI", "SI", "WU", "WU"], 
                                      ["EN", "DE","EN", "DE","EN", "DE","EN", "DE","EN", "DE"]],
                       columns=["TRANS"])
print(trans_df)
        TRANS
YI  EN    ONE
    DE   EINS
ER  EN    TWO
    DE   ZWEI
SAN EN  THREE
    DE   DREI
SI  EN   FOUR
    DE   VIER
WU  EN   FIVE
    DE   FUNF

5.2. 多级索引

5.2.1. 显式创建多级索引

# 通过数组创建
midx = pd.MultiIndex.from_arrays([list("AAABBBCCC"), [1,2,3,1,2,3,1,2,3]])
print("通过数组创建:midx = \n", midx)
通过数组创建:midx = 
 MultiIndex(levels=[['A', 'B', 'C'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 1, 2, 2, 2], [0, 1, 2, 0, 1, 2, 0, 1, 2]])
#  通过tuple创建
midx = pd.MultiIndex.from_tuples([("A", 1),("A", 2), ("A", 3),
                                 ("B", 1),("B", 2), ("B", 3),
                                 ("C", 1),("C", 2), ("C", 3),])
print("通过元组创建:midx = \n", midx)
通过元组创建:midx = 
 MultiIndex(levels=[['A', 'B', 'C'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 1, 2, 2, 2], [0, 1, 2, 0, 1, 2, 0, 1, 2]])
# 通过笛卡尔积创建(CartesianProduct)
midx = pd.MultiIndex.from_product([list("ABC"), [1,2,3]])
print("通过笛卡尔积创建:midx = \n", midx)
通过笛卡尔积创建:midx = 
 MultiIndex(levels=[['A', 'B', 'C'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 1, 2, 2, 2], [0, 1, 2, 0, 1, 2, 0, 1, 2]])

5.2.2. 多级索引的等级名称

对多级索引的每个等级进行命名可以方便以后的操作,一般可以直接通过对所含的属性names进行赋值得到。

midx.names =["NAME", "LEVEL"]
print("带名字的多级索引 midx = \n", midx)
带名字的多级索引 midx = 
 MultiIndex(levels=[['A', 'B', 'C'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 1, 2, 2, 2], [0, 1, 2, 0, 1, 2, 0, 1, 2]],
           names=['NAME', 'LEVEL'])

5.2.3. 多级列索引

列索引也可以包含多级,通过多级行索引,列所含能实现多维数据的创建和操作。

# 行多级索引
midx_row = pd.MultiIndex.from_product([list("ABC"), [1,2,3]])

# 列多级索引
midx_col = pd.MultiIndex.from_product([["I", "II", "III"], [1,2]])

#准备数据
data = np.arange(54).reshape(9,6)

df = pd.DataFrame(data, index=midx_row, columns=midx_col)
print(df)

      I      II     III    
      1   2   1   2   1   2
A 1   0   1   2   3   4   5
  2   6   7   8   9  10  11
  3  12  13  14  15  16  17
B 1  18  19  20  21  22  23
  2  24  25  26  27  28  29
  3  30  31  32  33  34  35
C 1  36  37  38  39  40  41
  2  42  43  44  45  46  47
  3  48  49  50  51  52  53

5.3. 多级索引的的取值和切片

多级索引的取值和切片和和简单的数组的取值和切片很类似,我们这里先介绍Series的多级索引的取值和切片,再介绍DataFrame的取值和切片用法。

对多级索引使用切片的前提是:索引为有序索引!!!

5.3.1. Series的多级索引取值操作

# 创建数据

trans = pd.Series(digits, index=[["YI", "YI", "ER", "ER", "SAN", "SAN","SI", "SI", "WU", "WU"], 
                                      ["EN", "DE","EN", "DE","EN", "DE","EN", "DE","EN", "DE"]],
                     )
print("Series的数据: \n", trans_df)
Series的数据: 
 YI   EN      ONE
     DE     EINS
ER   EN      TWO
     DE     ZWEI
SAN  EN    THREE
     DE     DREI
SI   EN     FOUR
     DE     VIER
WU   EN     FIVE
     DE     FUNF
dtype: object
# 对trans的取值操作

# 提取单个一级索引的值
print("trans['YI', 'DE'] = \n", trans['YI'])

# 使用二级索引提取单个的值
print("\n trans['YI', 'DE'] = ", trans['YI', 'DE'])
trans['YI', 'DE'] = 
 EN     ONE
DE    EINS
dtype: object

 trans['YI', 'DE'] =  EINS
# 切片的前提是index必须有序
# 准备有序的index

data = np.arange(100, 700, 100)

midx = pd.MultiIndex.from_product([list("ABC"), [1,2]])
df = pd.Series(data, index=midx)

print("df = \n", df)
df = 
 A  1    100
   2    200
B  1    300
   2    400
C  1    500
   2    600
dtype: int64
# 对一级索引进行切片
print("一级索引进行切片 df[:'B'] = \n", df[:'B'])
一级索引进行切片 df[:'B'] = 
 A  1    100
   2    200
B  1    300
   2    400
dtype: int64
# 对二级索引选取
print("\n 二级索引进行切片 df.loc[:'B', :2] =\n", df.loc[:'B', :2])
 二级索引进行切片 df.loc[:'B', :2] =
 A  1    100
   2    200
B  1    300
   2    400
dtype: int64
# 掩码选取
print("y掩码选取数据 df[df < 400] = \n", df[df < 400])
y掩码选取数据 df[df < 400] = 
 A  1    100
   2    200
B  1    300
dtype: int64
# 花哨索引
print(df[['A','C']].loc[:, 1])
A    100
C    500
dtype: int64

5.3.2. DataFrame取值操作

DataFrame的切片操作比较麻烦,对元组的切片还容易引起错误,pandas给提供了专门的IndexSlice来专门帮助处理切片。

## 准备数据

# 行多级索引
midx_row = pd.MultiIndex.from_product([list("ABC"), [1,2,3]])

# 列多级索引
midx_col = pd.MultiIndex.from_product([["I", "II", "III"], [1,2]])

#准备数据
data = np.arange(54).reshape(9,6)

df = pd.DataFrame(data, index=midx_row, columns=midx_col)
print(df)

      I      II     III    
      1   2   1   2   1   2
A 1   0   1   2   3   4   5
  2   6   7   8   9  10  11
  3  12  13  14  15  16  17
B 1  18  19  20  21  22  23
  2  24  25  26  27  28  29
  3  30  31  32  33  34  35
C 1  36  37  38  39  40  41
  2  42  43  44  45  46  47
  3  48  49  50  51  52  53
# 使用列索引获取数据
print("获取DF的一列 df['I',2] = \n", df['I', 2])
获取DF的一列 df['I',2] = 
 A  1     1
   2     7
   3    13
B  1    19
   2    25
   3    31
C  1    37
   2    43
   3    49
Name: (I, 2), dtype: int64
# 最好使用索引器
print("获取DF的一列 df['I',2] = \n", df.loc['A'])
获取DF的一列 df['I',2] = 
     I      II     III    
    1   2   1   2   1   2
1   0   1   2   3   4   5
2   6   7   8   9  10  11
3  12  13  14  15  16  17
# 使用IndexSlice
idx = pd.IndexSlice
df.loc[idx[:, 2], idx[:'II', :]]

5.4. 多级索引的行列转换

Pandas提供了很多方法,可以让数据在内容保持不变的情况下,按照需要进行行列转换。

5.4.1. 有序和无序索引

MultiIndex如果不是有序索引,则大多数切片可能失败。

为了让Index有序,我们一般使用sort_index方法对齐进行处理,使之有序后在进行操作。

# 准备无序MultiIndex的数据

trans = pd.Series(digits, index=[["YI", "YI", "ER", "ER", "SAN", "SAN","SI", "SI", "WU", "WU"], 
                                      ["EN", "DE","EN", "DE","EN", "DE","EN", "DE","EN", "DE"]],
                     )
print("Series的数据: \n", trans)
Series的数据: 
 YI   EN      ONE
     DE     EINS
ER   EN      TWO
     DE     ZWEI
SAN  EN    THREE
     DE     DREI
SI   EN     FOUR
     DE     VIER
WU   EN     FIVE
     DE     FUNF
dtype: object
# 上面trans是无序index,在对其进行切片操作的时候,会报错
# 使用sort_index进行排序后,进行切片操作就正常

trans2 = trans.sort_index()
print("排序后的 trans2[:'WU'] = \n", trans2[:'WU'] )
排序后的 trans2[:'WU'] = 
 ER   DE     ZWEI
     EN      TWO
SAN  DE     DREI
     EN    THREE
SI   DE     VIER
     EN     FOUR
WU   DE     FUNF
     EN     FIVE
dtype: object

5.4.2. stack和unstack

这两个互为逆操作。

# unstack把低维度数据扩展成高维度数据

trans4 = trans.unstack()
print("二维数据是: trans.unstack = \n", trans4)
二维数据是: trans.unstack = 
        DE     EN
ER   ZWEI    TWO
SAN  DREI  THREE
SI   VIER   FOUR
WU   FUNF   FIVE
YI   EINS    ONE
# unstack可以按照指定级别进行扩展
trans4 = trans.unstack(level=0)
print("二维数据是: trans.unstack = \n", trans4)
二维数据是: trans.unstack = 
       ER    SAN    SI    WU    YI
DE  ZWEI   DREI  VIER  FUNF  EINS
EN   TWO  THREE  FOUR  FIVE   ONE

5.4.3. 索引的设置和重置

set_index和reset_index可以通过重新设置index来改变数据的表现形式。

# 吧索引当做数据插入到二维数组中

trans6 = trans.reset_index()
print("重置索引后数据 trans.reset_index = \n", trans6)
print(type(trans6))
重置索引后数据 trans.reset_index = 
   level_0 level_1      0
0      YI      EN    ONE
1      YI      DE   EINS
2      ER      EN    TWO
3      ER      DE   ZWEI
4     SAN      EN  THREE
5     SAN      DE   DREI
6      SI      EN   FOUR
7      SI      DE   VIER
8      WU      EN   FIVE
9      WU      DE   FUNF
<class 'pandas.core.frame.DataFrame'>
# 可以使用drop参数来表明是否丢弃index
# 默认drop=False
trans7 = trans.reset_index(drop=True)
print("重置索引并丢弃后 trans.reset_index(drop=True) = \n", trans7)
重置索引并丢弃后 trans.reset_index(drop=True) = 
 0      ONE
1     EINS
2      TWO
3     ZWEI
4    THREE
5     DREI
6     FOUR
7     VIER
8     FIVE
9     FUNF
dtype: object
# set_index是reset_index的逆操作
# 参数为必填项,必须明确指明哪个columns呗当做index
trans8 = trans6.set_index(['level_0'])
print("重置索引后的数据 trans6.set_index('level_0') = \n", trans8)
重置索引后的数据 trans6.set_index('level_0') = 
         level_1      0
level_0               
YI           EN    ONE
YI           DE   EINS
ER           EN    TWO
ER           DE   ZWEI
SAN          EN  THREE
SAN          DE   DREI
SI           EN   FOUR
SI           DE   VIER
WU           EN   FIVE
WU           DE   FUNF

5.5. 多级索引的数据累计方法

对于多级索引,我们可能需要对某一个索引进行数据处理,比如求mean,sum等操作,此时需要用到参数level,通过对参数level的设置,pandas会选择相应的数据进行处理。

import numpy as np
import pandas as pd

## 准备数据

# 行多级索引
midx_row = pd.MultiIndex.from_product([list("ABC"), [1,2,3]], names=["Upper", "row_digit"])

# 列多级索引
midx_col = pd.MultiIndex.from_product([["I", "II", "III"], [1,2]], names=["ROM", "col_digit"])

#准备数据
data = np.arange(54).reshape(9,6)

df = pd.DataFrame(data, index=midx_row, columns=midx_col)
print(df)
ROM               I      II     III    
col_digit         1   2   1   2   1   2
Upper row_digit                        
A     1           0   1   2   3   4   5
      2           6   7   8   9  10  11
      3          12  13  14  15  16  17
B     1          18  19  20  21  22  23
      2          24  25  26  27  28  29
      3          30  31  32  33  34  35
C     1          36  37  38  39  40  41
      2          42  43  44  45  46  47
      3          48  49  50  51  52  53

5.5.1. level的使用

level参数可以对某一索引进行聚合操作

## 上面例子,对A,B,C进行求和

data_sum = df.sum(level="Upper")
print(data_sum)
ROM          I        II       III     
col_digit    1    2    1    2    1    2
Upper                                  
A           18   21   24   27   30   33
B           72   75   78   81   84   87
C          126  129  132  135  138  141

5.6. level配合axis

level和axis可以配合一起使用,这样就可以死对任意维度进行操作,还可以连续操作,最终得到想要的结果。

# 配合axis使用

data_sum = data_sum.sum(axis=1, level="ROM")
print(data_sum)
ROM      I   II  III
Upper               
A       39   51   63
B      147  159  171
C      255  267  279