ChatGPT解决这个技术问题 Extra ChatGPT

Python Pandas - 查找两个数据帧之间的差异

我有两个数据框 df1 和 df2,其中 df2 是 df1 的子集。如何获得一个新的数据框(df3),这是两个数据框之间的区别?

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

https://i.stack.imgur.com/aOCGb.png

最简单的方法将取决于您的数据框的结构(即是否可以使用索引等)。这是一个很好的例子,说明了为什么您应该始终在 pandas 问题中包含 reproducible example
我添加了数据框示例图像

b
bryant1410

通过使用 drop_duplicates

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

Update :

上述方法仅适用于那些本身还没有重复的数据帧。例如:

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

如何做到这一点?

方法 1:将 isintuple 一起使用

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

方法 2:mergeindicator

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

在查找重复项时,您还可以确定要考虑哪些列:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
@Szpaqn 注意这个方法不会处理特殊情况。 :-)
@DtechNet 您需要使两个数据框具有相同的名称
方法 2 (indicator=True) 是一个非常通用且有用的工具,我很想在这个答案的顶部看到它,但是使用“外部”而不是“左”连接来涵盖所有 3 种情况。
您能解释一下 apply(tuple,1) 的含义吗?
j
jpp

对于行,试试这个,其中 Name 是联合索引列(可以是多个公共列的列表,或指定 left_onright_on):

m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)

indicator=True 设置很有用,因为它添加了一个名为 _merge 的列,其中包含 df1df2 之间的所有更改,分为 3 种可能的类型:“left_only”、“right_only”或“both”。

对于列,试试这个:

set(df1.columns).symmetric_difference(df2.columns)

Downvoter愿意发表评论吗? mergeindicator=True 是按给定字段比较数据帧的经典解决方案。
t
toecsnar42

Accepted answer 方法 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.Series 对象。每个元组包含来自 df1/df2 的整行。然后我们在 df1 上应用 isin 方法来检查每个元组是否“在”df2 中。结果是具有布尔值的 pd.Series。如果来自 df1 的元组在 df2 中,则为真。最后,我们用 ~ 符号否定结果,并对 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]

这是一个很好的答案,但作为一个单行代码是难以理解的。如果一个人将每个步骤分开并了解它的作用,那么它如何完成工作就会变得非常清楚。
添加了解释。我希望它有帮助!
S
SpeedCoder5
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

'~' 是什么意思?
'~' 不适用于布尔索引。请参阅:pandas.pydata.org/pandas-docs/stable/user_guide/…
V
VirtualScooter

edit2,我想出了一个不需要设置索引的新解决方案

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

好的,我发现最高投票的答案已经包含了我的想法。是的,我们只能在每两个 dfs 中没有重复项的情况下使用此代码。

我有一个棘手的方法。首先,我们将“名称”设置为问题给出的两个数据框的索引。由于我们在两个 df 中具有相同的“名称”,我们可以从“较大”的 df 中删除“较小的”df 的索引。这是代码。

df1.set_index('Name',inplace=True)
df2.set_index('Name',inplace=True)
newdf=df1.drop(df2.index)

你可能是说 pd.concat([df1,df2]).drop_duplicates(keep=False)
M
MrE

也许是更简单的单行,具有相同或不同的列名。即使 df2['Name2'] 包含重复值也可以工作。

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

简单有效。添加了 errors='ignore' 以解决目标值不在源(即交集)中的问题,最后重置索引会带来与原始值相似的 df。
L
Luchko

除了公认的答案之外,我还想提出一种更广泛的解决方案,它可以找到具有任何 index/columns 的两个数据帧的 2D 集差异(它们可能不会对两个数据帧重合) .该方法还允许为 float 元素设置容差以进行数据帧比较(它使用 np.isclose


import numpy as np
import pandas as pd

def get_dataframe_setdiff2d(df_new: pd.DataFrame, 
                            df_old: pd.DataFrame, 
                            rtol=1e-03, atol=1e-05) -> pd.DataFrame:
    """Returns set difference of two pandas DataFrames"""

    union_index = np.union1d(df_new.index, df_old.index)
    union_columns = np.union1d(df_new.columns, df_old.columns)

    new = df_new.reindex(index=union_index, columns=union_columns)
    old = df_old.reindex(index=union_index, columns=union_columns)

    mask_diff = ~np.isclose(new, old, rtol, atol)

    df_bool = pd.DataFrame(mask_diff, union_index, union_columns)

    df_diff = pd.concat([new[df_bool].stack(),
                         old[df_bool].stack()], axis=1)

    df_diff.columns = ["New", "Old"]

    return df_diff

例子:

In [1]

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

print("df1:\n", df1, "\n")

print("df2:\n", df2, "\n")

diff = get_dataframe_setdiff2d(df1, df2)

print("diff:\n", diff, "\n")
Out [1]

df1:
   A  C
0  2  2
1  1  1
2  2  2 

df2:
   A  B
0  1  1
1  1  1 

diff:
     New  Old
0 A  2.0  1.0
  B  NaN  1.0
  C  2.0  NaN
1 B  NaN  1.0
  C  1.0  NaN
2 A  2.0  NaN
  C  2.0  NaN 

A
Arun Pal

如前所述 here

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]})

在这种情况下,上述解决方案将提供 Empty DataFrame,而您应该在从每个 datframe 中删除重复项后使用 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)

问题的作者要求返回 df1 中不在 df2 中的所有值。因此,即使在这种情况下,df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))] 也是正确答案。如果您想获得 df1 或 df2 中的值,但不能同时获得两者,那么您建议的方法是正确的(需要注意的是从原始数据帧中删除重复项)。
J
Jayen

当一侧有重复项而另一侧至少有一个重复项时,我在处理重复项时遇到了问题,因此我使用 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

S
Serge Ballesta

不需要更改现有数据帧索引的漂亮@liangli 解决方案的轻微变化:

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

D
DOS

通过索引发现差异。假设 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


n
ntjess

我发现 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]]}

S
SherylHohman

使用 lambda 函数,您可以过滤具有 _merge“left_only” 的行,以获取 df1df2 中缺少的所有行

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'")"
I
Iopheam

定义我们的数据框:

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) 时有效。如果 df2 比 df1 长,您可以反转表达式: df2[~df2.isin(df1)].dropna()


Y
Yushan ZHANG

Pandas 现在提供了一个 new API 来做数据帧差异:pandas.DataFrame.compare

df.compare(df2)
  col1       col3
  self other self other
0    a     c  NaN   NaN
2  NaN   NaN  3.0   4.0