将多个filter应用于pandas DataFrame或Series的高效方法

我有一个场景,用户想要应用几个filterpandasDataFrame或系列对象。 从本质上讲,我想要有效地链接一堆由用户在运行时指定的过滤(比较操作)。

filter应该是添加剂(也就是每个应用应该缩小结果)。

我目前正在使用reindex()但是这每次创build一个新的对象,并复制底层的数据(如果我正确理解文档)。 所以,在过滤大型的Series或者DataFrame时,这可能是非常低效的。

我在想,使用apply()map()或类似的东西可能会更好。 对于pandas来说,我还是很新的,尽pipe如此还是试图把我的头围绕在一切之上。

TL; DR

我想采取以下forms的字典,并将每个操作应用到给定的Series对象,并返回一个“过滤的”Series对象。

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

长例子

我将从我目前所拥有的一个例子开始,只是过滤一个Series对象。 以下是我目前使用的function:

  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 

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

此外,我想扩大这个,所以传入的字典可以包括运营商的列和基于input字典过滤整个dataframe。 不过,我假设系列的任何作品可以很容易地扩展到一个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 

更新: pandas0.13有一个查询方法这种用例,假设列名是有效的标识符下面的作品(并可以更有效的大型框架,因为它使用numexpr幕后):

 In [21]: df.query('col1 <= 1 & 1 <= col1') Out[21]: col1 col2 1 1 11 In [22]: df.query("col1 <= 1 and 1 <= df['col1']")  # use df[] syntax if not a valid identifier Out[22]: col1 col2 1 1 11 

链条条件造成排长队,这是pep8阻止。 使用.query方法强制使用string,这是强大的,但不强调,而不是非常dynamic的。

一旦每个filter就位,一种方法是

 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)每个单独的条件运行在整个初始数据上。 不过,我希望这对于许多应用程序来说足够有效,并且非常可读。

为什么不这样做?

 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') 

结果:

  ab 1 2 4 2 3 3 3 4 2 4 5 1 

您可以看到列“a”已被过滤,其中a> = 2。

这比运算符链稍快(键入时间,不是性能)。 你当然可以把导入的文件的顶部。