Tengo muchos DataFrames que necesito fusionar.

Digamos:

base: id  constraint
      1   'a'
      2   'b'
      3   'c'

df_1: id value constraint
      1  1     'a'
      2  2     'a'
      3  3     'a'

df_2: id value constraint
      1  1     'b'
      2  2     'b'
      3  3     'b'


df_3: id value constraint
      1  1     'c'
      2  2     'c'
      3  3     'c'

Si trato de fusionarlos a todos (estará en un bucle), obtengo:

a = pd.merge(base, df_1, on=['id', 'constraint'], how='left')
b = pd.merge(a, df_2, on=['id', 'constraint'], how='left')
c = pd.merge(b, df_3, on=['id', 'constraint'], how='left')
id constraint value   value_x  value_y
1  'a'        1       NaN      NaN
2  'b'        NaN     2        NaN
3  'c'        NaN     NaN      3

El resultado deseado sería:

id constraint value
1  'a'        1 
2  'b'        2
3  'c'        3

Sé acerca de combine_first y funciona, pero no puedo tener este enfoque porque es miles de veces más lento.

¿Hay un merge que pueda reemplazar los valores en caso de superposición de columnas?

Es algo similar a esta pregunta, sin respuestas.

1
Gustavo Lopes 7 oct. 2019 a las 21:21

4 respuestas

La mejor respuesta

Dado su MCVE:

import pandas as pd

base = pd.DataFrame([1,2,3], columns=['id'])
df1 = pd.DataFrame([[1,1]], columns=['id', 'value'])
df2 = pd.DataFrame([[2,2]], columns=['id', 'value'])
df3 = pd.DataFrame([[3,3]], columns=['id', 'value'])

Sugeriría concat primero su marco de datos (usando un bucle si es necesario):

df = pd.concat([df1, df2, df3])

Y luego fusionar:

pd.merge(base, df, on='id')

Cede:

   id  value
0   1      1
1   2      2
2   3      3

Actualizar

Ejecutando el código con la nueva versión de su pregunta y la entrada proporcionada por @Celius Stingher:

a = {'id':[1,2,3],'constrains':['a','b','c']}
b = {'id':[1,2,3],'value':[1,2,3],'constrains':['a','a','a']}
c = {'id':[1,2,3],'value':[1,2,3],'constrains':['b','b','b']}
d = {'id':[1,2,3],'value':[1,2,3],'constrains':['c','c','c']}
base = pd.DataFrame(a)
df1 = pd.DataFrame(b)
df2 = pd.DataFrame(c)
df3 = pd.DataFrame(d)

Obtenemos:

   id constrains  value
0   1          a      1
1   2          b      2
2   3          c      3

Lo que parece ser compatible con su salida esperada.

3
jlandercy 7 oct. 2019 a las 18:52

Si debe solo fusionar todos los marcos de datos con la base:

Basado en editar

import pandas as pd
a = {'id':[1,2,3],'constrains':['a','b','c']}
b = {'id':[1,2,3],'value':[1,2,3],'constrains':['a','a','a']}
c = {'id':[1,2,3],'value':[1,2,3],'constrains':['b','b','b']}
d = {'id':[1,2,3],'value':[1,2,3],'constrains':['c','c','c']}
base = pd.DataFrame(a)
df_1 = pd.DataFrame(b)
df_2 = pd.DataFrame(c)
df_3 = pd.DataFrame(d)

dataframes = [df_1,df_2,df_3]
for i in dataframes:
    base = base.merge(i,how='left',on=['id','constrains'])
summation = [col for col in base if col.startswith('value')]
base['value'] = base[summation].sum(axis=1)
base = base.dropna(how='any',axis=1)
print(base)

Salida:

   id constrains  value
0   1          a    1.0
1   2          b    2.0
2   3          c    3.0
1
Celius Stingher 7 oct. 2019 a las 18:46

Puede usar ffill() para el propósito:

df_1 = pd.DataFrame({'val':[1]}, index=[1])
df_2 = pd.DataFrame({'val':[2]}, index=[2])
df_3 = pd.DataFrame({'val':[3]}, index=[3])

(pd.concat((df_1,df_2,df_3), axis=1)
   .ffill(1)
   .iloc[:,-1]
)

Salida:

1    1.0
2    2.0
3    3.0
Name: val, dtype: float64

Para sus nuevos datos:

base.merge(pd.concat((df1,df2,df3)),
           on=['id','constraint'],
           how='left')

Salida:

   id constraint  value
0   1        'a'      1
1   2        'b'      2
2   3        'c'      3

Conclusión: en realidad está buscando la opción how='left' en merge

3
Quang Hoang 7 oct. 2019 a las 19:17

Para aquellos que quieren simplemente hacer un merge, anulando los valores (que es mi caso), pueden lograrlo usando este método, que es realmente similar a Respuesta de Celius Stingher.

La versión documentada está en la esencia original.

import pandas as pa

def rmerge(left,right,**kwargs):
    # Function to flatten lists from http://rosettacode.org/wiki/Flatten_a_list#Python
    def flatten(lst):
        return sum( ([x] if not isinstance(x, list) else flatten(x) for x in lst), [] )

    # Set default for removing overlapping columns in "left" to be true
    myargs = {'replace':'left'}
    myargs.update(kwargs)

    # Remove the replace key from the argument dict to be sent to
    # pandas merge command
    kwargs = {k:v for k,v in myargs.items() if k is not 'replace'}

    if myargs['replace'] is not None:
        # Generate a list of overlapping column names not associated with the join
        skipcols = set(flatten([v for k, v in myargs.items() if k in ['on','left_on','right_on']]))
        leftcols = set(left.columns)
        rightcols = set(right.columns)
        dropcols = list((leftcols & rightcols).difference(skipcols))

        # Remove the overlapping column names from the appropriate DataFrame
        if myargs['replace'].lower() == 'left':
            left = left.copy().drop(dropcols,axis=1)
        elif myargs['replace'].lower() == 'right':
            right = right.copy().drop(dropcols,axis=1)

    df = pa.merge(left,right,**kwargs)

    return df
0
Gustavo Lopes 8 oct. 2019 a las 15:34
58275117