DataFrameのマルチインデックスについて解説していきます。マルチインデックスは一見とっつきにくいですが、実は普段何気なくやるようなことで、これを使うことで気軽にカテゴリ化してデータを解析することができるようになります。
ここでは、以下のようにpandasをインポートしているものとして進めていきます。
import pandas as pd
サンプルデータ
ここではseabornのtipsデータセットのサブセット(一部改変)を用います。
以下のようにしてDataFrameに取り込みできます。
df = pd.read_excel(r'https://biotech-lab.org/wp-content/uploads/2020/03/dataframe-tips-1.xlsx')
マルチインデックスとは?
pandasのDataFrameには通常のインデックスではなくマルチインデックスを指定することができますが、いまいちわかりにくい概念でとっつきにくい人が多いのではないでしょうか。しかし、実際の例を見てみれば非常に理解しやすい概念であり、決して難しいものではありません。
今回用いるデータを再掲します。
dayとtimeとsexは、同じものをまとめてみると見やすくなりそうですね。
これが、day、time、sexの3つを項目名(インデックス)として指定した状態、つまり、マルチインデックスを指定した状態になります。dayが大項目、timeが中項目、sexが小項目というふうに、階層構造をもって行ラベルを考えることができるようになります。
これをDataFrameで再現すると次のようになります。set_indexメソッドで指定した列をインデックスとすることができます。
df = df.set_index(['day', 'time', 'sex'])
print(df)
※ df : サンプルデータ
total_bill tip smoker size
day time sex
Sun Dinner Female 16.99 1.01 No 2
Male 10.34 1.66 No 3
Sat Lunch Male 20.65 3.35 No 3
Dinner Male 17.92 4.08 No 2
Female 20.92 4.08 No 2
Fri Dinner Male 28.97 3.00 Yes 2
Male 22.49 3.50 No 2
Lunch Female 5.75 1.00 Yes 2
Thur Dinner Male 27.20 4.00 No 4
Lunch Male 22.76 3.00 No 2
Female 10.65 1.50 No 2
day / time / sexが他の項目とは違う扱いになっていて、インデックスとして指定されたことが分かります。
このようにマルチインデックスは大項目・中項目・小項目・…というふうに階層構造をもって項目分けして行ラベルを設定する方法です。下のエクセルの表のイメージで理解しやすいと思います。
マルチインデックスの構造
マルチインデックスをもつDataFrameがどのような構造になっているか見てみましょう。
まずは、行ラベル・列ラベルを表示します。
# 行ラベル
df = df.set_index(['day', 'time', 'sex'])
print(df.index)
※ df : サンプルデータ
MultiIndex([( 'Sun', 'Dinner', 'Female'),
( 'Sun', 'Dinner', 'Male'),
( 'Sat', 'Lunch', 'Male'),
( 'Sat', 'Dinner', 'Male'),
( 'Sat', 'Dinner', 'Female'),
( 'Fri', 'Dinner', 'Male'),
( 'Fri', 'Dinner', 'Male'),
( 'Fri', 'Lunch', 'Female'),
('Thur', 'Dinner', 'Male'),
('Thur', 'Lunch', 'Male'),
('Thur', 'Lunch', 'Female')],
names=['day', 'time', 'sex'])
# 列ラベル
df = df.set_index(['day', 'time', 'sex'])
print(df.columns)
※ df : サンプルデータ
Index(['total_bill', 'tip', 'smoker', 'size'], dtype='object')
確かに行ラベルにはマルチインデックスが指定されているのが分かりました。そして、マルチインデックスの実体は、Indexオブジェクトのリストの要素をタプルで与えただけのもののようです。
マルチインデックスを指定すると、行ラベルは次のようにLevelが設定され、階層的に扱うことができます。大項目から Level 0, 1, 2, … となります。

DataFrameの成分へのアクセス
DataFrameの成分へのアクセスはマルチインデックスが設定されている場合でも基本は変わりません。

loc属性で指定する
行ラベル・列ラベルで指定する方法です。
行ラベルがマルチインデックスとなっているので、行ラベルはタプルで指定します。それ以外は通常のDataFrameのloc属性と同じです。ただし、得られる結果のデータ型には違いがあります。
行ラベルと列ラベルで特定の要素を取り出す
「土曜・昼食・男性」のチップの支払い額を指定します。
df = df.set_index(['day', 'time', 'sex'])
print(df.loc[('Sat', 'Lunch', 'Male'), 'tip'])
※ df : サンプルデータ
day time sex Sat Lunch Male 3.35 Name: tip, dtype: float64
該当するデータは1つしかないので、上記のように3.35という結果が得られました。ただし、戻り値のデータ型はSeriesとなっています。
また、例えば「金曜・夕食・男性」のデータは2件あるので、この時は以下のようになります。
df = df.set_index(['day', 'time', 'sex'])
print(df.loc[('Fri', 'Dinner', 'Male'), 'tip'])
※ df : サンプルデータ
day time sex
Fri Dinner Male 3.0
Male 3.5
Name: tip, dtype: float64
戻り値のデータ型はSeriesです。
このように、行ラベルの設定の仕方ではloc属性でデータを一意に絞ることはできません。
行全体を取得する
行全体を取得するときは、次のようにします。これはloc[('Sat', 'Lunch', 'Male'), : ]と同じです。
df = df.set_index(['day', 'time', 'sex'])
print(df.loc[('Sat', 'Lunch', 'Male')])
※ df : サンプルデータ
total_bill tip smoker size day time sex Sat Lunch Male 20.65 3.35 No 3
ここで得られる結果のデータ型はDataFrameとなります。
マルチインデックスの一部で指定する
マルチインデックスが指定されている場合は、そのすべての項目で指定するのではなく、一部の項目だけで条件を指定して抽出することもできます。
例えば、「日曜・夕食」のみで絞り込んでみます。
df = df.set_index(['day', 'time', 'sex'])
print(df.loc[('Sun', 'Dinner')])
※ df : サンプルデータ
total_bill tip smoker size sex Female 16.99 1.01 No 2 Male 10.34 1.66 No 3
この時は、女性/男性は指定していませんので、行ラベルとして残された状態でDataFrameとして結果が得られます。
次に、「金曜」のみで絞り込んでみます。
df = df.set_index(['day', 'time', 'sex'])
print(df.loc['Fri'])
※ df : サンプルデータ
total_bill tip smoker size
time sex
Dinner Male 28.97 3.0 Yes 2
Male 22.49 3.5 No 2
Lunch Female 5.75 1.0 Yes 2
このように、簡易的に条件指定してデータの抽出ができるのがマルチインデックスの特徴となります。
iloc属性で指定する
iloc属性では行番号、列番号でデータを指定するので、行ラベルがマルチインデックスになっていても関係ありません。
次のようにマルチインデックスが設定されていない場合と同じように扱うことができます。
df = df.set_index(['day', 'time', 'sex'])
print(df.iloc[3, 0])
※ df : サンプルデータ
17.92
ただし、列番号は行ラベルに設定した列は除いてカウントされるので注意してください。
マルチインデックスをソートする
DataFrameにインデックスを指定すると、インデックスでソートすることが可能です。マルチインデックスの場合も同様で、sort_indexメソッドでソートできます。
例えば、次のようなDataFrameがあったとします。
print(df_sample)
total_bill tip smoker size
day time sex
Fri Lunch Female 5.75 1.00 Yes 2
Sun Dinner Male 10.34 1.66 No 3
Thur Lunch Female 10.65 1.50 No 2
Sun Dinner Female 16.99 1.01 No 2
Sat Dinner Male 17.92 4.08 No 2
Lunch Male 20.65 3.35 No 3
Dinner Female 20.92 4.08 No 2
Fri Dinner Male 22.49 3.50 No 2
Thur Lunch Male 22.76 3.00 No 2
Dinner Male 27.20 4.00 No 4
Fri Dinner Male 28.97 3.00 Yes 2
day / time / sex でマルチインデックスを設定していますが、順番はバラバラですね。
df_sample_sorted = df_sample.sort_index()
print(df_sample_sorted)
total_bill tip smoker size
day time sex
Fri Dinner Male 22.49 3.50 No 2
Male 28.97 3.00 Yes 2
Lunch Female 5.75 1.00 Yes 2
Sat Dinner Female 20.92 4.08 No 2
Male 17.92 4.08 No 2
Lunch Male 20.65 3.35 No 3
Sun Dinner Female 16.99 1.01 No 2
Male 10.34 1.66 No 3
Thur Dinner Male 27.20 4.00 No 4
Lunch Female 10.65 1.50 No 2
Male 22.76 3.00 No 2
このようにして並べ替えることができます。
マルチインデックスを解除する
行ラベルとして設定した列はreset_indexメソッドを用いて行ラベルから元のデータに戻すことができます。
# 一旦マルチインデックスを指定します
df = df.set_index(['day', 'time', 'sex'])
# マルチインデックスを解除します
df = df.reset_index()
print(df)
※ df : サンプルデータ
day time sex total_bill tip smoker size 0 Sun Dinner Female 16.99 1.01 No 2 1 Sun Dinner Male 10.34 1.66 No 3 2 Sat Lunch Male 20.65 3.35 No 3 3 Sat Dinner Male 17.92 4.08 No 2 4 Sat Dinner Female 20.92 4.08 No 2 5 Fri Dinner Male 28.97 3.00 Yes 2 6 Fri Dinner Male 22.49 3.50 No 2 7 Fri Lunch Female 5.75 1.00 Yes 2 8 Thur Dinner Male 27.20 4.00 No 4 9 Thur Lunch Male 22.76 3.00 No 2 10 Thur Lunch Female 10.65 1.50 No 2
コメント