insertar filas datos data crear columnas columna agregar python html pandas dataframe panel

python - filas - Salida de diferencia en dos marcos de datos de Pandas uno al lado del otro-destacando la diferencia



pandas dataframe (10)

Destacando la diferencia entre dos DataFrames

Es posible utilizar la propiedad de estilo DataFrame para resaltar el color de fondo de las celdas donde hay una diferencia.

Usando los datos de ejemplo de la pregunta original

El primer paso es concatenar los DataFrames horizontalmente con la función concat y distinguir cada cuadro con el parámetro keys :

df_all = pd.concat([df.set_index(''id''), df2.set_index(''id'')], axis=''columns'', keys=[''First'', ''Second'']) df_all

Probablemente sea más fácil intercambiar los niveles de columna y poner los mismos nombres de columna uno al lado del otro:

df_final = df_all.swaplevel(axis=''columns'')[df.columns[1:]] df_final

Ahora, es mucho más fácil detectar las diferencias en los cuadros. Pero, podemos ir más allá y usar la propiedad de style para resaltar las celdas que son diferentes. Definimos una función personalizada para hacer esto que puede ver en esta parte de la documentación .

def highlight_diff(data, color=''yellow''): attr = ''background-color: {}''.format(color) other = data.xs(''First'', axis=''columns'', level=-1) return pd.DataFrame(np.where(data.ne(other, level=0), attr, ''''), index=data.index, columns=data.columns) df_final.style.apply(highlight_diff, axis=None)

Esto resaltará las celdas que tienen valores faltantes. Puede llenarlos o proporcionar una lógica adicional para que no se destaquen.

Estoy tratando de resaltar exactamente qué cambió entre dos dataframes.

Supongamos que tengo dos marcos de datos de Python Pandas:

"StudentRoster Jan-1": id Name score isEnrolled Comment 111 Jack 2.17 True He was late to class 112 Nick 1.11 False Graduated 113 Zoe 4.12 True "StudentRoster Jan-2": id Name score isEnrolled Comment 111 Jack 2.17 True He was late to class 112 Nick 1.21 False Graduated 113 Zoe 4.12 False On vacation

Mi objetivo es generar una tabla HTML que:

  1. Identifica las filas que han cambiado (podría ser int, float, boolean, string)
  2. Salidas de filas con valores iguales, ANTERIORES y NUEVOS (idealmente en una tabla HTML) para que el consumidor pueda ver claramente qué cambió entre dos marcos de datos:

    "StudentRoster Difference Jan-1 - Jan-2": id Name score isEnrolled Comment 112 Nick was 1.11| now 1.21 False Graduated 113 Zoe 4.12 was True | now False was "" | now "On vacation"

Supongo que podría hacer una comparación fila por columna y columna por columna, pero ¿hay alguna manera más fácil?


Aquí hay otra forma de usar seleccionar y fusionar:

In [6]: # first lets create some dummy dataframes with some column(s) different ...: df1 = pd.DataFrame({''a'': range(-5,0), ''b'': range(10,15), ''c'': range(20,25)}) ...: df2 = pd.DataFrame({''a'': range(-5,0), ''b'': range(10,15), ''c'': [20] + list(range(101,105))}) In [7]: df1 Out[7]: a b c 0 -5 10 20 1 -4 11 21 2 -3 12 22 3 -2 13 23 4 -1 14 24 In [8]: df2 Out[8]: a b c 0 -5 10 20 1 -4 11 101 2 -3 12 102 3 -2 13 103 4 -1 14 104 In [10]: # make condition over the columns you want to comapre ...: condition = df1[''c''] != df2[''c''] ...: ...: # select rows from each dataframe where the condition holds ...: diff1 = df1[condition] ...: diff2 = df2[condition] In [11]: # merge the selected rows (dataframes) with some suffixes (optional) ...: diff1.merge(diff2, on=[''a'',''b''], suffixes=(''_before'', ''_after'')) Out[11]: a b c_before c_after 0 -4 11 21 101 1 -3 12 22 102 2 -2 13 23 103 3 -1 14 24 104

Aquí está lo mismo de una captura de pantalla de Jupyter:


Después de jugar con la respuesta de @ journois, pude hacer que funcione usando MultiIndex en lugar de Panel debido a la privación del Panel .

Primero, crea algunos datos ficticios:

df1 = pd.DataFrame({ ''id'': [''111'', ''222'', ''333'', ''444'', ''555''], ''let'': [''a'', ''b'', ''c'', ''d'', ''e''], ''num'': [''1'', ''2'', ''3'', ''4'', ''5''] }) df2 = pd.DataFrame({ ''id'': [''111'', ''222'', ''333'', ''444'', ''666''], ''let'': [''a'', ''b'', ''c'', ''D'', ''f''], ''num'': [''1'', ''2'', ''Three'', ''4'', ''6''], })

Luego, defina su función diff , en este caso report_diff de su respuesta. report_diff mantiene igual:

def report_diff(x): return x[0] if x[0] == x[1] else ''{} | {}''.format(*x)

Luego, voy a concatenar los datos en un marco de datos MultiIndex:

df_all = pd.concat( [df1.set_index(''id''), df2.set_index(''id'')], axis=''columns'', keys=[''df1'', ''df2''], join=''outer'' ) df_all = df_all.swaplevel(axis=''columns'')[df1.columns[1:]]

Y finalmente voy a aplicar el report_diff en cada grupo de columnas:

df_final.groupby(level=0, axis=1).apply(lambda frame: frame.apply(report_diff, axis=1))

Esto produce:

let num 111 a 1 222 b 2 333 c 3 | Three 444 d | D 4 555 e | nan 5 | nan 666 nan | f nan | 6

¡Y eso es todo!


Esta respuesta simplemente amplía la de @Andy Hayden, haciéndola resistente a cuando los campos numéricos son nan , y envolviéndolo en una función.

import pandas as pd import numpy as np def diff_pd(df1, df2): """Identify differences between two pandas DataFrames""" assert (df1.columns == df2.columns).all(), / "DataFrame column names are different" if any(df1.dtypes != df2.dtypes): "Data Types are different, trying to convert" df2 = df2.astype(df1.dtypes) if df1.equals(df2): return None else: # need to account for np.nan != np.nan returning True diff_mask = (df1 != df2) & ~(df1.isnull() & df2.isnull()) ne_stacked = diff_mask.stack() changed = ne_stacked[ne_stacked] changed.index.names = [''id'', ''col''] difference_locations = np.where(diff_mask) changed_from = df1.values[difference_locations] changed_to = df2.values[difference_locations] return pd.DataFrame({''from'': changed_from, ''to'': changed_to}, index=changed.index)

Entonces, con sus datos (ligeramente editados para tener un NaN en la columna de puntaje):

import sys if sys.version_info[0] < 3: from StringIO import StringIO else: from io import StringIO DF1 = StringIO("""id Name score isEnrolled Comment 111 Jack 2.17 True "He was late to class" 112 Nick 1.11 False "Graduated" 113 Zoe NaN True " " """) DF2 = StringIO("""id Name score isEnrolled Comment 111 Jack 2.17 True "He was late to class" 112 Nick 1.21 False "Graduated" 113 Zoe NaN False "On vacation" """) df1 = pd.read_table(DF1, sep=''/s+'', index_col=''id'') df2 = pd.read_table(DF2, sep=''/s+'', index_col=''id'') diff_pd(df1, df2)

Salida:

from to id col 112 score 1.11 1.21 113 isEnrolled True False Comment On vacation


Extendiendo la respuesta de @cge, que es bastante bueno para una mayor legibilidad del resultado:

a[a != b][np.any(a != b, axis=1)].join(DataFrame(''a<->b'', index=a.index, columns=[''a<=>b''])).join( b[a != b][np.any(a != b, axis=1)] ,rsuffix=''_b'', how=''outer'' ).fillna('''')

Ejemplo completo de demostración:

a = DataFrame(np.random.randn(7,3), columns=list(''ABC'')) b = a.copy() b.iloc[0,2] = np.nan b.iloc[1,0] = 7 b.iloc[3,1] = 77 b.iloc[4,2] = 777 a[a != b][np.any(a != b, axis=1)].join(DataFrame(''a<->b'', index=a.index, columns=[''a<=>b''])).join( b[a != b][np.any(a != b, axis=1)] ,rsuffix=''_b'', how=''outer'' ).fillna('''')


La primera parte es similar a Constantine, puedes obtener el booleano cuyas filas están vacías *:

In [21]: ne = (df1 != df2).any(1) In [22]: ne Out[22]: 0 False 1 True 2 True dtype: bool

Entonces podemos ver qué entradas han cambiado:

In [23]: ne_stacked = (df1 != df2).stack() In [24]: changed = ne_stacked[ne_stacked] In [25]: changed.index.names = [''id'', ''col''] In [26]: changed Out[26]: id col 1 score True 2 isEnrolled True Comment True dtype: bool

Aquí la primera entrada es el índice y la segunda las columnas que se han cambiado.

In [27]: difference_locations = np.where(df1 != df2) In [28]: changed_from = df1.values[difference_locations] In [29]: changed_to = df2.values[difference_locations] In [30]: pd.DataFrame({''from'': changed_from, ''to'': changed_to}, index=changed.index) Out[30]: from to id col 1 score 1.11 1.21 2 isEnrolled True False Comment None On vacation

* Nota: es importante que df1 y df2 compartan el mismo índice aquí. Para superar esta ambigüedad, puedes asegurarte de que solo miras las etiquetas compartidas usando df1.index & df2.index , pero creo que lo dejo como ejercicio.


Me he enfrentado a este problema, pero encontré una respuesta antes de encontrar esta publicación:

Basado en la respuesta de unutbu, carga tus datos ...

import pandas as pd import io texts = [''''''/ id Name score isEnrolled Date 111 Jack True 2013-05-01 12:00:00 112 Nick 1.11 False 2013-05-12 15:05:23 Zoe 4.12 True '''''', ''''''/ id Name score isEnrolled Date 111 Jack 2.17 True 2013-05-01 12:00:00 112 Nick 1.21 False Zoe 4.12 False 2013-05-01 12:00:00''''''] df1 = pd.read_fwf(io.BytesIO(texts[0]), widths=[5,7,25,17,20], parse_dates=[4]) df2 = pd.read_fwf(io.BytesIO(texts[1]), widths=[5,7,25,17,20], parse_dates=[4])

... define tu función diff ...

def report_diff(x): return x[0] if x[0] == x[1] else ''{} | {}''.format(*x)

Entonces puedes simplemente usar un Panel para concluir:

my_panel = pd.Panel(dict(df1=df1,df2=df2)) print my_panel.apply(report_diff, axis=0) # id Name score isEnrolled Date #0 111 Jack nan | 2.17 True 2013-05-01 12:00:00 #1 112 Nick 1.11 | 1.21 False 2013-05-12 15:05:23 | NaT #2 nan | nan Zoe 4.12 True | False NaT | 2013-05-01 12:00:00

Por cierto, si estás en IPython Notebook, te puede gustar usar una función de color diff para dar colores dependiendo de si las celdas son diferentes, iguales o nulas izquierda / derecha:

from IPython.display import HTML pd.options.display.max_colwidth = 500 # You need this, otherwise pandas # will limit your HTML strings to 50 characters def report_diff(x): if x[0]==x[1]: return unicode(x[0].__str__()) elif pd.isnull(x[0]) and pd.isnull(x[1]): return u''<table style="background-color:#00ff00;font-weight:bold;">''+/ ''<tr><td>%s</td></tr><tr><td>%s</td></tr></table>'' % (''nan'', ''nan'') elif pd.isnull(x[0]) and ~pd.isnull(x[1]): return u''<table style="background-color:#ffff00;font-weight:bold;">''+/ ''<tr><td>%s</td></tr><tr><td>%s</td></tr></table>'' % (''nan'', x[1]) elif ~pd.isnull(x[0]) and pd.isnull(x[1]): return u''<table style="background-color:#0000ff;font-weight:bold;">''+/ ''<tr><td>%s</td></tr><tr><td>%s</td></tr></table>'' % (x[0],''nan'') else: return u''<table style="background-color:#ff0000;font-weight:bold;">''+/ ''<tr><td>%s</td></tr><tr><td>%s</td></tr></table>'' % (x[0], x[1]) HTML(my_panel.apply(report_diff, axis=0).to_html(escape=False))


Si sus dos marcos de datos tienen los mismos identificadores, descubrir qué fue lo que cambió realmente es bastante fácil. Simplemente haciendo frame1 != frame2 le dará un DataFrame booleano donde cada True es información que ha cambiado. A partir de ahí, puede obtener fácilmente el índice de cada fila modificada haciendo changedids = frame1.index[np.any(frame1 != frame2,axis=1)] .


Un enfoque diferente que usa concat y drop_duplicates:

import sys if sys.version_info[0] < 3: from StringIO import StringIO else: from io import StringIO import pandas as pd DF1 = StringIO("""id Name score isEnrolled Comment 111 Jack 2.17 True "He was late to class" 112 Nick 1.11 False "Graduated" 113 Zoe NaN True " " """) DF2 = StringIO("""id Name score isEnrolled Comment 111 Jack 2.17 True "He was late to class" 112 Nick 1.21 False "Graduated" 113 Zoe NaN False "On vacation" """) df1 = pd.read_table(DF1, sep=''/s+'', index_col=''id'') df2 = pd.read_table(DF2, sep=''/s+'', index_col=''id'') #%% dictionary = {1:df1,2:df2} df=pd.concat(dictionary) df.drop_duplicates(keep=False)

Salida:

Name score isEnrolled Comment id 1 112 Nick 1.11 False Graduated 113 Zoe NaN True 2 112 Nick 1.21 False Graduated 113 Zoe NaN False On vacation


import pandas as pd import io texts = [''''''/ id Name score isEnrolled Comment 111 Jack 2.17 True He was late to class 112 Nick 1.11 False Graduated 113 Zoe 4.12 True '''''', ''''''/ id Name score isEnrolled Comment 111 Jack 2.17 True He was late to class 112 Nick 1.21 False Graduated 113 Zoe 4.12 False On vacation''''''] df1 = pd.read_fwf(io.BytesIO(texts[0]), widths=[5,7,25,21,20]) df2 = pd.read_fwf(io.BytesIO(texts[1]), widths=[5,7,25,21,20]) df = pd.concat([df1,df2]) print(df) # id Name score isEnrolled Comment # 0 111 Jack 2.17 True He was late to class # 1 112 Nick 1.11 False Graduated # 2 113 Zoe 4.12 True NaN # 0 111 Jack 2.17 True He was late to class # 1 112 Nick 1.21 False Graduated # 2 113 Zoe 4.12 False On vacation df.set_index([''id'', ''Name''], inplace=True) print(df) # score isEnrolled Comment # id Name # 111 Jack 2.17 True He was late to class # 112 Nick 1.11 False Graduated # 113 Zoe 4.12 True NaN # 111 Jack 2.17 True He was late to class # 112 Nick 1.21 False Graduated # 113 Zoe 4.12 False On vacation def report_diff(x): return x[0] if x[0] == x[1] else ''{} | {}''.format(*x) changes = df.groupby(level=[''id'', ''Name'']).agg(report_diff) print(changes)

huellas dactilares

score isEnrolled Comment id Name 111 Jack 2.17 True He was late to class 112 Nick 1.11 | 1.21 False Graduated 113 Zoe 4.12 True | False nan | On vacation