i panda ottengono righe che NON si trovano in altri frame di dati


230

Ho due frame di dati Panda che hanno alcune righe in comune.

Supponiamo che dataframe2 sia un sottoinsieme di dataframe1.

Come posso ottenere le righe di dataframe1 che non sono in dataframe2?

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 Non riesco a vedere come la risposta che hai fornito sia quella corretta. Se ho due frame di dati di cui uno è un sottoinsieme dell'altro, devo rimuovere tutte quelle righe che si trovano nel sottoinsieme. Non voglio rimuovere i duplicati. Voglio rimuovere completamente il sottoinsieme.
jukebox

Risposte:


172

Un metodo sarebbe quello di memorizzare il risultato di una unione interna da entrambi i dfs, quindi possiamo semplicemente selezionare le righe quando i valori di una colonna non sono in questo comune:

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

MODIFICARE

Un altro metodo che hai trovato è quello di isinprodurre NaNrighe che puoi rilasciare:

In [138]:

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

Tuttavia, se df2 non avvia le righe nello stesso modo, questo non funzionerà:

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

produrrà l'intero df:

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')sembra fare il trucco. Grazie comunque - la tua risposta mi ha aiutato a trovare una soluzione.
Pensa cose carine il

5
Si noti che l'utilizzo isinrichiede che entrambi i dfs inizino con gli stessi valori di riga, quindi ad esempio se df2 il df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})tuo metodo non funzionerà
EdChum

2
questo ha convertito tutti gli ints in float!
Chris Nielsen,

3
@SergeyZakharov questa risposta pubblicata circa 3 anni fa era corretta per quanto riguarda l'OP e per il loro problema, l'altra risposta è una risposta migliore e gestisce un problema più ampio che non ha mai fatto parte della domanda originale, non è corretto affermare che questo la risposta è errata, è corretta dato il problema come indicato. Inoltre qualcuno ha annullato il voto senza spiegazione, c'è poco che posso fare in quanto questa è una risposta accettata, l'OP non ha cambiato idea e non ho intenzione di cannibalizzare un'altra risposta per farlo bene .
EdChum,

1
@Cecilia è necessario passare keep=False: df0.append(df1).drop_duplicates(keep=False), per impostazione predefinita mantiene il primo duplicato, si vuole far cadere tutti i duplicati
EdChum

190

La soluzione attualmente selezionata produce risultati errati. Per risolvere correttamente questo problema, possiamo eseguire un join sinistro da df1a df2, assicurandoci innanzitutto di ottenere solo le righe univoche df2.

Innanzitutto, è necessario modificare il DataFrame originale per aggiungere la riga con i dati [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

Eseguire un join sinistro, eliminando i duplicati in df2modo che ogni riga di df1join con esattamente 1 riga di df2. Utilizzare il parametro indicatorper restituire una colonna aggiuntiva che indica da quale tabella proveniva la riga.

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

Crea una condizione booleana:

df_all['_merge'] == 'left_only'

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

Perché altre soluzioni sono sbagliate

Alcune soluzioni commettono lo stesso errore: controllano solo che ciascun valore sia indipendente in ciascuna colonna, non insieme nella stessa riga. L'aggiunta dell'ultima riga, che è unica ma ha i valori di entrambe le colonne df2espone l'errore:

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

Questa soluzione ottiene lo stesso risultato errato:

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

2
ma, suppongo, stavano assumendo che il col1 sia unico essendo un indice (non menzionato nella domanda, ma ovvio). Quindi, se non esiste mai un caso del genere in cui ci sono due valori di col2 per lo stesso valore di col1 (non possono esserci due col1 = 3 righe) le risposte sopra sono corrette.
pashute,

14
Non è certo ovvio, quindi il tuo punto non è valido. La mia soluzione si generalizza in più casi.
Ted Petrou,

Domanda, non sarebbe più semplice creare una sezione anziché un array booleano? Poiché l'obiettivo è quello di ottenere le righe.
Matías Romo,

5
Usa df_all[df_all['_merge'] == 'left_only']per avere un df con i risultati
gies0r

77

Supponendo che gli indici siano coerenti nei frame di dati (non tenendo conto dei valori di col effettivi):

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

1
@ChrisNielsen negazione della condizione. Quindi in questo esempio significa "prendere le righe da df1cui NON sono presenti gli indici df2.index". Ulteriori informazioni sulla negazione: stackoverflow.com/q/19960077/304209 (sorprendentemente, non ho trovato alcuna menzione di tilde nei documenti di Panda).
Dennis Golomazov,

Sembra che i dfs debbano avere la stessa lunghezza, no? RicevoValueError: Item wrong length x instead of y.
parole per il

@wordsforthewise no, non lo fanno. La maschera ha la lunghezza di df1 e viene applicata anche a df1. Puoi fornire il tuo esempio?
Dennis Golomazov,

Per correggere il problema della lunghezza dell'articolo, aggiungi .loc
Moreno il

13

Come già accennato, isin richiede che colonne e indici siano gli stessi per una partita. Se la corrispondenza deve essere solo sui contenuti delle righe, un modo per ottenere la maschera per filtrare le righe presenti è convertire le righe in un (Multi) Index:

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

Se si deve tenere conto dell'indice, set_index ha l'argomento della parola chiave append per aggiungere colonne all'indice esistente. Se le colonne non si allineano, l'elenco (df.columns) può essere sostituito con le specifiche delle colonne per allineare i dati.

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

in alternativa potrebbe essere usato per creare gli indici, anche se dubito che sia più efficiente.


@ Dev_123 Rimuovere ~ all'inizio. Il core è creare un elenco di predicati se le righe in df1 si verificano anche in df2, quindi le righe in df1 non sono univoche a df1, ~ lo nega a un elenco di predicati se le righe in df1 non si verificano in df2.
Rune Lyngsoe,

11

Supponiamo di avere due frame di dati, df_1 e df_2 con più campi (column_names) e si desidera trovare le sole voci in df_1 che non si trovano in df_2 sulla base di alcuni campi (ad es. Fields_x, fields_y), attenersi alla seguente procedura.

Passaggio 1: aggiungere una colonna key1 e key2 rispettivamente a df_1 e df_2.

Passaggio 2: riunire i frame di dati come mostrato di seguito. field_x e field_y sono le nostre colonne desiderate.

Passaggio 3: selezionare solo quelle righe da df_1 in cui chiave1 non è uguale a chiave2.

Passaggio 4: rilasciare il tasto 1 e il tasto 2.

Questo metodo risolverà il tuo problema e funziona velocemente anche con set di big data. L'ho provato per i frame di dati con oltre 1.000.000 di righe.

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)

Non penso che questo sia tecnicamente ciò che vuole: vuole sapere quali righe sono uniche per quale df. ma, penso che questa soluzione restituisca un df di righe che erano o uniche per il primo df o il secondo df.
Legit Stack


3

puoi farlo usando il metodo isin (dict) :

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

Spiegazione:

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

Questo produce il risultato sbagliato. Vedi la mia spiegazione di seguito.
Ted Petrou,

2

È inoltre possibile concat df1, df2:

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

e quindi rimuovere tutti i duplicati:

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

Benvenuto in StackOverflow: se pubblichi esempi di codice, XML o dati, evidenzia quelle righe nell'editor di testo e fai clic sul pulsante "Esempi di codice" ({}) sulla barra degli strumenti dell'editor o usando Ctrl + K sulla tastiera per formattare in modo corretto e la sintassi lo evidenzia!
WhatsThePoint,

4
Questo restituirà tutti i dati che si trovano in entrambi i set, non solo i dati che si trovano solo in df1.
Jamie Marshall,

1

Cosa ne pensi di questo:

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

Ecco un altro modo di risolvere questo:

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

O:

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

0

Il mio modo di fare questo consiste nell'aggiungere una nuova colonna unica per un frame di dati e usarla per scegliere se mantenere una voce

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

Questo lo rende così ogni voce in df1 ha un codice - 0 se è univoco per df1, 1 se si trova in entrambi i frame di dati. Quindi lo usi per limitarti a ciò che desideri

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

0
estrarre le righe diverse utilizzando la funzione di unione
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
salva le righe diverse in CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.