我有两个数据帧df1和df2,其中df2是df1的子集。我如何得到一个新的数据帧(df3),这是两个数据帧之间的差异?

换句话说,一个在df1中所有的行/列都不在df2中的数据帧?


当前回答

也许是一个简单的单行程序,具有相同或不同的列名。即使df2['Name2']包含重复的值也能正常工作。

newDf = df1.set_index('Name1')
           .drop(df2['Name2'], errors='ignore')
           .reset_index(drop=False)

其他回答

import pandas as pd
# given
df1 = pd.DataFrame({'Name':['John','Mike','Smith','Wale','Marry','Tom','Menda','Bolt','Yuswa',],
    'Age':[23,45,12,34,27,44,28,39,40]})
df2 = pd.DataFrame({'Name':['John','Smith','Wale','Tom','Menda','Yuswa',],
    'Age':[23,12,34,44,28,40]})

# find elements in df1 that are not in df2
df_1notin2 = df1[~(df1['Name'].isin(df2['Name']) & df1['Age'].isin(df2['Age']))].reset_index(drop=True)

# output:
print('df1\n', df1)
print('df2\n', df2)
print('df_1notin2\n', df_1notin2)

# df1
#     Age   Name
# 0   23   John
# 1   45   Mike
# 2   12  Smith
# 3   34   Wale
# 4   27  Marry
# 5   44    Tom
# 6   28  Menda
# 7   39   Bolt
# 8   40  Yuswa
# df2
#     Age   Name
# 0   23   John
# 1   12  Smith
# 2   34   Wale
# 3   44    Tom
# 4   28  Menda
# 5   40  Yuswa
# df_1notin2
#     Age   Name
# 0   45   Mike
# 1   27  Marry
# 2   39   Bolt

正如这里提到的 那

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]

是正确的解决方案,但它会产生错误的输出如果

df1=pd.DataFrame({'A':[1],'B':[2]})
df2=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})

在这种情况下,上面的溶液会给出 空数据帧,相反,你应该使用concat方法后,从每个数据帧删除重复。

使用concate和drop_duplicate

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

通过使用drop_duplicate

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

更新:

上面的方法只适用于那些本身没有副本的数据帧。例如:

df1=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})
df2=pd.DataFrame({'A':[1],'B':[2]})

它将输出如下所示,这是错误的

错误输出:

pd.concat([df1, df2]).drop_duplicates(keep=False)
Out[655]: 
   A  B
1  2  3

正确的输出

Out[656]: 
   A  B
1  2  3
2  3  4
3  3  4

如何实现这一目标?

方法一:将isin与tuple结合使用

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]
Out[657]: 
   A  B
1  2  3
2  3  4
3  3  4

方法二:与指标合并

df1.merge(df2,indicator = True, how='left').loc[lambda x : x['_merge']!='both']
Out[421]: 
   A  B     _merge
1  2  3  left_only
2  3  4  left_only
3  3  4  left_only

我在处理副本时遇到了问题,当一边有副本,另一边至少有一个副本时,所以我使用了Counter。集合做一个更好的差异,确保双方有相同的计数。这不会返回副本,但如果双方有相同的计数,则不会返回任何副本。

from collections import Counter

def diff(df1, df2, on=None):
    """
    :param on: same as pandas.df.merge(on) (a list of columns)
    """
    on = on if on else df1.columns
    df1on = df1[on]
    df2on = df2[on]
    c1 = Counter(df1on.apply(tuple, 'columns'))
    c2 = Counter(df2on.apply(tuple, 'columns'))
    c1c2 = c1-c2
    c2c1 = c2-c1
    df1ondf2on = pd.DataFrame(list(c1c2.elements()), columns=on)
    df2ondf1on = pd.DataFrame(list(c2c1.elements()), columns=on)
    df1df2 = df1.merge(df1ondf2on).drop_duplicates(subset=on)
    df2df1 = df2.merge(df2ondf1on).drop_duplicates(subset=on)
    return pd.concat([df1df2, df2df1])
> df1 = pd.DataFrame({'a': [1, 1, 3, 4, 4]})
> df2 = pd.DataFrame({'a': [1, 2, 3, 4, 4]})
> diff(df1, df2)
   a
0  1
0  2

方法1对于有nan的数据帧无效,因为pd.np.nan != pd.np.nan !我不确定这是否是最好的方法,但它可以避免

df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]

它更慢,因为它需要将数据转换为字符串,但由于这个转换pd.np.nan == pd.np.nan。

让我们浏览一下代码。首先,我们将值转换为字符串,并将tuple函数应用于每一行。

df1.astype(str).apply(tuple, 1)
df2.astype(str).apply(tuple, 1)

多亏了这个,我们得到了pd。具有元组列表的系列对象。每个元组包含df1/df2的整行。 然后我们对df1应用isin方法来检查每个元组是否“在”df2中。 结果是pd。带有bool值的系列。如果tuple from df1在df2中,则为True。最后,我们用~符号对结果求反,并对df1进行滤波。长话短说,我们只能从df1中得到那些不在df2中的行。

为了使它更具可读性,我们可以这样写:

df1_str_tuples = df1.astype(str).apply(tuple, 1)
df2_str_tuples = df2.astype(str).apply(tuple, 1)
df1_values_in_df2_filter = df1_str_tuples.isin(df2_str_tuples)
df1_values_not_in_df2 = df1[~df1_values_in_df2_filter]