Vergleichen von zwei Datenrahmen und Ermitteln der Unterschiede


89

Ich habe zwei Datenrahmen. Beispiele:

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

Jeder Datenrahmen hat das Datum als Index. Beide Datenrahmen haben die gleiche Struktur.

Was ich tun möchte, ist, diese beiden Datenrahmen zu vergleichen und herauszufinden, welche Zeilen in df2 sind, die nicht in df1 sind. Ich möchte das Datum (Index) und die erste Spalte (Banane, APple usw.) vergleichen, um festzustellen, ob sie in df2 und df1 vorhanden sind.

Ich habe folgendes versucht:

Beim ersten Ansatz wird folgende Fehlermeldung angezeigt : "Ausnahme: Kann nur identisch beschriftete DataFrame-Objekte vergleichen" . Ich habe versucht, das Datum als Index zu entfernen, erhalte jedoch den gleichen Fehler.

Beim dritten Ansatz erhalte ich die Zusicherung, False zurückzugeben, kann aber nicht herausfinden, wie die verschiedenen Zeilen tatsächlich angezeigt werden.

Hinweise wären willkommen


Wenn Sie dies tun: cookbook-r.com/Manipulating_data/… , wird dann die Ausnahme "Identisch gekennzeichnete DataFrame-Objekte" entfernt?
Anthony Kong

Ich habe die Spaltennamen oft geändert, um zu versuchen, das Problem ohne Glück zu umgehen.
Eric D. Brown

1
FWIW, ich habe die Spaltennamen in beiden Datenrahmen in "a, b, c, d" geändert und erhalte dieselbe Fehlermeldung.
Eric D. Brown

Antworten:


102

Dieser Ansatz df1 != df2funktioniert nur für Datenrahmen mit identischen Zeilen und Spalten. Tatsächlich werden alle Datenrahmenachsen mit der _indexed_sameMethode verglichen , und es wird eine Ausnahme ausgelöst , wenn Unterschiede festgestellt werden, selbst in der Reihenfolge der Spalten / Indizes.

Wenn ich Sie richtig verstanden habe, möchten Sie keine Änderungen finden, sondern symmetrische Unterschiede. Ein Ansatz könnte darin bestehen, Datenrahmen zu verketten:

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

gruppiere nach

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

Index der eindeutigen Datensätze abrufen

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

Filter

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

Das war die Antwort. Ich habe den "Date" -Index entfernt und bin diesem Ansatz gefolgt, und ich erhalte die richtige Ausgabe.
Eric D. Brown

7
Gibt es eine einfache Möglichkeit, ein Flag hinzuzufügen, um zu sehen, welche Zeilen von df1 nach df2 entfernt / hinzugefügt / geändert wurden?
PyCthon

@alko Ich habe mich gefragt, fügt dies pd.concatnur die fehlenden Elemente aus dem hinzu df1? Oder ersetzt es df1komplett durch df2?
Jake Wong

@jakewong pd.concat- wie hier verwendet - führt eine äußere Verknüpfung durch. Mit anderen Worten, es verbindet alle Indizes von beiden df's und dies ist in der Tat das Standardverhalten für pd.concat(), hier ist die docs pandas.pydata.org/pandas-docs/stable/merging.html
Thanos

Was ist die maximale Anzahl von Datensätzen, die wir mit Pandas vergleichen können?
Pyd

25

Wenn Sie die Datenrahmen an concat in einem Wörterbuch übergeben, erhalten Sie einen Datenrahmen mit mehreren Indizes, aus dem Sie die Duplikate einfach löschen können. Dies führt zu einem Datenrahmen mit mehreren Indizes mit den Unterschieden zwischen den Datenrahmen:

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)

Ergebnis:

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

1
Dies ist eine viel einfachere Methode. Nur eine weitere Überarbeitung kann es einfacher machen. Keine Notwendigkeit, Concat in einem Wörterbuch, Verwendung df = pd.concat ([DF1, DF2]) würde das Gleiche tun
ling

Sie sollten das integrierte Schlüsselwort nicht überschreiben dict!
Denfromufa

Gibt es eine Möglichkeit, dies zu ergänzen, um zu bestimmen, welcher Datenrahmen die eindeutige Zeile enthielt?
Jlewkovich

Sie können an der ersten Ebene im Multiindex erkennen, welche den Schlüssel des Datenrahmens im Wörterbuch enthält (ich habe die Ausgabe mit den richtigen Schlüsseln aktualisiert)
jur

23

Aktualisieren und platzieren, irgendwo ist es für andere leichter zu finden, lings Kommentar zu der obigen Antwort von jur .

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

Testen mit diesen Datenrahmen:

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

Ergebnisse in diesem: Geben Sie hier die Bildbeschreibung ein


5

Aufbauend auf der Antwort von alko, die fast für mich funktioniert hat, mit Ausnahme des Filterungsschritts (wo ich :) bekomme ValueError: cannot reindex from a duplicate axis, ist hier die endgültige Lösung, die ich verwendet habe:

# 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]

Schöne Ergänzung zur Antwort. Vielen Dank
Eric D. Brown

1
Ich erhalte den Fehler ' IndexError: index out of bounds', wenn ich versuche, die dritte Zeile auszuführen.
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']

Dies ist die beste Antwort
Moshevi

3

Es gibt eine einfachere Lösung, die schneller und besser ist, und wenn die Zahlen unterschiedlich sind, können Sie sogar Mengenunterschiede feststellen:

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_'])

Hier ist df_diff eine Zusammenfassung der Unterschiede. Sie können es sogar verwenden, um die Mengenunterschiede zu ermitteln. In Ihrem Beispiel:

Geben Sie hier die Bildbeschreibung ein

Erläuterung: Um zwei Listen effizient zu vergleichen, sollten wir sie zuerst ordnen und dann vergleichen (die Konvertierung der Liste in Sets / Hashing wäre ebenfalls schnell; beide sind eine unglaubliche Verbesserung der einfachen O (N ^ 2) -Doppelvergleichsschleife

Hinweis: Der folgende Code erzeugt die Tabellen:

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

Gründer hier eine einfache Lösung:

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

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


1
Willkommen bei Stack Overflow Tom2shoes. Bitte geben Sie keine Nur-Link-Antworten an, versuchen Sie, den Inhalt aus dem Link zu extrahieren und lassen Sie ihn nur als Referenz (da der Inhalt des Links gelöscht werden kann oder der Link selbst brechen kann). Weitere Informationen finden Sie unter "Wie schreibe ich eine gute Antwort?" . Wenn Sie der Meinung sind, dass diese Frage bereits in einer anderen Frage beantwortet wurde, markieren Sie sie bitte als Duplikat.
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

Ich habe diese Lösung. Hilft dir das?

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

Ergebnis

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

Danke für die Hilfe. Ich habe die Antwort von @alko gesehen und dieser Code hat gut funktioniert.
Eric D. Brown

1

Da haben pandas >= 1.1.0wir DataFrame.compareund Series.compare.

Hinweis: Die Methode kann nur identisch beschriftete DataFrame-Objekte vergleichen. Dies bedeutet, dass DataFrames identische Zeilen- und Spaltenbeschriftungen aufweisen.

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

Danke für diese Information. Ich bin noch nicht zu 1.1 übergegangen, aber das ist gut zu wissen.
Eric D. Brown

0

Ein wichtiges Detail, das Sie beachten sollten, ist, dass Ihre Daten doppelte Indexwerte aufweisen . Um einen einfachen Vergleich durchführen zu können, müssen wir alles als eindeutig festlegendf.reset_index() und können daher eine Auswahl basierend auf den Bedingungen durchführen. Sobald in Ihrem Fall der Index definiert ist, gehe ich davon aus, dass Sie den Index beibehalten möchten, damit es eine einzeilige Lösung gibt:

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

Sobald das Ziel aus pythonischer Sicht darin besteht, die Lesbarkeit zu verbessern, können wir ein wenig brechen:

# 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

Hoffe das wäre nützlich für dich. ^ 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

Ich habe diese Methode ausprobiert und sie hat funktioniert. Ich hoffe es kann auch helfen:

"""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']]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.