将多个过滤器应用于 pandas DataFrame 或 Series 的有效方法

新手上路,请多包涵

我有一个场景,用户想要 将多个过滤器应用于 Pandas DataFrame 或 Series 对象。本质上,我想有效地将用户在运行时指定的一堆过滤(比较操作)链接在一起。

  • 过滤器应该是可 的(也就是应用的每个过滤器都应该缩小结果)。
  • 我目前正在使用 reindex() (如下所示)但这每次都会创建一个新对象并复制基础数据(如果我正确理解文档)。我想避免这种不必要的复制,因为在过滤大型 Series 或 DataFrame 时效率非常低。
  • 我认为使用 apply()map() 或类似的东西可能会更好。虽然我对 Pandas 还很陌生,但我仍然在努力思考所有事情。
  • 另外,我想扩展它,以便 传入的字典可以包含要操作的列, 并根据输入字典过滤整个 DataFrame。但是,我假设任何适用于 Series 的东西都可以轻松扩展到 DataFrame。

长话短说

我想采用以下形式的字典并将每个操作应用于给定的 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

同样,我上述方法的“问题”是我认为中间步骤有很多可能不必要的数据复制。

原文由 durden2.0 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 458
2 个回答

Pandas(和 numpy)允许使用 boolean indexing ,这会更有效率:

 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

原文由 Andy Hayden 发布,翻译遵循 CC BY-SA 4.0 许可协议

链接条件会产生长行,PEP8 不鼓励这样做。使用 .query 方法强制使用字符串,它功能强大但非 pythonic 且不是很动态。

一旦每个过滤器就位,一种方法可能是:

 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(c_1,c_2,c_3)]

np.logical 运行速度快,但不超过两个参数,由 functools.reduce 处理。

请注意,这仍然有一些冗余:

  • 捷径不会在全球范围内发生
  • 每个单独的条件都在整个初始数据上运行

不过,我希望这对许多应用程序来说足够高效,而且可读性很强。您还可以使用 np.logical_or 进行析取(其中只有一个条件需要为真):

 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(c_1,c_2,c_3)]

原文由 Gecko 发布,翻译遵循 CC BY-SA 4.0 许可协议

推荐问题