查找两个数据框之间的差异
- 2024-12-20 08:37:00
- admin 原创
- 79
问题描述:
我有两个数据框 df1 和 df2,其中 df2 是 df1 的子集。如何获取两个数据框之间的差异的新数据框 (df3)?
换句话说,数据框包含 df1 中所有不在 df2 中的行/列?
解决方案 1:
通过使用drop_duplicates
pd.concat([df1,df2]).drop_duplicates(keep=False)
Update :
The above method only works for those data frames that don't already have duplicates themselves. For example:
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: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
方法 2:merge
使用indicator
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
解决方案 2:
对于行,尝试这个,其中Name
是联合索引列(可以是多个公共列的列表,或者指定left_on
和right_on
):
m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)
该indicator=True
设置很有用,因为它添加了一个名为的列,其中包含和_merge
之间的所有更改,分为 3 种可能的类型:“left_only”、“right_only”或“both”。df1
`df2`
对于列,尝试以下操作:
set(df1.columns).symmetric_difference(df2.columns)
解决方案 3:
接受的答案方法 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
。然后我们应用isin
方法df1
检查每个元组是否“在” 中df2
。结果为布尔值。如果来自 的元组在 中,则结果pd.Series
为 True 。最后,我们用符号取反结果,然后应用过滤器。长话短说,我们只得到那些不在 中的行。df1
`df2~
df1df1
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]
解决方案 4:
Pandas 现在提供了一个新的 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
解决方案 5:
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
', df1)
print('df2
', df2)
print('df_1notin2
', 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
解决方案 6:
pandas 中有一个新方法DataFrame.compare
,可以比较两个不同的数据框并返回数据记录中每列发生变化的值。
例子
第一个数据框
Id Customer Status Date
1 ABC Good Mar 2023
2 BAC Good Feb 2024
3 CBA Bad Apr 2022
第二个数据框
Id Customer Status Date
1 ABC Bad Mar 2023
2 BAC Good Feb 2024
5 CBA Good Apr 2024
比较数据框
print("Dataframe difference --
")
print(df1.compare(df2))
print("Dataframe difference keeping equal values --
")
print(df1.compare(df2, keep_equal=True))
print("Dataframe difference keeping same shape --
")
print(df1.compare(df2, keep_shape=True))
print("Dataframe difference keeping same shape and equal values --
")
print(df1.compare(df2, keep_shape=True, keep_equal=True))
结果
Dataframe difference --
Id Status Date
self other self other self other
0 NaN NaN Good Bad NaN NaN
2 3.0 5.0 Bad Good Apr 2022 Apr 2024
Dataframe difference keeping equal values --
Id Status Date
self other self other self other
0 1 1 Good Bad Mar 2023 Mar 2023
2 3 5 Bad Good Apr 2022 Apr 2024
Dataframe difference keeping same shape --
Id Customer Status Date
self other self other self other self other
0 NaN NaN NaN NaN Good Bad NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN
2 3.0 5.0 NaN NaN Bad Good Apr 2022 Apr 2024
Dataframe difference keeping same shape and equal values --
Id Customer Status Date
self other self other self other self other
0 1 1 ABC ABC Good Bad Mar 2023 Mar 2023
1 2 2 BAC BAC Good Good Feb 2024 Feb 2024
2 3 5 CBA CBA Bad Good Apr 2022 Apr 2024
解决方案 7:
也许是一个更简单的一行代码,具有相同或不同的列名。即使 df2['Name2'] 包含重复值也能正常工作。
newDf = df1.set_index('Name1')
.drop(df2['Name2'], errors='ignore')
.reset_index(drop=False)
解决方案 8:
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)
解决方案 9:
除了已接受的答案之外,我想提出一个更广泛的解决方案,可以找到具有任何/ 的两个数据框的2D 集差异(它们可能与两个数据框不一致)。此外,方法还允许设置元素的容差以进行数据框比较(它使用)index
`columnsfloat
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:
", df1, "
")
print("df2:
", df2, "
")
diff = get_dataframe_setdiff2d(df1, df2)
print("diff:
", diff, "
")
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
解决方案 10:
对称差分
如果您只对其中一个数据框中的行感兴趣,但不对两者同时感兴趣,那么您正在寻找集合差异:
pd.concat([df1,df2]).drop_duplicates(keep=False)
⚠️仅当两个数据框都不包含任何重复项时才有效。
集合差分 / 关系代数差分
如果您对关系代数差异/集合差异感兴趣,即df1-df2
或df1df2
:
pd.concat([df1,df2,df2]).drop_duplicates(keep=False)
⚠️仅当两个数据框都不包含任何重复项时才有效。
解决方案 11:
正如这里提到
的
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)
解决方案 12:
当一侧有重复项,而另一侧至少有一个重复项时,我在处理重复项时会遇到问题,因此我过去Counter.collections
会进行更好的 diff,以确保两侧的计数相同。这不会返回重复项,但如果两侧的计数相同,则不会返回任何重复项。
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
解决方案 13:
@liangli 的解决方案略有变化,不需要更改现有数据框的索引:
newdf = df1.drop(df1.join(df2.set_index('Name').index))
解决方案 14:
通过索引查找差异。假设 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
解决方案 15:
定义我们的数据框:
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()
解决方案 16:
我发现这个deepdiff
库是一个很棒的工具,如果需要不同的细节或顺序很重要,它也可以很好地扩展到数据框。您可以尝试使用 diffing 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]]}
解决方案 17:
另一个可能的解决方案是使用numpy broadcasting
:
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
解决方案 18:
_merge
使用 lambda 函数,您可以过滤具有值的行以获取所有缺失的“left_only”
行df1
`df2`
df3 = df1.merge(df2, how = 'outer' ,indicator=True).loc[lambda x :x['_merge']=='left_only']
df
解决方案 19:
尝试一下这个:df_new = df1.merge(df2, how='outer', indicator=True).query('_merge == "left_only"').drop('_merge', 1)
它将产生一个具有差异的新数据框:df1 中存在但不在 df2 中的值。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件