比较两个数据框并获得差异


89

我有两个数据框。例子:

df1:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green

df2:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange

每个数据框都有日期作为索引。两个数据框具有相同的结构。

我想做的是比较这两个数据帧,找到df2中哪些行不在df1中。我想比较日期(索引)和第一列(香蕉,APple等),以查看它们是否存在于df2与df1中。

我尝试了以下方法:

对于第一种方法,我得到此错误:“异常:只能比较标记相同的DataFrame对象”。我尝试删除日期作为索引,但得到相同的错误。

第三种方法上,我得到断言返回False,但无法弄清楚如何实际看到不同的行。

任何指针都将受到欢迎


如果您执行以下操作:cookbook-r.com/Manipulating_data/…,它会摆脱“标签相同的DataFrame对象”异常吗?
Anthony Kong

我已经多次更改了列名,以尝试解决这个问题而没有运气。
埃里克·布朗

1
FWIW,我在两个数据帧上将列名更改为“ a,b,c,d”,并收到相同的错误消息。
埃里克·布朗

Answers:


103

这种方法df1 != df2仅适用于具有相同行和列的数据帧。实际上,所有数据帧轴都与_indexed_same方法进行了比较,如果发现差异(即使是按列/索引的顺序),也会引发异常。

如果我说对了,那么您不希望找到变化,而是希望找到对称的差异。为此,一种方法可能是连接数据帧:

>>> df = pd.concat([df1, df2])
>>> df = df.reset_index(drop=True)

通过...分组

>>> df_gpby = df.groupby(list(df.columns))

获取唯一记录的索引

>>> idx = [x[0] for x in df_gpby.groups.values() if len(x) == 1]

过滤

>>> df.reindex(idx)
         Date   Fruit   Num   Color
9  2013-11-25  Orange   8.6  Orange
8  2013-11-25   Apple  22.1     Red

这就是答案。我删除了“日期”索引,并遵循了这种方法,我得到了正确的输出。
埃里克·布朗

8
有没有简单的方法可以向其中添加标志,以查看从df1到df2删除/添加/更改了哪些行?
pyCthon 2015年

@alko我想知道,这是否pd.concat仅添加了缺少的项目df1?还是完全替换df1df2
jake wong

@jakewong-如此处所pd.concat用-进行外部联接。换句话说,它将所有来自df的索引连接在一起,这实际上是的默认行为pd.concat(),这是docs pandas.pydata.org/pandas-docs/stable/merging.html
Thanos

我们可以使用熊猫进行比较的最大记录数是多少?
pyd

25

将数据帧传递给字典中的concat,将得到一个多索引数据帧,您可以从中轻松删除重复项,从而得到一个具有多个数据帧之间差异的多索引数据帧:

import sys
if sys.version_info[0] < 3:
    from StringIO import StringIO
else:
    from io import StringIO
import pandas as pd

DF1 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
""")
DF2 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange""")


df1 = pd.read_table(DF1, sep='\s+')
df2 = pd.read_table(DF2, sep='\s+')
#%%
dfs_dictionary = {'DF1':df1,'DF2':df2}
df=pd.concat(dfs_dictionary)
df.drop_duplicates(keep=False)

结果:

             Date   Fruit   Num   Color
DF2 4  2013-11-25   Apple  22.1     Red
    5  2013-11-25  Orange   8.6  Orange

1
这是一种简单得多的方法,只需再进行一次修订即可使其更容易。无需在字典中使用concat,使用df = pd.concat([df1,df2])会做同样的事情
ling

您不应该覆盖内置关键字dict
denfromufa

是否可以添加此方法以确定哪个数据帧包含唯一行?
jlewkovich

您可以通过包含在词典数据帧的关键的多指标的第一级告诉(我更新的输出与正确的键)
JUR

24

ling在上述jur的回答中指出,将更新和放置在其他人更容易找到的地方。

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

使用以下数据帧进行测试:

df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})

df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})

结果: 在此处输入图片说明


5

在alko的答案几乎对我有用的基础上,除了过滤步骤(我得到了:)ValueError: cannot reindex from a duplicate axis,这是我使用的最终解决方案:

# join the dataframes
united_data = pd.concat([data1, data2, data3, ...])
# group the data by the whole row to find duplicates
united_data_grouped = united_data.groupby(list(united_data.columns))
# detect the row indices of unique rows
uniq_data_idx = [x[0] for x in united_data_grouped.indices.values() if len(x) == 1]
# extract those unique values
uniq_data = united_data.iloc[uniq_data_idx]

很好的答案。谢谢
埃里克·布朗

1
IndexError: index out of bounds'当我尝试运行第三行时,出现错误。
Moondra

5
# THIS WORK FOR ME

# Get all diferent values
df3 = pd.merge(df1, df2, how='outer', indicator='Exist')
df3 = df3.loc[df3['Exist'] != 'both']


# If you like to filter by a common ID
df3  = pd.merge(df1, df2, on="Fruit", how='outer', indicator='Exist')
df3  = df3.loc[df3['Exist'] != 'both']

这是最好的答案
moshevi

3

有一个更简单,更快,更好的解决方案,如果数量不同,甚至可以为您带来数量差异:

df1_i = df1.set_index(['Date','Fruit','Color'])
df2_i = df2.set_index(['Date','Fruit','Color'])
df_diff = df1_i.join(df2_i,how='outer',rsuffix='_').fillna(0)
df_diff = (df_diff['Num'] - df_diff['Num_'])

此处df_diff是差异的提要。您甚至可以使用它来找到数量上的差异。在您的示例中:

在此处输入图片说明

说明:与比较两个列表类似,要高效执行此操作,我们首先应对其进行排序,然后对其进行比较(将列表转换为集合/哈希也将很快;两者都是对简单O(N ^ 2)双重比较循环的不可思议的改进。

注意:以下代码生成表:

df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})
df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})

3

方正这里有一个简单的解决方案:

https://stackoverflow.com/a/47132808/9656339

pd.concat([df1, df2]).loc[df1.index.symmetric_difference(df2.index)]


1
欢迎使用Stack Overflow Tom2shoes。请不要提供仅链接的答案,请尝试从链接中提取内容,并将其留作参考(因为链接中的内容可以删除,或者链接本身可能会断裂)。有关更多信息,请参阅“如何写一个好的答案?” 。如果您认为该问题已在其他问题中得到解答,请将其标记为重复。
GGG

2
# given
df1=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green']})
df2=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,1000,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange']})

# find which rows are in df2 that aren't in df1 by Date and Fruit
df_2notin1 = df2[~(df2['Date'].isin(df1['Date']) & df2['Fruit'].isin(df1['Fruit']) )].dropna().reset_index(drop=True)

# output
print('df_2notin1\n', df_2notin1)
#      Color        Date   Fruit   Num
# 0     Red  2013-11-25   Apple  22.1
# 1  Orange  2013-11-25  Orange   8.6

1

我有这个解决方案。这对您有帮助吗?

text = """df1:
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green

df2:
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange



argetz45
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 118.6 Orange
2013-11-24 Apple 74.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25     Nuts    45.8 Brown
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange
2013-11-26   Pear 102.54    Pale"""

from collections import OrderedDict
import re

r = re.compile('([a-zA-Z\d]+).*\n'
               '(20\d\d-[01]\d-[0123]\d.+\n?'
               '(.+\n?)*)'
               '(?=[ \n]*\Z'
                  '|'
                  '\n+[a-zA-Z\d]+.*\n'
                  '20\d\d-[01]\d-[0123]\d)')

r2 = re.compile('((20\d\d-[01]\d-[0123]\d) +([^\d.]+)(?<! )[^\n]+)')

d = OrderedDict()
bef = []

for m in r.finditer(text):
    li = []
    for x in r2.findall(m.group(2)):
        if not any(x[1:3]==elbef for elbef in bef):
            bef.append(x[1:3])
            li.append(x[0])
    d[m.group(1)] = li


for name,lu in d.iteritems():
    print '%s\n%s\n' % (name,'\n'.join(lu))

结果

df1
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green

df2
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange

argetz45
2013-11-25     Nuts    45.8 Brown
2013-11-26   Pear 102.54    Pale

谢谢您的帮助。我看到了@alko的答案,并且该代码运行良好。
埃里克·布朗

1

既然pandas >= 1.1.0我们有DataFrame.compareSeries.compare

注意:该方法只能比较标记相同的DataFrame对象,这意味着具有相同的行和列标签的DataFrame。

df1 = pd.DataFrame({'A': [1, 2, 3],
                    'B': [4, 5, 6],
                    'C': [7, np.NaN, 9]})

df2 = pd.DataFrame({'A': [1, 99, 3],
                    'B': [4, 5, 81],
                    'C': [7, 8, 9]})

   A  B    C
0  1  4  7.0
1  2  5  NaN
2  3  6  9.0 

    A   B  C
0   1   4  7
1  99   5  8
2   3  81  9
df1.compare(df2)

     A          B          C      
  self other self other self other
1  2.0  99.0  NaN   NaN  NaN   8.0
2  NaN   NaN  6.0  81.0  NaN   NaN

谢谢你提供信息。我还没有升级到1.1,但这很高兴。
埃里克·布朗

0

需要注意的一个重要细节是您的数据具有重复的索引值,因此要进行任何直接的比较,我们需要将所有内容都设为唯一,df.reset_index()因此我们可以根据条件进行选择。一旦定义了索引,就假定您要保留de index,因此有一种单行解决方案:

[~df2.reset_index().isin(df1.reset_index())].dropna().set_index('Date')

从pythonic的角度来看,一旦目标是提高可读性,我们就可以打破一点:

# keep the index name, if it does not have a name it uses the default name
index_name = df.index.name if df.index.name else 'index' 

# setting the index to become unique
df1 = df1.reset_index()
df2 = df2.reset_index()

# getting the differences to a Dataframe
df_diff = df2[~df2.isin(df1)].dropna().set_index(index_name)

0

希望这对您有用。^ o ^

df1 = pd.DataFrame({'date': ['0207', '0207'], 'col1': [1, 2]})
df2 = pd.DataFrame({'date': ['0207', '0207', '0208', '0208'], 'col1': [1, 2, 3, 4]})
print(f"df1(Before):\n{df1}\ndf2:\n{df2}")
"""
df1(Before):
   date  col1
0  0207     1
1  0207     2

df2:
   date  col1
0  0207     1
1  0207     2
2  0208     3
3  0208     4
"""

old_set = set(df1.index.values)
new_set = set(df2.index.values)
new_data_index = new_set - old_set
new_data_list = []
for idx in new_data_index:
    new_data_list.append(df2.loc[idx])

if len(new_data_list) > 0:
    df1 = df1.append(new_data_list)
print(f"df1(After):\n{df1}")
"""
df1(After):
   date  col1
0  0207     1
1  0207     2
2  0208     3
3  0208     4
"""

0

我尝试了这种方法,它奏效了。我希望它也可以帮助您:

"""Identify differences between two pandas DataFrames"""
df1.sort_index(inplace=True)
df2.sort_index(inplace=True)
df_all = pd.concat([df1, df12], axis='columns', keys=['First', 'Second'])
df_final = df_all.swaplevel(axis='columns')[df1.columns[1:]]
df_final[df_final['change this to one of the columns'] != df_final['change this to one of the columns']]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.