Python Pandas - Finden Sie den Unterschied zwischen zwei Datenrahmen


103

Ich habe zwei Datenrahmen df1 und df2, wobei df2 eine Teilmenge von df1 ist. Wie bekomme ich einen neuen Datenrahmen (df3), der den Unterschied zwischen den beiden Datenrahmen darstellt?

Mit anderen Worten, ein Datenrahmen, der alle Zeilen / Spalten in df1 enthält, die nicht in df2 enthalten sind?

Geben Sie hier die Bildbeschreibung ein


3
Der einfachste Weg, dies zu tun, hängt davon ab, wie Ihre Datenrahmen strukturiert sind (dh ob die Indizes verwendet werden können usw.). Dies ist ein gutes Beispiel dafür, warum Sie in Pandas-Fragen immer ein reproduzierbares Beispiel einfügen sollten.
Cmaher

1
Ich habe das Dataframe-Beispielbild hinzugefügt
userPyGeo

Antworten:


162

Durch die Nutzung drop_duplicates

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

Update :

Above method only working for those dataframes they do not have duplicate itself, For example

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

Es wird wie unten ausgegeben, was falsch ist

Falsche Ausgabe:

pd.concat([df1, df2]).drop_duplicates(keep=False)
Out[655]: 
   A  B
1  2  3

Richtige Ausgabe

Out[656]: 
   A  B
1  2  3
2  3  4
3  3  4

Wie erreicht man das?

Methode 1: Verwenden isinmittuple

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

Methode 2: mergemitindicator

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

4
Sie können auch festlegen, welche Spalten bei der Suche nach Duplikaten berücksichtigt werden sollen:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
Szpaqn

1
@Szpaqn Beachten Sie, dass diese Methode den Sonderfall nicht behandelt. :-)
BENY

Beachten Sie, dass dies dazu führen kann, dass unerwartete Zeilen im Ergebnis verbleiben, wenn einer Ihrer Datentypen float(weil 12.00000000001 != 12) ist. Eine bessere Vorgehensweise besteht darin, den festgelegten Schnittpunkt der IDs in zwei Datenrahmen zu ermitteln und den Unterschied daraus zu ermitteln.
Jiāgěng

1
@DtechNet Sie müssen zwei
Datenrahmen

2
Methode 2 ( indicator=True) ist ein sehr vielseitiges und nützliches Werkzeug. Ich würde es gerne oben in dieser Antwort sehen, aber mit 'äußerer' nicht 'linker' Verbindung, um alle 3 Situationen abzudecken.
Mirekphd

34

Versuchen Sie dies für Zeilen, wobei Namesich die gemeinsame Indexspalte befindet (kann eine Liste für mehrere gemeinsame Spalten sein oder angeben left_onund right_on):

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

Die indicator=TrueEinstellung ist nützlich, da sie eine Spalte _mergemit allen Änderungen zwischen df1und hinzufügt df2, die in drei mögliche Arten unterteilt ist: "left_only", "right_only" oder "both".

Versuchen Sie für Spalten Folgendes:

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

9
Downvoter gerne kommentieren? mergewith indicator=Trueist die klassische Lösung zum Vergleichen von Datenrahmen nach bestimmten Feldern.
Jpp

9

Akzeptierte Antwort Methode 1 funktioniert nicht für Datenrahmen mit darin enthaltenen NaNs pd.np.nan != pd.np.nan. Ich bin mir nicht sicher, ob dies der beste Weg ist, aber es kann durch vermieden werden

df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]

6

edit2, ich habe eine neue Lösung gefunden, ohne den Index setzen zu müssen

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

Okay, ich fand, dass die Antwort der höchsten Stimmen bereits das enthält, was ich herausgefunden habe. Ja, wir können diesen Code nur unter der Bedingung verwenden, dass in zwei dfs keine Duplikate vorhanden sind.


Ich habe eine knifflige Methode. Zuerst setzen wir 'Name' als Index für zwei Datenrahmen, die von der Frage angegeben werden. Da wir in zwei dfs denselben 'Namen' haben, können wir einfach den Index des 'kleineren' df aus dem 'größeren' df löschen. Hier ist der Code.

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

1
Sie meinten wahrscheinlich pd.concat ([df1, df2]). drop_duplicates (keep = False)
Manaslu

4
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

Was bedeutet '~'?
Piotrek Leśniak

'~' ist nicht für die boolesche Indizierung. Siehe: pandas.pydata.org/pandas-docs/stable/user_guide/…
SpeedCoder5

3

Vielleicht ein einfacher Einzeiler mit identischen oder unterschiedlichen Spaltennamen. Funktionierte auch dann, wenn df2 ['Name2'] doppelte Werte enthielt.

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

2
einfach und effektiv. Hinzugefügte Fehler = 'ignorieren', um das Problem für den Fall zu beheben, dass sich die Zielwerte nicht in der Quelle befinden (dh Schnittmenge) und das Zurücksetzen des Index am Ende eine df ergibt, die dem Original ähnlich ist.
MrE

0

Eine kleine Variation der Lösung von nice @ liangli, bei der der Index vorhandener Datenrahmen nicht geändert werden muss:

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

0

Differenz anhand des Index ermitteln. Angenommen, df1 ist eine Teilmenge von df2 und die Indizes werden bei der Teilmenge vorgetragen

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


0

Zusätzlich zur akzeptierten Antwort möchte ich eine weitere umfassendere Lösung vorschlagen, die einen 2D- Satzunterschied von zwei Datenrahmen mit einem beliebigen index/ findet columns(sie stimmen möglicherweise nicht für beide Datenrahmen überein). Die Methode ermöglicht auch das Einrichten der Toleranz für floatElemente für den Datenrahmenvergleich (verwendet 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

Beispiel:

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 

0

Wie hier erwähnt das

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]

ist die richtige Lösung, aber es wird eine falsche Ausgabe erzeugen, wenn

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

In diesem Fall gibt die obige Lösung Empty DataFrame an , stattdessen sollten Sie verwendenconcat Methode verwenden, nachdem Sie Duplikate aus jedem Datenrahmen entfernt haben.

Verwenden concate with drop_duplicates

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

Der Autor der Frage hat darum gebeten, alle Werte in df1 zurückzugeben, die nicht in df2 enthalten sind. Daher df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]ist das auch in diesem Fall die richtige Antwort. Wenn Sie Werte erhalten möchten, die entweder in df1 oder df2, aber nicht in beiden vorliegen, ist Ihr vorgeschlagener Ansatz korrekt (mit der Einschränkung, dass Duplikate aus den ursprünglichen Datenrahmen entfernt werden).
ira
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.