找出两个数据框之间的差异。

264

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

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

enter image description here


6
要完成这个任务,最简单的方法取决于你的数据帧的结构(例如是否可以使用索引等)。这是为什么在 pandas 问题中应始终包含一个可重现示例的好示例。 - cmaher
1
我已经添加了数据框示例图像。 - userPyGeo
类似于 https://dev59.com/AWIj5IYBdhLWcg3weFCq - SpeedCoder5
19个回答

3

当一方有重复项而另一方至少有一个重复项时,我在处理重复项时遇到了问题,因此我使用了 Counter.collections 进行更好的差异比较,确保两侧具有相同的计数。这不会返回重复项,但如果两侧计数相同,则不会返回任何结果。

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

3

此处所述,提到了

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 with drop_duplicates
df1=df1.drop_duplicates(keep="first") 
df2=df2.drop_duplicates(keep="first") 
pd.concat([df1,df2]).drop_duplicates(keep=False)

1
问题的作者要求返回df1中所有不在df2中的值。因此,df1 [~ df1.apply(tuple,1).isin(df2.apply(tuple,1))]即使在这种情况下也是正确答案。如果您想获取仅在df1或df2中而不是两者都存在的值,则建议的方法是正确的(但需要从原始数据帧中删除重复项)。 - ira

1
通过索引找到差异。假设df1是df2的子集,并且在子集时索引被保留。
df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

# Example

df1 = pd.DataFrame({"gender":np.random.choice(['m','f'],size=5), "subject":np.random.choice(["bio","phy","chem"],size=5)}, index = [1,2,3,4,5])

df2 =  df1.loc[[1,3,5]]

df1

 gender subject
1      f     bio
2      m    chem
3      f     phy
4      m     bio
5      f     bio

df2

  gender subject
1      f     bio
3      f     phy
5      f     bio

df3 = df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

df3

  gender subject
2      m    chem
4      m     bio


1
定义我们的数据框架:
df1 = pd.DataFrame({
    'Name':
        ['John','Mike','Smith','Wale','Marry','Tom','Menda','Bolt','Yuswa'],
    'Age':
        [23,45,12,34,27,44,28,39,40]
})

df2 = df1[df1.Name.isin(['John','Smith','Wale','Tom','Menda','Yuswa'])

df1

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

df2

    Name  Age
0   John   23
2  Smith   12
3   Wale   34
5    Tom   44
6  Menda   28
8  Yuswa   40

两者之间的区别是:

df1[~df1.isin(df2)].dropna()

    Name   Age
1   Mike  45.0
4  Marry  27.0
7   Bolt  39.0

在哪里使用:

  • df1.isin(df2) 返回 df1 中也存在于 df2 中的行。
  • ~(逐元素逻辑非)在表达式前面否定结果,因此我们得到 df1不在 df2 中的元素——两者之间的差异。
  • .dropna() 删除具有 NaN 的行,呈现所需的输出

注意:仅当 len(df1) >= len(df2) 时才有效。如果 df2df1 更长,则可以反转表达式:df2[~df2.isin(df1)].dropna()


1

我发现 deepdiff 库是一个非常好用的工具,如果需要不同的详细信息或者顺序方面的考虑,对于数据帧也可以进行扩展。你可以试着使用 to_dict('records')to_numpy() 和其他导出方式来进行比较:

import pandas as pd
from deepdiff import DeepDiff

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

df2 = df1[df1.Name.isin(['John','Smith','Wale','Tom','Menda','Yuswa'])]

DeepDiff(df1.to_dict(), df2.to_dict())
# {'dictionary_item_removed': [root['Name'][1], root['Name'][4], root['Name'][7], root['Age'][1], root['Age'][4], root['Age'][7]]}

1

另一个可能的解决方案是使用numpy广播

df1[np.all(~np.all(df1.values == df2.values[:, None], axis=2), axis=0)]

输出:

    Name  Age
1   Mike   45
4  Marry   27
7   Bolt   39

1

这是@liangli的好方法的轻微变化,不需要更改现有数据帧的索引:

newdf = df1.drop(df1.join(df2.set_index('Name').index))

0
试试这个: df_new = df1.merge(df2, how='outer', indicator=True).query('_merge == "left_only"').drop('_merge', 1) 它将生成一个新的数据框,其中包含差异:存在于df1中但不存在于df2中的值。

0
使用lambda函数,您可以过滤掉_merge值为“left_only”的行,以获取df1中所有缺失于df2的行。
df3 = df1.merge(df2, how = 'outer' ,indicator=True).loc[lambda x :x['_merge']=='left_only']
df

聪明的你,也可以使用query("_merge == 'left_only'")代替loc中的lambda表达式。df1.merge(df2, how = 'outer' ,indicator=True).query("_merge == 'left_only'")" - dimButTries

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