问题:将多个过滤器应用于pandas DataFrame或Series的有效方法
我有一种情况,用户想要将多个过滤器应用于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。
回答 0
熊猫(和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
链接条件会产生长行,而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)每个单独条件都在整个初始数据上运行。不过,我希望它对于许多应用程序都足够有效,并且可读性强。
回答 2
最简单的解决方案:
用:
filtered_df = df[(df['col1'] >= 1) & (df['col1'] <= 5)]另一个示例,要过滤数据框以查找属于Feb-2018的值,请使用以下代码
filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]回答 3
由于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回答 4
为什么不这样做呢?
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。
这比操作员链接略快(键入时间,而不是性能)。您当然可以将导入文件放在文件顶部。
回答 5
e还可以基于不在列表中或不可迭代的列的值来选择行。我们将像以前一样创建布尔变量,但是现在我们将〜放在前面来否定布尔变量。
例如
list = [1, 0]
df[df.col1.isin(list)]
