Pandas erhalten Zeilen, die sich NICHT in einem anderen Datenrahmen befinden


228

Ich habe zwei Pandas-Datenrahmen, die einige Zeilen gemeinsam haben.

Angenommen, Datenrahmen2 ist eine Teilmenge von Datenrahmen1.

Wie kann ich die Zeilen von Datenrahmen1 abrufen, die sich nicht in Datenrahmen2 befinden?

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 'col2' : [10, 11, 12]})

1
@ TedPetrou Ich sehe nicht, wie die von Ihnen angegebene Antwort die richtige ist. Wenn ich zwei Datenrahmen habe, von denen einer eine Teilmenge des anderen ist, muss ich alle Zeilen entfernen, die in der Teilmenge enthalten sind. Ich möchte keine Duplikate entfernen. Ich möchte die Teilmenge vollständig entfernen.
Jukebox

Mögliches Duplikat des Löschens
Jim G.

Antworten:


173

Eine Methode wäre, das Ergebnis einer inneren Zusammenführung aus beiden dfs zu speichern. Dann können wir einfach die Zeilen auswählen, wenn die Werte einer Spalte nicht so häufig sind:

In [119]:

common = df1.merge(df2,on=['col1','col2'])
print(common)
df1[(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))]
   col1  col2
0     1    10
1     2    11
2     3    12
Out[119]:
   col1  col2
3     4    13
4     5    14

BEARBEITEN

Eine andere Methode, die Sie gefunden haben, besteht darin isin, NaNZeilen zu erstellen, die Sie löschen können:

In [138]:

df1[~df1.isin(df2)].dropna()
Out[138]:
   col1  col2
3     4    13
4     5    14

Wenn df2 Zeilen jedoch nicht auf die gleiche Weise startet, funktioniert dies nicht:

df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11, 12,13]})

wird das gesamte df produzieren:

In [140]:

df1[~df1.isin(df2)].dropna()
Out[140]:
   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14

13
df1[~df1.isin(df2)].dropna(how = 'all')scheint den Trick zu tun. Trotzdem danke - Ihre Antwort hat mir geholfen, eine Lösung zu finden.
Denken Sie schöne Dinge

5
Beachten Sie, dass für die Verwendung isinbeide dfs mit denselben Zeilenwerten beginnen müssen. Wenn also beispielsweise df2 war, funktioniert df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})Ihre Methode nicht
EdChum

2
Dies wandelte alle Ints in Floats um!
Chris Nielsen

@EdChum, die Daten, die ich verwendet habe, waren der genaue Code in Ihrem obigen Beispiel. Ich habe Ihre Beispiele einfach in ein neues Jupyter-Notizbuch gelegt und den Code Schritt für Schritt ausgeführt. Zeile 3 ging 4 | 13zum 4.0 | 13.0Beispiel von bis . Dies geschah nach diesem Schritt:df1[~df1.isin(df2)].dropna()
Chris Nielsen

3
@SergeyZakharov Diese Antwort, die vor fast 3 Jahren veröffentlicht wurde, war in Bezug auf das OP korrekt. Für ihr Problem ist die andere Antwort eine bessere Antwort und behandelt ein umfassenderes Problem, das nie Teil der ursprünglichen Frage war. Es ist falsch, dies anzugeben Die Antwort ist falsch, sie ist angesichts des dargelegten Problems richtig. Zusätzlich hat jemand dies ohne Erklärung abgelehnt, es gibt wenig, was ich tun kann, da dies eine akzeptierte Antwort ist, das OP seine Meinung nicht geändert hat und ich keine andere Antwort ausschlachten werde, um es richtig zu machen .
EdChum

189

Die aktuell ausgewählte Lösung führt zu falschen Ergebnissen. Um dieses Problem richtig zu lösen, können wir eine Linksverknüpfung von df1bis durchführen df2und sicherstellen, dass zuerst nur die eindeutigen Zeilen für abgerufen werden df2.

Zuerst müssen wir den ursprünglichen DataFrame ändern, um die Zeile mit Daten hinzuzufügen [3, 10].

df1 = pd.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 
                           'col2' : [10, 11, 12, 13, 14, 10]}) 
df2 = pd.DataFrame(data = {'col1' : [1, 2, 3],
                           'col2' : [10, 11, 12]})

df1

   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
5     3    10

df2

   col1  col2
0     1    10
1     2    11
2     3    12

Führen Sie eine Linksverknüpfung durch, wobei Sie Duplikate entfernen, df2sodass jede Verknüpfungsreihe df1genau 1 Zeile enthält df2. Verwenden Sie den Parameter indicator, um eine zusätzliche Spalte zurückzugeben, die angibt, aus welcher Tabelle die Zeile stammt.

df_all = df1.merge(df2.drop_duplicates(), on=['col1','col2'], 
                   how='left', indicator=True)
df_all

   col1  col2     _merge
0     1    10       both
1     2    11       both
2     3    12       both
3     4    13  left_only
4     5    14  left_only
5     3    10  left_only

Erstellen Sie eine boolesche Bedingung:

df_all['_merge'] == 'left_only'

0    False
1    False
2    False
3     True
4     True
5     True
Name: _merge, dtype: bool

Warum andere Lösungen falsch sind

Einige Lösungen machen den gleichen Fehler - sie prüfen nur, ob jeder Wert in jeder Spalte unabhängig ist und nicht zusammen in derselben Zeile. Das Hinzufügen der letzten Zeile, die eindeutig ist, aber die Werte aus beiden Spalten enthält, df2macht den Fehler sichtbar:

common = df1.merge(df2,on=['col1','col2'])
(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))
0    False
1    False
2    False
3     True
4     True
5    False
dtype: bool

Diese Lösung führt zum gleichen falschen Ergebnis:

df1.isin(df2.to_dict('l')).all(1)

2
aber ich nehme an, sie gingen davon aus, dass col1 als Index eindeutig ist (in der Frage nicht erwähnt, aber offensichtlich). Wenn es also nie einen solchen Fall gibt, in dem es zwei Werte von col2 für denselben Wert von col1 gibt (es können nicht zwei col1 = 3 Zeilen vorhanden sein), sind die obigen Antworten korrekt.
Pashute

14
Es ist sicherlich nicht offensichtlich, also ist Ihr Punkt ungültig. Meine Lösung verallgemeinert sich auf weitere Fälle.
Ted Petrou

Frage: Wäre es nicht einfacher, ein Slice als ein boolesches Array zu erstellen? Da ist das Ziel, die Zeilen zu bekommen.
Matías Romo

5
Verwenden Sie df_all[df_all['_merge'] == 'left_only'], um eine df mit den Ergebnissen zu haben
gies0r

76

Angenommen, die Indizes sind in den Datenrahmen konsistent (ohne Berücksichtigung der tatsächlichen Spaltenwerte):

df1[~df1.index.isin(df2.index)]

1
@ ChrisNielsen Negation der Bedingung. In diesem Beispiel bedeutet dies also "Nehmen Sie die Zeilen, aus df1denen sich die Indizes NICHT befinden df2.index". Weitere Informationen zur Negation: stackoverflow.com/q/19960077/304209 (überraschenderweise konnte ich in Pandas-Dokumenten keine Erwähnungen von Tilde finden).
Dennis Golomazov

Scheint, als müssten die dfs gleich lang sein, oder? Ich bekommeValueError: Item wrong length x instead of y.
Worte für den

@wordsforthewise nein, tun sie nicht. Die Maske hat die Länge von df1 und wird auch auf df1 angewendet. Können Sie Ihr Beispiel geben?
Dennis Golomazov

Um das Problem mit der Artikellänge zu beheben, sollten Sie .loc
Moreno

13

Wie bereits angedeutet, erfordert isin, dass Spalten und Indizes für eine Übereinstimmung identisch sind. Wenn die Übereinstimmung nur für den Zeileninhalt gelten soll, besteht eine Möglichkeit, die Maske zum Filtern der vorhandenen Zeilen zu erhalten, darin, die Zeilen in einen (Mehrfach-) Index zu konvertieren:

In [77]: df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 'col2' : [10, 11, 12, 13, 14, 10]})
In [78]: df2 = pandas.DataFrame(data = {'col1' : [1, 3, 4], 'col2' : [10, 12, 13]})
In [79]: df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)]
Out[79]:
   col1  col2
1     2    11
4     5    14
5     3    10

Wenn der Index berücksichtigt werden soll, wird bei set_index das Schlüsselwortargument angehängt, um Spalten an den vorhandenen Index anzuhängen. Wenn die Spalten nicht ausgerichtet sind, kann die Liste (df.columns) durch Spaltenspezifikationen ersetzt werden, um die Daten auszurichten.

pandas.MultiIndex.from_tuples(df<N>.to_records(index = False).tolist())

könnte alternativ verwendet werden, um die Indizes zu erstellen, obwohl ich bezweifle, dass dies effizienter ist.


@ Dev_123 Entferne das ~ am Anfang. Der Kern besteht darin, eine Prädikatenliste zu erstellen, die angibt, ob Zeilen in df1 auch in df2 vorkommen, sodass die Zeilen in df1 nicht nur für df1 gelten. ~ Negiert dies zu einer Prädikatenliste, ob Zeilen in df1 in df2 nicht vorkommen.
Rune Lyngsoe

11

Angenommen, Sie haben zwei Datenrahmen, df_1 und df_2 mit mehreren Feldern (Spaltennamen), und Sie möchten anhand einiger Felder (z. B. Felder_x, Felder_y) die einzigen Einträge in df_1 finden, die nicht in df_2 enthalten sind. Führen Sie die folgenden Schritte aus.

Schritt 1. Fügen Sie df_1 bzw. df_2 eine Spalte key1 und key2 hinzu.

Schritt 2. Führen Sie die Datenrahmen wie unten gezeigt zusammen. field_x und field_y sind unsere gewünschten Spalten.

Schritt 3.Wählen Sie nur die Zeilen aus df_1 aus, in denen Schlüssel1 nicht gleich Schlüssel2 ist.

Step4.Drop key1 und key2.

Diese Methode löst Ihr Problem und funktioniert auch bei großen Datenmengen schnell. Ich habe es für Datenrahmen mit mehr als 1.000.000 Zeilen versucht.

df_1['key1'] = 1
df_2['key2'] = 1
df_1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'left')
df_1 = df_1[~(df_1.key2 == df_1.key1)]
df_1 = df_1.drop(['key1','key2'], axis=1)

Ich denke nicht, dass dies technisch das ist, was er will - er will wissen, welche Zeilen für welchen df einzigartig waren. Ich denke jedoch, dass diese Lösung einen df von Zeilen zurückgibt, die entweder für den ersten df oder den zweiten df eindeutig waren.
Legit Stack


3

Sie können dies mit der isin (dict) -Methode tun :

In [74]: df1[~df1.isin(df2.to_dict('l')).all(1)]
Out[74]:
   col1  col2
3     4    13
4     5    14

Erläuterung:

In [75]: df2.to_dict('l')
Out[75]: {'col1': [1, 2, 3], 'col2': [10, 11, 12]}

In [76]: df1.isin(df2.to_dict('l'))
Out[76]:
    col1   col2
0   True   True
1   True   True
2   True   True
3  False  False
4  False  False

In [77]: df1.isin(df2.to_dict('l')).all(1)
Out[77]:
0     True
1     True
2     True
3    False
4    False
dtype: bool

Dies führt zu einem falschen Ergebnis. Siehe meine Erklärung unten.
Ted Petrou

2

Sie können auch verketten df1, df2:

x = pd.concat([df1, df2])

und entfernen Sie dann alle Duplikate:

y = x.drop_duplicates(keep=False, inplace=False)

Willkommen bei StackOverflow: Wenn Sie Code-, XML- oder Datenbeispiele veröffentlichen, markieren Sie diese Zeilen im Texteditor und klicken Sie auf die Schaltfläche "Codebeispiele" ({}) in der Editor-Symbolleiste oder verwenden Sie Strg + K auf Ihrer Tastatur, um das Format zu optimieren und Syntax heben es hervor!
WhatsThePoint

4
Dies gibt alle Daten zurück, die sich in einem der beiden Sätze befinden, nicht nur die Daten, die sich nur in df1 befinden.
Jamie Marshall

1

Wie wäre es damit:

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 
                               'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 
                               'col2' : [10, 11, 12]})
records_df2 = set([tuple(row) for row in df2.values])
in_df2_mask = np.array([tuple(row) in records_df2 for row in df1.values])
result = df1[~in_df2_mask]

1

Hier ist eine andere Möglichkeit, dies zu lösen:

df1[~df1.index.isin(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

Oder:

df1.loc[df1.index.difference(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

0

Um dies zu tun, füge ich eine neue Spalte hinzu, die für einen Datenrahmen eindeutig ist, und wähle anhand dieser Spalte aus, ob ein Eintrag beibehalten werden soll

df2[col3] = 1
df1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'outer')
df1['Empt'].fillna(0, inplace=True)

Dadurch hat jeder Eintrag in df1 einen Code - 0, wenn er für df1 eindeutig ist, 1, wenn er in beiden dataFrames enthalten ist. Sie verwenden dies dann, um sich auf das zu beschränken, was Sie möchten

answer = nonuni[nonuni['Empt'] == 0]

0
Extrahieren Sie die unterschiedlichen Zeilen mit der Zusammenführungsfunktion
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
Speichern Sie die unterschiedlichen Zeilen in CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
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.