Encuentra la diferencia entre dos marcos de datos

6 minutos de lectura

avatar de usuario de userPyGeo
usuarioPyGeo

Tengo dos marcos de datos df1 y df2, donde df2 es un subconjunto de df1. ¿Cómo obtengo un nuevo marco de datos (df3) que es la diferencia entre los dos marcos de datos?

En otras palabras, ¿un marco de datos que tiene todas las filas/columnas en df1 que no están en df2?

ingrese la descripción de la imagen aquí

  • La forma más fácil de hacer esto dependerá de cómo estén estructurados sus marcos de datos (es decir, si se pueden usar los índices, etc.). Este es un buen ejemplo de por qué siempre debe incluir un ejemplo reproducible en las preguntas de pandas.

    – cmaher

    06/02/2018 a las 16:30

  • He agregado la imagen de muestra del marco de datos

    – usuarioPyGeo

    6 de febrero de 2018 a las 16:33

  • similar a stackoverflow.com/q/20225110

    – SpeedCoder5

    7 de junio de 2018 a las 13:48

Avatar de usuario de BENY
beny

Mediante el uso drop_duplicates

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

Update :

The above method only works for those data frames that don't already have duplicates themselves. For example:

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

Saldrá como a continuación, lo cual es incorrecto

Salida incorrecta:

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

Salida correcta

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

¿Cómo lograr eso?

Método 1: Usar isin con tuple

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

Método 2: merge con indicator

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

  • También puede determinar qué columnas se deben considerar al buscar duplicados: pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)

    – Szpaqn

    8 de enero de 2019 a las 8:15

  • @Szpaqn nota que este método no manejará el caso especial. 🙂

    – BENY

    9 de enero de 2019 a las 17:10

  • @DtechNet necesita hacer que dos marcos de datos tengan el mismo nombre

    – BENY

    16 oct 2019 a las 20:14

  • Método 2 (indicator=True) es una herramienta muy versátil y útil, me encantaría verla en la parte superior de esta respuesta, pero con ‘exterior’, no ‘izquierda’, únase para cubrir las 3 situaciones.

    – mirekphd

    2 de mayo de 2020 a las 11:07

  • ¿Podría usted amablemente explicar el significado de apply(tuple,1) ?

    – liangli

    29 de mayo de 2020 a las 4:22

avatar de usuario de jpp
jpp

Para las filas, prueba esto, donde Name es la columna de índice conjunto (puede ser una lista de varias columnas comunes o especificar left_on y right_on):

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

los indicator=True La configuración es útil ya que agrega una columna llamada _mergecon todos los cambios entre df1 y df2clasificados en 3 tipos posibles: “solo_izquierda”, “solo_derecha” o “ambos”.

Para las columnas, intente esto:

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

  • ¿A los votantes negativos les importa comentar? merge con indicator=True es la solución clásica para comparar marcos de datos por campos dados.

    – jpp

    8 de enero de 2019 a las 9:17

avatar de usuario de toecsnar42
toecsnar42

Respuesta aceptada El Método 1 no funcionará para marcos de datos con NaN dentro, ya que pd.np.nan != pd.np.nan. No estoy seguro de si esta es la mejor manera, pero se puede evitar

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

Es más lento, porque necesita convertir datos en una cadena, pero gracias a esta conversión pd.np.nan == pd.np.nan.

Vamos a repasar el código. Primero lanzamos valores a la cadena y aplicamos tuple función a cada fila.

df1.astype(str).apply(tuple, 1)
df2.astype(str).apply(tuple, 1)

Gracias a eso, conseguimos pd.Series objeto con lista de tuplas. Cada tupla contiene una fila completa de df1/df2. Luego aplicamos isin método en df1 para comprobar si cada tupla “está dentro” df2. El resultado es pd.Series con valores booleanos. Verdadero si tupla de df1 es en df2. Al final, negamos los resultados con ~ firmar y aplicar filtro en df1. Para resumir, solo obtenemos esas filas de df1 que no están en df2.

Para hacerlo más legible, podemos escribirlo como:

df1_str_tuples = df1.astype(str).apply(tuple, 1)
df2_str_tuples = df2.astype(str).apply(tuple, 1)
df1_values_in_df2_filter = df1_str_tuples.isin(df2_str_tuples)
df1_values_not_in_df2 = df1[~df1_values_in_df2_filter]

  • Esta es una gran respuesta, pero es incomprensible como una sola línea. Si uno separa cada paso y comprende lo que hace, queda muy claro cómo se hace el trabajo.

    – rioZg

    10 de febrero de 2021 a las 9:52


  • Explicación añadida. ¡Espero que ayude!

    – toecsnar42

    17 de febrero de 2021 a las 22:19

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

Avatar de usuario de Cherif Diallo
Cherif Diallo

Tal vez una sola línea más simple, con nombres de columna idénticos o diferentes. Trabajó incluso cuando df2[‘Name2’] contenía valores duplicados.

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

  • sencillo y eficaz. Se agregaron errores = ‘ignorar’ para resolver el problema en el caso de que los valores de destino no estén en la fuente (es decir, la intersección) y al restablecer el índice al final se obtiene un df similar al original.

    – Sr. E

    3 de febrero de 2020 a las 3:50

Avatar de usuario de VirtualScooter
patinete virtual

edit2, descubrí una nueva solución sin la necesidad de configurar el índice

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

De acuerdo, encontré que la respuesta del voto más alto ya contiene lo que he descubierto. Sí, solo podemos usar este código con la condición de que no haya duplicados en cada dos dfs.


Tengo un método complicado. Primero establecemos ‘Nombre’ como el índice de dos marcos de datos dados por la pregunta. Dado que tenemos el mismo ‘Nombre’ en dos df, podemos eliminar el índice del df ‘más pequeño’ del df ‘más grande’. Aquí está el código.

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

  • sencillo y eficaz. Se agregaron errores = ‘ignorar’ para resolver el problema en el caso de que los valores de destino no estén en la fuente (es decir, la intersección) y al restablecer el índice al final se obtiene un df similar al original.

    – Sr. E

    3 de febrero de 2020 a las 3:50

Avatar de usuario de Yushan ZHANG
Yushan Zhang

Pandas ahora ofrece una nueva API para hacer la diferencia del marco de datos: pandas.DataFrame.compare

df.compare(df2)
  col1       col3
  self other self other
0    a     c  NaN   NaN
2  NaN   NaN  3.0   4.0

¿Ha sido útil esta solución?