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.
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.
pd.merge(df1, df2, on='name')
# df1.merge(df2, on='name') # works also
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 :
df1.merge(df2, left_on='dice', right_on='value', indicator='type of merge') # merge with 2 different columns
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.
df1.merge(df2, on=['name','dice']) # merge on two columns
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.
df1.merge(df2, on='name', how="left", indicator='merge') # how="right" would keep all df2 + (df1 ∩ df2)
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 :
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')
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 :
logs.merge(users, on='uid', how="left").set_index(logs.index)
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_?
.
df1.merge(df2, on='name', how="outer", indicator="merge", validate='1:m') # see bellow for validate
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' |
df1.set_index('name').join(df2.set_index('name'), lsuffix='_left', how='outer')
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).
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)
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 :
pd.concat({'table1': df1, 'table2': df2}, sort=False)
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)
pd.concat([df1,df2], axis=1, keys=['df1', 'df2'])
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...