Pythonのマルチインデックス(MultiIndex)

スポンサーリンク
スポンサーリンク

マルチインデックスの作成

基本

df.set_index(カラム名)

  • カラム名をリスト型で指定することにより、複数のインデックスを作成することができます。
  • 個別に2回指定した場合は、以前のインデックスは削除されます。
df = pd.DataFrame(
    [
        ["Tokyo","a",1,2],
        ["Tokyo","b",3,4],
        ["Osaka","a",5,6],
        ["Osaka","b",7,8],
        ["Kyoto","a",9,10],
    ],
    columns = ["pref", "aaa", "bbb", "ccc"]
)
df = df.set_index(['pref',"aaa"], drop = True)

print(df)
#            bbb  ccc
# pref  aaa          
# Tokyo a      1    2
#       b      3    4
# Osaka a      5    6
#       b      7    8
# Kyoto a      9   10

日時(datetime型)を用いたマルチインデックスの作成

インデックスの日時(datetime型)から年月日や時分秒、曜日などを取得してインデックスを追加します。

こちらもdf.set_index(値)を使用しますが、カラム名を指定するのではなく、インデックスの値を指定し、その後、インデックス名を設定します。

df = df.set_index([df.index, df.index.hour, df.index.minute])

  • インデックスの値を指定する際に、元のインデックスを指定しないと削除されます。
    上記の場合は、元のインデックスと時、分をマルチインデックスとして指定しています。

df.index.names = ['date', 'hour', 'minute']

  • リスト型のデータを用いて、一括でインデックス名を指定します。

DatetimeIndexから取得できる年月日や時分秒、曜日などのデータは以下のドキュメントを参照してください。

pandas.DatetimeIndex — pandas 2.0.2 documentation
df = pd.DataFrame(
    [
        ["2019-01-01 00:00:00+00:00",1,2],
        ["2019-01-01 00:30:00+00:00",3,4],
        ["2019-01-01 01:00:00+00:00",5,6],
        ["2019-01-01 01:30:00+00:00",7,8],
        ["2019-01-01 02:00:00+00:00",9,10],
    ],
    columns = ["date", "aaa", "ccc"]
)

df.index = pd.DatetimeIndex(pd.to_datetime(df['date']), name='date')
del df['date']

print(df)
#                            aaa  ccc
# date                               
# 2019-01-01 00:00:00+00:00    1    2
# 2019-01-01 00:30:00+00:00    3    4
# 2019-01-01 01:00:00+00:00    5    6
# 2019-01-01 01:30:00+00:00    7    8
# 2019-01-01 02:00:00+00:00    9   10

df = df.set_index([df.index, df.index.hour, df.index.minute]) 
df.index.names = ['date', 'hour', 'minute']

print(df)
#                                        aaa  ccc
# date                      hour minute          
# 2019-01-01 00:00:00+00:00 0    0         1    2
# 2019-01-01 00:30:00+00:00 0    30        3    4
# 2019-01-01 01:00:00+00:00 1    0         5    6
# 2019-01-01 01:30:00+00:00 1    30        7    8
# 2019-01-01 02:00:00+00:00 2    0         9   10

マルチインデックスの解除(削除)

df.reset_index(level='インデックスのカラム名', drop=True)

  • 通常のインデックスの削除と同じですが、levelでインデックスのカラム名を指定します。
  • df.reset_index()のみの場合は、全てインデックスが削除されます。
  • drop=Trueは削除後に、単に削除する(True)か、カラムとして残すか(False)です。
    デフォルトはFalseです。
df = df.reset_index(level='minute', drop=True)
print(df)

#                                 aaa  ccc
# date                      hour          
# 2019-01-01 00:00:00+00:00 0       1    2
# 2019-01-01 00:30:00+00:00 0       3    4
# 2019-01-01 01:00:00+00:00 1       5    6
# 2019-01-01 01:30:00+00:00 1       7    8
# 2019-01-01 02:00:00+00:00 2       9   10

集計

基本

df.max()のような形で通常通り集計することができます。

print(df.max())
# aaa     9
# ccc    10
# dtype: int64

インデックスのレベルを指定して集計

単一のレベルを指定して集計

df.max(level='hour')

上記のようにインデックス名をlevelで指定するだけです。

print(df.max())
# aaa     9
# ccc    10
# dtype: int64

print(df.max(level='hour'))
#       aaa  ccc
# hour          
# 0       3    4
# 1       7    8
# 2       9   10

print(df.max(level='minute'))
#         aaa  ccc
# minute          
# 0         9   10
# 30        7    8

複数のレベルを指定して集計

df.max(level=[‘hour’,’minute’])

上記のようにインデックス名をリスト型で複数個を指定するだけです。

print(df.max(level=['hour','minute']))
#              aaa  ccc
# hour minute          
# 0    0         1    2
#      30        3    4
# 1    0         5    6
#      30        7    8
# 2    0         9   10

マルチインデックスの順番(レベル)を変更

df.swaplevel(カラム名1,カラム名2)

  • 順番を変更するカラム名を指定します。
df = df.swaplevel('hour', 'minute')
print(df)

#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:00:00+00:00 0      0       1    2
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 01:30:00+00:00 30     1       7    8
# 2019-01-01 02:00:00+00:00 0      2       9   10

データフレームをソート(順番を変更)

df.sort_index(level='minute', ascending=False)

  • 通常のデータフレームのソートと同じく、df.sort_indexを使用しますが、をlevel指定します。
  • ascendingは通常のdf.sort_indexと同じく表示順を指定します。
    (True=昇順(デフォルト)、Flase=降順)

以下の後半は、通常のソートです。

df = df.sort_index(level='minute', ascending=False)
print(df)
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 01:30:00+00:00 30     1       7    8
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 02:00:00+00:00 0      2       9   10
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 00:00:00+00:00 0      0       1    2

df = df.sort_index()
print(df)
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:00:00+00:00 0      0       1    2
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 01:30:00+00:00 30     1       7    8
# 2019-01-01 02:00:00+00:00 0      2       9   10

インデックスでデータを選択

マルチインデックスは、少し癖があり、データベースのように簡単に範囲指定などができません。

前提(データ)

以下のデータがある前提です。

  • df:インデックスが文字列
  • df_date:インデックスが日時(datetime)
df = pd.DataFrame(
    [
        ["Tokyo","a",1,2],
        ["Tokyo","b",3,4],
        ["Osaka","a",5,6],
        ["Osaka","b",7,8],
        ["Kyoto","a",9,10],
    ],
    columns = ["pref", "aaa", "bbb", "ccc"]
)
df = df.set_index(['pref',"aaa"], drop = True)

print(df)
#            bbb  ccc
# pref  aaa          
# Tokyo a      1    2
#       b      3    4
# Osaka a      5    6
#       b      7    8
# Kyoto a      9   10

df_date = pd.DataFrame(
    [
        ["2019-01-01 00:00:00+00:00",1,2],
        ["2019-01-01 00:30:00+00:00",3,4],
        ["2019-01-01 01:00:00+00:00",5,6],
        ["2019-01-01 01:30:00+00:00",7,8],
        ["2019-01-01 02:00:00+00:00",9,10],
    ],
    columns = ["date", "aaa", "ccc"]
)
df_date.index = pd.DatetimeIndex(pd.to_datetime(df_date['date']), name='date')
del df_date['date']

df_date = df_date.set_index([df_date.index, df_date.index.hour, df_date.index.minute]) 
df_date.index.names = ['date', 'hour', 'minute']
df_date = df_date.swaplevel('hour', 'minute')
print(df_date)
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:00:00+00:00 0      0       1    2
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 01:30:00+00:00 30     1       7    8
# 2019-01-01 02:00:00+00:00 0      2       9   10

インデックスを単一で指定

df.loc[インデックスの値]

locを使用します。

以下のように、日時の場合、単一での指定でも、LIKEで指定することが可能です。

print(df.loc['Tokyo'])
#      bbb  ccc
# aaa          
# a      1    2
# b      3    4

print(df_date.loc['2019-01-01 01'])
#                                        aaa  ccc
# date                      hour minute          
# 2019-01-01 01:00:00+00:00 1    0         5    6
# 2019-01-01 01:30:00+00:00 1    30        7    8

インデックスを単一で指定し、単一のカラムの値のみ取得

df.loc[インデックスの値, : カラム名]

  • コロンで区切ってカラム名を指定します。
  • インデックスの値のあとに、カンマ(,) がないとエラーになります。
    df.loc[‘Tokyo’, : “bbb”]
print(df.loc['Tokyo', : "bbb"])
#      bbb
# aaa     
# a      1
# b      3

print(df_date.loc['2019-01-01 01',:"aaa"])
#                                        aaa
# date                      minute hour     
# 2019-01-01 01:00:00+00:00 0      1       5
# 2019-01-01 01:30:00+00:00 30     1       7

同一インデックスを複数で指定

df.loc[['Tokyo', 'Kyoto']]

  • 同じようにlocを使用しますが、リスト型でインデックスの値を指定します。
  • 日時については先程のようにLIKEで指定するとエラーになります。
  • なお、この複数指定は範囲指定ではありません
print(df.loc[['Tokyo', 'Kyoto']])
#            bbb  ccc
# pref  aaa          
# Tokyo a      1    2
#       b      3    4
# Kyoto a      9   10

print(df_date.loc[['2019-01-01 01:00:00+00:00', '2019-01-01 02:00:00+00:00']])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 02:00:00+00:00 0      2       9   10

異なるインデックスを複数で指定

df.loc[(第1レベルのインデックスの値, 第2レベルのインデックスの値), ]

  • タプル型(カッコで囲いカンマ区切り)で第2レベルの値を指定します。
  • 注意点としてタプル型の終わりにカンマがないとエラーになります。
    df.loc[([‘Tokyo’, ‘Kyoto’], ‘a’), ]
print(df.loc[(['Tokyo', 'Kyoto'], 'a'), ])
#            bbb  ccc
# pref  aaa          
# Tokyo a      1    2
# Kyoto a      9   10

print(df_date.loc[('2019-01-01 01', 30), ])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 01:30:00+00:00 30     1       7    8

第2階層のインデックスを複数で指定

  • 第1階層と同じように、第2階層のインデックス値をリスト型で指定します。
  • 日時データの場合は、コロン(:)がないとエラーになります。
    df_date.loc[‘2019-01-01 01’,[0,10], :]
  • なお、この複数指定は範囲指定ではありません
print(df.loc[('Tokyo', ['a', 'b']), ])
#            bbb  ccc
# pref  aaa          
# Tokyo a      1    2
#       b      3    4

print(df_date.loc['2019-01-01 01',[0,10], :])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 01:00:00+00:00 0      1       5    6

print(df_date.loc['2019-01-01 01',[0,30], :])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 01:30:00+00:00 30     1       7    8

第2階層のインデックスのみで指定

df.loc[(slice(None), 第2レベルのインデックスの値), ]

  • 第1レベルはslice(None)としてエスケープさせ、第2レベルのみ指定します。
print(df.loc[(slice(None), 'a'), :])
#            bbb  ccc
# pref  aaa          
# Tokyo a      1    2
# Osaka a      5    6
# Kyoto a      9   10

print(df_date.loc[(slice(None), 30), ])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:30:00+00:00 30     1       7    8

範囲指定・期間指定(スライス)

範囲や期間指定するスライスには以下の2種類があります。

  • df.loc + pd.IndexSlice
    各階層を指定する際に少し面倒ですが、実行結果はインデックス値も残ります。
  • df.xs + slice
    各階層を指定するのが簡単ですが、実行結果に指定したインデックスは出力されません。

前提(データ)

データは上記の「インデックスでデータを選択」で使用したものですが、以下のようにdfについてはソートしています。

ソートしない範囲選択でエラーになります。

df = df.sort_index()
print(df)
#            bbb  ccc
# pref  aaa          
# Kyoto a      9   10
# Osaka a      5    6
#       b      7    8
# Tokyo a      1    2
#       b      3    4

df.loc + pd.IndexSlice

第1階層の範囲指定

df.loc[pd.IndexSlice['Kyoto':'Osaka', ], ]

  • pd.IndexSliceを用いて通常のようにスライスします。
  • なお、以下のように2つのカンマがないとエラーになります。
    df.loc[pd.IndexSlice[‘Kyoto’:’Osaka’, ], ]
print(df.loc[pd.IndexSlice['Kyoto':'Osaka', ], ])
#            bbb  ccc
# pref  aaa          
# Kyoto a      9   10
# Osaka a      5    6
#       b      7    8

print(df_date.loc[pd.IndexSlice['2019-01-01 00':'2019-01-01 01', ], ])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:00:00+00:00 0      0       1    2
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 01:30:00+00:00 30     1       7    8

第1階層と第2階層で範囲指定

  • 第1階層のみと同じように、第2階層にpd.IndexSliceで指定しますが、タプル型(カッコで囲う)にします。
  • また終わりのカンマを忘れないように注意してください。
print(df.loc[(pd.IndexSlice['Kyoto':'Osaka', ], pd.IndexSlice['b':, ]), ])
#            bbb  ccc
# pref  aaa          
# Osaka b      7    8

print(df_date.loc[(pd.IndexSlice['2019-01-01 00':'2019-01-01 01', ], pd.IndexSlice[1:, ]), ])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:30:00+00:00 30     1       7    8

第2階層または第3階層のみで範囲指定

第1階層はslice(None)でエスケープさせます。

print(df.loc[(slice(None), pd.IndexSlice['b':, ]), ])
#            bbb  ccc
# pref  aaa          
# Osaka b      7    8
# Tokyo b      3    4

print(df_date.loc[(slice(None), pd.IndexSlice[1:, ]), ])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 00:30:00+00:00 30     0       3    4
# 2019-01-01 01:30:00+00:00 30     1       7    8

print(df_date.loc[(slice(None), slice(None), pd.IndexSlice[1:, ]), ])
#                                        aaa  ccc
# date                      minute hour          
# 2019-01-01 01:00:00+00:00 0      1       5    6
# 2019-01-01 01:30:00+00:00 30     1       7    8
# 2019-01-01 02:00:00+00:00 0      2       9   10

df.xs + slice

1つのレベルの範囲指定

print(df.xs(slice('Kyoto', 'Osaka'), level='pref'))
#      bbb  ccc
# aaa          
# a      9   10
# a      5    6
# b      7    8

print(df.xs(slice('a', 'b'), level='aaa'))
#        bbb  ccc
# pref           
# Kyoto    9   10
# Osaka    5    6
# Osaka    7    8
# Tokyo    1    2
# Tokyo    3    4

print(df_date.xs(slice('2019-01-01 00', '2019-01-01 01'), level='date'))
#              aaa  ccc
# minute hour          
# 0      0       1    2
# 30     0       3    4
# 0      1       5    6
# 30     1       7    8

print(df_date.xs(slice(1, ), level='hour'))
#                                   aaa  ccc
# date                      minute          
# 2019-01-01 00:00:00+00:00 0         1    2
# 2019-01-01 00:30:00+00:00 30        3    4
# 2019-01-01 01:00:00+00:00 0         5    6
# 2019-01-01 01:30:00+00:00 30        7    8

2つのレベルの範囲指定

範囲、レベルともにタプル型で指定します。

しかし、以下のように、文字列の場合、第2レベルの指定の挙動がおかしいです。
この場合は、df.loc + pd.IndexSliceを使った方がいいと思います。

print(df.xs((slice('Kyoto', 'Osaka'), slice('b', ),), level=('pref', 'aaa')))
#            bbb  ccc
# pref  aaa          
# Kyoto a      9   10
# Osaka a      5    6
#       b      7    8

print(df_date.xs((slice('2019-01-01 00', '2019-01-01 01'), slice(1, )), level=('date', 'hour')))
#         aaa  ccc
# minute          
# 0         1    2
# 30        3    4
# 0         5    6
# 30        7    8

一つの範囲指定と一つのインデックス指定

単に、インデックスの範囲とインデックスの指定のミックスです。

print(df.xs((slice('Kyoto', 'Osaka'), 'a'), level=('pref', 'aaa')))
#            bbb  ccc
# pref  aaa          
# Kyoto a      9   10
# Osaka a      5    6

print(df_date.xs((slice('2019-01-01 00', '2019-01-01 01'), 1), level=('date', 'hour')))
#         aaa  ccc
# minute          
# 0         5    6
# 30        7    8

 

コメント

タイトルとURLをコピーしました