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

The filters should be additive (aka each one applied should narrow results). I'm currently using reindex() (as below) but this creates a new object each time and copies the underlying data (if I understand the documentation correctly). I want to avoid this unnecessary copying as it will be really inefficient when filtering a big Series or DataFrame. I'm thinking that using apply(), map(), or something similar might be better. I'm pretty new to Pandas though so still trying to wrap my head around everything. Also, I would like to expand this so that the dictionary passed in can include the columns to operate on and filter an entire DataFrame based on the input dictionary. However, I'm assuming whatever works for a Series can be easily expanded to a DataFrame.

博士TL;

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

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


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

如果你想为此编写helper函数,可以考虑以下几点:

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

连锁条件会造成长队,而这是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(c_1,c_2,c_3)]

np。Logical操作on和fast,但不接受两个以上的参数,由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)]

为什么不这样做呢?

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。

这比操作符链接稍微快一点(输入时间,而不是性能)。当然,您可以将导入放在文件的顶部。


最简单的解决方案:

Use:

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

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

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

自从熊猫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 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

E还可以基于不在列表或任何可迭代对象中的列的值选择行。我们将像以前一样创建布尔变量,但是现在我们将通过在前面放置~来对布尔变量求反。

例如

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

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

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