los pandas obtienen filas que NO están en otro marco de datos


230

Tengo dos marcos de datos de pandas que tienen algunas filas en común.

Supongamos que dataframe2 es un subconjunto de dataframe1.

¿Cómo puedo obtener las filas de dataframe1 que no están en 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 No veo cómo la respuesta que proporcionó es la correcta. Si tengo dos marcos de datos de los cuales uno es un subconjunto del otro, necesito eliminar todas esas filas, que están en el subconjunto. No quiero eliminar duplicados. Quiero eliminar completamente el subconjunto.
jukebox

Respuestas:


172

Un método sería almacenar el resultado de una combinación interna de ambos dfs, luego podemos simplemente seleccionar las filas cuando los valores de una columna no son tan comunes:

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

EDITAR

Otro método que haya encontrado es usar el isinque producirá NaNfilas que puede soltar:

In [138]:

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

Sin embargo, si df2 no ​​inicia filas de la misma manera, esto no funcionará:

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

producirá todo el 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')Parece funcionar. Gracias de todos modos, su respuesta me ayudó a encontrar una solución.
Piensa cosas buenas

55
Tenga en cuenta que el uso isinrequiere que ambos dfs comiencen con los mismos valores de fila, por ejemplo, si df2 era df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})su método no funcionaría
EdChum,

2
¡Esto convirtió todas las entradas en flotadores!
Chris Nielsen

3
@SergeyZakharov esta respuesta publicada hace casi 3 años fue correcta en lo que respecta al OP y para su problema, la otra respuesta es una mejor respuesta y maneja un problema más amplio que nunca fue parte de la pregunta original, es incorrecto afirmar que esto la respuesta es incorrecta, es correcta dado el problema tal como se expone. Además, alguien ha rechazado esto sin explicación, hay poco que pueda hacer ya que esta es una respuesta aceptada, el OP no ha cambiado de opinión y no voy a canibalizar otra respuesta para que sea correcta .
EdChum

1
@Cecilia que necesita pasar keep=False: df0.append(df1).drop_duplicates(keep=False)por defecto mantiene el primer duplicado, desea eliminar todos los duplicados
EdChum

190

La solución actualmente seleccionada produce resultados incorrectos. Para resolver correctamente este problema, podemos realizar una unión izquierda desde df1hasta df2, asegurándonos de obtener primero solo las filas únicas para df2.

Primero, necesitamos modificar el DataFrame original para agregar la fila con datos [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

Realice una unión izquierda, eliminando duplicados df2para que cada fila de df1uniones con exactamente 1 fila de df2. Use el parámetro indicatorpara devolver una columna adicional que indique de qué tabla era la fila.

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

Crear una condición booleana:

df_all['_merge'] == 'left_only'

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

¿Por qué otras soluciones están mal?

Algunas soluciones cometen el mismo error: solo verifican que cada valor sea independiente en cada columna, no juntos en la misma fila. Agregar la última fila, que es única pero tiene los valores de ambas columnas, df2expone el error:

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

Esta solución obtiene el mismo resultado incorrecto:

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

2
pero, supongo, estaban asumiendo que col1 es único por ser un índice (no mencionado en la pregunta, pero obvio). Entonces, si nunca hay un caso en el que haya dos valores de col2 para el mismo valor de col1 (no puede haber dos col1 = 3 filas), las respuestas anteriores son correctas.
pashute

14
Ciertamente no es obvio, por lo que su punto no es válido. Mi solución se generaliza a más casos.
Ted Petrou

Pregunta, ¿no sería más fácil crear un segmento en lugar de una matriz booleana? Dado que el objetivo es obtener las filas.
Matías Romo

55
Utilice df_all[df_all['_merge'] == 'left_only']tener un df con los resultados
gies0r

77

Suponiendo que los índices son consistentes en los marcos de datos (sin tener en cuenta los valores reales de col):

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

1
@ChrisNielsen negación de la condición. Entonces, en este ejemplo, significa "tomar las filas de las df1cuales los índices NO están df2.index". Más información sobre la negación: stackoverflow.com/q/19960077/304209 (sorprendentemente, no pude encontrar ninguna mención de tilde en los documentos de pandas).
Dennis Golomazov

Parece que los dfs tienen que tener la misma longitud, ¿no? Me estoy poniendoValueError: Item wrong length x instead of y.
palabrasforthewise

@wordsforthewise no, no lo hacen. La máscara tiene la longitud de df1 y también se aplica a df1. ¿Puedes dar tu ejemplo?
Dennis Golomazov

Para corregir el problema de longitud del elemento, debe agregar .loc
Moreno

13

Como ya se insinuó, isin requiere que las columnas y los índices sean los mismos para una coincidencia. Si la coincidencia solo debe estar en el contenido de la fila, una forma de obtener la máscara para filtrar las filas presentes es convertir las filas en un Índice (Multi):

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

Si se debe tener en cuenta el índice, set_index tiene un argumento de palabra clave anexar para agregar columnas al índice existente. Si las columnas no se alinean, la lista (df.columns) se puede reemplazar con especificaciones de columna para alinear los datos.

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

alternativamente podría usarse para crear los índices, aunque dudo que esto sea más eficiente.


@ Dev_123 Elimina el ~ al principio. El núcleo es crear una lista de predicados de si las filas en df1 también ocurren en df2, por lo que las filas en df1 que no son exclusivas de df1, ~ niega esto a una lista de predicados de si las filas en df1 no ocurren en df2.
Rune Lyngsoe

11

Suponga que tiene dos marcos de datos, df_1 y df_2 con múltiples campos (nombres de columna) y desea encontrar las únicas entradas en df_1 que no están en df_2 sobre la base de algunos campos (por ejemplo, fields_x, fields_y), siga los siguientes pasos.

Paso 1.Agregue una columna key1 y key2 a df_1 y df_2 respectivamente.

Paso 2. Combina los marcos de datos como se muestra a continuación. field_x y field_y son nuestras columnas deseadas.

Paso 3. Seleccione solo aquellas filas de df_1 donde key1 no es igual a key2.

Paso4.Drop key1 y key2.

Este método resolverá su problema y funciona rápido incluso con grandes conjuntos de datos. Lo he probado para marcos de datos con más de 1,000,000 de filas.

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)

No creo que esto sea técnicamente lo que quiere: quiere saber qué filas eran exclusivas de cada df. pero, creo que esta solución devuelve un df de filas que eran exclusivas del primer df o del segundo df.
Legit Stack


3

puedes hacerlo usando el método isin (dict) :

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

Explicación:

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

Esto produce el resultado incorrecto. Vea mi explicación a continuación.
Ted Petrou el

2

También puede concat df1, df2:

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

y luego elimine todos los duplicados:

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

Bienvenido a StackOverflow: si publica código, XML o muestras de datos, resalte esas líneas en el editor de texto y haga clic en el botón "muestras de código" ({}) en la barra de herramientas del editor o use Ctrl + K en su teclado para formatear bien y sintaxis resaltarlo!
WhatsThePoint

44
Esto devolverá todos los datos que están en cualquiera de los conjuntos, no solo los datos que están solo en df1.
Jamie Marshall

1

Qué tal esto:

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

Aquí hay otra forma de resolver esto:

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

Mi forma de hacerlo implica agregar una nueva columna que sea única para un marco de datos y usar esto para elegir si se debe mantener una entrada

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

Esto hace que cada entrada en df1 tenga un código: 0 si es único para df1, 1 si está en ambos dataFrames. Luego usa esto para restringir a lo que quieres

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

0
extraer las filas diferentes usando la función de fusión
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
guardar las filas diferentes en CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.