YutaKaのPython教室

Python の文法やライブラリ、API、環境構築について画像・動画・ソースコード付きで徹底解説!

pandas Multiindexのデータ抽出まとめ|.locからxs()まで

pandasでは、インデックス列を階層構造にしてMultiindexとして扱うことができます。

MultiindexDataFrame, Seriesはデータの構造化には便利なのですが…。

使い慣れたシングルインデックスと異なり、データ抽出で次のような問題に直面することも…

  • Multiindexからデータ抽出する方法がわからない!
  • .loc[]で指定する時、Multiindexの場合はどう指定するの?
  • 内側のレベルだけ指定して抽出する方法は?

この記事では、これらの問題を解決するためにMultiindexのデータの抽出方法ついてわかりやすく図解付きで解説していきます。

Multiindexからのデータ抽出まとめ

MultiindexDataFrameからのデータ抽出方法について、次の3つの方法を紹介します。

  1. df.loc[]によるデータ抽出
  2. idx[]によるデータ抽出
  3. .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の基本的な構成、生成方法について、あまり慣れていな方は、事前に次の記事を読むとよく理解できると思います。

≫pandas Multiindex図解で解説|基本構造・変更・追加・解除・並べ替え
MultiindexのDataFrame, Seriesはデータの階層構造が視覚的にも把握しやすく、データの集計・分析が効率的に行えます。一方で、Multiindexでは次のような問題に直面することも…①普段使わないからMultiindexの中身がよくわからない②どうやっMultiindexを設定・解除するの?③インデックスを変更、入れ替え、ソートする方法は?この記事では、Multiindexの基本構造、設定・解除方法、変更・入れ替え・ソートについてわかりやすく図解付きで解説していきます。
www.yutaka-note.com/entry/pandas_multiind_set
 

df.loc[]で抽出

Multiindexでも通常のdf同様にdf.loc[]で特定の行・列を抽出できます。

以下では、次の順番でMultiindex特有の指定方法について解説していきます。

  1. 特定の行・列を指定
  2. 複数の行・列を指定
  3. スライス(行・列の範囲指定)
  4. 内側のレベルで抽出

特定の行・列を指定

特定の行・列を指定する場合は、シングルインデックスの場合と同様に、df.loc[インデックスの指定, カラムの指定]の順で指定します。

インデックスおよびカラムを単独または両方指定した場合のパターンを下表にまとめています。

指定方法 .locでの書き方
インデックスのみ指定 df.loc[インデックス指定,:] or
df.loc[インデックス指定]
カラムのみ指定 df.loc[:,カラム指定] or
df[カラム指定]
両方指定 df.loc[インデックス指定,カラム指定]
  • 【参考】通常のシングルインデックスの指定方法は次の記事を参考にしてください。
≫pandas DataFrameの行・列を抽出|loc, ilocなどわかりやすく解説!
pandasのDataFrameを使うと、行と列で構成されたデータを簡単に取り扱うことができます。この記事では、「DataFrameの特定の行、列のデータを抽出する方法」、「インデックス参照、.loc、.iloc、at、iat[]の使い方」をわかりやすい図解付きで解説しています。
www.yutaka-note.com/entry/pandas_access
 

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の基本的な使い方は次のとおりです。

  1. idx = pd.IndexSlice |idxの読み込み
  2. 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[:, "内側ラベル名"]]と挙動はほぼ同じですが、背景の概念的なものが異なるのだと思います。

xsCross-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とデータ分析の勉強方法

今回はMultiindexDataFrameからのデータ抽出方法について解説しました。

pandasは便利すぎて操作方法がわかりにくいことがよくあります…。

結局はコツコツ学ぶのが、pandasマスターの近道ですよね!

≫【ブログカテゴリー:pandas】

データ分析初心者の方にはこちらの記事もおススメです。

私がこれまで勉強してきた経験をもとに考えたおススメの勉強本の紹介記事です。

何から始めて、どうやってレベルアップしていけばいいのか、初心者の方にぜひおススメしたい本を紹介しました。

≫独学でデータ分析を勉強するオススメ学習本
独学でのpythonデータ分析勉強に役立ったおススメ書籍を紹介していきます。業務でそれなりにデータ分析を行えるまで、いろいろな試行錯誤をしてきましたが、もし自分が今ゼロから勉強する立場ならどうするのがいいのか考えてみました。以下では、入門書、個別モジュール用、実践用の3つの視点でおススメ本を紹介していきます。
www.yutaka-note.com/entry/data_analysis
 

オススメのpandas本については、次の記事で紹介しています。

≫【レビュー】「Python実践データ分析100本ノック」|100本終えたらpandasが好きになっていた
Python実践データ分析100本ノックで、実際に100本終了したレビューです。pythonでのデータ分析の入門書としてかなりの良書だったと思います。・python2~3冊目に何を勉強しようか迷っている人・時間をかけずにデータ分析の基本を学びたい人・pandasへの抵抗を減らしたい人
www.yutaka-note.com/entry/nock_100
 
≫【レビュー】「Pythonによるデータ分析入門」| pandas開発者によるpandasユーザーのためのpandasの教科書!
「Pythonによるデータ分析入門」を、最初から最後まで実際に実践してみたレビューです。具体的にどのようなことができるようになったかを実例付きで紹介します!・DataFrameの生成方法・欠損値の処理方法・グラフ化の方法気になる学習時間は…?
www.yutaka-note.com/entry/2019/12/07/230219