Pandas MultiIndex DataFrameの行を選択する


145

の行を選択/フィルタリングする最も一般的なパンダの方法は何ですか インデックスがMultiIndexでデータフレームのは何ですか?

  • 単一の値/ラベルに基づくスライス
  • 1つ以上のレベルの複数のラベルに基づくスライス
  • ブール条件と式でのフィルタリング
  • どの方法がどのような状況で適用可能か

単純化の前提:

  1. 入力データフレームに重複するインデックスキーがありません
  2. 下の入力データフレームには2つのレベルしかありません。(ここに示されているほとんどのソリューションは、Nレベルに一般化されています)

入力例:

mux = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    list('tuvwtuvwtuvwtuvw')
], names=['one', 'two'])

df = pd.DataFrame({'col': np.arange(len(mux))}, mux)

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    u      5
    v      6
    w      7
    t      8
c   u      9
    v     10
d   w     11
    t     12
    u     13
    v     14
    w     15

質問1:単一のアイテムを選択する

レベル「1」に「a」がある行を選択するにはどうすればよいですか?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

さらに、出力でレベル「1」をドロップするにはどうすればよいですか?

     col
two     
t      0
u      1
v      2
w      3

質問1b
レベル「2」で値「t」のすべての行をスライスするにはどうすればよいですか?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

質問2:レベルで複数の値を選択する

レベル「one」の項目「b」および「d」に対応する行を選択するにはどうすればよいですか?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

質問2b
レベル「2」の「t」と「w」に対応するすべての値を取得するにはどうすればよいですか?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

質問3:単一の断面をスライスする (x, y)

断面、つまりインデックスの特定の値を持つ単一の行を取得するにはどうすればよいdfですか?具体的に、どのように私はの断面を取得しない('c', 'u')で与えられ、

         col
one two     
c   u      9

質問4:複数の断面をスライスする [(a, b), (c, d), ...]

('c', 'u')とに対応する2つの行を選択するにはどうすればよい('a', 'w')ですか。

         col
one two     
c   u      9
a   w      3

質問5:レベルごとにスライスされた1つのアイテム

レベル「1」の「a」またはレベル「2」の「t」に対応するすべての行を取得するにはどうすればよいですか?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

質問6:任意のスライス

特定の断面をスライスするにはどうすればよいですか?「a」と「b」の場合、サブレベル「u」と「v」のすべての行を選択し、「d」の場合、サブレベル「w」の行を選択します。

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

質問7では、数値レベルで構成される固有の設定を使用します。

np.random.seed(0)
mux2 = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    np.random.choice(10, size=16)
], names=['one', 'two'])

df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2)

         col
one two     
a   5      0
    0      1
    3      2
    3      3
b   7      4
    9      5
    3      6
    5      7
    2      8
c   4      9
    7     10
d   6     11
    8     12
    8     13
    1     14
    6     15

質問7:マルチインデックスの個々のレベルでの数値的不等式によるフィルタリング

レベル「2」の値が5より大きいすべての行を取得するにはどうすればよいですか?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

注:この記事はなりませんそれらに割り当て操作を実行する方法、MultiIndexesを作成する方法を通過、または任意のパフォーマンス関連の議論(これらは別の時間のために別のトピックです)。

回答:


166

MultiIndex / Advanced Indexing


この投稿は次のように構成されます。

  1. OPで出された質問は、1つずつ対処されます
  2. 各質問について、この問題を解決して期待される結果を得るのに適用できる1つ以上の方法が示されます。

注意(このようなもの)は、追加機能、実装の詳細、および目下のトピックにざっと触れたその他の情報について学習したい読者のために含まれます。これらのメモは、ドキュメントを精査し、さまざまなあいまいな機能を明らかにすることによって、および私自身の(確かに限られた)経験から編集されています。

すべてのコードサンプルは、pandas v0.23.4、python3.7で作成およびテストされています。不明な点がある場合、または事実が正しくない場合、またはユースケースに該当する解決策が見つからなかった場合は、編集を提案したり、コメントで説明を要求したり、新しい質問を開いたりしてください。 。

これは、私たちが頻繁に再訪するいくつかの一般的なイディオム(以降、4つのイディオムと呼ばれます)の紹介です。

  1. DataFrame.loc-ラベルによる選択の一般的なソリューション(+ pd.IndexSliceスライスを含むより複雑なアプリケーションの場合)

  2. DataFrame.xs -Series / DataFrameから特定の断面を抽出します。

  3. DataFrame.query-スライス操作やフィルタリング操作を動的に指定します(つまり、動的に評価される式として。他のシナリオよりも一部のシナリオに適しています。MultiIndexesでのクエリについては、ドキュメントのこのセクションもご覧ください。

  4. を使用して生成されたマスクを使用したブールインデックスMultiIndex.get_level_values(多くの場合Index.isin、特に複数の値でフィルタリングする場合)。これは、状況によっては非常に役立ちます。

与えられた状況に何が適用できるかをよりよく理解するために、4つのイディオムの観点からさまざまなスライシングおよびフィルタリングの問題を調べることは有益です。すべてのイディオムが(あるとしても)すべての状況で等しく機能するわけではないことを理解することは非常に重要です。以下の問題の潜在的な解決策としてイディオムがリストされていない場合は、その問題にイディオムを効果的に適用できないことを意味します。


質問1

レベル「1」に「a」がある行を選択するにはどうすればよいですか?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

locほとんどの状況に適用できる汎用ソリューションとして、を使用できます。

df.loc[['a']]

この時点で、

TypeError: Expected tuple, got str

つまり、古いバージョンのパンダを使用しているということです。アップグレードを検討してください!それ以外の場合は、を使用しますdf.loc[('a', slice(None)), :]

あるいは、xs単一の断面を抽出しているため、ここで使用できます。levelsaxis引数に注意してください(妥当なデフォルトがここで想定されます)。

df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)

ここで、drop_level=False議論は防ぐために必要ですxs結果のレベル「1」(スライスしたレベル)を落とさ。

ここでのさらに別のオプションは以下を使用することqueryです:

df.query("one == 'a'")

インデックスに名前がない場合、クエリ文字列を次のように変更する必要があります "ilevel_0 == 'a'"

最後に、使用get_level_values

df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']

さらに、出力でレベル「1」をドロップするにはどうすればよいですか?

     col
two     
t      0
u      1
v      2
w      3

これはどちらかを使用して簡単に行うことができます

df.loc['a'] # Notice the single string argument instead the list.

または、

df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')

drop_level引数を省略できることに注意してください(Trueデフォルトであると想定されています)。


DataFrameを印刷するときに表示されない場合でも、フィルターされたDataFrameにはすべてのレベルが含まれていることがあります。例えば、

v = df.loc[['a']]
print(v)
         col
one two     
a   t      0
    u      1
    v      2
    w      3

print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

以下を使用してこれらのレベルを取り除くことができますMultiIndex.remove_unused_levels

v.index = v.index.remove_unused_levels()

print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

質問1b

レベル「2」で値「t」のすべての行をスライスするにはどうすればよいですか?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

直感的には、次のようなものが必要ですslice()

df.loc[(slice(None), 't'), :]

It Just Works!™しかし、不格好です。pd.IndexSliceここでAPI を使用して、より自然なスライス構文を促進できます。

idx = pd.IndexSlice
df.loc[idx[:, 't'], :]

これははるかにきれいです。

列全体
の末尾スライスが:必要なのはなぜですか?これは、loc両方の軸(axis=0または axis=1)に沿って選択およびスライスするために使用できるためです。スライスを実行する軸を明確に指定しないと、操作があいまいになります。スライスに関するドキュメントの大きな赤いボックスを参照してください

あいまいさの陰影を取り除きたい場合locは、axis パラメーターを受け入れます。

df.loc(axis=0)[pd.IndexSlice[:, 't']]

axisパラメータなしで(つまり、を実行するだけでdf.loc[pd.IndexSlice[:, 't']])、スライスは列上で行われると想定されKeyError、この状況ではa が発生します。

これはスライサーで文書化されています。ただし、この投稿では、すべての軸を明示的に指定します。

ではxs、それはあります

df.xs('t', axis=0, level=1, drop_level=False)

ではquery、それはあります

df.query("two == 't'")
# Or, if the first level has no name, 
# df.query("ilevel_1 == 't'") 

そして最後に、でget_level_values、あなたは

df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']

すべて同じ効果に。


質問2

レベル「one」の項目「b」および「d」に対応する行を選択するにはどうすればよいですか?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

locを使用すると、これはリストを指定することによって同様の方法で行われます。

df.loc[['b', 'd']]

上記の「b」と「d」を選択する問題を解決するには、次のコマンドを使用することもできますquery

items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')


はい、デフォルトのパーサーはですが'pandas'、この構文が従来のpythonではないことを強調することが重要です。Pandasパーサーは、式とは少し異なる解析ツリーを生成します。これは、いくつかの操作をより直感的に指定するために行われます。詳細については、pd.eval()を使用したパンダでの動的式評価に関する私の投稿を参照してください 。

そして、get_level_values+ Index.isin

df[df.index.get_level_values("one").isin(['b', 'd'])]

質問2b

レベル「2」の「t」と「w」に対応するすべての値を取得するにはどうすればよいですか?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

ではloc、これはとの併用でのみ可能pd.IndexSliceです。

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 

最初のコロン:pd.IndexSlice[:, ['t', 'w']]、最初のレベルをスライスすることを意味します。照会されるレベルの深さが増えると、スライスされるレベルごとに1つずつ、より多くのスライスを指定する必要があります。ただし、スライスするレベルを超えるレベル指定する必要はありません。

query、これは

items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas') 
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')

get_level_valuesし、Index.isin(上記と同様):

df[df.index.get_level_values('two').isin(['t', 'w'])]

質問3

断面、つまりインデックスの特定の値を持つ単一の行を取得するにはどうすればよいdfですか?具体的に、どのように私はの断面を取得しない('c', 'u')で与えられ、

         col
one two     
c   u      9

locキーのタプルを指定して使用します。

df.loc[('c', 'u'), :]

または、

df.loc[pd.IndexSlice[('c', 'u')]]


この時点で、PerformanceWarning次のようなに遭遇する可能性があります。

PerformanceWarning: indexing past lexsort depth may impact performance.

これは、インデックスが並べ替えられていないことを意味します。パンダは、最適な検索と取得のために、ソートされているインデックス(この場合、文字列値を扱っているため、辞書式順序)に依存しています。簡単な修正は、事前にを使用してDataFrameをソートすることDataFrame.sort_indexです。これは、このような複数のクエリを並行して実行する場合は、パフォーマンスの観点から特に望ましいものです。

df_sort = df.sort_index()
df_sort.loc[('c', 'u')]

を使用MultiIndex.is_lexsorted()して、インデックスがソートされているかどうかを確認することもできます。この関数はTrueまたはを返しますFalse。この関数を呼び出して、追加のソート手順が必要かどうかを判断できます。

ではxs、これもまた、最初の引数として単一のタプルを渡すだけで、他のすべての引数は適切なデフォルトに設定されています。

df.xs(('c', 'u'))

ではquery、物事は少し不格好になります:

df.query("one == 'c' and two == 'u'")

これを一般化するのが比較的難しいことがわかります。しかし、この特定の問題についてはまだ問題ありません。

複数のレベルにまたがるアクセスでget_level_valuesも、引き続き使用できますが、推奨されません。

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]

問題4

('c', 'u')とに対応する2つの行を選択するにはどうすればよい('a', 'w')ですか。

         col
one two     
c   u      9
a   w      3

を使用してもloc、これは次のように簡単です。

df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]

ではquery、断面とレベルを反復処理してクエリ文字列を動的に生成する必要があります。

cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses) 

query = '(' + ') or ('.join([
    ' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)]) 
    for cs in cses
]) + ')'

print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))

df.query(query)

100%推奨しない!しかし、それは可能です。


問題5

レベル「1」の「a」またはレベル「2」の「t」に対応するすべての行を取得するにはどうすればよいですか?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

これは実際に行うことは非常に困難であるloc正確さを確保しながら、かつ、まだコードの明瞭さを維持します。df.loc[pd.IndexSlice['a', 't']]が正しくない、と解釈されますdf.loc[pd.IndexSlice[('a', 't')]](つまり、断面の選択)。pd.concat各ラベルを個別に処理するソリューションを考えることができます:

pd.concat([
    df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])

         col
one two     
a   t      0
    u      1
    v      2
    w      3
    t      0   # Does this look right to you? No, it isn't!
b   t      4
    t      8
d   t     12

しかし、行の1つが重複していることに気づくでしょう。これは、その行が両方のスライス条件を満たしたため、2回出現したためです。代わりに行う必要があります

v = pd.concat([
        df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]

ただし、DataFrameに本質的に重複したインデックスが含まれている場合(必要な場合)、インデックスは保持されません。 細心の注意を払って使用してください

を使用するとquery、これは愚かなほど簡単です。

df.query("one == 'a' or two == 't'")

ではget_level_values、これはまだ単純ですが、それほどエレガントではありません。

m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2] 

質問6

特定の断面をスライスするにはどうすればよいですか?「a」と「b」の場合、サブレベル「u」と「v」のすべての行を選択し、「d」の場合、サブレベル「w」の行を選択します。

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

これは、4つのイディオムの適用性を理解するために追加した特別なケースです。これは、スライスが非常に効率的であるため、どれも効果的に機能しないケースです。具体的であり、実際のパターンに従っていないです。

通常、このような問題をスライスするには、キーのリストをに明示的に渡す必要がありますloc。これを行う1つの方法は、

keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]

タイピングを保存したい場合は、「a」、「b」、およびそのサブレベルをスライスするパターンがあることを認識するので、スライスタスクを2つの部分に分割してconcat、結果を得ることができます。

pd.concat([
     df.loc[(('a', 'b'), ('u', 'v')), :], 
     df.loc[('d', 'w'), :]
   ], axis=0)

"a"と "b"のスライス指定(('a', 'b'), ('u', 'v'))は、インデックスが付けられている同じサブレベルが各レベルで同じであるため、少しわかりやすくなっています。


質問7

レベル「2」の値が5より大きいすべての行を取得するにはどうすればよいですか?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

これはquery

df2.query("two > 5")

そしてget_level_values

df2[df2.index.get_level_values('two') > 5]


この例と同様に、これらの構成を使用して任意の条件に基づいてフィルタリングできます。一般的には、あることを覚えておくと便利ですlocし、xsラベルベースのインデックス作成のために特別にされ、しばらくqueryして get_level_values、フィルタリングのための一般的な条件付きのマスクを構築するために役立ちます。


ボーナス質問

MultiIndex をスライスする必要がある場合はどうなりますか?

実際、ここでのほとんどの解決策は、小さな変更を加えて列にも適用できます。考慮してください:

np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
        list('ABCD'), list('efgh')
], names=['one','two'])

df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)

one  A           B           C           D         
two  e  f  g  h  e  f  g  h  e  f  g  h  e  f  g  h
0    5  0  3  3  7  9  3  5  2  4  7  6  8  8  1  6
1    7  7  8  1  5  9  8  9  4  3  0  3  5  0  2  3
2    8  1  3  3  3  7  0  1  9  9  0  4  7  3  2  7

これらは、列を操作するために4つのイディオムに加える必要がある次の変更です。

  1. でスライスするにはloc

    df3.loc[:, ....] # Notice how we slice across the index with `:`. 

    または、

    df3.loc[:, pd.IndexSlice[...]]
  2. xs適切に使用するには、引数を渡しますaxis=1

  3. を使用して、列レベルの値に直接アクセスできますdf.columns.get_level_values。次に、次のようなことをする必要があります

    df.loc[:, {condition}] 

    Where {condition}は、を使用して構築された条件を表しcolumns.get_level_valuesます。

  4. を使用するためのquery唯一のオプションは、転置、インデックスのクエリ、および転置です。

    df3.T.query(...).T

    推奨されません。他の3つのオプションのいずれかを使用してください。


5

最近、3つ以上のレベルのマルチインデックスデータフレームがあり、上記の解決策のどれもが私が探していた結果を生み出すことができなかったユースケースに遭遇しました。上記のソリューションが私のユースケースでもちろん機能することは十分にあり得ます、そして私はいくつかを試しました、しかし、私が彼らが利用可能であった時間でそれらを機能させることができませんでした。

私は専門家とはほど遠いですが、上記の包括的な回答にリストされていないソリューションを偶然見つけました。私は、ソリューションが何らかの方法で最適であることを保証しません。

これは、上記の質問6とは少し異なる結果を得る別の方法です。(そしておそらく他の質問も)

具体的に私は探していました:

  1. インデックスの1つのレベルから2つ以上の値を選択し、インデックスの別のレベルから1つの値を選択する方法、および
  2. 前の操作のインデックス値をデータフレーム出力に残す方法。

ギアのモンキーレンチとして(ただし完全に固定可能):

  1. インデックスには名前がありませんでした。

以下のおもちゃのデータフレーム:

    index = pd.MultiIndex.from_product([['a','b'],
                               ['stock1','stock2','stock3'],
                               ['price','volume','velocity']])

    df = pd.DataFrame([1,2,3,4,5,6,7,8,9,
                      10,11,12,13,14,15,16,17,18], 
                       index)

                        0
    a stock1 price      1
             volume     2
             velocity   3
      stock2 price      4
             volume     5
             velocity   6
      stock3 price      7
             volume     8
             velocity   9
    b stock1 price     10
             volume    11
             velocity  12
      stock2 price     13
             volume    14
             velocity  15
      stock3 price     16
             volume    17
             velocity  18

もちろん、以下の作品を使用します:

    df.xs(('stock1', 'velocity'), level=(1,2))

        0
    a   3
    b  12

しかし、私は別の結果が欲しかったので、その結果を得るための私の方法は:

   df.iloc[df.index.isin(['stock1'], level=1) & 
           df.index.isin(['velocity'], level=2)] 

                        0
    a stock1 velocity   3
    b stock1 velocity  12

また、あるレベルの2つ以上の値と別のレベルの1つ(または2+)の値が必要な場合:

    df.iloc[df.index.isin(['stock1','stock3'], level=1) & 
            df.index.isin(['velocity'], level=2)] 

                        0
    a stock1 velocity   3
      stock3 velocity   9
    b stock1 velocity  12
      stock3 velocity  18

上記の方法はおそらく少し不格好ですが、私はそれが私のニーズを満たし、ボーナスとして私が理解して読んだ方が簡単であることがわかりました。


2
ニース、levelへの議論について知りませんでしたIndex.isin
cs95
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.