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

239

我正在通过两列中的值筛选数据框中的行。

因某种原因,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个回答

377

正如您所看到的,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 的习惯。


75

虽然回答有些晚,但你也可以使用query()函数, 例如:

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

22

一些数理逻辑理论

"非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)]

1
根据德摩根定理,(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)]       

这基本上与被接受的答案相同。 - rachwa
你在两年后重新发布了已被接受的解决方案中的一行代码。请不要这样做 - 这对讨论没有任何帮助。 - NineTails

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接