DataFrameにマルチインデックスを設定する【Python】

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

関連記事・スポンサーリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です