DataFrame 多重索引


在〈二維的 DataFrame〉中看過,可以透過 DataFrameindex 來建立或改變每一列資料的索引名稱,而在〈Pandas 輸入輸出〉中看過,read_csv 可以透過 index_col 來指定 CSV 中哪一行要作為索引。

DataFrame 實例的 set_index 方法也可以用來指定哪行要作為索引,例如:

import pandas as pd

scores = pd.DataFrame({
    '座號' : ['No.01', 'No.02', 'No.03', 'No.04', 'No.05'],
    '數學' : [90, 99, 92, 87, 85],
    '英文' : [99, 87, 85, 67, 89],
    '物理' : [100, 94, 76, 72, 67],
})

scores.set_index('座號', inplace = True)

print(scores)

在上例指定了 '座號' 該行資料作為索引,inplace 用來指定是否原地改變資料,若不指定,傳回的 DataFrame 實例才會是修改後的結果,執行的結果如下:

       數學  英文   物理
座號
No.01  90  99  100
No.02  99  87   94
No.03  92  85   76
No.04  87  67   72
No.05  85  89   67

set_index 可以接受清單,也就是在必要的情況下,也可以指定多行來建立多重索引。例如:

import pandas as pd

scores = pd.DataFrame({
    '座號' : ['No.01', 'No.02', 'No.03', 'No.04', 'No.05'],
    '姓名' : ['Justin', 'Monica', 'Irene', 'Bush', 'Dora'],
    '數學' : [90, 99, 92, 87, 85],
    '英文' : [99, 87, 85, 67, 89],
    '物理' : [100, 94, 76, 72, 67],
})

scores.set_index(['座號', '姓名'], inplace = True)

print(scores)

這會顯示以下的結果:

              數學  英文   物理
座號    姓名
No.01 Justin  90  99  100
No.02 Monica  99  87   94
No.03 Irene   92  85   76
No.04 Bush    87  67   72
No.05 Dora    85  89   67

如果要指定多重索引取得某列,可以像是 scores.loc[('No.01', 'Justin')] 的方式,這會傳回 Series,上例若是僅指定 scores.loc['No.01'],會傳回 DataFrame,其中使用 '姓名' 作為索引行。

如果你的資料來源是 scores.csv,其中有以下資料:

DataFrame 多重索引

如果你直接讀入這個 CSV 的話,座號部份會有一些 NaN

      座號  段考  數學  英文   物理
0  No.01  一段  90  99  100
1    NaN  二段  88  77   84
2  No.02  一段  99  87   94
3    NaN  二段  75  88   94
4  No.03  一段  92  85   76
5    NaN  二段  89  98   89
6  No.04  一段  87  67   72
7    NaN  二段  99  78   85
8  No.05  一段  85  89   67
9    NaN  二段  85  99   78

DataFrame 有一些可以處理 NaN 的方法,像是 dropna 可以直接丟棄 NaN 的行或列,fillna 可以為 NaN 填值,這邊要使用 fillna,在軸 0 方向用 NaN 前一個值來補上:

import pandas as pd

scores = pd.read_csv('scores.csv')
scores.fillna(method = 'ffill', axis = 0, inplace = True)
print(scores)

這會顯示以下結果:

      座號  段考  數學  英文   物理
0  No.01  一段  90  99  100
1  No.01  二段  88  77   84
2  No.02  一段  99  87   94
3  No.02  二段  75  88   94
4  No.03  一段  92  85   76
5  No.03  二段  89  98   89
6  No.04  一段  87  67   72
7  No.04  二段  99  78   85
8  No.05  一段  85  89   67
9  No.05  二段  85  99   78

接下來的問題是,如果為這個 DataFrame 設定索引,以便能透過 loc 來指定取得某列?例如 No.04 的第二次段考分數呢?單獨指定 '座號'段考,都不能達到這個要求,你必須同時指定,例如:

import pandas as pd

scores = pd.read_csv('scores.csv')
scores.fillna(method = 'ffill', axis = 0, inplace = True)

scores.set_index(['座號', '段考'], inplace = True)

print(scores)
print(scores.loc[('No.04', '二段')])

這會顯示以下的結果:

          數學  英文   物理
座號    段考
No.01 一段  90  99  100
      二段  88  77   84
No.02 一段  99  87   94
      二段  75  88   94
No.03 一段  92  85   76
      二段  89  98   89
No.04 一段  87  67   72
      二段  99  78   85
No.05 一段  85  89   67
      二段  85  99   78
數學    99
英文    78
物理    85
Name: (No.04, 二段), dtype: int64

有點難以理解嗎?其實資料來源 scores.csv 中的資料,應該要分為兩張資料表,一張記錄第一次段考,另一張記錄第二次段考,例如,第一張資料表應該長得像這樣:

DataFrame 多重索引

也就是說,資料來源 scores.csv 是兩張資料表合在一起了:

DataFrame 多重索引

這看來像是三維的資料了,若要表示三維的資料,在過去會使用 Pandas 的 Panel,不過它已經被標為棄用了(Deprecated)了,如果使用的話,會出現以下訊息:

DeprecationWarning: Panel is deprecated and will be removed in a future version. The recommended way to represent these types of 3-dimensional data are with a MultiIndex on a DataFrame, via the Panel.to_frame() method. Alternatively, you can use the xarray package http://xarray.pydata.org/en/stable/. Pandas provides a .to_xarray() method to help automate this conversion.

也就是說,其中建議透過 DataFrameMultiIndex,也就是多重索引的方式來表示三維資料。

這也表示,若你有多個 DataFrame,各表示二維的資料,例如各表示第一次與第二次段考的成績,也可以合併為一個 DataFrame,並設定多重索引。例如:

import pandas as pd

scores1 = pd.DataFrame({
    '座號' : ['No.01', 'No.02', 'No.03', 'No.04', 'No.05'],
    '數學' : [90, 99, 92, 87, 85],
    '英文' : [99, 87, 85, 67, 89],
    '物理' : [100, 94, 76, 72, 67]
})

scores2 = pd.DataFrame({
    '座號' : ['No.01', 'No.02', 'No.03', 'No.04', 'No.05'],
    '數學' : [88, 75, 89, 99, 85],
    '英文' : [77, 88, 98, 78, 99],
    '物理' : [84, 94, 89, 85, 78]
})

# 串接 DataFrame
scores = pd.concat([scores1, scores2])
scores.sort_values('座號', inplace = True)

# 加入新的一行
scores['段考'] = ['一段', '二段'] * 5

# 多重索引
scores.set_index(['座號', '段考'], inplace = True)

print(scores)

有關於 DataFrame 的串接、合併等操作,可以進一步參考〈Merge, join, concatenate and compare〉,有關於索引方面的操作,可以進一步參考〈MultiIndex / advanced indexing〉。