如何从另一个pandas数据框中删除一个数据框。

69
如何从另一个 pandas dataframe 中移除一个 dataframe,就像进行集合减法一样:
a=[1,2,3,4,5]
b=[1,5]
a-b=[2,3,4]

现在我们有两个Pandas数据框,如何从df1中删除df2:

In [5]: df1=pd.DataFrame([[1,2],[3,4],[5,6]],columns=['a','b'])
In [6]: df1
Out[6]:
   a  b
0  1  2
1  3  4
2  5  6


In [9]: df2=pd.DataFrame([[1,2],[5,6]],columns=['a','b'])
In [10]: df2
Out[10]:
   a  b
0  1  2
1  5  6

那么我们期望df1-df2的结果将是:

In [14]: df
Out[14]:
   a  b
0  3  4

如何做?

谢谢。


可能是pandas的集合差异问题的重复。 - AKS
@176coding 请在您的真实数据集上对我们的答案进行时间测试 - 我很想知道哪个更快。 - knagaev
10个回答

114

解决方案

使用pd.concat,然后跟着drop_duplicates(keep=False)

pd.concat([df1, df2, df2]).drop_duplicates(keep=False)

看起来好像

   a  b
1  3  4

说明

pd.concat函数将两个DataFrame拼接在一起。如果有任何重叠,它将被drop_duplicates方法捕获并去除重复行。但是,默认情况下,drop_duplicates只保留第一条观测结果,删除其余所有的重复观测结果。在这种情况下,我们希望删除所有的重复项。因此,使用keep=False参数实现这一操作。

特别提醒:当只有一个df2时,df2中没有出现在df1中的行不会被视为重复行而会被保留。当df2df1的子集时,这种仅使用一个df2的解决方案可以正常工作。但是,如果我们将df2复制一次进行拼接,就有保证可以去除所有重复项。


谢谢,它有效,并且我们可以使用 pd.concat(df1,df2).drop_duplicates(keep=False) 或者 df1.append(df2).drop_duplicates(keep=False) - 176coding
2
@piRSquared 您的答案不正确 - 您计算的是对称差,而不是简单差。 - knagaev
3
这个不起作用。如果您连接的df具有未包含在已检查df中的其他记录,则它们将被添加到其中... - clg4
2
主要的数据框为df1。我正在将3个数据框连接起来,其中df1连接一次,df2连接两次。由于df2被连接了两次,根据定义,其中的所有内容都将被复制。因此,删除重复项将不会留下任何在df2中存在的内容。 - piRSquared
6
此外,这种方法无法解决的唯一问题是初始数据框中是否已存在重复项。这假设初始数据框中没有重复项。 - piRSquared
显示剩余3条评论

17

您可以使用.duplicated,这样做的好处是相当表达:

%%timeit
combined = df1.append(df2)
combined[~combined.index.duplicated(keep=False)]

1000 loops, best of 3: 875 µs per loop

作比较:

%timeit df1.loc[pd.merge(df1, df2, on=['a','b'], how='left', indicator=True)['_merge'] == 'left_only']

100 loops, best of 3: 4.57 ms per loop


%timeit pd.concat([df1, df2, df2]).drop_duplicates(keep=False)

1000 loops, best of 3: 987 µs per loop


%timeit df2[df2.apply(lambda x: x.value not in df2.values, axis=1)]

1000 loops, best of 3: 546 µs per loop
总之,使用np.array比较最快。不需要在那里使用.tolist()

小心:只有在被减去的数据框中仅包含第一个数据框中已包含的数据时,此方法才有效。但我喜欢这个答案。 - Florian Fasmeyer
这些中哪个也适用于Series呢? - jtlz2

13

获取包含所有在DF1中且不在DF2中的记录的数据框

DF=DF1[~DF1.isin(DF2)].dropna(how = 'all')

如此优雅和Pythonic的解决方案。对我很有效。谢谢。 - Selim
我一点也不认为熊猫的语法是符合Python风格的,但这似乎是迄今为止最好的解决方案,无需销毁原始数据框的副本来删除另一个数据框的内容。先生,我给您点赞了。 - E.Serra

6
一种集合逻辑方法。将df1df2的行转化为集合。然后使用集合减法来定义新的DataFrame
idx1 = set(df1.set_index(['a', 'b']).index)
idx2 = set(df2.set_index(['a', 'b']).index)

pd.DataFrame(list(idx1 - idx2), columns=df1.columns)

   a  b
0  3  4

4

当你的df_to_drop是主数据框data的一个子集时,这个解决方案是可行的。

data_clean = data.drop(df_to_drop.index)


2

一个掩码方法

df1[df1.apply(lambda x: x.values.tolist() not in df2.values.tolist(), axis=1)]

   a  b
1  3  4

不需要 .tolist() - Stefan

2

以下是与问题相关的合并df1和df2的代码:

使用'indicator'参数

In [74]: df1.loc[pd.merge(df1, df2, on=['a','b'], how='left', indicator=True)['_merge'] == 'left_only']
Out[74]: 
   a  b
1  3  4

2
解释一下会让这个答案更丰富。您提到该方法成功的关键是“indicator”参数,将其设置为true将向每行添加位置信息,在最后一步中使用您的解决方案进行过滤,仅保留出现在左数据帧中的行(indicator == 'left_only')。 - Dannid

0

如果df1包含重复项+保留索引的解决方案。

这是piRSquared答案的修改版本,它保留了df1中不出现在df2中的重复项,同时保持索引。

df1[df1.apply(lambda x: (x == pd.concat([df1.drop_duplicates(), df2, df2]).drop_duplicates(keep=False)).all(1).any(), axis=1)]

如果你的数据框很大,你可能想要存储结果。
pd.concat([df1.drop_duplicates(), df2, df2]).drop_duplicates(keep=False)

在调用 df1.apply 前将其存储在变量中。


0

最简单的方法是使用索引。

  1. 将df1和df2连接并重置它们的索引。

    df = df1.concat(df2)
    df.reset_index(inplace=True)

  2. 例如:
    这将给df2索引

    indexes_df2 = df.index[ (df["a"].isin(df2["a"]) ) & (df["b"].isin(df2["b"]) ) result_index = df.index[~index_df2] result_data = df.iloc[ result_index,:]

希望这对新读者有所帮助,尽管问题发布了一段时间 :)


0
我认为第一个 tolist() 需要被移除,但是保留第二个。
df1[df1.apply(lambda x: x.values() not in df2.values.tolist(), axis=1)]

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