複数のフィルターをpandas DataFrameまたはSeriesに適用する効率的な方法


148

ユーザーがPandas DataFrameまたはSeriesオブジェクトにいくつかのフィルターを適用したいというシナリオがあります。基本的に、実行時にユーザーが指定した一連のフィルタリング(比較操作)を効率的にチェーン化したいと考えています。

フィルターは付加的である必要があります(別名を適用すると結果が絞り込まれます)。

私は現在使用してreindex()いますが、これにより毎回新しいオブジェクトが作成され、基になるデータがコピーされます(ドキュメントを正しく理解している場合)。そのため、大きなSeriesまたはDataFrameをフィルタリングする場合、これは非常に非効率的です。

私が使用していることを考えているapply()map()または類似した何かが良いかもしれません。私はパンダにかなり慣れていないので、すべてに頭を包み込もうとしています。

TL; DR

次の形式のディクショナリを取得して、各操作を特定のSeriesオブジェクトに適用し、「フィルター処理された」Seriesオブジェクトを返します。

relops = {'>=': [1], '<=': [1]}

長い例

私が現在持っているものの例から始めて、単一のSeriesオブジェクトをフィルタリングするだけです。以下は私が現在使用している関数です:

   def apply_relops(series, relops):
        """
        Pass dictionary of relational operators to perform on given series object
        """
        for op, vals in relops.iteritems():
            op_func = ops[op]
            for val in vals:
                filtered = op_func(series, val)
                series = series.reindex(series[filtered])
        return series

ユーザーは、実行する操作を含む辞書を提供します。

>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
   col1  col2
0     0    10
1     1    11
2     2    12

>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1       1
2       2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1       1
Name: col1

繰り返しますが、上記のアプローチの「問題」は、中間のステップのデータのコピーがおそらく不必要である可能性が高いということです。

また、これを拡張して、渡されたディクショナリに演算子の列を含め、入力ディクショナリに基づいてDataFrame全体をフィルタリングできるようにします。ただし、シリーズで機能するものはすべてDataFrameに簡単に拡張できると想定しています。


また、この問題へのアプローチがうまくいかない可能性があることも十分に承知しています。したがって、アプローチ全体を再考することは有用でしょう。ユーザーが実行時に一連のフィルター操作を指定して実行できるようにしたいだけです。
durden2.0

パンダがRのdata.tableと同様のことができるかどうか疑問に思っています:df [col1 <1 ,,] [col2> = 1]
xappppp

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

回答:


245

パンダ(およびnumpy)はブールインデックスを可能にします。これははるかに効率的です。

In [11]: df.loc[df['col1'] >= 1, 'col1']
Out[11]: 
1    1
2    2
Name: col1

In [12]: df[df['col1'] >= 1]
Out[12]: 
   col1  col2
1     1    11
2     2    12

In [13]: df[(df['col1'] >= 1) & (df['col1'] <=1 )]
Out[13]: 
   col1  col2
1     1    11

このためのヘルパー関数を作成する場合は、次の行に沿って検討してください。

In [14]: def b(x, col, op, n): 
             return op(x[col],n)

In [15]: def f(x, *b):
             return x[(np.logical_and(*b))]

In [16]: b1 = b(df, 'col1', ge, 1)

In [17]: b2 = b(df, 'col1', le, 1)

In [18]: f(df, b1, b2)
Out[18]: 
   col1  col2
1     1    11

更新:pandas 0.13には、次のような列名が有効な識別子であると想定して、この種のユースケースのクエリメソッドがあります(シーンの背後でnumexprを使用するため、大きなフレームではより効率的です)。

In [21]: df.query('col1 <= 1 & 1 <= col1')
Out[21]:
   col1  col2
1     1    11

1
あなたの権利、ブール値はデータのコピーを作成しないため、より効率的です。ただし、私のシナリオは、あなたの例よりも少しトリッキーです。私が受け取る入力は、適用するフィルターを定義する辞書です。私の例では、次のようなことができますdf[(ge(df['col1'], 1) & le(df['col1'], 1)]。私にとっての問題は、フィルター付きのディクショナリーに多くの演算子が含まれる可能性があり、それらを一緒にチェーンすることが面倒なことです。多分私はそれぞれの中間ブール配列を大きな配列に追加し、それを使っmapand演算子をそれらに適用することができますか?
durden2.0

@ durden2.0ヘルパー関数のアイデアを追加しました。これは、あなたが探しているものに似ていると思います:)
Andy Hayden '29

それは私が思いついたものに非常に似ています!例をありがとう。なぜ単にでf()*bなく取る必要があるのbですか?これはそうですか?のユーザーはf()まだオプションのoutパラメータを使用できますlogical_and()か?これは別の小さな疑問につながります。配列経由で渡すことと、out()返されたものを使用することのパフォーマンス上の利点/トレードオフとは何logical_and()ですか?再度、感謝します!
durden2.0

気にしないで、私は十分近くに見えませんでした。*bあなたは二つの配列を渡しているので、必要であるb1b2し、呼び出し時にあなたがそれらを解凍する必要がありますlogical_and。しかし、他の問題はまだ残っています。outパラメータを介して配列に渡すと、logical_and()その戻り値を使用するだけでなく、パフォーマンス上の利点はありますか?
durden2.0

2
@dwandersonでは、複数の条件に対してnp.logical_and.reduceに条件のリストを渡すことができます。例:np.logical_and.reduce([df ['a'] == 3、df ['b']> 10、df ['c']。isin(1,3,5)])
Kuzenbo

39

条件をチェーンすると長い行が作成されますが、pep8では推奨されません。.queryメソッドを使用すると、文字列の使用が強制されます。これは、強力ですが非Pythonicであり、あまり動的ではありません。

各フィルターが配置されたら、1つのアプローチは

import numpy as np
import functools
def conjunction(*conditions):
    return functools.reduce(np.logical_and, conditions)

c_1 = data.col1 == True
c_2 = data.col2 < 64
c_3 = data.col3 != 4

data_filtered = data[conjunction(c1,c2,c3)]

np.logicalは動作し、高速ですが、functools.reduceによって処理される3つ以上の引数を取りません。

これにはまだいくつかの冗長性があることに注意してください:a)ショートカットはグローバルレベルでは発生しませんb)個々の条件のそれぞれは初期データ全体で実行されます。それでも、これは多くのアプリケーションにとって十分効率的であり、非常に読みやすいと思います。

np.logical_or代わりに次のコマンドを使用して、論理和(条件の1つのみが真である必要がある)を作成することもできます。

import numpy as np
import functools
def disjunction(*conditions):
    return functools.reduce(np.logical_or, conditions)

c_1 = data.col1 == True
c_2 = data.col2 < 64
c_3 = data.col3 != 4

data_filtered = data[disjunction(c1,c2,c3)]

1
さまざまな数の条件に対してこれを実装する方法はありますか?私はそれぞれを追加しようとしたc_1c_2c_3...、c_nリストにし、次に渡すdata[conjunction(conditions_list)]が、エラーが取得ValueError: Item wrong length 5 instead of 37.も試みたdata[conjunction(*conditions_list)]が、私はとは異なる結果を得るdata[conjunction(c_1, c_2, c_3, ... c_n )]ことを確認何が起こっているではありません、。
user5359531 2017

他の場所でエラーの解決策を見つけました。data[conjunction(*conditions_list)]データフレームをリストにパックし、適切な場所にリスト
user5359531

1
私は上記の答えにだらしないバージョンでコメントを残したところ、あなたの答えに気づきました。とてもきれいで、とても気に入っています!
dwanderson 2017

これは素晴らしい答えです!
チャーリークラウン

1
私が使用したい:df[f_2 & f_3 & f_4 & f_5 ]f_2 = df["a"] >= 0その機能についてなどありません必要...(高階関数の素敵な使用していますが...)
A. RABUS

19

最もシンプルなソリューション:

使用する:

filtered_df = df[(df['col1'] >= 1) & (df['col1'] <= 5)]

別の例、Feb-2018に属する値のデータフレームをフィルタリングするには、以下のコードを使用します

filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]

定数ではなく変数を使用しています。エラーが発生しました。df [df []] [df []]は警告メッセージを出しますが、正しい答えを与えます。
Nguai al

8

以来パンダ0.22アップデート、比較オプションは次のように使用できます。

  • gt(より大きい)
  • lt(より小さい)
  • eq(等しい)
  • ne(等しくない)
  • ge(以上)

などなど。これらの関数はブール配列を返します。それらを使用する方法を見てみましょう:

# sample data
df = pd.DataFrame({'col1': [0, 1, 2,3,4,5], 'col2': [10, 11, 12,13,14,15]})

# get values from col1 greater than or equals to 1
df.loc[df['col1'].ge(1),'col1']

1    1
2    2
3    3
4    4
5    5

# where co11 values is better 0 and 2
df.loc[df['col1'].between(0,2)]

 col1 col2
0   0   10
1   1   11
2   2   12

# where col1 > 1
df.loc[df['col1'].gt(1)]

 col1 col2
2   2   12
3   3   13
4   4   14
5   5   15

2

なぜこれをしませんか?

def filt_spec(df, col, val, op):
    import operator
    ops = {'eq': operator.eq, 'neq': operator.ne, 'gt': operator.gt, 'ge': operator.ge, 'lt': operator.lt, 'le': operator.le}
    return df[ops[op](df[col], val)]
pandas.DataFrame.filt_spec = filt_spec

デモ:

df = pd.DataFrame({'a': [1,2,3,4,5], 'b':[5,4,3,2,1]})
df.filt_spec('a', 2, 'ge')

結果:

   a  b
 1  2  4
 2  3  3
 3  4  2
 4  5  1

列aが> = 2でフィルターされていることがわかります。

これは、演算子チェーンよりもわずかに高速です(タイピング時間であり、パフォーマンスではありません)。もちろん、インポートをファイルの先頭に置くこともできます。


1

eは、リストまたは反復可能でない列の値に基づいて行を選択することもできます。前と同じようにブール変数を作成しますが、ここでは〜を前に置くことでブール変数を無効にします。

例えば

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