pandas:是不是可以使用任意长的布尔标准过滤数据帧?

Posted

技术标签:

【中文标题】pandas:是不是可以使用任意长的布尔标准过滤数据帧?【英文标题】:pandas: Is it possible to filter a dataframe with arbitrarily long boolean criteria?pandas:是否可以使用任意长的布尔标准过滤数据帧? 【发布时间】:2016-05-20 02:40:03 【问题描述】:

如果您确切知道要如何过滤数据框,则解决方案很简单:

df[(df.A == 1) & (df.B == 1)]

但是,如果您正在接受用户输入并且事先不知道用户想要使用多少个标准呢?例如,用户想要一个过滤数据框,其中列 [A, B, C] == 1。是否可以执行以下操作:

def filterIt(*args, value):
    return df[(df.*args == value)]

所以如果用户调用filterIt(A, B, C, value=1),它会返回:

df[(df.A == 1) & (df.B == 1) & (df.C == 1)]

【问题讨论】:

可能的欺骗:***.com/questions/11869910/… ***.com/questions/13611065/… 您是否总是想比较具有相同值的不同列? (在这种情况下是 1 个?) 【参考方案1】:

这是另一种方法。它更简洁、性能更高,并且具有columns 可以为空的优点(在这种情况下会返回整个数据帧)。

def filter(df, value, *columns):
    return df.loc[df.loc[:, columns].eq(value).all(axis=1)]

说明

    values = df.loc[:, columns] 只选择我们感兴趣的列。 masks = values.eq(value) 给出一个布尔数据框,表示与目标值相等。 mask = masks.all(axis=1) 跨列应用 AND(返回索引掩码)。请注意,您可以将 masks.any(axis=1) 用于 OR。 return df.loc[mask] 将索引掩码应用于数据框。

演示

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(0, 2, (100, 3)), columns=list('ABC'))

# both columns
assert np.all(filter(df, 1, 'A', 'B') == df[(df.A == 1) & (df.B == 1)])

# no columns
assert np.all(filter(df, 1) == df)

# different values per column
assert np.all(filter(df, [1, 0], 'A', 'B') == df[(df.A == 1) & (df.B == 0)])

替代方案

对于少量列 (steven's answer 的解决方案比上述解决方案性能更高,但灵活性较差。照原样,它不适用于空的 columns 集,也无法在每列使用不同的值。

from operator import and_

def filter(df, value, *columns):
    return df.loc[reduce(and_, (df[column] == value for column in columns))]

通过键检索 Series 对象 (df[column]) 比围绕列子集构造 DataFrame 对象 (df.loc[:, columns]) 快得多。

In [4]: %timeit df['A'] == 1
100 loops, best of 3: 17.3 ms per loop

In [5]: %timeit df.loc[:, ['A']] == 1
10 loops, best of 3: 48.6 ms per loop

尽管如此,当处理大量列时,这种加速变得可以忽略不计。瓶颈变成了将掩码与在一起,reduce(and_, ...) 比内置的 Pandas all(axis=1) 慢得多。

【讨论】:

【参考方案2】:

这很混乱,但它似乎有效。

import operator

def filterIt(value,args):
    stuff = [getattr(b,thing) == value for thing in args]
    return reduce(operator.and_, stuff)

a = 'A':[1,2,3],'B':[2,2,2],'C':[3,2,1]
b = pd.DataFrame(a)
filterIt(2,['A','B','C'])

0    False
1     True
2    False
dtype: bool



(b.A == 2) & (b.B == 2) & (b.C ==2)

0    False
1     True
2    False
dtype: bool

【讨论】:

return reduce(operator.and_, stuff) 是您要查找的内容(而不是 for 循环)。【参考方案3】:

感谢各位的帮助。在发现 df.query() 之后,我想出了类似于 Marius 的东西:

def makeQuery(cols, equivalence=True, *args):
operator = ' == ' if equivalence else ' != '
query = ''
for arg in args:
    for col in cols:
        query = query + "()".format(col, operator, arg) + ' & '

return query[:-3]


query = makeQuery([A, B, C], False, 1, 2)

查询的内容是一个字符串:

(A != 1) & (B != 1) & (C != 1) & (A != 2) & (B != 2) & (C != 2) 

可以传递给 df.query(query)

【讨论】:

'&'.join('()'.format(col, operator, arg) for col in cols) 可以替换你的内循环。【参考方案4】:

我认为最优雅的方法是使用df.query(),您可以在其中建立一个包含所有条件的字符串,例如:

import pandas as pd
import numpy as np

cols = 
for col in ('A', 'B', 'C', 'D', 'E'):
    cols[col] = np.random.randint(1, 5, 20)
df = pd.DataFrame(cols)

def filter_df(df, filter_cols, value):
    conditions = []
    for col in filter_cols:
        conditions.append('c == v'.format(c=col, v=value))
    query_expr = ' and '.join(conditions)
    print('querying with: q'.format(q=query_expr))
    return df.query(query_expr)

示例输出(由于随机生成的数据,您的结果可能会有所不同):

filter_df(df, ['A', 'B'], 1)
querying with: A == 1 and B == 1
    A  B  C  D  E
6   1  1  1  2  1
11  1  1  2  3  4

【讨论】:

以上是关于pandas:是不是可以使用任意长的布尔标准过滤数据帧?的主要内容,如果未能解决你的问题,请参考以下文章

Pandas,用于布尔索引的 loc 与非 loc

查询pandas中的timedelta列,过滤行

数据视图行过滤器未按预期工作

Pandas 按功能过滤数据帧行

Python/Pandas:通过匹配的索引标准对 Dataframe 进行子集化

在使用 Pandas 的 Python 中,是不是可以逐块读取 4B 行并针对内存中已经存在的 30M 行数据帧过滤每个卡盘?