pandasのマルチインデックスのチートシート

pandasで`groupby()`や`pivot_table()`を使うと、Multi indexを持つ多層のテーブルが出てくることがよくあります。分かりやすいし便利機能かと思いきや、解除や結合ができないためにこの操作で時間を溶かすことがあまりにも多いので、よく使う方法をまとめておきます。

# 導入
今回はかの有名なタイタニック号のデータ([signateより取得](https://signate.jp/competitions/102/data))を使って作成した、マルチインデックスを持つピボットテーブルをサンプルとして説明していきます。

使用するテーブルとコードは以下をご覧ください。
```python
import pandas as pd
df = pd.read_csv("train.tsv", sep="\t") # 学習用データ読み込み

avg = pd.pivot_table(df, index='pclass', columns='sex', values = ['age', 'fare'])    # チケットクラス・性別ごとの年齢・料金の平均値 
```

| <br> sex <br>pclass | age<br><br>female |<br><br>male|fare<br><br>female|<br><br>male|
|---------:|--------------------:|------------------:|---------------------:|-------------------:|
|        1 |               33.52 |             38.08 |                98.23 |              79.28 |
|        2 |               28.07 |             30.29 |                22.67 |              20.93 |
|        3 |               21.06 |             26.87 |                16.86 |              13.45 |

# そもそもマルチインデックスの形って？
マルチインデックスの各部分の名称は通常のデータフレームの場合と異なります。`columns`という概念が`MultiIndex`に代わるようなイメージ（下図参照）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3601602/2ef47185-fb91-d385-7e00-c0fc649670b7.png)

実際に`columns`メソッドや`index`メソッドを使って結果を確認してみると以下のようになります。
```python
# columnsを確認
print(avg.columns)

# 出力
MultiIndex([( 'age', 'female'),
            ( 'age',   'male'),
            ('fare', 'female'),
            ('fare',   'male')],
           names=[None, 'sex'])
```
``` python
# indexを確認
print(avg.index)

# 出力
Index([1, 2, 3], dtype='int64', name='pclass')
```
# マルチインデックスのチートシート
## 1. マルチインデックスからカラムを取り出す
マルチインデックスのカラムの層は、levelで指定することができます。下の図の例から分かるように、levelは0から始まり、上段に進むとlevelが増えていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3601602/f8e5116b-3d9c-2532-cad6-f60f46209276.png)

以下のコードで各レベルのカラムを取得することができます。
```Python
# level0のカラム名を表示
print(avg.columns.droplevel(0))

# 出力
Index(['female', 'male', 'female', 'male'], dtype='object', name='sex')
```

## 2. マルチインデックスのインデックス編集
### 2-1. インデックスの解除
通常のデータフレームと同じく、以下コードでインデックスの解除ができます。**ここで解除されるのはインデックスのみで、マルチインデックス構造自体は解除されないので注意です。** 解除されたインデックスについていた名前は上段のカラム名となります(`drop=True`オプションをつけると削除されます)。

```Python
# インデックスの解除
avg.reset_index()
```
|  sex  |pclass <br><br>|<br>female |age<br>male |<br>female|fare<br>male|
|---:|-----------------:|--------------------:|------------------:|---------------------:|-------------------:|
|  0 |             1.00 |               33.52 |             38.08 |                98.23 |              79.28 |
|  1 |             2.00 |               28.07 |             30.29 |                22.67 |              20.93 |
|  2 |             3.00 |               21.06 |             26.87 |                16.86 |              13.45 |

### 2-2. インデックス列に名前をつける
こちらも通常のデータフレームと同じく、インデックスの列に名前をつけることができます。**ここでも命名されるのはインデックス列のみで、マルチインデックス構造自体には変更がありません。**
```Python
# 上記の方法で既存のインデックスを解除
reset_index_avg = avg.reset_index()
# 名無しのインデックスに'id'という名前をつける
reset_index_avg.index.name = 'id'
reset_index_avg
```
|  <br>sex<br>id  |pclass <br><br><br>|<br><br>female |<br>age<br>male |<br><br>female|<br>fare<br>male|
|---:|-----------------:|--------------------:|------------------:|---------------------:|-------------------:|
|  0 |             1.00 |               33.52 |             38.08 |                98.23 |              79.28 |
|  1 |             2.00 |               28.07 |             30.29 |                22.67 |              20.93 |
|  2 |             3.00 |               21.06 |             26.87 |                16.86 |              13.45 |

## 3. マルチインデックスのテーブルを通常のデータフレームに変換
マルチインデックスの多段構成のデータフレームだと、結合やカラムの抽出に難があるので、通常のデータフレームに直したいという時に便利な方法があります。

まずは抽出したいlevelのカラムを取得し、それをデータフレームのカラムとして指定します。その上で必要があればインデックスを解除します。
```Python
avg_level0 = avg.copy()
# level0のカラムだけ抽出
avg_level0.columns = avg.columns.droplevel(0)
# 設定されているインデックスを解除
avg_level0.reset_index(inplace=True)
avg_level0
```
| sex   |   pclass |   female |   male |   female |   male |
|---:|---------:|---------:|-------:|---------:|-------:|
|  0 |     1.00 |    33.52 |  38.08 |    98.23 |  79.28 |
|  1 |     2.00 |    28.07 |  30.29 |    22.67 |  20.93 |
|  2 |     3.00 |    21.06 |  26.87 |    16.86 |  13.45 |

このままだとlevel1のカラム情報が失われてしまい、female・maleというカラムが二つずつある状態になってしまうので、区別できるようにカラム名を付け直すと以下のようになります（※この操作は不要であれば実行しなくても大丈夫です！）。
```Python
avg_level0.set_axis(['pclass', 'age_female', 'age_male', 'fare_female', 'fare_male'], axis=1)
```
|    |   pclass |   age_female |   age_male |   fare_female |   fare_male |
|---:|---------:|-------------:|-----------:|--------------:|------------:|
|  0 |     1.00 |        33.52 |      38.08 |         98.23 |       79.28 |
|  1 |     2.00 |        28.07 |      30.29 |         22.67 |       20.93 |
|  2 |     3.00 |        21.06 |      26.87 |         16.86 |       13.45 |

以上の手順で、**マルチインデックス構成だった時の情報をほとんど失わずに通常のデータフレームを作る** ことができます。

## 4. マルチインデックスを持つテーブル同士の結合
マルチインデックスを持つテーブルは基本的に結合処理ができません。しかし、**マルチインデックスの層構造（=level）が同じテーブル同士であれば結合が可能** です。

例えば、以下の例のように`avg`テーブルとマルチインデックスのlevelが等しい`avg2`テーブルを用意した場合のことを考えます。
```Python
avg2 = pd.pivot_table(df, index='pclass', columns='embarked', values = ['age', 'fare'])
avg2
```
|<br>emberked<br>pclass |<br>C<br><br>|<br>Q <br><br>|age<br>S<br><br> |<br>C<br><br>|<br>Q<br><br>|fare<br>S<br><br> |
|---------:|---------------:|---------------:|---------------:|----------------:|----------------:|----------------:|
|        1 |          36.06 |         nan    |          35.08 |          105.91 |          nan    |           79.15 |
|        2 |          21.75 |          30.00 |          30.10 |           26.04 |           12.35 |           21.26 |
|        3 |          21.72 |          27.64 |          25.81 |           11.55 |            9.31 |           16.03 |

この`avg`, `avg2`テーブルたちをそれぞれのインデックスをキーとして結合を行うと以下のように二つのテーブルがそのまま横並びになったようなテーブルを作ることができます。
```Python
pd.merge(avg, avg2, left_index=True, right_index=True, how='left')
```
|<br><br><br><br><br><br>pclass|<br><br><br>female|age<br><br>male|<br><br><br>female|fare<br><br>male|<br><br><br><br>C|<br><br><br><br>Q|age<br><br><br>S|<br><br><br><br>C|<br><br><br><br>Q|fare<br><br><br>S|
|---------:|--------------------:|------------------:|---------------------:|-------------------:|---------------:|---------------:|---------------:|----------------:|----------------:|----------------:|
|        1 |               33.52 |             38.08 |                98.23 |              79.28 |          36.06 |         nan    |          35.08 |          105.91 |          nan    |           79.15 |
|        2 |               28.07 |             30.29 |                22.67 |              20.93 |          21.75 |          30.00 |          30.10 |           26.04 |           12.35 |           21.26 |
|        3 |               21.06 |             26.87 |                16.86 |              13.45 |          21.72 |          27.64 |          25.81 |           11.55 |            9.31 |           16.03 |

ここではインデックスを結合キーとしましたが、マルチインデックスのタプルをキーとして結合することも可能です。

# 最後に　
もうマルチインデックスは怖くない（はず）！
マルチインデックスに関する小技やよく使う処理などが他にもあればコメントいただけたらと思います。