Python Pandas: encuentra la diferencia entre dos marcos de datos

98

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í

userPyGeo
fuente
3
La forma más sencilla de hacerlo dependerá de cómo estén estructurados sus marcos de datos (es decir, si se pueden utilizar los índices, etc.). Este es un buen ejemplo de por qué siempre debe incluir un ejemplo reproducible en las preguntas de pandas.
cmaher
He agregado la imagen de muestra del marco de datos
userPyGeo
similar a stackoverflow.com/q/20225110
SpeedCoder5

Respuestas:

153

Mediante el uso 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]})

Saldrá como a continuación, lo cual está mal

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 lograrlo?

Método 1: usar isincontuple

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: mergeconindicator

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
BEN_YO
fuente
3
También puede determinar qué columnas se deben considerar al buscar duplicados:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
Szpaqn
@Szpaqn nota que este método no manejará el caso especial. :-)
BEN_YO
Tenga en cuenta que esto puede hacer que queden filas inesperadas en el resultado si uno de sus tipos de datos es float(porque 12.00000000001 != 12). Una mejor práctica es encontrar la intersección establecida de los ID en dos marcos de datos y obtener la diferencia basada en eso.
Jiāgěng
1
@DtechNet necesita hacer que dos marcos de datos tengan el mismo nombre
BEN_YO
2
El método 2 ( indicator=True) es una herramienta muy versátil y útil, me encantaría verlo en la parte superior de esta respuesta, pero con la combinación 'externa' y no 'izquierda' para cubrir las 3 situaciones.
mirekphd
32

Para las filas, intente esto, dónde Nameestá la columna de índice conjunto (puede ser una lista para varias columnas comunes o especificar left_ony right_on):

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

La indicator=Trueconfiguración es útil ya que agrega una columna llamada _merge, con todos los cambios entre df1y df2, categorizados en 3 tipos posibles: "left_only", "right_only" o "ambos".

Para columnas, intente esto:

set(df1.columns).symmetric_difference(df2.columns)
jpp
fuente
8
¿Le importaría comentar al votante negativo? mergewith indicator=Truees la solución clásica para comparar marcos de datos por campos dados.
jpp
9

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

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

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 a 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. Como tenemos el mismo 'Nombre' en dos dfs, podemos simplemente eliminar el índice de 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)
liangli
fuente
probablemente te refieres a 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
SpeedCoder5
fuente
¿Qué significa '~'?
Piotrek Leśniak
'~' no es para indexación booleana. Ver: pandas.pydata.org/pandas-docs/stable/user_guide/…
SpeedCoder5
3

Quizás una línea más simple, con nombres de columna idénticos o diferentes. Funcionó incluso cuando df2 ['Nombre2'] contenía valores duplicados.

newDf = df1.set_index('Name1')
           .drop(df2['Name2'], errors='ignore')
           .reset_index(drop=False)
Cherif Diallo
fuente
2
simple y eficaz. Errores agregados = 'ignorar' para resolver el problema del caso en el que los valores de destino no están en la fuente (es decir, la intersección) y restablecer el índice al final genera un gl que es similar al original.
MrE
0

Una ligera variación de la solución de nice @ liangli que no requiere cambiar el índice de los marcos de datos existentes:

newdf = df1.drop(df1.join(df2.set_index('Name').index))
Serge Ballesta
fuente
0

Encontrar diferencias por índice. Suponiendo que df1 es un subconjunto de df2 y los índices se trasladan al subconjunto

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

DOS
fuente
0

Además de la respuesta aceptada, me gustaría proponer una solución más amplia que pueda encontrar una diferencia de conjunto 2D de dos marcos de datos con cualquier index/ columns(es posible que no coincidan para ambos marcos de datos). También el método permite configurar la tolerancia para los floatelementos para la comparación de marcos de datos (utiliza 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

Ejemplo:

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 
Luchko
fuente
0

Como se mencionó aquí que

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

es la solución correcta pero producirá una salida incorrecta si

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

En ese caso, la solución anterior dará un marco de datos vacío , en su lugar, debe usar el concatmétodo después de eliminar los duplicados de cada marco de datos.

Utilizar concate with drop_duplicates

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