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


148

我有一种情况,用户想要将多个过滤器应用于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。但是,我假设该系列的所有工作都可以轻松扩展到DataFrame。


另外,我完全意识到,解决该问题的方法可能遥遥无期。因此,重新考虑整个方法可能会很有用。我只想允许用户在运行时指定一组过滤器操作并执行它们。
durden2.0

我想知道熊猫是否可以在R中执行与data.table类似的操作:df [col1 <1 ,,] [col2> = 1]
xappppp

df.querypd.eval似乎是很好的配合你的使用情况。有关pd.eval()功能系列,其功能和使用案例的信息,请访问使用pd.eval()在熊猫中进行动态表达评估
cs95

Answers:


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 2012年

看起来非常接近我的想法!谢谢你的例子。为什么f()需要服用*b而不仅仅是服用b?这是否是的用户f()仍可以使用可选out参数logical_and()?这导致了另一个小问题。out()与通过使用返回的数组相比,通过数组传递的性能收益/权衡是什么logical_and()?再次感谢!
durden2.0

没关系,我看起来不够近。*b之所以必须这样做,是因为您要传递两个数组b1b2并且在调用时需要解压缩它们logical_and。但是,另一个问题仍然存在。通过out参数将数组传递给logical_and()vs仅使用其返回值是否对性能有好处?
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方法会强制使用字符串,该字符串功能强大但不具有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.ologic可以运行并且运行很快,但是不超过两个参数,这些参数由functools.reduce处理。

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


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

在其他地方找到错误的解决方案。data[conjunction(*conditions_list)]在将数据帧打包到列表中并在适当的位置解
user5359531

1
我只是在上面的答案上留下了一个草率的评论,然后注意到了您的答案。很干净,我很喜欢!
dwanderson

这是一个很好的答案!
查理·皇冠

1
我曾经使用过:df[f_2 & f_3 & f_4 & f_5 ]with 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

由于pandas 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)]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.