パンダ:演算子チェーンを使用してDataFrameの行をフィルター処理する


329

でほとんどの操作pandasオペレータチェーン(で達成することができgroupbyaggregateapply、など)が、私は、フィルタ行に見つけた唯一の方法は、通常のブラケットのインデックス経由で

df_filtered = df[df['column'] == value]

df値にフィルターをかける前に変数に割り当てる必要があるため、これは魅力的ではありません。次のようなものはありますか?

df_filtered = df.mask(lambda x: x['column'] == value)

df.queryそしてpd.eval、このユースケースのために良いフィットのように見えます。pd.eval()関数のファミリー、その機能、および使用例については、pd.eval()を使用したパンダでの動的式評価をご覧ください。
cs95 2018

回答:


384

私はあなたが何を望んでいるのか完全にはわかりません、そしてあなたの最後のコード行も助けにはなりませんが、とにかく:

「連鎖」フィルタリングは、ブールインデックスの基準を「連鎖」することによって行われます。

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

メソッドをチェーンしたい場合は、独自のマスクメソッドを追加して使用できます。

In [90]: def mask(df, key, value):
   ....:     return df[df[key] == value]
   ....:

In [92]: pandas.DataFrame.mask = mask

In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD'))

In [95]: df.ix['d','A'] = df.ix['a', 'A']

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [97]: df.mask('A', 1)
Out[97]:
   A  B  C  D
a  1  4  9  1
d  1  3  9  6

In [98]: df.mask('A', 1).mask('D', 6)
Out[98]:
   A  B  C  D
d  1  3  9  6

2
正解です。では(df.A == 1) & (df.D == 6)、「&」はパンダのオーバーロードされた演算子ですか?
ショーン


それは本当に良い解決策です-私はあなたがpythonでそのような方法を陪審にかけることができることさえ知らなかった。このような機能はパンダ自体に持っていると本当に良いでしょう。
naught101

これに関して私が持っている唯一の問題は、の使用ですpandas.。あなたがすべきimport pandas as pdです。
荒巻大輔2017

3
確かにimport pandas as pd今では一般的な習慣です。質問に答えたのは間違いだった。
Wouter Overmeire 2017

108

フィルターはPandas クエリを使用してチェーンできます

df = pd.DataFrame(np.random.randn(30, 3), columns=['a','b','c'])
df_filtered = df.query('a > 0').query('0 < b < 2')

フィルターは、単一のクエリに結合することもできます。

df_filtered = df.query('a > 0 and 0 < b < 2')

3
クエリでpython変数を参照する必要がある場合、ドキュメントには、「@ a + bのような '@'文字を前に付けることで、環境内の変数を参照できます」と記載されています。次が有効であることに注意してください:df.query('a in list([1,2])')s = set([1,2]); df.query('a in @s')
user3780389 2016年

2
一方、列名に特定の特殊文字(「Place.Name」など)が含まれている場合、クエリの評価は失敗するようです。
user3780389 2016年

2
連鎖は、クエリが設計されたものです。
piRSquared 2018

66

@lodagroからの回答は素晴らしいです。マスク関数を次のように一般化することで拡張します。

def mask(df, f):
  return df[f(df)]

その後、次のようなことができます:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)

8
便利な一般化!DataFrameすでにに直接統合されているといいのですが!
ダックワースド、

24

バージョン0.18.1以降、この.locメソッドは選択用の呼び出し可能オブジェクトを受け入れます。ラムダ関数と組み合わせることで、非常に柔軟なチェーン可能なフィルターを作成できます。

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df.loc[lambda df: df.A == 80]  # equivalent to df[df.A == 80] but chainable

df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A]

フィルタリングのみを行う場合は、を省略することもできます.loc


16

追加の例としてこれを提供します。これはhttps://stackoverflow.com/a/28159296/と同じ答えです

この投稿をより便利にするために、他の編集を追加します。

pandas.DataFrame.query
queryまさにこの目的のために作られました。データフレームを検討するdf

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(10, size=(10, 5)),
    columns=list('ABCDE')
)

df

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
6  8  7  6  4  7
7  6  2  6  6  5
8  2  8  7  5  8
9  4  7  6  1  5

queryすべての行をフィルタリングするために使用してみましょうD > B

df.query('D > B')

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

連鎖

df.query('D > B').query('C > B')
# equivalent to
# df.query('D > B and C > B')
# but defeats the purpose of demonstrating chaining

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

これは基本的に、stackoverflow.com / a / 28159296と同じ答えで はありませんか?明確にする必要があると思われる、その答えに欠けているものはありますか?
bscan

9

基準をOR条件に結合することを除いて、同じ質問がありました。Wouter Overmeireによって指定された形式は、基準をAND条件に組み合わせて、両方を満たす必要があるようにします。

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

しかし、各条件をラップし(... == True)て基準をパイプで結合すると、基準はOR条件で結合され、どちらかがtrueの場合は常に満たされることがわかりました。

df[((df.A==1) == True) | ((df.D==6) == True)]

12
df[(df.A==1) | (df.D==6)]あなたが達成しようとしていることには十分ではないでしょうか?
eenblam

いいえ、そうではありません。条件を満たしているすべてのデータをフィルタリングする上ではなく、ブール値(True vs False)を返すからです。私がそれを明らかにしたことを願っています。
MGB.py

8

パンダは、上書きを必要としないWouter Overmeireの回答に2つの選択肢を提供します。1つは.loc[.]、次のように呼び出し可能

df_filtered = df.loc[lambda x: x['column'] == value]

もう一方は.pipe()

df_filtered = df.pipe(lambda x: x['column'] == value)

7

私の答えは他の人に似ています。新しい関数を作成したくない場合は、パンダがすでに定義したものを使用できます。pipeメソッドを使用します。

df.pipe(lambda d: d[d['column'] == value])

これは、次のようなチェーンコマンドにしたい場合は、あなたが望むものであるa.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
のDisplayName

4

一般的なブールマスクと汎用マスクのすべてを適用したい場合は、次のファイルをチャックして、すべてを次のように割り当てるだけです。

pd.DataFrame = apply_masks()

使用法:

A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"])
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary

少しハッキーですが、フィルターに従ってデータセットを連続的に細かく変更している場合は、少しクリーンになります。上記のDaniel Velkovからgen_mask関数に適応された汎用フィルターもあり、必要に応じてラムダ関数などで使用できます。

保存するファイル(私はmasks.pyを使用します):

import pandas as pd

def eq_mask(df, key, value):
    return df[df[key] == value]

def ge_mask(df, key, value):
    return df[df[key] >= value]

def gt_mask(df, key, value):
    return df[df[key] > value]

def le_mask(df, key, value):
    return df[df[key] <= value]

def lt_mask(df, key, value):
    return df[df[key] < value]

def ne_mask(df, key, value):
    return df[df[key] != value]

def gen_mask(df, f):
    return df[f(df)]

def apply_masks():

    pd.DataFrame.eq_mask = eq_mask
    pd.DataFrame.ge_mask = ge_mask
    pd.DataFrame.gt_mask = gt_mask
    pd.DataFrame.le_mask = le_mask
    pd.DataFrame.lt_mask = lt_mask
    pd.DataFrame.ne_mask = ne_mask
    pd.DataFrame.gen_mask = gen_mask

    return pd.DataFrame

if __name__ == '__main__':
    pass

3

このソリューションは実装の点ではハックですが、使用方法の点でははるかにクリーンであり、提案された他のソリューションよりも一般的です。

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py

リポジトリ全体をダウンロードする必要はありません。ファイルを保存して実行する

from where import where as W

十分なはずです。次に、次のように使用します。

df = pd.DataFrame([[1, 2, True],
                   [3, 4, False], 
                   [5, 7, True]],
                  index=range(3), columns=['a', 'b', 'c'])
# On specific column:
print(df.loc[W['a'] > 2])
print(df.loc[-W['a'] == W['b']])
print(df.loc[~W['c']])
# On entire - or subset of a - DataFrame:
print(df.loc[W.sum(axis=1) > 3])
print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1])

少し愚かな使用例:

data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)]

ちなみに、ブールcolsを使用している場合でも、

df.loc[W['cond1']].loc[W['cond2']]

よりもはるかに効率的です

df.loc[W['cond1'] & W['cond2']]

これはcond2cond1がである場所のみを評価するためですTrue

免責事項:私はこれを見ていないので、私は最初に他の場所でこの答えを出しました。


2

loc行だけでなく列でフィルタリングするためのを使用したデモンストレーションを追加したいだけで、連鎖操作にいくつかのメリットがあります。

以下のコードは、値で行をフィルタリングできます。

df_filtered = df.loc[df['column'] == value]

これを少し変更することで、列をフィルタリングすることもできます。

df_filtered = df.loc[df['column'] == value, ['year', 'column']]

では、なぜチェーンメソッドが必要なのでしょうか。答えは、操作が多いと読みやすいということです。例えば、

res =  df\
    .loc[df['station']=='USA', ['TEMP', 'RF']]\
    .groupby('year')\
    .agg(np.nanmean)

2

df値にフィルターをかける前に変数に割り当てる必要があるため、これは魅力的ではありません。

df[df["column_name"] != 5].groupby("other_column_name")

動作するようです:[]演算子をネストすることもできます。多分彼らはあなたが質問をしたのでそれを追加しました。


1
チェーンdfの前の部分の出力を参照する必要がないため、これはチェーンではほとんど意味がありません。
Daan Luttik

@DaanLuttik:同意しました。チェーンではなく、ネストです。あなたにとって良い?
serv-inc

1

列をインデックスとして検索するように設定すると、を使用DataFrame.xs()して断面を取得できます。これはquery回答ほど用途が広いわけではありませんが、状況によっては役立つ場合があります。

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(3, size=(10, 5)),
    columns=list('ABCDE')
)

df
# Out[55]: 
#    A  B  C  D  E
# 0  0  2  2  2  2
# 1  1  1  2  0  2
# 2  0  2  0  0  2
# 3  0  2  2  0  1
# 4  0  1  1  2  0
# 5  0  0  0  1  2
# 6  1  0  1  1  1
# 7  0  0  2  0  2
# 8  2  2  2  2  2
# 9  1  2  0  2  1

df.set_index(['A', 'D']).xs([0, 2]).reset_index()
# Out[57]: 
#    A  D  B  C  E
# 0  0  2  2  2  2
# 1  0  2  1  1  0

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.