pandas:索引数据框时多条件-意外行为

246 投票
5 回答
661243 浏览
提问于 2025-04-17 23:31

我正在通过两个列中的值来过滤数据框中的行。

但不知为什么,OR 操作符的表现就像我期待的 AND 操作符一样,反之亦然。

这是我的测试代码:

df = pd.DataFrame({'a': range(5), 'b': range(5) })

# let's insert some -1 values
df['a'][1] = -1
df['b'][1] = -1
df['a'][3] = -1
df['b'][4] = -1

df1 = df[(df.a != -1) & (df.b != -1)]
df2 = df[(df.a != -1) | (df.b != -1)]

print(pd.concat([df, df1, df2], axis=1,
                keys = [ 'original df', 'using AND (&)', 'using OR (|)',]))

这是结果:

      original df      using AND (&)      using OR (|)    
             a  b              a   b             a   b
0            0  0              0   0             0   0
1           -1 -1            NaN NaN           NaN NaN
2            2  2              2   2             2   2
3           -1  3            NaN NaN            -1   3
4            4 -1            NaN NaN             4  -1

[5 rows x 6 columns]

如你所见,AND 操作符会删除每一行中至少有一个值等于 -1 的行。而 OR 操作符则需要两个值都等于 -1 才会删除它们。我本来期待的结果正好相反。有人能解释一下这种行为吗?

我使用的是 pandas 0.13.1。

5 个回答

1

使用括号

如果你来到这个页面是因为过滤操作没有给出正确的结果,尽管条件在逻辑上是正确的,那么首先要检查的就是你是否使用了括号来分隔条件。

举个例子,如果你想过滤掉列 'a''b' 中值不等于 -1 的行,那么写以下代码

df[df['a'] != -1 & df['b'] != -1]      # <--- forgot parenthesis

会产生完全意想不到的输出,因为 &/| 的优先级比比较运算符如 !=/== 等要高。你可以通过使用括号分别评估每个条件来获得正确的输出:

df[(df['a'] != -1) & (df['b'] != -1)]  # <--- used parentheses

注意,@Pedro的回答 使用 query() 方法消除了这个需求,因为在 query 中评估的数值表达式里,比较运算符实际上是先于 and/or 等被评估的。


编写正确的逻辑表达式

根据德摩根定律,(i) 取并集的否定是各自否定的交集,(ii) 取交集的否定是各自否定的并集,也就是说,

A AND B <=> not A OR not B
A OR B  <=> not A AND not B

如果目标是

删除每一行中至少有一个值等于 -1 的行

你可以使用 AND 运算符来识别要保留的行,或者使用 OR 运算符来识别要删除的行。

# select rows where both a and b values are not equal to -1
df2_0 = df[df['a'].ne(-1) & df['b'].ne(-1)]

# index of rows where at least one of a or b equals -1
idx = df.index[df.eval('a == -1 or b == -1')]
# drop `idx` rows
df2_1 = df.drop(idx)

df2_0.equals(df2_1) # True

另一方面,如果目标是

删除每一行中两个值都等于 -1 的行

你需要做完全相反的事情;要么使用 OR 运算符来识别要保留的行,要么使用 AND 运算符来识别要删除的行。

1

你可以试试下面这个:

df1 = df[(df['a'] != -1) & (df['b'] != -1)]       
23

这里有一点数学逻辑理论

"不是a并且不是b""不是(a或者b)"是一样的,所以:

"a不是-1并且b不是-1"等同于"不是(a是-1或者b是-1)",这就是"(a是-1或者b是-1)"的相反结果。

所以如果你想要得到完全相反的结果,df1和df2应该如下:

df1 = df[(df.a != -1) & (df.b != -1)]
df2 = df[(df.a == -1) | (df.b == -1)]
78

虽然回答得有点晚,但你也可以使用 query() 这个方法,比如:

df_filtered = df.query('a == 4 & b != 2')
383

如你所见,AND运算符会删除每一行中至少有一个值等于-1的记录。而OR运算符则要求两个值都等于-1才会删除它们。

没错。记住,你写条件时是为了说明你想要保留什么,而不是想要删除什么。对于df1

df1 = df[(df.a != -1) & (df.b != -1)]

你是在说“保留那些df.a不等于-1并且df.b也不等于-1的行”,这就等于删除每一行中至少有一个值是-1的记录。

对于df2

df2 = df[(df.a != -1) | (df.b != -1)]

你是在说“保留那些df.a或者df.b不等于-1的行”,这就等于删除那些两个值都是-1的行。

另外,像df['a'][1] = -1这样的链式访问可能会让你遇到麻烦。养成使用.loc.iloc的习惯会更好。

撰写回答