注
この投稿は次のように構成されます。
- OPで出された質問は、1つずつ対処されます
- 各質問について、この問題を解決して期待される結果を得るのに適用できる1つ以上の方法が示されます。
注意(このようなもの)は、追加機能、実装の詳細、および目下のトピックにざっと触れたその他の情報について学習したい読者のために含まれます。これらのメモは、ドキュメントを精査し、さまざまなあいまいな機能を明らかにすることによって、および私自身の(確かに限られた)経験から編集されています。
すべてのコードサンプルは、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)
ここで、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つのイディオムに加える必要がある次の変更です。
でスライスするには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}]
Where {condition}
は、を使用して構築された条件を表しcolumns.get_level_values
ます。
を使用するためのquery
唯一のオプションは、転置、インデックスのクエリ、および転置です。
df3.T.query(...).T
推奨されません。他の3つのオプションのいずれかを使用してください。
level
への議論について知りませんでしたIndex.isin
!