Comparar dos marcos de datos y obtener las diferencias

89

Tengo dos marcos de datos. Ejemplos:

df1:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green

df2:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange

Cada marco de datos tiene la fecha como índice. Ambos marcos de datos tienen la misma estructura.

Lo que quiero hacer es comparar estos dos marcos de datos y encontrar qué filas están en df2 y no en df1. Quiero comparar la fecha (índice) y la primera columna (Banana, APple, etc.) para ver si existen en df2 vs df1.

He probado lo siguiente:

Para el primer enfoque, aparece este error: "Excepción: solo se pueden comparar objetos DataFrame con etiquetas idénticas" . Intenté eliminar la fecha como índice pero aparece el mismo error.

En el tercer enfoque , obtengo que la aserción devuelva False pero no puedo averiguar cómo ver realmente las diferentes filas.

Cualquier sugerencia sería bienvenida

Eric D. Brown
fuente
Si hace esto: cookbook-r.com/Manipulating_data/… , ¿eliminará la excepción de 'objetos DataFrame etiquetados de forma idéntica'?
Anthony Kong
He cambiado los nombres de las columnas muchas veces para intentar solucionar el problema sin suerte.
Eric D. Brown
1
FWIW, cambié los nombres de las columnas para que sean "a, b, c, d" en ambos marcos de datos y recibo el mismo mensaje de error.
Eric D. Brown

Respuestas:

103

Este enfoque df1 != df2funciona solo para marcos de datos con filas y columnas idénticas. De hecho, todos los ejes de los marcos de datos se comparan con el _indexed_samemétodo y se genera una excepción si se encuentran diferencias, incluso en el orden de columnas / índices.

Si lo entendí bien, no querrá encontrar cambios, sino diferencias simétricas. Para eso, un enfoque podría ser concatenar marcos de datos:

>>> df = pd.concat([df1, df2])
>>> df = df.reset_index(drop=True)

agrupar por

>>> df_gpby = df.groupby(list(df.columns))

obtener índice de registros únicos

>>> idx = [x[0] for x in df_gpby.groups.values() if len(x) == 1]

filtrar

>>> df.reindex(idx)
         Date   Fruit   Num   Color
9  2013-11-25  Orange   8.6  Orange
8  2013-11-25   Apple  22.1     Red
alko
fuente
Esta fue la respuesta. Eliminé el índice "Fecha" y seguí este enfoque y obtuve el resultado correcto.
Eric D. Brown
8
¿Existe una manera fácil de agregar una bandera a esto para ver qué filas se eliminaron / agregaron / cambiaron de df1 a df2?
pyCthon
@alko Me preguntaba, ¿esto pd.concatagrega solo los elementos que faltan del df1? ¿O reemplaza df1completamente con df2?
jake wong
@jakewong pd.concat, como se usa aquí, hace una combinación externa. En otras palabras, se une a todos los índices de ambos df y éste es, de hecho, el comportamiento predeterminado de pd.concat(), aquí está la documentación pandas.pydata.org/pandas-docs/stable/merging.html
Thanos
¿Cuál es el número máximo de registros que podemos comparar usando pandas?
pyd
25

Pasar los marcos de datos a concat en un diccionario, da como resultado un marco de datos de múltiples índices del que puede eliminar fácilmente los duplicados, lo que da como resultado un marco de datos de múltiples índices con las diferencias entre los marcos de datos:

import sys
if sys.version_info[0] < 3:
    from StringIO import StringIO
else:
    from io import StringIO
import pandas as pd

DF1 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
""")
DF2 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange""")


df1 = pd.read_table(DF1, sep='\s+')
df2 = pd.read_table(DF2, sep='\s+')
#%%
dfs_dictionary = {'DF1':df1,'DF2':df2}
df=pd.concat(dfs_dictionary)
df.drop_duplicates(keep=False)

Resultado:

             Date   Fruit   Num   Color
DF2 4  2013-11-25   Apple  22.1     Red
    5  2013-11-25  Orange   8.6  Orange
jur
fuente
1
Este es un método mucho más fácil, solo una revisión más puede hacerlo más fácil. No hay necesidad de concat en un diccionario, use df = pd.concat ([df1, df2]) haría lo mismo
ling
¡No debe sobrescribir la palabra clave incorporada dict!
denfromufa
¿Hay alguna forma de agregar a esto para determinar qué marco de datos contenía la fila única?
jlewkovich
Puede saber por el primer nivel en el multiíndice que contiene la clave del marco de datos en el diccionario (actualicé la salida con las claves correctas)
jur
24

Actualizando y colocando, en algún lugar donde sea más fácil para otros encontrar, el comentario de ling sobre la respuesta de jur anterior.

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

Prueba con estos marcos de datos:

df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})

df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})

Resultados en esto: ingrese la descripción de la imagen aquí

leerssej
fuente
5

Sobre la base de la respuesta de alko que casi funcionó para mí, excepto por el paso de filtrado (donde obtengo :) ValueError: cannot reindex from a duplicate axis, aquí está la solución final que utilicé:

# join the dataframes
united_data = pd.concat([data1, data2, data3, ...])
# group the data by the whole row to find duplicates
united_data_grouped = united_data.groupby(list(united_data.columns))
# detect the row indices of unique rows
uniq_data_idx = [x[0] for x in united_data_grouped.indices.values() if len(x) == 1]
# extract those unique values
uniq_data = united_data.iloc[uniq_data_idx]
fnl
fuente
Buena adición a la respuesta. Gracias
Eric D. Brown
1
Recibo el error ' IndexError: index out of bounds', cuando intento ejecutar la tercera línea.
Moondra
5
# THIS WORK FOR ME

# Get all diferent values
df3 = pd.merge(df1, df2, how='outer', indicator='Exist')
df3 = df3.loc[df3['Exist'] != 'both']


# If you like to filter by a common ID
df3  = pd.merge(df1, df2, on="Fruit", how='outer', indicator='Exist')
df3  = df3.loc[df3['Exist'] != 'both']
Ivan Moran
fuente
esta es la mejor respuesta
moshevi
3

Existe una solución más simple, más rápida y mejor, y si los números son diferentes, incluso pueden darte diferencias de cantidades:

df1_i = df1.set_index(['Date','Fruit','Color'])
df2_i = df2.set_index(['Date','Fruit','Color'])
df_diff = df1_i.join(df2_i,how='outer',rsuffix='_').fillna(0)
df_diff = (df_diff['Num'] - df_diff['Num_'])

Aquí df_diff es una sinopsis de las diferencias. Incluso puede usarlo para encontrar las diferencias en cantidades. En tu ejemplo:

ingrese la descripción de la imagen aquí

Explicación: De manera similar a comparar dos listas, para hacerlo de manera eficiente, primero debemos ordenarlas y luego compararlas (convertir la lista a conjuntos / hash también sería rápido; ambos son una mejora increíble para el simple ciclo de comparación doble O (N ^ 2)

Nota: el siguiente código produce las tablas:

df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})
df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})
ntg
fuente
3

Fundador de una solución simple aquí:

https://stackoverflow.com/a/47132808/9656339

pd.concat([df1, df2]).loc[df1.index.symmetric_difference(df2.index)]

Tom2shoes
fuente
1
Bienvenido a Stack Overflow Tom2shoes. No proporcione respuestas de solo enlace, intente extraer el contenido del enlace y déjelo solo como referencia (ya que el contenido del enlace se puede eliminar o el enlace en sí se puede romper). Para obtener más información, consulte "¿Cómo escribo una buena respuesta?" . Si cree que esta pregunta ya fue respondida en otra pregunta, márquela como un duplicado.
GGG
2
# given
df1=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green']})
df2=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,1000,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange']})

# find which rows are in df2 that aren't in df1 by Date and Fruit
df_2notin1 = df2[~(df2['Date'].isin(df1['Date']) & df2['Fruit'].isin(df1['Fruit']) )].dropna().reset_index(drop=True)

# output
print('df_2notin1\n', df_2notin1)
#      Color        Date   Fruit   Num
# 0     Red  2013-11-25   Apple  22.1
# 1  Orange  2013-11-25  Orange   8.6
SpeedCoder5
fuente
1

Tengo esta solución. ¿Esto te ayuda?

text = """df1:
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green

df2:
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange



argetz45
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 118.6 Orange
2013-11-24 Apple 74.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25     Nuts    45.8 Brown
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange
2013-11-26   Pear 102.54    Pale"""

.

from collections import OrderedDict
import re

r = re.compile('([a-zA-Z\d]+).*\n'
               '(20\d\d-[01]\d-[0123]\d.+\n?'
               '(.+\n?)*)'
               '(?=[ \n]*\Z'
                  '|'
                  '\n+[a-zA-Z\d]+.*\n'
                  '20\d\d-[01]\d-[0123]\d)')

r2 = re.compile('((20\d\d-[01]\d-[0123]\d) +([^\d.]+)(?<! )[^\n]+)')

d = OrderedDict()
bef = []

for m in r.finditer(text):
    li = []
    for x in r2.findall(m.group(2)):
        if not any(x[1:3]==elbef for elbef in bef):
            bef.append(x[1:3])
            li.append(x[0])
    d[m.group(1)] = li


for name,lu in d.iteritems():
    print '%s\n%s\n' % (name,'\n'.join(lu))

resultado

df1
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green

df2
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange

argetz45
2013-11-25     Nuts    45.8 Brown
2013-11-26   Pear 102.54    Pale
eyquem
fuente
Gracias por la ayuda. Vi la respuesta de @alko y ese código funcionó bien.
Eric D. Brown
1

Dado pandas >= 1.1.0que tenemos DataFrame.comparey Series.compare.

Nota: el método solo puede comparar objetos DataFrame con etiquetas idénticas, esto significa DataFrames con etiquetas de fila y columna idénticas.

df1 = pd.DataFrame({'A': [1, 2, 3],
                    'B': [4, 5, 6],
                    'C': [7, np.NaN, 9]})

df2 = pd.DataFrame({'A': [1, 99, 3],
                    'B': [4, 5, 81],
                    'C': [7, 8, 9]})

   A  B    C
0  1  4  7.0
1  2  5  NaN
2  3  6  9.0 

    A   B  C
0   1   4  7
1  99   5  8
2   3  81  9
df1.compare(df2)

     A          B          C      
  self other self other self other
1  2.0  99.0  NaN   NaN  NaN   8.0
2  NaN   NaN  6.0  81.0  NaN   NaN
Erfan
fuente
Gracias por esta información. No me he movido a 1.1 todavía, pero es bueno saberlo.
Eric D. Brown
0

Un detalle importante a tener en cuenta es que sus datos tienen valores de índice duplicados , por lo que para realizar cualquier comparación sencilla, debemos convertir todo en único con df.reset_index()y, por lo tanto, podemos realizar selecciones en función de las condiciones. Una vez que en su caso se define el índice, supongo que le gustaría mantener el índice para que haya una solución de una línea:

[~df2.reset_index().isin(df1.reset_index())].dropna().set_index('Date')

Una vez que el objetivo desde una perspectiva pitónica es mejorar la legibilidad, podemos romper un poco:

# keep the index name, if it does not have a name it uses the default name
index_name = df.index.name if df.index.name else 'index' 

# setting the index to become unique
df1 = df1.reset_index()
df2 = df2.reset_index()

# getting the differences to a Dataframe
df_diff = df2[~df2.isin(df1)].dropna().set_index(index_name)
Gandreoti
fuente
0

Espero que esto te sea de utilidad. ^ o ^

df1 = pd.DataFrame({'date': ['0207', '0207'], 'col1': [1, 2]})
df2 = pd.DataFrame({'date': ['0207', '0207', '0208', '0208'], 'col1': [1, 2, 3, 4]})
print(f"df1(Before):\n{df1}\ndf2:\n{df2}")
"""
df1(Before):
   date  col1
0  0207     1
1  0207     2

df2:
   date  col1
0  0207     1
1  0207     2
2  0208     3
3  0208     4
"""

old_set = set(df1.index.values)
new_set = set(df2.index.values)
new_data_index = new_set - old_set
new_data_list = []
for idx in new_data_index:
    new_data_list.append(df2.loc[idx])

if len(new_data_list) > 0:
    df1 = df1.append(new_data_list)
print(f"df1(After):\n{df1}")
"""
df1(After):
   date  col1
0  0207     1
1  0207     2
2  0208     3
3  0208     4
"""
Carson
fuente
0

Probé este método y funcionó. Espero que también pueda ayudar:

"""Identify differences between two pandas DataFrames"""
df1.sort_index(inplace=True)
df2.sort_index(inplace=True)
df_all = pd.concat([df1, df12], axis='columns', keys=['First', 'Second'])
df_final = df_all.swaplevel(axis='columns')[df1.columns[1:]]
df_final[df_final['change this to one of the columns'] != df_final['change this to one of the columns']]
depurando XD
fuente