【Pandas】マルチインデックスから複数列を指定する
PandasでDataFrameの列がマルチインデックスになっている場合に複数列を選択する方法。
個別の列指定であれば、タプルで簡単にできる。
しかし、スライスを使う場合、MultiIndexの部分をソートする必要がある。
列の並び替えが発生するため、あまり実用的ではない。
データ読み込み
以下のように列がマルチインデックスになっているデータを使う。
1excel_file1 = pd.ExcelFile("./data/マルチインデックス_野菜1.xlsx")
2
3df1 = pd.read_excel(excel_file1, header=[0, 1])
4print(df1)
5# 分類 価格
6# 大分類 中分類 小分類 名称 販売価格 仕入価格 販売数量 売上高
7# 0 食べ物 野菜 葉物 ほうれん草 198 60 1 198
8# 1 食べ物 野菜 葉物 キャベツ 298 120 2 596
9# 2 食べ物 野菜 根菜 ごぼう 128 80 3 384
10# 3 食べ物 野菜 根菜 レンコン 298 200 4 1192
11# 4 食べ物 果物 果物 リンゴ 498 250 1 498
12# 5 食べ物 果物 果物 イチゴ 598 400 2 1196
13# 6 食べ物 果物 果物 ぶどう 1980 1000 3 5940
マルチインデックスの列は以下のようになっている。
1df1.columns
2# MultiIndex([('分類', '大分類'),
3# ('分類', '中分類'),
4# ('分類', '小分類'),
5# ('分類', '名称'),
6# ('価格', '販売価格'),
7# ('価格', '仕入価格'),
8# ('価格', '販売数量'),
9# ('価格', '売上高')],
10# )
スライスを使わない場合、列をタプルで指定するだけで取得可能。
スライスを使わない列指定
スライスを使わないので、個別に列を指定していくことになる。
マルチインデックスのレベル毎にタプルで列を指定していく。
1列指定
列部分をタプルで指定する。
1df1.loc[:, ("分類", "中分類")]
2# 0 野菜
3# 1 野菜
4# 2 野菜
5# 3 野菜
6# 4 果物
7# 5 果物
8# 6 果物
9# Name: (分類, 中分類), dtype: object
1列のみの場合はSeriesになる。
Seriesのnameは指定した通りにタプルになる。
複数列指定
複数指定したいレベルの列はタプルにする。
1df1.loc[:, ("分類", ("中分類", "名称"))]
2
3# 分類
4# 中分類 名称
5# 0 野菜 ほうれん草
6# 1 野菜 キャベツ
7# 2 野菜 ごぼう
8# 3 野菜 レンコン
9# 4 果物 リンゴ
10# 5 果物 イチゴ
11# 6 果物 ぶどう
2列以上指定するとDataFrameになる。
スライスを使う場合
列の並び替えが必要なため、スライスを使う前に列がどのように並べ替えられるのかの確認が必須になる。
ソートされていないとUnsortedIndexError
1df1.loc[:, (slice("分類"), slice("中分類", "名称"))]
2# UnsortedIndexError: 'MultiIndex slicing requires the index to be lexsorted: slicing on levels [0, 1], lexsort depth 0'
UnsotedIndexErrorが発生する。
列をソートする必要があるため、sort_valuesではなくsortlevelでマルチインデックス自体をソートする必要がある。
列を並び替える
sort_indexで並び替える方法と、sortlevelで並び替える方法がある。
sort_indexはDataFrameが返ってくるため、コードが短くなって読みやすい。
sort_indexで列を並び替える
1df1.sort_index(axis=1, level=0)
2
3# 価格 分類
4# 仕入価格 売上高 販売価格 販売数量 中分類 名称 大分類 小分類
5# 0 60 198 198 1 野菜 ほうれん草 食べ物 葉物
6# 1 120 596 298 2 野菜 キャベツ 食べ物 葉物
7# 2 80 384 128 3 野菜 ごぼう 食べ物 根菜
8# 3 200 1192 298 4 野菜 レンコン 食べ物 根菜
9# 4 250 498 498 1 果物 リンゴ 食べ物 果物
10# 5 400 1196 598 2 果物 イチゴ 食べ物 果物
11# 6 1000 5940 1980 3 果物 ぶどう 食べ物 果物
列が昇順で並び替えられる。
sortlevelで列を並び替える。
sortlevelはMultiIndexのメソッドで、並び替えられたMultiIndexが返ってくるため、列の並び替えられたDataFrameとして使うために、一手間必要となる。
MultiIndexに対してsortlevelを行うと以下のような内容が返ってくる。
1df1.columns.sortlevel(level=0)
2# (MultiIndex([('価格', '仕入価格'),
3# ('価格', '売上高'),
4# ('価格', '販売価格'),
5# ('価格', '販売数量'),
6# ('分類', '中分類'),
7# ('分類', '名称'),
8# ('分類', '大分類'),
9# ('分類', '小分類')],
10# ),
11# array([5, 7, 4, 6, 1, 3, 0, 2], dtype=int64))
リストが返ってくる。
0番目は並べ替えられたMultiIndex自体が、1番目は並び替えられた列の番号が返ってくる。
この操作では列を並び変えることはできないため、この並び替えられた列を元に列を並び替える必要がある。
1df1[df1.columns.sortlevel(level=0)[0]]
2
3# 価格 分類
4# 仕入価格 売上高 販売価格 販売数量 中分類 名称 大分類 小分類
5# 0 60 198 198 1 野菜 ほうれん草 食べ物 葉物
6# 1 120 596 298 2 野菜 キャベツ 食べ物 葉物
7# 2 80 384 128 3 野菜 ごぼう 食べ物 根菜
8# 3 200 1192 298 4 野菜 レンコン 食べ物 根菜
9# 4 250 498 498 1 果物 リンゴ 食べ物 果物
10# 5 400 1196 598 2 果物 イチゴ 食べ物 果物
11# 6 1000 5940 1980 3 果物 ぶどう 食べ物 果物
日本語的には整理されていた列の順序がバラバラになる・・・。
この並び替えられたDataFrameに対して、スライスを使用していくことになる。
pd.IndexSliceを使う方法
分類の中分類から大分類までを取得する場合の例。
1# sort_indexで列並び替えの場合
2df1.sort_index(axis=1, level=0).loc[:, pd.IndexSlice["分類", "中分類": "大分類"]]
3# sortlevelで列並び替えの場合
4df1[df1.columns.sortlevel(level=0)[0]].loc[:, pd.IndexSlice["分類", "中分類": "大分類"]]
5# 分類
6# 中分類 名称 大分類
7# 0 野菜 ほうれん草 食べ物
8# 1 野菜 キャベツ 食べ物
9# 2 野菜 ごぼう 食べ物
10# 3 野菜 レンコン 食べ物
11# 4 果物 リンゴ 食べ物
12# 5 果物 イチゴ 食べ物
13# 6 果物 ぶどう 食べ物
sliceを使う方法
1# sort_indexで列並び替えの場合
2df1.sort_index(axis=1, level=0).loc[:, ("分類", slice("中分類", "大分類"))]
3# sortlevelで列並び替えの場合
4df1[df1.columns.sortlevel(level=0)[0]].loc[:, ("分類", slice("中分類", "大分類"))]
5# 分類
6# 中分類 名称 大分類
7# 0 野菜 ほうれん草 食べ物
8# 1 野菜 キャベツ 食べ物
9# 2 野菜 ごぼう 食べ物
10# 3 野菜 レンコン 食べ物
11# 4 果物 リンゴ 食べ物
12# 5 果物 イチゴ 食べ物
13# 6 果物 ぶどう 食べ物
slice()の中身はカンマで区切ることに注意。
これぐらいのコードであれば、pd.IndexSliceでもsliceでもどちらでも見やすさはそこまで変わらない。
それよりも、列がソートされて入れ替わる方が大きな問題となる。(大体、列がマルチインデックスになる場合、50音順ではなく、意味をもった順序になっていることが多いため)
列を並び替えずにスライスで取得したい場合は、マルチインデックスを解除するしかない。
まとめ
1# 元データ
2# 分類 価格
3# 大分類 中分類 小分類 名称 販売価格 仕入価格 販売数量 売上高
4# 0 食べ物 野菜 葉物 ほうれん草 198 60 1 198
5# 1 食べ物 野菜 葉物 キャベツ 298 120 2 596
6# 2 食べ物 野菜 根菜 ごぼう 128 80 3 384
7# 3 食べ物 野菜 根菜 レンコン 298 200 4 1192
8# 4 食べ物 果物 果物 リンゴ 498 250 1 498
9# 5 食べ物 果物 果物 イチゴ 598 400 2 1196
10# 6 食べ物 果物 果物 ぶどう 1980 1000 3 5940
11
12# 列指定で取得(スライスを使わない場合)
13df1.loc[:, ("分類", ("中分類", "名称"))]
14
15# 列の並び替え
16# sort_indexを使う場合
17df1,sort_index(axis=1, level=0)
18# sortlevelを使う場合
19df1[df1.columns.sortlevel(level=0)[0]]
20# 列が並び替えられたデータ
21# 価格 分類
22# 仕入価格 売上高 販売価格 販売数量 中分類 名称 大分類 小分類
23# 0 60 198 198 1 野菜 ほうれん草 食べ物 葉物
24# 1 120 596 298 2 野菜 キャベツ 食べ物 葉物
25# 2 80 384 128 3 野菜 ごぼう 食べ物 根菜
26# 3 200 1192 298 4 野菜 レンコン 食べ物 根菜
27# 4 250 498 498 1 果物 リンゴ 食べ物 果物
28# 5 400 1196 598 2 果物 イチゴ 食べ物 果物
29# 6 1000 5940 1980 3 果物 ぶどう 食べ物 果物
30
31# スライスを使った複数列の取得
32# pd.IndexSliceを使う場合
33df1,sort_index(axis=1, level=0).loc[:, pd.IndexSlice["分類", "中分類": "大分類"]]
34# または
35df1[df1.columns.sortlevel(level=0)[0]].loc[:, pd.IndexSlice["分類", "中分類": "大分類"]]
36
37# sliceを使う場合
38df1.sort_index(axis=1, level=0).loc[:, ("分類", slice("中分類", "大分類"))]
39# または
40df1[df1.columns.sortlevel(level=0)[0]].loc[:, ("分類", slice("中分類", "大分類"))]