pandas de tres vías que unen múltiples marcos de datos en columnas

191

Tengo 3 archivos CSV. Cada uno tiene la primera columna como los nombres (de cadena) de las personas, mientras que todas las demás columnas en cada marco de datos son atributos de esa persona.

¿Cómo puedo "unir" los tres documentos CSV para crear un único CSV con cada fila que tenga todos los atributos para cada valor único del nombre de la cadena de la persona?

La join()función en pandas especifica que necesito un índice múltiple, pero estoy confundido acerca de qué tiene que ver un esquema de indexación jerárquica con una unión basada en un índice único.

montaña rusa
fuente
2
No necesita un índice múltiple. Establece en los documentos de unión que si no tiene un índice múltiple al pasar varias columnas para unirse, se encargará de eso.
cwharland
1
En mis pruebas, df1.join([df2, df3], on=[df2_col1, df3_col1])no funcionó.
lollercoaster
Necesita encadenarlos juntos como en la respuesta dada. Combinar df1 y df2 y luego combinar el resultado con df3
cwharland

Respuestas:

475

Importaciones supuestas:

import pandas as pd

La respuesta de John Galt es básicamente una reduceoperación. Si tengo más de un puñado de marcos de datos, los pondría en una lista como esta (generada a través de comprensiones de listas o bucles o cualquier otra cosa):

dfs = [df0, df1, df2, dfN]

Suponiendo que tengan alguna columna común, como nameen su ejemplo, haría lo siguiente:

df_final = reduce(lambda left,right: pd.merge(left,right,on='name'), dfs)

De esa manera, su código debería funcionar con cualquier cantidad de marcos de datos que desee fusionar.

Editar 1 de agosto de 2016 : para aquellos que usan Python 3: reducese ha movido a functools. Entonces, para usar esta función, primero deberá importar ese módulo:

from functools import reduce
Equipo
fuente
11
Acabo de intentar usar esto y falló porque reducefue reemplazado por functools.reduceSoimport functools functools.reduce(.......)
MattR
3
¿Cómo funcionará esta solución si los nombres de los campos a unir son diferentes? Por ejemplo, en tres marcos de datos que podría tener name1, name2y name3respectivamente.
ps0604
2
¿No significa esto que tenemos n-1llamadas a la función de fusión? Supongo que en este caso donde el número de marcos de datos es pequeño, no importa, pero me pregunto si hay una solución más escalable.
eapolinario
1
Esto no funcionó para mis dfcorreos electrónicos con múltiples índices de columna (estaba inyectando el 'on' como una columna que funcionó para la primera fusión, pero las fusiones posteriores fallaron), en cambio conseguí que funcionara con:df = reduce(lambda left, right: left.join(right, how='outer', on='Date'), dfs)
Adrian Torrie
+1 a ps0604. ¿Qué pasa si las columnas de unión son diferentes? ¿Funciona esto? ¿deberíamos ir con pd.merge en caso de que las columnas de unión sean diferentes? gracias
steve
107

Puedes probar esto si tienes 3 marcos de datos

# Merge multiple dataframes
df1 = pd.DataFrame(np.array([
    ['a', 5, 9],
    ['b', 4, 61],
    ['c', 24, 9]]),
    columns=['name', 'attr11', 'attr12'])
df2 = pd.DataFrame(np.array([
    ['a', 5, 19],
    ['b', 14, 16],
    ['c', 4, 9]]),
    columns=['name', 'attr21', 'attr22'])
df3 = pd.DataFrame(np.array([
    ['a', 15, 49],
    ['b', 4, 36],
    ['c', 14, 9]]),
    columns=['name', 'attr31', 'attr32'])

pd.merge(pd.merge(df1,df2,on='name'),df3,on='name')

alternativamente, como lo menciona cwharland

df1.merge(df2,on='name').merge(df3,on='name')
Cero
fuente
34
Para una apariencia más limpia que les puede encadenar df1.merge(df2,on='name').merge(df3,on='name')
cwharland
1
¿Cómo funcionará esta solución si los nombres de los campos a unir son diferentes? Por ejemplo, en tres marcos de datos que podría tener name1, name2y name3respectivamente
ps0604
44
@ ps0604df1.merge(df2,left_on='name1', right_on='name2').merge(df3,left_on='name1', right_on='name3').drop(columns=['name2', 'name3']).rename(columns={'name1':'name'})
Michael H.
y además, cómo hacer esto usando el índice. No parece funcionar si 'nombre' es el índice y no el nombre de una columna.
Brian D
86

Esta es una situación ideal para el joinmétodo.

El joinmétodo está construido exactamente para este tipo de situaciones. Puede unir cualquier número de DataFrames junto con él. El DataFrame de llamada se une con el índice de la colección de DataFrames aprobados. Para trabajar con múltiples DataFrames, debe colocar las columnas de unión en el índice.

El código se vería así:

filenames = ['fn1', 'fn2', 'fn3', 'fn4',....]
dfs = [pd.read_csv(filename, index_col=index_col) for filename in filenames)]
dfs[0].join(dfs[1:])

Con los datos de @ zero, puede hacer esto:

df1 = pd.DataFrame(np.array([
    ['a', 5, 9],
    ['b', 4, 61],
    ['c', 24, 9]]),
    columns=['name', 'attr11', 'attr12'])
df2 = pd.DataFrame(np.array([
    ['a', 5, 19],
    ['b', 14, 16],
    ['c', 4, 9]]),
    columns=['name', 'attr21', 'attr22'])
df3 = pd.DataFrame(np.array([
    ['a', 15, 49],
    ['b', 4, 36],
    ['c', 14, 9]]),
    columns=['name', 'attr31', 'attr32'])

dfs = [df1, df2, df3]
dfs = [df.set_index('name') for df in dfs]
dfs[0].join(dfs[1:])

     attr11 attr12 attr21 attr22 attr31 attr32
name                                          
a         5      9      5     19     15     49
b         4     61     14     16      4     36
c        24      9      4      9     14      9
Ted Petrou
fuente
44
Unirse a todos los DFS a una trama de datos vacía también funciona: pd.DataFrame().join(dfs, how="outer"). Esto puede ser más limpio en algunas situaciones.
Dominik
44
Este es un consejo decente y ahora se ha incorporado a la combinación de pandas 101 (consulte la sección sobre la combinación de múltiples marcos de datos). Vale la pena señalar que si las llaves de combinación son únicos, utilizando pd.concatdará lugar a la sintaxis más simple: pd.concat([df.set_index('name') for df in dfs], axis=1, join='inner').reset_index(). concattambién es más versátil cuando se trata de nombres de columna duplicados en múltiples dfs ( joinno es tan bueno en esto) aunque solo puede realizar uniones internas o externas con él.
cs95
dfs[0].join(dfs[1:])debe editarse dfs[0].join(dfs[1:], sort=False) porque de lo contrario FutureWarningaparecerá una ventana emergente. Gracias por el buen ejemplo.
gies0r
Me da un error al intentar eso: ValueError: Indexes have overlapping valuesaunque, al inspeccionar los marcos de datos individuales en la lista, no parecen tener valores superpuestos.
SomJura
17

Esto también se puede hacer de la siguiente manera para obtener una lista de marcos de datos df_list:

df = df_list[0]
for df_ in df_list[1:]:
    df = df.merge(df_, on='join_col_name')

o si los marcos de datos están en un objeto generador (por ejemplo, para reducir el consumo de memoria):

df = next(df_list)
for df_ in df_list:
    df = df.merge(df_, on='join_col_name')
AlexG
fuente
11

En python3.6.3 con pandas0.22.0 también puede usar concatsiempre que establezca como índice las columnas que desea usar para la unión

pd.concat(
    (iDF.set_index('name') for iDF in [df1, df2, df3]),
    axis=1, join='inner'
).reset_index()

donde df1, df2y df3se definen como en la respuesta de John Galt

import pandas as pd
df1 = pd.DataFrame(np.array([
    ['a', 5, 9],
    ['b', 4, 61],
    ['c', 24, 9]]),
    columns=['name', 'attr11', 'attr12']
)
df2 = pd.DataFrame(np.array([
    ['a', 5, 19],
    ['b', 14, 16],
    ['c', 4, 9]]),
    columns=['name', 'attr21', 'attr22']
)
df3 = pd.DataFrame(np.array([
    ['a', 15, 49],
    ['b', 4, 36],
    ['c', 14, 9]]),
    columns=['name', 'attr31', 'attr32']
)
Igor Fobia
fuente
2
Esta debería ser la respuesta aceptada. Es el mas rapido.
R. Zhu
4

No se necesita un índice múltiple para realizar operaciones de unión . Uno solo necesita establecer correctamente la columna de índice en la que realizar las operaciones de unión (qué comando, df.set_index('Name')por ejemplo)

La joinoperación se realiza por defecto en el índice. En su caso, solo tiene que especificar que la Namecolumna corresponde a su índice. Debajo hay un ejemplo

Un tutorial puede ser útil.

# Simple example where dataframes index are the name on which to perform the join operations
import pandas as pd
import numpy as np
name = ['Sophia' ,'Emma' ,'Isabella' ,'Olivia' ,'Ava' ,'Emily' ,'Abigail' ,'Mia']
df1 = pd.DataFrame(np.random.randn(8, 3), columns=['A','B','C'], index=name)
df2 = pd.DataFrame(np.random.randn(8, 1), columns=['D'],         index=name)
df3 = pd.DataFrame(np.random.randn(8, 2), columns=['E','F'],     index=name)
df = df1.join(df2)
df = df.join(df3)

# If you a 'Name' column that is not the index of your dataframe, one can set this column to be the index
# 1) Create a column 'Name' based on the previous index
df1['Name']=df1.index
# 1) Select the index from column 'Name'
df1=df1.set_index('Name')

# If indexes are different, one may have to play with parameter how
gf1 = pd.DataFrame(np.random.randn(8, 3), columns=['A','B','C'], index=range(8))
gf2 = pd.DataFrame(np.random.randn(8, 1), columns=['D'], index=range(2,10))
gf3 = pd.DataFrame(np.random.randn(8, 2), columns=['E','F'], index=range(4,12))

gf = gf1.join(gf2, how='outer')
gf = gf.join(gf3, how='outer')
Guillaume Jacquenot
fuente
4

Aquí hay un método para fusionar un diccionario de marcos de datos mientras se mantienen sincronizados los nombres de las columnas con el diccionario. También completa los valores faltantes si es necesario:

Esta es la función para fusionar un dict de marcos de datos

def MergeDfDict(dfDict, onCols, how='outer', naFill=None):
  keys = dfDict.keys()
  for i in range(len(keys)):
    key = keys[i]
    df0 = dfDict[key]
    cols = list(df0.columns)
    valueCols = list(filter(lambda x: x not in (onCols), cols))
    df0 = df0[onCols + valueCols]
    df0.columns = onCols + [(s + '_' + key) for s in valueCols] 

    if (i == 0):
      outDf = df0
    else:
      outDf = pd.merge(outDf, df0, how=how, on=onCols)   

  if (naFill != None):
    outDf = outDf.fillna(naFill)

  return(outDf)

OK, permite generar datos y probar esto:

def GenDf(size):
  df = pd.DataFrame({'categ1':np.random.choice(a=['a', 'b', 'c', 'd', 'e'], size=size, replace=True),
                      'categ2':np.random.choice(a=['A', 'B'], size=size, replace=True), 
                      'col1':np.random.uniform(low=0.0, high=100.0, size=size), 
                      'col2':np.random.uniform(low=0.0, high=100.0, size=size)
                      })
  df = df.sort_values(['categ2', 'categ1', 'col1', 'col2'])
  return(df)


size = 5
dfDict = {'US':GenDf(size), 'IN':GenDf(size), 'GER':GenDf(size)}   
MergeDfDict(dfDict=dfDict, onCols=['categ1', 'categ2'], how='outer', naFill=0)
rz1317
fuente
3

Solución simple:

Si los nombres de las columnas son similares:

 df1.merge(df2,on='col_name').merge(df3,on='col_name')

Si los nombres de las columnas son diferentes:

df1.merge(df2,left_on='col_name1', right_on='col_name2').merge(df3,left_on='col_name1', right_on='col_name3').drop(columns=['col_name2', 'col_name3']).rename(columns={'col_name1':'col_name'})
Gil Baggio
fuente
2

Hay otra solución de la documentación de pandas (que no veo aquí),

utilizando la .append

>>> df = pd.DataFrame([[1, 2], [3, 4]], columns=list('AB'))
   A  B
0  1  2
1  3  4
>>> df2 = pd.DataFrame([[5, 6], [7, 8]], columns=list('AB'))
   A  B
0  5  6
1  7  8
>>> df.append(df2, ignore_index=True)
   A  B
0  1  2
1  3  4
2  5  6
3  7  8

Se ignore_index=Trueutiliza para ignorar el índice del marco de datos adjunto, reemplazándolo con el siguiente índice disponible en el origen.

Si hay diferentes nombres de columna, Nanse introducirá.

Sylhare
fuente
es semántico, para alguien que usa la palabra "unirse" para decir que reúne los dos marcos de datos. (no necesariamente como la operación de unión SQL)
Sylhare
1

Los tres marcos de datos son

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Combinemos estos marcos usando pd.merge anidado

ingrese la descripción de la imagen aquí

Aquí vamos, tenemos nuestro marco de datos combinado.

Feliz análisis !!!

decision_scientist_noah
fuente