207

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

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

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

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

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

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

另外,我想扩展它,以便传入的字典可以包含要操作的列,并根据输入字典过滤整个 DataFrame。但是,我假设适用于 Series 的任何东西都可以轻松扩展为 DataFrame。

4

7 回答 7

320

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
于 2012-11-28T23:38:41.263 回答
54

链接条件会产生长行,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)]
于 2015-06-11T10:28:54.297 回答
33

最简单的解决方案:

采用:

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

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

filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]
于 2018-02-27T12:16:04.507 回答
13

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
于 2018-03-17T12:11:05.493 回答
5

为什么不这样做?

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。

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

于 2016-05-18T10:06:20.270 回答
3

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

例如

list = [1, 0]
df[df.col1.isin(list)]
于 2019-03-05T08:34:26.180 回答
3

如果要检查任何/所有多个列的值,可以执行以下操作:

df[(df[['HomeTeam', 'AwayTeam']] == 'Fulham').any(axis=1)]
于 2020-10-30T11:53:25.923 回答