Python Pandas - 查找两个数据帧之间的差异
Posted
技术标签:
【中文标题】Python Pandas - 查找两个数据帧之间的差异【英文标题】:Python Pandas - Find difference between two data frames 【发布时间】:2018-07-16 18:47:11 【问题描述】:我有两个数据框 df1 和 df2,其中 df2 是 df1 的子集。如何获得一个新的数据帧(df3),这是两个数据帧之间的差异?
换句话说,一个数据框包含 df1 中所有不在 df2 中的行/列?
【问题讨论】:
最简单的方法将取决于您的数据框的结构(即是否可以使用索引等)。这是一个很好的例子,说明为什么你应该在 pandas 问题中始终包含 reproducible example。 我已经添加了数据框示例图片 类似于***.com/q/20225110 【参考方案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
如何实现?
方法一:使用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
方法二: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
【讨论】:
您还可以确定在查找重复项时要考虑哪些列:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
@Szpaqn 注意这个方法不会处理特殊情况。 :-)
@DtechNet 你需要让两个数据框同名
方法 2 (indicator=True
) 是一个非常通用和有用的工具,我很想在这个答案的顶部看到它,但是使用“外部”而不是“左”连接来覆盖所有3 种情况。
您能解释一下apply(tuple,1)
的含义吗?【参考方案2】:
定义我们的数据框:
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
中的行。
表达式前面的~
(Element-wiselogical NOT) 否定了结果,所以我们得到df1
中的元素 NOT 在df2
中——两者的区别。
.dropna()
删除带有 NaN
的行,呈现所需的输出
注意这仅适用于
len(df1) >= len(df2)
。如果df2
比df1
长,您可以反转表达式:df2[~df2.isin(df1)].dropna()
【讨论】:
【参考方案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
【讨论】:
【参考方案4】: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]
【讨论】:
这是一个很好的答案,但作为一个单行字难以理解。如果一个人将每个步骤分开并了解它的作用,那么它如何完成工作就会变得非常清楚。 添加说明。希望对您有所帮助!【参考方案5】:使用 lambda 函数,您可以过滤具有 _merge
值 “left_only”
的行,以获取 df1
中 df2
中缺少的所有行
df3 = df1.merge(df2, how = 'outer' ,indicator=True).loc[lambda x :x['_merge']=='left_only']
df
【讨论】:
【参考方案6】:edit2,我想出了一个不需要设置索引的新解决方案
newdf=pd.concat([df1,df2]).drop_duplicates(keep=False)
好的,我发现最高投票的答案已经包含我想出的内容。是的,我们只能在每两个 df 中没有重复项的情况下使用此代码。
我有一个棘手的方法。首先,我们将“名称”设置为问题给出的两个数据框的索引。由于我们在两个 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)【参考方案7】:对于行,试试这个,其中Name
是联合索引列(可以是多个公共列的列表,或指定left_on
和right_on
):
m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)
indicator=True
设置很有用,因为它添加了一个名为 _merge
的列,在 df1
和 df2
之间进行了所有更改,分为 3 种可能的类型:“left_only”、“right_only”或“both”。
对于列,试试这个:
set(df1.columns).symmetric_difference(df2.columns)
【讨论】:
反对者愿意发表评论吗?merge
和 indicator=True
是按给定字段比较数据帧的经典解决方案。【参考方案8】:
如上所述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 中的值,但不能同时获得,那么您建议的方法是正确的(需要注意的是从原始数据帧中删除重复项)。【参考方案9】:
也许是更简单的单行,具有相同或不同的列名。即使 df2['Name2'] 包含重复值也可以工作。
newDf = df1.set_index('Name1')
.drop(df2['Name2'], errors='ignore')
.reset_index(drop=False)
【讨论】:
简单有效。添加了 errors='ignore' 以解决目标值不在源中(即交集)并最终重置索引会带来与原始值相似的 df 的问题。【参考方案10】:除了接受的答案之外,我想提出一个更广泛的解决方案,可以找到两个数据帧的 2D 集差异与任何 index
/columns
(它们可能不重合两个数据帧)。该方法还允许为 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
【讨论】:
【参考方案11】:通过索引发现差异。假设 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
【讨论】:
【参考方案12】:不错的@liangli 解决方案的轻微变化,不需要更改现有数据帧的索引:
newdf = df1.drop(df1.join(df2.set_index('Name').index))
【讨论】:
【参考方案13】: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/…以上是关于Python Pandas - 查找两个数据帧之间的差异的主要内容,如果未能解决你的问题,请参考以下文章
如何在Pandas数据帧(Python)中查找语料库中最常用的单词
python 使用datetime列查找pandas数据帧中的时间漏洞