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

Posted

技术标签:

【中文标题】将多个过滤器应用于 pandas DataFrame 或 Series 的有效方法【英文标题】:Efficient way to apply multiple filters to pandas DataFrame or Series 【发布时间】:2012-11-16 15:47:19 【问题描述】:

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

过滤器应该是相加的(也就是每个应用都应该缩小结果)。

我目前正在使用reindex(),但这每次都会创建一个新对象并复制基础数据(如果我正确理解文档的话)。因此,在过滤大型 Series 或 DataFrame 时,这可能非常低效。

我认为使用apply()map() 或类似的东西可能会更好。虽然我对 Pandas 还很陌生,但我仍然想把所有东西都包起来。

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。但是,我假设适用于 Series 的任何东西都可以轻松扩展为 DataFrame。

【问题讨论】:

另外,我完全意识到这种解决问题的方法可能还很遥远。所以也许重新思考整个方法会很有用。我只是想让用户在运行时指定一组过滤操作并执行它们。 我想知道 pandas 是否可以做与 R 中的 data.table 类似的事情:df[col1=1] df.querypd.eval 似乎很适合您的用例。有关pd.eval() 系列函数、它们的特性和用例的信息,请访问Dynamic Expression Evaluation in pandas using pd.eval()。 【参考方案1】:

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 has a query method 对于这些用例,假设列名是有效的标识符,以下工作(并且对于大帧可能更有效,因为它在幕后使用 numexpr):

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

【讨论】:

你的权利,布尔值更有效,因为它不会复制数据。但是,我的情况比您的示例更棘手。我收到的输入是一个字典,定义了要应用的过滤器。我的示例可以执行类似df[(ge(df['col1'], 1) &amp; le(df['col1'], 1)] 的操作。对我来说,真正的问题是带有过滤器的字典可能包含很多运算符,并且将它们链接在一起很麻烦。也许我可以将每个中间布尔数组添加到一个大数组中,然后使用mapand 运算符应用于它们? @durden2.0 我添加了一个辅助函数的想法,我认为它与您正在寻找的类似:) 这看起来非常接近我的想法!谢谢你的例子。为什么f() 需要取*b 而不仅仅是b?这样f() 的用户是否仍然可以使用logical_and() 的可选out 参数?这就引出了另一个小问题。通过out() 传入数组与使用从logical_and() 返回的数组相比,性能优势/权衡是什么?再次感谢! 没关系,我看的不够近。 *b 是必要的,因为您要传递两个数组 b1b2,并且在调用 logical_and 时需要将它们解包。然而,另一个问题仍然存在。通过out 参数将数组传递给logical_and() 与仅使用其返回值相比,是否有性能优势? @dwanderson 您可以将条件列表传递给 np.logical_and.reduce 以获得多个条件。示例:np.logical_and.reduce([df['a']==3, df['b']>10,df['c'].isin(1,3,5)])【参考方案2】:

链接条件会产生长行,pep8 不鼓励这样做。 使用 .query 方法强制使用字符串,这很强大但不符合 Python 标准,而且不是很动态。

一旦每个过滤器都到位,一种方法是

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 处理。

请注意,这仍然有一些冗余:a)快捷方式不会发生在全局级别 b)每个单独的条件都在整个初始数据上运行。不过,我希望这对于许多应用程序来说足够高效,而且可读性很强。

您也可以通过使用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)]

【讨论】:

有没有办法针对可变数量的条件实现这一点?我尝试将每个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 )],不知道发生了什么。 在别处找到了错误的解决方案。 data[conjunction(*conditions_list)] 在将数据帧打包成列表后确实有效,unpacking the list in place 我刚刚在上面的答案上留下了一个更草率的版本的评论,然后注意到了你的答案。很干净,很喜欢! 这是一个很好的答案! 我用过:df[f_2 &amp; f_3 &amp; f_4 &amp; f_5 ]f_2 = df["a"] &gt;= 0等。不需要那个函数......(虽然很好地使用了高阶函数......)【参考方案3】:

为什么不这样做?

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”已被过滤,其中 a >=2。

这比运算符链接稍快(键入时间,而不是性能)。您当然可以将导入放在文件的顶部。

【讨论】:

【参考方案4】:

最简单的解决方案:

用途:

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

另一个示例,要过滤数据帧中属于 2018 年 2 月的值,请使用以下代码

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

【讨论】:

我使用的是变量而不是常量。得到错误。 df[df[ ]][df[ ]] 给出警告信息但给出正确答案。【参考方案5】:

由于pandas 0.22 update,比较选项如下:

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 between 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

【讨论】:

【参考方案6】:

e 还可以根据不在列表或任何可迭代的列的值选择行。我们将像以前一样创建布尔变量,但现在我们将通过将 ~ 放在前面来否定布尔变量。

例如

list = [1, 0]
df[df.col1.isin(list)]

【讨论】:

【参考方案7】:

如果您想检查多个列的任何/所有值,您可以这样做:

df[(df[['HomeTeam', 'AwayTeam']] == 'Fulham').any(axis=1)]

【讨论】:

以上是关于将多个过滤器应用于 pandas DataFrame 或 Series 的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

pandas一些基本操作(DataFram和Series)_1

Pandas:将 Lambda 应用于多个数据帧

如何将 5Hz 的低通滤波器应用于 pandas 数据帧?

pandas一些基本操作(DataFram和Series)_3

pandas一些基本操作(DataFram和Series)_4

pandas一些基本操作(DataFram和Series)_2