MultiIndex
APIは長年にわたって人気を集めていますが、構造、動作、および関連する操作に関して、そのすべてが完全に理解されているわけではありません。
1つの重要な操作はfilteringです。フィルタリングは一般的な要件ですが、ユースケースはさまざまです。したがって、特定の方法および機能は、他のユースケースよりもいくつかのユースケースに適用できます。
要約すると、この投稿の目的は、いくつかの一般的なフィルタリングの問題とユースケースに触れ、これらの問題を解決するためのさまざまな方法を示し、それらの適用可能性を議論することです。この投稿が対処しようとしている高レベルの質問のいくつかは
これらの問題は、以下に列挙する6つの具体的な質問に分割されています。簡単にするために、以下のセットアップのサンプルDataFramesには2つのレベルのみがあり、重複するインデックスキーはありません。問題に対して提示されるほとんどのソリューションは、Nレベルに一般化できます。
この投稿では、notMultiIndexの作成方法、割り当て操作の実行方法、またはパフォーマンス関連のディスカッション(これらは別のトピックです) )。
質問1-6は、以下の設定に関連して尋ねられます。
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')
と('a', 'w')
に対応する2つの行を選択するにはどうすればよいですか?
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
注
この投稿は次のように構成されます。
- OPに記載された質問は、1つずつ対処されます
- 質問ごとに、この問題を解決して期待される結果を得るために適用可能な1つ以上の方法が示されます。
注s(このようなもの)は、追加の機能、実装の詳細、その他の手近なトピックに関する情報に興味がある読者のために含まれます。 。これらのノートは、ドキュメントを精査し、さまざまな不明瞭な機能を明らかにすることによって、そして私自身の(明らかに制限された)経験から編集されました。
すべてのコードサンプルは、pandas v0.23.4、python3.7で作成およびテストされています。不明な点や事実に誤りがある場合、またはユースケースに該当する解決策が見つからなかった場合は、気軽に編集を提案したり、コメントで説明を求めたり、新しい質問を開いたりしてください。 。
ここでは、よく見られるいくつかの一般的なイディオム(以下、4つのイディオムと呼びます)を紹介します。
DataFrame.loc
-ラベルによる選択の一般的なソリューション(+pd.IndexSlice
より複雑なアプリケーション用スライスを含む)
DataFrame.xs
-Series/DataFrameから特定の断面を抽出します。
DataFrame.query
-動的にスライスおよび/またはフィルタリング操作を指定します(つまり、動的に評価される式として。他のシナリオよりも一部のシナリオに適用されます。 ドキュメントのこのセクション MultiIndexesでのクエリ用。
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
を使用できます。これは、単一の断面を抽出しているためです。 levels
およびaxis
引数に注意してください(ここでは適切なデフォルトを想定できます)。
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
ここでは、xs
が結果(スライスしたレベル)のレベル「1」をドロップしないようにするために、drop_level=False
引数が必要です。
ここでのもう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
これは、easilyまたは
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'), :]
それだけで機能します!™しかし、それは不格好です。ここで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
が発生します。これは slicers で文書化されています。ただし、この投稿では、すべての軸を明示的に指定します。
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
レベル「1」の項目「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()を使用したpandasの動的式評価 の投稿を参照してください。
そして、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
と組み合わせて可能ですonly。
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.
これは、インデックスがソートされていないことを意味します。 pandasは、最適な検索と取得のために、ソートされるインデックスに依存します(この場合、文字列値を扱っているため、辞書式になります)。簡単な修正方法は、事前に
DataFrame.sort_index
を使用してDataFrameをソートすることです。このようなクエリを複数同時に実行する予定がある場合、これはパフォーマンスの観点から特に望ましいものです。df_sort = df.sort_index() df_sort.loc[('c', 'u')]
MultiIndex.is_lexsorted()
を使用して、インデックスがソートされているかどうかを確認することもできます。この関数は、それに応じてTrue
またはFalse
を返します。この関数を呼び出して、追加の並べ替え手順が必要かどうかを判断できます。
xs
を使用する場合、これも最初の引数として単一のTupleを渡し、他のすべての引数を適切なデフォルトに設定するだけです。
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')
および('a', 'w')
に対応する2つの行を選択するにはどうすればよいですか?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') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
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つのイディオムの適用可能性を理解するために追加した特別なケースです。これは、スライスがvery固有であるため、どれも効果的に機能しない1つのケースです。実際のパターンに従っていません。
通常、このようなスライスの問題では、キーのリストを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
columnをスライスする必要がある場合はどうなりますか?
実際、ここでのほとんどのソリューションは列にも適用できますが、わずかな変更が加えられています。考慮してください:
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つのイディオムに行う必要がある次の変更です。
loc
でスライスするには、使用します
df3.loc[:, ....] # Notice how we slice across the index with `:`.
または、
df3.loc[:, pd.IndexSlice[...]]
必要に応じてxs
を使用するには、引数axis=1
を渡すだけです。
df.columns.get_level_values
を使用して、列レベルの値に直接アクセスできます。その後、次のようなことをする必要があります
df.loc[:, {condition}]
{condition}
は、columns.get_level_values
を使用して構築された条件を表します。
query
を使用するための唯一のオプションは、転置、インデックスのクエリ、および転置です。
df3.T.query(...).T
推奨されません。他の3つのオプションのいずれかを使用してください。