1. ホーム
  2. パイソン

[解決済み】pandasのDataFrameやSeriesに複数のフィルタを適用する効率的な方法

2022-04-18 23:19:52

質問

PandasのDataFrameやSeriesオブジェクトに複数のフィルタを適用したいシナリオがあります。 基本的には、ユーザーが実行時に指定したフィルタリング(比較演算)を効率的に連鎖させたいのです。

フィルタは加算式でなければならない(つまり、適用するごとに結果が狭くなるようにしなければならない)。

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

私が考えているのは apply() , map() などの方がいいかもしれません。 私はパンダの初心者なので、まだすべてを理解しようとしています。

TL;DR

以下の形式の辞書を取得し、与えられたSeriesオブジェクトに各操作を適用して、「フィルタリングされた」Seriesオブジェクトを返したい。

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

長い例

まずは現在持っているもので、Seriesオブジェクトを1つだけフィルタリングする例から紹介します。 以下は、私が現在使用している関数である。

   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全体をフィルタリングできるようにしたいと思います。 しかし、Seriesで動作するものは、DataFrameに簡単に拡張できるものと思っています。

どのように解決するのですか?

Pandas(と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