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)
<class 'pandas.errors.UnsortedIndexError'>
'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的三維與四維形式。但大多數情況多級索引已經夠用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章