Comment assembler 2 tableaux pour n'en faire qu'un ? Pandas offre 3 méthodes qui permettent à peu près tout :

  • merge pour fusionner les 2 tableaux (casse l'index)
  • join colle les tableaux l'un à coté de l'autre (concatène les colonnes)
  • concat colle les tableaux l'un au dessus de l'autre (concatène les lignes)

Chaque méthode a des options qui permettent de nombreuses variantes.

Merge¶

merge permet d'indiquer une ou plusieurs colonnes de référence pour guider la fusion. Ensuite il est possible d'indiquer si on ne retient que les lignes communes aux deux tableaux, si on prend aussi un des tableaux entier voire les deux.

In [1]:
import pandas as pd
import numpy as np
import IPython.display as ds
np.random.seed(3)

df1 = pd.DataFrame({'name': ['Diana', 'Charles', 'Franck', 'Brigitte'],
                    'dice': [3,4,5,2]},
                    index = ['11:12','11:22','11:23','12:01'])

df2 = pd.DataFrame({'name': ['Boris', 'Charles', 'Diana', 'Danielle', 'Franck', 'Franck'],
                    'dice': [6,3,3,3,2,3], 
                    'value': np.random.randint(4, size=6)})
ds.display(df1, df2)
name dice
11:12 Diana 3
11:22 Charles 4
11:23 Franck 5
12:01 Brigitte 2
name dice value
0 Boris 6 2
1 Charles 3 0
2 Diana 3 1
3 Danielle 3 3
4 Franck 2 0
5 Franck 3 0

df1 ∩ df2 sur les lignes¶

On garde

  • les lignes dont la valeur de la colonne choisie existe dans les tableaux
  • toutes les colonnes (quitte à le renommer si deux colonnes ont le même nom)

Le résultat a un nouvel index.

In [2]:
pd.merge(df1, df2, on='name')
# df1.merge(df2, on='name')   # works also
Out[2]:
name dice_x dice_y value
0 Diana 3 3 1
1 Charles 4 3 0
2 Franck 5 2 0
3 Franck 5 3 0

Note : l'index résultant est ordonné comme celui du premier tableau (Diane est avant Charles dans notre exemple).

Il est possible possible d'indiquer un nom de colonne différent pour chaque tableau :

In [3]:
df1.merge(df2, left_on='dice', right_on='value', indicator='type of merge')  # merge with 2 different columns
Out[3]:
name_x dice_x name_y dice_y value type of merge
0 Diana 3 Danielle 3 3 both
1 Brigitte 2 Boris 6 2 both

On note que les valeurs de dice_x et value sont bien les mêmes pour chaque ligne.

2 colonnes en commun¶

Il est possible de demander à ce que la fusion se fasse en cherchant les valeurs communes dans 2 colonnes.

In [4]:
df1.merge(df2, on=['name','dice'])  # merge on two columns
Out[4]:
name dice value
0 Diana 3 1

df1 + (df1 ∩ df2)¶

En indiquant la méthode de fusion à gauche avec how = "left" on a

  • tout le tableau de gauche
  • toutes les lignes du tableau de droite dont les valeurs dans la colonne choisie existent dans le tableau de gauche
  • tout en préservant l'ordre de la colonne choisie tel qu'il est dans le tableau de gauche

df1 ∩ df2 peut donner des lignes de df2 qui ne sont pas dans df1 si elles ont une valeur commune dans la colonne choisie (dans notre exemple on récupère un Franck de df2 qui n'est pas dans df1).

Dans ce cas certaines lignes du premier tableau n'auront pas de valeur pour les colonnes du 2nd tableau qui ne sont pas en commun. Aussi Pandas met NaN dans les cases sans valeur.

In [5]:
df1.merge(df2, on='name', how="left", indicator='merge')  # how="right" would keep all df2 + (df1 ∩ df2)
Out[5]:
name dice_x dice_y value merge
0 Diana 3 3.0 1.0 both
1 Charles 4 3.0 0.0 both
2 Franck 5 2.0 0.0 both
3 Franck 5 3.0 0.0 both
4 Brigitte 2 NaN NaN left_only

Exemple pratique: Soit deux tableaux avec une colonne en commun qui sert d'identifiant, on va injecter des informations d'un tableau pour enrichir l'autre.

Ici on a l'UID de chaque utilisateur en commun entre

  • le fichier des utilisateurs
  • les logs d'utilisation d'une machine

Dans les logs on n'a pas les noms de personnes mais seulement leur UID. Avec un merge en mode left (ou right) on peut créer un tableau de logs avec les noms :

In [6]:
users = pd.DataFrame({'uid':range(4), 'name':['Alice','Robert','Charles','Diana']})
logs = pd.DataFrame({'uid':[1,3,0,1,1], 'logs':['log in','log in','log out','log out','log in']},
                    index=['11:12','11:22','11:23','12:01','12:21'])
pd.merge(logs, users, on='uid', how='left')
Out[6]:
uid logs name
0 1 log in Robert
1 3 log in Diana
2 0 log out Alice
3 1 log out Robert
4 1 log in Robert

On a perdu l'index du tableau des logs ce qui est dommage, aussi remettons le :

In [7]:
logs.merge(users, on='uid', how="left").set_index(logs.index)
Out[7]:
uid logs name
11:12 1 log in Robert
11:22 3 log in Diana
11:23 0 log out Alice
12:01 1 log out Robert
12:21 1 log in Robert

A ∪ B¶

L'union des 2 tableaux avec le mode outer permet de fusionner toutes les valeurs dans un grand tableau mais avec beaucoup de trous.

Notons que si la colonne dice avait exactement les mêmes valeurs dans les 2 tableaux, il serait préférable de faire la fusion avec on = ['name','dice'] pour éviter les 2 colonnes dice_?.

In [8]:
df1.merge(df2, on='name', how="outer", indicator="merge", validate='1:m') # see bellow for validate
Out[8]:
name dice_x dice_y value merge
0 Boris NaN 6.0 2.0 right_only
1 Brigitte 2.0 NaN NaN left_only
2 Charles 4.0 3.0 0.0 both
3 Danielle NaN 3.0 3.0 right_only
4 Diana 3.0 3.0 1.0 both
5 Franck 5.0 2.0 0.0 both
6 Franck 5.0 3.0 0.0 both

Vérification de la cohérence de l'opération¶

Lors de la fusion il est possible de vérifier que le valeur de la colonne choisie est unique ou pas avec les options de validation 1:1, 1:m, m:1 ou m:m (one to one, one to many...). Ainsi imposer 1:1 dans le cas précédent provoquerait une erreur puisque Franck apparait 2 fois dans le second tableau.

Join¶

join est le pendant de concat pour les colonnes. Pour fusionner les lignes join se base sur l'index qui donc doivent être au moins compatibles.

Pour fusionner suivant une colonne il faut redéfinir l'index avec set_index.

Comme pour merge il est possible de choisir le mode :

mode how
intersection how = 'inner'
gauche how = 'left'
droite how = 'right'
union how = 'outer'
In [9]:
df1.set_index('name').join(df2.set_index('name'), lsuffix='_left', how='outer')
Out[9]:
dice_left dice value
name
Boris NaN 6.0 2.0
Brigitte 2.0 NaN NaN
Charles 4.0 3.0 0.0
Danielle NaN 3.0 3.0
Diana 3.0 3.0 1.0
Franck 5.0 2.0 0.0
Franck 5.0 3.0 0.0

Notons quelques différences avec merge :

  • join est une méthode et seulement une méthode
  • par défaut le mode à how = left
  • les suffixes doivent être spécifiés

Concaténation¶

La concaténation permet d'accoler deux tableaux à la suite. Si une colonne n'existe pas dans l'un des tableaux, des NaN remplissent les trous.

Attention les index peuvent être de types différents ce qui donne quelque chose de bizarre à l'arrivée. On peut utiliser ignore_index = True pour que le résultat ait un nouvel index.

Note : concat est une fonction qui ne prend qu'un seul argument (une liste de tableau ou un dictionnaire de tableau).

In [10]:
pd.concat([df1,df2], sort=False)  # sort non-concatenation axis? Default value of sort is changing 
                                  # so we must specify for this version (or getting a warning)
Out[10]:
name dice value
11:12 Diana 3 NaN
11:22 Charles 4 NaN
11:23 Franck 5 NaN
12:01 Brigitte 2 NaN
0 Boris 6 2.0
1 Charles 3 0.0
2 Diana 3 1.0
3 Danielle 3 3.0
4 Franck 2 0.0
5 Franck 3 0.0

On peut garder l'information de l'origine des lignes en ajoutant un index avec le nom du tableau d'où viennent les valeurs. Cela se fait avec un dictionnaire à la place d'un tableau :

In [11]:
pd.concat({'table1': df1, 'table2': df2}, sort=False)
Out[11]:
name dice value
table1 11:12 Diana 3 NaN
11:22 Charles 4 NaN
11:23 Franck 5 NaN
12:01 Brigitte 2 NaN
table2 0 Boris 6 2.0
1 Charles 3 0.0
2 Diana 3 1.0
3 Danielle 3 3.0
4 Franck 2 0.0
5 Franck 3 0.0

Jouons avec les axes¶

Si la concaténation se fait suivant les lignes par défaut, il est possible de la faire suivant les colonnes en spécifiant l'axe.

  • axis=0 opère le long des lignes (vertical)
  • axis=1 opère le long de colonnes (horizontal)
In [12]:
pd.concat([df1,df2], axis=1, keys=['df1', 'df2'])
Out[12]:
df1 df2
name dice name dice value
11:12 Diana 3.0 NaN NaN NaN
11:22 Charles 4.0 NaN NaN NaN
11:23 Franck 5.0 NaN NaN NaN
12:01 Brigitte 2.0 NaN NaN NaN
0 NaN NaN Boris 6.0 2.0
1 NaN NaN Charles 3.0 0.0
2 NaN NaN Diana 3.0 1.0
3 NaN NaN Danielle 3.0 3.0
4 NaN NaN Franck 2.0 0.0
5 NaN NaN Franck 3.0 0.0

L'utilité de ce résultat n'est pas très probante...

In [ ]: