pandas
では、インデックス列を階層構造にしてMultiindex
として扱うことができます。
Multiindex
のDataFrame
, Series
はデータの構造化には便利なのですが…。
使い慣れたシングルインデックスと異なり、データ抽出で次のような問題に直面することも…
Multiindex
からデータ抽出する方法がわからない!.loc[]
で指定する時、Multiindex
の場合はどう指定するの?- 内側のレベルだけ指定して抽出する方法は?
この記事では、これらの問題を解決するためにMultiindex
のデータの抽出方法ついてわかりやすく図解付きで解説していきます。
Multiindexからのデータ抽出まとめ
Multiindex
のDataFrame
からのデータ抽出方法について、次の3つの方法を紹介します。
df.loc[]
によるデータ抽出idx[]
によるデータ抽出.xs()
メソッドによるデータ抽出
これらの3つの方法には、主に次のような特徴があります。
抽出方法 | メリット/デメリット |
---|---|
df.loc[] |
●(メリット)シングルインデックスと方法が似ている ●(デメリット)内側のレベルのみ指定する際に工夫が必要 |
idx[] |
●(メリット)ラベル指定、複数ラベル指定、スライスの手法が一貫 ●(デメリット)事前に pd.IndexSlice をインポートする必要あり |
.xs() |
●(メリット)抽出レベルによらず手法が一貫している ●(デメリット)スライスなどできないことがある |
以下では、次のサンプルデータを使って、それぞれの手法について詳しく解説していきます。
import pandas as pd
import numpy as np
# インデックスがMultiindexの例
mult_index = pd.MultiIndex.from_product([["Apple", "Banana", "Carrot"],
["ShopA", "ShopB", "ShopC"]],
names=["Item", "Shop"])
df = pd.DataFrame({"2020":[1,2,3,4,5,6,7,8,9], "2021":[10,20,30,40,50,60,70,80,90]},
index=mult_index)
# 2020 2021
# Item Shop
# Apple ShopA 1 10
# ShopB 2 20
# Banana ShopA 3 30
# ShopB 4 40
# Carrot ShopA 5 50
# ShopB 6 60
Multiindex
の基本的な構成、生成方法について、あまり慣れていな方は、事前に次の記事を読むとよく理解できると思います。
df.loc[]で抽出
Multiindex
でも通常のdf
同様にdf.loc[]
で特定の行・列を抽出できます。
以下では、次の順番でMultiindex
特有の指定方法について解説していきます。
- 特定の行・列を指定
- 複数の行・列を指定
- スライス(行・列の範囲指定)
- 内側のレベルで抽出
特定の行・列を指定
特定の行・列を指定する場合は、シングルインデックスの場合と同様に、df.loc[インデックスの指定, カラムの指定]
の順で指定します。
インデックスおよびカラムを単独または両方指定した場合のパターンを下表にまとめています。
指定方法 | .locでの書き方 |
---|---|
インデックスのみ指定 | df.loc[インデックス指定,:] ordf.loc[インデックス指定] |
カラムのみ指定 | df.loc[:,カラム指定] ordf[カラム指定] |
両方指定 | df.loc[インデックス指定,カラム指定] |
- 【参考】通常のシングルインデックスの指定方法は次の記事を参考にしてください。
Multiindex
では外側から順番に必要な箇所までラベルを指定します。
"レベル0のラベル名"
("レベル0のラベル名", "レベル1のラベル名", …)
("レベル0のラベル名", "レベル1のラベル名", "レベル2のラベル名" …)
指定レベルと抽出内容の対応を図解すると次のようになります。
抽出結果は、行・列のラベル名の個数に応じて、DataFrame
, Series
, スカラー
になります。
サンプルコードで、挙動を確認してみましょう。
# レベル0のみ指定
df.loc["Apple"]
# 2020 2021
# Shop
# ShopA 1 10
# ShopB 2 20
# ShopC 3 30
# インデックス(レベル0,1)の指定
df.loc[("Banana","ShopB"),:]
# 2020 5
# 2021 50
# Name: (Banana, ShopB), dtype: int64
# インデックス(レベル0)とカラムの指定
df.loc[("Carrot", "ShopC"),"2020"]
# 9
ただし、注意点として、df.loc[]
では外側から順にラベル名を指定するため、内側のレベルだけを指定することはできません。
df.loc[(:,"ShopB"),:]
# SyntaxError: invalid syntax
これでは(タプル)
に:
を渡すことになってしまうのでエラーです。
内側のレベルだけを指定する方法は、slice(None)
, pd.IndexSlice
, ix()
, query()
を使う方法があります(後述)。
複数の行・列を指定
複数の行・列を指定する場合は、インデックス指定
, カラム指定
で[複数のラベルのリスト]
を指定します。
["レベル0のラベル名①","レベル0のラベル②",…]
[(レベル順ラベル名のタプル①),(レベル順ラベル名のタプル②),…]
図にすると次のようなイメージになります。
飛び飛びのラベルを指定して、データを抽出することができます。
サンプルコードで挙動を確認してみましょう。
df.loc[["Apple","Carrot"],:]
# 2020 2021
# Item Shop
# Apple ShopA 1 10
# ShopB 2 20
# ShopC 3 30
# Carrot ShopA 7 70
# ShopB 8 80
# ShopC 9 90
df.loc[[("Apple","ShopA"),("Carrot","ShopB")],:]
# 2020 2021
# Item Shop
# Apple ShopA 1 10
# Carrot ShopB 8 80
注意点として、[リスト]
内のタプルの要素数は等しくないといけません。
1つ目はレベル0まで、2つ目はレベル1までといった設定は不可のようです。
df.loc[[("Apple","ShopA"),("Carrot",)],:]
# IndexError: tuple index out of range
スライス(行・列の範囲指定)
Multiindex
でも、シングルインデックスと同様にスライスすることができます。
インデックス指定・カラム指定で、始点と終点を:
で繋げます。
Multiindex
を指定する場合は、外側のレベルから順にラベル名を指定した(タプル)
にする必要があります
"始点のレベル0のラベル名":"終点のレベル0のラベル名"
(始点のレベル順ラベル名タプル):(終点のレベル順ラベル名タプル)
下図をみると指定の方法がよくわかると思います。
始点が一番最初の行、終点が一番最後の行は、それぞれ省略することができます。
:"終点のラベル名"
"始点のラベル名":
これはシングルインデックスの操作と同様ですね。
サンプルコードで挙動を確認してみましょう。
# レベル0でスライス
df.loc["Apple":"Banana",:] # df.loc[:"Banana",:]
# 2020 2021
# Item Shop
# Apple ShopA 1 10
# ShopB 2 20
# ShopC 3 30
# Banana ShopA 4 40
# ShopB 5 50
# ShopC 6 60
# レベル順タプルでスライス
print(df.loc[('Banana',"ShopB"):('Carrot',"ShopC"),:])
# 2020 2021
# Item Shop
# Banana ShopB 5 50
# ShopC 6 60
# Carrot ShopA 7 70
# ShopB 8 80
# ShopC 9 90
内側のレベルで抽出
スライスを応用して、Multiindex
の内側のレベルのみ指定する方法を紹介します。
multiindex
の場合、シングルインデックスと同じ感覚で、外側レベルを:
で指定してもエラーになってしまいます。
df.loc[(:, "ShopB"),:]
# SyntaxError: invalid syntax
この場合は、:
のかわりにslice(None)
を指定します。
df.loc[(slice(None), "ShopB"),:]
# 2020 2021
# Item Shop
# Apple ShopB 2 20
# Banana ShopB 5 50
# Carrot ShopB 8 80
# df.loc[(slice(None), "ShopB"),
slice(None)
を覚えているだけで、簡単に内側のレベルで抽出できますね。
idxによる抽出(オススメ)
ここまでdf.loc[]
にラベル名を与える方法を解説してきました。
ここからは一手間加えて、pd.IndexSlice
でデータ抽出する方法を紹介します。
ラベル指定、複数ラベル指定、スライスを一貫した手順で実行できるので、個人的にはおススメです。
pd.IndexSlice
の基本的な使い方は次のとおりです。
idx = pd.IndexSlice |idxの読み込み
df.loc[idx[インデックス指定],idx[カラム指定]]
簡単に言えば、df.loc[インデックス指定, カラム指定]
をidx[インデックス指定]
, idx[カラム指定]
に置き換えるだけです。
主なラベル指定方法は以下の通りです。
抽出内容 | ラベル指定方法 | サンプル |
---|---|---|
特定のラベル | idx[レベル順の"ラベル名"] |
idx["Apple", "ShopA"] |
複数のラベル | idx[レベル順の["ラベル名"のリスト]] |
idx[["Apple","Carrot"], "ShopA"] |
内側のレベル指定 | idx[:,"内側のレベルのラベル名"] |
idx[:, "ShopB"] |
スライス | idx["始点":"終点"] |
idx["Banana":"Carrot", "ShopA":"ShopB"] ※始点が最初の行、終点が最後の行ならそれぞれ省略可 idx[:"Carrot"] |
最大の特徴として、内側のレベルのみ指定する際にidx[:,"内側のレベルのラベル名"]
が使用可能になります。
これにより、内側のレベル抽出をする際に特別な操作が不要になります。
サンプルコードで、各抽出例の挙動をみてみます。
# pd.IndexSlice読み込み
idx = pd.IndexSlice
# 特定の行抽出
df.loc[idx["Apple", "ShopA"],:]
# 2020 1
# 2021 10
# 複数の行抽出
df.loc[idx[["Apple","Carrot"], "ShopA"],:]
# 2020 2021
# Item Shop
# Apple ShopA 1 10
# Carrot ShopA 7 70
# 内側のレベルのみ指定
df.loc[idx[:, "ShopB"],:]
# 2020 2021
# Item Shop
# Apple ShopA 1 10
# Banana ShopA 4 40
# Carrot ShopA 7 70
# スライス
df.loc[idx["Banana":"Carrot", "ShopA":"ShopB"],:]
# 2020 2021
# Item Shop
# Banana ShopA 4 40
# ShopB 5 50
# Carrot ShopA 7 70
# ShopB 8 80
idx
を使用すると、multiindex
もシングルインデックスと似たような感覚で操作できますね。
xsによる抽出
.xs()
メソッドを使用しても、任意のレベルの特定のラベル名を指定したデータを抽出が可能です。
df.loc[(slice(None), "内側ラベル名")]
, df.loc[idx[:, "内側ラベル名"]]
と挙動はほぼ同じですが、背景の概念的なものが異なるのだと思います。
xs
はCross-Section
の略称です。
データ分析において、時系列データは表現の仕方で次のような呼び方をします。
- 横軸を時間軸にしたデータ:Time series
- 特定の時間断面のデータ:Cross section
こういった背景からも、.xs()
は時間断面の切り出しに近いイメージがあると思います。
実際には時間断面に限らず、次のようにラベル名を指定して、データ抽出できます。
抽出内容 | 指定方法 |
---|---|
特定のレベル指定 | df.xs("ラベル名",level=レベル番号orレベル名) |
複数のレベル | df.xs(("ラベル名"のタプル),level=(レベル番号or名のタプル)) |
次のような場合は、level
の指定は省略可能です。
- 特定のレベル指定で
level=0
のとき - 複数のレベル指定で
level=0,1,...
の連番のとき
次の時系列データで.xs()
の挙動を確認してみましょう。
multiindex = pd.MultiIndex.from_product([pd.date_range("2000-01-01",periods=30),
["ShopA","ShopB","ShopC"],["Data1","Data2"]],
names=["Date","Shop","Data"])
df=pd.DataFrame({"val":np.arange(30*3*2)}, index=multiindex)
# val
# Date Shop Data
# 2000-01-01 ShopA Data1 0
# Data2 1
# ShopB Data1 2
# Data2 3
# ShopC Data1 4
# ... ...
# 2000-01-30 ShopA Data2 175
# ShopB Data1 176
# Data2 177
# ShopC Data1 178
# Data2 179
.xs()
メソッドで、特定の時間、特定の店名のデータを抽出してみます。
# 特定の時間による抽出(level=0での抽出)
df.xs("2000-01-02", level="Date") # またはdf.xs("2000-01-02")
# val
# Shop Data
# ShopA Data1 6
# Data2 7
# ShopB Data1 8
# Data2 9
# ShopC Data1 10
# Data2 11
# 特定の店名による抽出(level="Shopでの抽出")
df.xs("ShopB", level="Shop")
# val
# Date Data
# 2000-01-01 Data1 2
# Data2 3
# 2000-01-02 Data1 8
# Data2 9
# ・・・(省略)・・・
# 2000-01-30 Data1 176
# Data2 177
.xs()
メソッドを使えば、抽出先のレベルによらず一貫した方法でデータの抽出が可能です。
ただし、.xs()
ではスライスができないため、状況によっては使いずらい場合もあるので注意が必要です。
オススメ|pandasとデータ分析の勉強方法
今回はMultiindex
のDataFrame
からのデータ抽出方法について解説しました。
pandas
は便利すぎて操作方法がわかりにくいことがよくあります…。
結局はコツコツ学ぶのが、pandas
マスターの近道ですよね!
データ分析初心者の方にはこちらの記事もおススメです。
私がこれまで勉強してきた経験をもとに考えたおススメの勉強本の紹介記事です。
何から始めて、どうやってレベルアップしていけばいいのか、初心者の方にぜひおススメしたい本を紹介しました。
オススメのpandas
本については、次の記事で紹介しています。