pandasには階層型インデックスという概念がある。

あるデータセットに対して、複数のインデックスを階層として持たせることができるのだ。

階層型インデックスは、ピボットテーブルの作成やデータの変形など、グループによるデータ操作をする場合に重要な機能となる。

階層型インデックスを用いたデータセット

簡単な例を見てみよう。

import pandas as pd
import numpy as np

data = pd.Series(np.random.randn(9),
                index=[
                    ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'],
                    [1, 2, 3, 1, 2, 3,1, 2, 3]
                ])

# a  1   -0.596496
#    2    0.526021
#    3   -2.395249
# b  1    0.382245
#    2   -1.460518
#    3    1.013924
# c  1   -0.154135
#    2   -0.994512
#    3   -0.694720
# dtype: float64

出力結果を見ると、1列目に空値が複数見られるが、これは直前の行のラベルをインデックスとして使うことを意味する。

別途indexプロパティを使えば詳しいインデックス情報を確認することができる。

MultiIndex(levels=[['a', 'b', 'c'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 1, 2, 2, 2], [0, 1, 2, 0, 1, 2, 0, 1, 2]])

部分インデックス参照

階層型インデックスのメリットとして、部分集合を簡単に抽出できることが挙げられる。

様々な抽出例を紹介する。

import pandas as pd
import numpy as np

data = pd.Series(np.random.randn(9),
                index=[
                    ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'],
                    [1, 2, 3, 1, 2, 3,1, 2, 3]
                ])

data['b']
# 1   -0.642686
# 2   -0.665147
# 3   -0.212816
# dtype: float64

data['a':'b']
# a  1   -1.001568
#    2   -1.201770
#    3   -0.500852
# b  1   -0.642686
#    2   -0.665147
#    3   -0.212816
# dtype: float64

data.iloc[2:3]
# a  3   -0.500852
# dtype: float64

data.loc[['a', 'c']]
# a  1   -1.001568
#    2   -1.201770
#    3   -0.500852
# c  1    0.855579
#    2    0.103584
#    3   -0.300320
# dtype: float64

内側のインデックスを指定して抽出することも可能だ。

data.loc[:, 2]
# a    1.209921
# b    0.229654
# c    1.858592
# dtype: float64

階層型データをデータフレームに変換する

unstack関数を使うことで、先程の例で使用したデータをデータフレームに変換することができる。

df = data.unstack()
#           1         2         3
# a  0.713211 -1.829338  0.001228
# b -1.479334  1.631776  1.436027
# c -0.310134 -0.661608  1.958301

また、逆にデータフレームを階層型インデックスを持つオブジェクトに戻す場合はstack関数を使う。

df.stack()
# a  1    1.443906
#    2    0.090950
#    3    1.545684
# b  1    1.202481
#    2   -1.680269
#    3    0.379805
# c  1    1.250931
#    2    1.960360
#    3    0.390611
# dtype: float64

データフレームのインデックスに階層を持たせる

ここまでは、シリーズをベースに例を紹介してきたが、データフレームのインデックスにも階層を持たせることができる。

import pandas as pd
import numpy as np

df = pd.DataFrame(np.arange(12).reshape(4, 3),
                 index=[
                     ['a', 'a', 'b', 'b'],
                     [1, 2, 1, 2]
                 ],
                 columns=[
                     ['Tokyo', 'Osaka', 'Osaka'],
                     ['Shinagawa', 'Umeda', 'Namba']
                 ])

#         Tokyo Osaka      
#     Shinagawa Umeda Namba
# a 1         0     1     2
#   2         3     4     5
# b 1         6     7     8
#   2         9    10    11

参照方法についてはシリーズ同様、様々な方法がある。

df['Tokyo']
#      Shinagawa
# a 1          0
#   2          3
# b 1          6
#   2          9

df['Osaka', 'Umeda']
# a  1     1
#    2     4
# b  1     7
#    2    10

df.loc['a',1]['Tokyo']
#      Shinagawa
# a 1          0
#   2          3

df.loc['a']['Tokyo']
#    Shinagawa
# 1          0
# 2          3