pandas à trois voies joignant plusieurs dataframes sur des colonnes


191

J'ai 3 fichiers CSV. Chacun a la première colonne en tant que noms (chaîne) des personnes, tandis que toutes les autres colonnes de chaque dataframe sont des attributs de cette personne.

Comment puis-je «joindre» les trois documents CSV pour créer un seul CSV avec chaque ligne ayant tous les attributs pour chaque valeur unique du nom de chaîne de la personne?

La join()fonction dans pandas spécifie que j'ai besoin d'un multiindex, mais je ne sais pas ce qu'un schéma d'indexation hiérarchique a à voir avec la création d'une jointure basée sur un seul index.


2
Vous n'avez pas besoin d'un multiindex. Il indique dans les documents de jointure que vous n'avez pas de multiindex lorsque vous passez plusieurs colonnes à joindre, alors il gérera cela.
cwharland

1
Dans mes essais, cela df1.join([df2, df3], on=[df2_col1, df3_col1])n'a pas fonctionné.
lollercoaster

Vous devez les enchaîner comme dans la réponse donnée. Fusionner df1 et df2 puis fusionner le résultat avec df3
cwharland

Réponses:


475

Importations présumées:

import pandas as pd

La réponse de John Galt est essentiellement une reduceopération. Si j'ai plus d'une poignée de dataframes, je les mettrais dans une liste comme celle-ci (générée via des compréhensions de listes ou des boucles ou autres):

dfs = [df0, df1, df2, dfN]

En supposant qu'ils aient une colonne commune, comme namedans votre exemple, je ferais ce qui suit:

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

De cette façon, votre code doit fonctionner avec le nombre de dataframes que vous souhaitez fusionner.

Edit 1er août 2016 : Pour ceux qui utilisent Python 3: reducea été déplacé vers functools. Donc, pour utiliser cette fonction, vous devez d'abord importer ce module:

from functools import reduce

11
J'ai juste essayé de l'utiliser et cela a échoué car il a reduceété remplacé par functools.reduceSoimport functools functools.reduce(.......)
MattR

3
Comment cette solution fonctionnera-t-elle si les noms des champs à rejoindre sont différents? Par exemple, dans trois cadres de données, je pourrais avoir name1, name2et name3respectivement.
ps0604

2
Cela ne signifie-t-il pas que nous avons des n-1appels à la fonction de fusion? Je suppose que dans ce cas où le nombre de dataframes est petit, cela n'a pas d'importance, mais je me demande s'il existe une solution plus évolutive.
eapolinario

1
Cela n'a pas tout à fait fonctionné pour mes dfindex multi-colonnes (il injectait le `` on '' en tant que colonne qui fonctionnait pour la première fusion, mais les fusions suivantes ont échoué), au lieu de cela, je l'ai fait fonctionner avec:df = reduce(lambda left, right: left.join(right, how='outer', on='Date'), dfs)
Adrian Torrie

+1 à ps0604. et si les colonnes de jointure sont différentes, cela fonctionne-t-il? devrions-nous utiliser pd.merge au cas où les colonnes de jointure seraient différentes? merci
steve

107

Vous pouvez essayer ceci si vous avez 3 dataframes

# 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')

alternativement, comme mentionné par cwharland

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

34
Pour des looks plus propres, vous pouvez les enchaîner df1.merge(df2,on='name').merge(df3,on='name')
cwharland

1
Comment cette solution fonctionnera-t-elle si les noms des champs à rejoindre sont différents? Par exemple, dans trois trames de données, je pourrais avoir name1, name2et name3respectivement
ps0604

4
@ 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.

et en outre, comment procéder à l'aide de l'index. Ne semble pas fonctionner si «nom» est l'index et non un nom de colonne.
Brian D

86

C'est une situation idéale pour la joinméthode

La joinméthode est conçue exactement pour ces types de situations. Vous pouvez joindre n'importe quel nombre de DataFrames avec lui. Le DataFrame appelant se joint à l'index de la collection de DataFrames passées. Pour travailler avec plusieurs DataFrames, vous devez placer les colonnes de jointure dans l'index.

Le code ressemblerait à ceci:

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

Avec les données de @ zero, vous pouvez faire ceci:

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

4
Rejoindre tous les SFD à un dataframe vide fonctionne aussi: pd.DataFrame().join(dfs, how="outer"). Cela peut être plus propre dans certaines situations.
Dominik

4
Ceci est un conseil décent et a maintenant été intégré dans la fusion de pandas 101 (voir la section sur la fusion de plusieurs dataframes). Il convient de noter que si vos clés de jointure sont uniques, à l' aide pd.concatse traduira par une syntaxe plus simple: pd.concat([df.set_index('name') for df in dfs], axis=1, join='inner').reset_index(). concatest également plus polyvalent lorsqu'il s'agit de gérer des noms de colonnes en double dans plusieurs fichiers dfs (ce joinn'est pas aussi bon pour cela) bien que vous ne puissiez effectuer que des jointures internes ou externes avec lui.
cs95

dfs[0].join(dfs[1:])devrait être édité dfs[0].join(dfs[1:], sort=False) car sinon un FutureWarningapparaîtra. Merci pour le bel exemple.
gies0r le

ValueError: Indexes have overlapping valuesJ'obtiens une erreur en essayant cela: bien que, en inspectant les dataframes individuels dans la liste, ils ne semblent pas avoir de valeurs qui se chevauchent.
SomJura

17

Cela peut également être fait comme suit pour une liste de dataframes df_list:

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

ou si les dataframes sont dans un objet générateur (par exemple pour réduire la consommation de mémoire):

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

11

Dans python3.6.3 avec pandas0.22.0, vous pouvez également utiliser concattant que vous définissez comme index les colonnes que vous souhaitez utiliser pour la jointure

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

df1, df2et df3sont définis comme dans la réponse 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']
)

2
Cela devrait être la réponse acceptée. C'est le plus rapide.
R. Zhu

4

On n'a pas besoin d'un multiindex pour effectuer des opérations de jointure . Il suffit de définir correctement la colonne d'index sur laquelle effectuer les opérations de jointure (quelle commande df.set_index('Name')par exemple)

L' joinopération est effectuée par défaut sur l'index. Dans votre cas, il vous suffit de préciser que la Namecolonne correspond à votre index. Ci-dessous un exemple

Un tutoriel peut être utile.

# 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')

4

Voici une méthode pour fusionner un dictionnaire de trames de données tout en gardant les noms de colonne synchronisés avec le dictionnaire. En outre, il remplit les valeurs manquantes si nécessaire:

Ceci est la fonction pour fusionner un dict de trames de données

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, permet de générer des données et de tester ceci:

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)

3

Solution simple:

Si les noms de colonnes sont similaires:

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

Si les noms de colonnes sont différents:

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'})

2

Il y a une autre solution de la documentation pandas (que je ne vois pas ici),

en utilisant le .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

Le ignore_index=Trueest utilisé pour ignorer l'index du dataframe ajouté, en le remplaçant par le prochain index disponible dans l'index source.

S'il existe différents noms de colonne, Nansera introduit.


c'est sémantique, pour quelqu'un qui utilise le mot «joindre» pour dire de rassembler les deux dataframe. (pas nécessairement comme l'opération de jointure SQL)
Sylhare

1

Les trois dataframes sont

entrez la description de l'image ici

entrez la description de l'image ici

Fusionnons ces cadres en utilisant pd.merge imbriqué

entrez la description de l'image ici

Ici nous allons, nous avons notre dataframe fusionné.

Bonne analyse !!!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.