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