Chapitre 8 Manipuler les données avec dplyr
Certaines parties de ce chapitre sont extraites du cours de Julien Barnier “Introduction à R et au tidyverse” (https://juba.github.io/tidyverse)
Le package dplyr
est une extension facilitant le traitement et la manipulation de données contenues dans une ou plusieurs tables. Elle propose une syntaxe claire et cohérente. Ses fonctions sont en général plus rapides que leur équivalent sous R de base, elles permettent donc de traiter efficacement des données de grande dimension.
Les fonctions de ce package peuvent s’appliquer à des tableaux de type data.frame
ou tibble
(package tibble
de tidyverse), et elles retournent systématiquement un `tibble.
Dans ce qui suit on va utiliser le jeu de données nycflights13
, contenu dans l’extension du même nom (qu’il faut donc avoir installé). Celui-ci correspond aux données de tous les vols au départ d’un des trois aéroports de New-York en 2013. Il a la particularité d’être réparti en trois tables :
flights
contient des informations sur les vols : date, départ, destination, horaires, retard…
year | month | day | dep_time | sched_dep_time | dep_delay | arr_time | sched_arr_time | arr_delay | carrier | flight | tailnum | origin | dest | air_time | distance | hour | minute | time_hour |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2013 | 1 | 1 | 517 | 515 | 2 | 830 | 819 | 11 | UA | 1545 | N14228 | EWR | IAH | 227 | 1400 | 5 | 15 | 2013-01-01T10:00:00Z |
2013 | 1 | 1 | 533 | 529 | 4 | 850 | 830 | 20 | UA | 1714 | N24211 | LGA | IAH | 227 | 1416 | 5 | 29 | 2013-01-01T10:00:00Z |
2013 | 1 | 1 | 542 | 540 | 2 | 923 | 850 | 33 | AA | 1141 | N619AA | JFK | MIA | 160 | 1089 | 5 | 40 | 2013-01-01T10:00:00Z |
2013 | 1 | 1 | 544 | 545 | -1 | 1004 | 1022 | -18 | B6 | 725 | N804JB | JFK | BQN | 183 | 1576 | 5 | 45 | 2013-01-01T10:00:00Z |
2013 | 1 | 1 | 554 | 600 | -6 | 812 | 837 | -25 | DL | 461 | N668DN | LGA | ATL | 116 | 762 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 554 | 558 | -4 | 740 | 728 | 12 | UA | 1696 | N39463 | EWR | ORD | 150 | 719 | 5 | 58 | 2013-01-01T10:00:00Z |
2013 | 1 | 1 | 555 | 600 | -5 | 913 | 854 | 19 | B6 | 507 | N516JB | EWR | FLL | 158 | 1065 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 557 | 600 | -3 | 709 | 723 | -14 | EV | 5708 | N829AS | LGA | IAD | 53 | 229 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 557 | 600 | -3 | 838 | 846 | -8 | B6 | 79 | N593JB | JFK | MCO | 140 | 944 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 558 | 600 | -2 | 753 | 745 | 8 | AA | 301 | N3ALAA | LGA | ORD | 138 | 733 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 558 | 600 | -2 | 849 | 851 | -2 | B6 | 49 | N793JB | JFK | PBI | 149 | 1028 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 558 | 600 | -2 | 853 | 856 | -3 | B6 | 71 | N657JB | JFK | TPA | 158 | 1005 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 558 | 600 | -2 | 924 | 917 | 7 | UA | 194 | N29129 | JFK | LAX | 345 | 2475 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 558 | 600 | -2 | 923 | 937 | -14 | UA | 1124 | N53441 | EWR | SFO | 361 | 2565 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 559 | 600 | -1 | 941 | 910 | 31 | AA | 707 | N3DUAA | LGA | DFW | 257 | 1389 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 559 | 559 | 0 | 702 | 706 | -4 | B6 | 1806 | N708JB | JFK | BOS | 44 | 187 | 5 | 59 | 2013-01-01T10:00:00Z |
2013 | 1 | 1 | 559 | 600 | -1 | 854 | 902 | -8 | UA | 1187 | N76515 | EWR | LAS | 337 | 2227 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 600 | 600 | 0 | 851 | 858 | -7 | B6 | 371 | N595JB | LGA | FLL | 152 | 1076 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 600 | 600 | 0 | 837 | 825 | 12 | MQ | 4650 | N542MQ | LGA | ATL | 134 | 762 | 6 | 0 | 2013-01-01T11:00:00Z |
2013 | 1 | 1 | 601 | 600 | 1 | 844 | 850 | -6 | B6 | 343 | N644JB | EWR | PBI | 147 | 1023 | 6 | 0 | 2013-01-01T11:00:00Z |
airports
contient des informations sur les aéroports
faa | name | lat | lon | alt | tz | dst | tzone |
---|---|---|---|---|---|---|---|
04G | Lansdowne Airport | 41.13047 | -80.61958 | 1044 | -5 | A | America/New_York |
06A | Moton Field Municipal Airport | 32.46057 | -85.68003 | 264 | -6 | A | America/Chicago |
06C | Schaumburg Regional | 41.98934 | -88.10124 | 801 | -6 | A | America/Chicago |
06N | Randall Airport | 41.43191 | -74.39156 | 523 | -5 | A | America/New_York |
09J | Jekyll Island Airport | 31.07447 | -81.42778 | 11 | -5 | A | America/New_York |
0A9 | Elizabethton Municipal Airport | 36.37122 | -82.17342 | 1593 | -5 | A | America/New_York |
0G6 | Williams County Airport | 41.46731 | -84.50678 | 730 | -5 | A | America/New_York |
0G7 | Finger Lakes Regional Airport | 42.88356 | -76.78123 | 492 | -5 | A | America/New_York |
0P2 | Shoestring Aviation Airfield | 39.79482 | -76.64719 | 1000 | -5 | U | America/New_York |
0S9 | Jefferson County Intl | 48.05381 | -122.81064 | 108 | -8 | A | America/Los_Angeles |
0W3 | Harford County Airport | 39.56684 | -76.20240 | 409 | -5 | A | America/New_York |
10C | Galt Field Airport | 42.40289 | -88.37511 | 875 | -6 | U | America/Chicago |
17G | Port Bucyrus-Crawford County Airport | 40.78156 | -82.97481 | 1003 | -5 | A | America/New_York |
19A | Jackson County Airport | 34.17586 | -83.56160 | 951 | -5 | U | America/New_York |
1A3 | Martin Campbell Field Airport | 35.01581 | -84.34683 | 1789 | -5 | A | America/New_York |
1B9 | Mansfield Municipal | 42.00013 | -71.19677 | 122 | -5 | A | America/New_York |
1C9 | Frazier Lake Airpark | 54.01333 | -124.76833 | 152 | -8 | A | America/Vancouver |
1CS | Clow International Airport | 41.69597 | -88.12923 | 670 | -6 | U | America/Chicago |
1G3 | Kent State Airport | 41.15139 | -81.41511 | 1134 | -5 | A | America/New_York |
1G4 | Grand Canyon West Airport | 35.89990 | -113.81567 | 4813 | -7 | A | America/Phoenix |
airlines
contient des données sur les compagnies aériennes
carrier | name |
---|---|
9E | Endeavor Air Inc. |
AA | American Airlines Inc. |
AS | Alaska Airlines Inc. |
B6 | JetBlue Airways |
DL | Delta Air Lines Inc. |
EV | ExpressJet Airlines Inc. |
F9 | Frontier Airlines Inc. |
FL | AirTran Airways Corporation |
HA | Hawaiian Airlines Inc. |
MQ | Envoy Air |
OO | SkyWest Airlines Inc. |
UA | United Air Lines Inc. |
US | US Airways Inc. |
VX | Virgin America |
WN | Southwest Airlines Co. |
YV | Mesa Airlines Inc. |
On va charger les trois tables du jeu de données :
library(nycflights13)
Trois objets correspondant aux trois tables ont dû apparaître dans votre environnement.
On charge le package dplyr
:
library(dplyr)
8.1 La fonction slice
La fonction slice
sélectionne des lignes du tableau selon leur position. On lui passe un chiffre ou un vecteur de chiffres.
Si on souhaite sélectionner la 345e ligne du tableau airports
:
slice(airports, 345)
Si on veut sélectionner les 5 premières lignes :
slice(airports, 1:5)
8.2 La fonction filter
La fonction filter
sélectionne des lignes d’une table selon une condition. On lui passe en paramètre un test, et seules les lignes pour lesquelles ce test renvoie TRUE
sont conservées.
Par exemple, si on veut sélectionner les vols du mois de janvier, on peut filtrer sur la variable month
de la manière suivante :
filter(flights, month == 1)
Si on veut uniquement les vols avec un retard au départ (variable dep_delay
) compris entre 10 et 15 minutes :
filter(flights, dep_delay >= 10 & dep_delay <= 15)
On peut également placer des fonctions dans les tests, qui nous permettent par exemple de sélectionner les vols avec la plus grande distance :
filter(flights, distance == max(distance))
8.3 La fonction select
La fonction select
permet de sélectionner des colonnes d’un tableau de données. Ainsi, si on veut extraire les colonnes lat
et lon
du tableau airports :
select(airports, lat, lon)
Si on fait précéder le nom d’un -
, la colonne est éliminée plutôt que sélectionnée :
select(airports, -lat, -lon)
La syntaxe colonne1:colonne2
permet de sélectionner toutes les colonnes situées entre colonne1
et colonne2
incluses :
select(flights, year:day)
select
peut être utilisée pour réordonner les colonnes d’une table en utilisant la fonction everything()
, qui sélectionne l’ensemble des colonnes non encore sélectionnées. Ainsi, si on souhaite faire passer la colonne name
en première position de la table airports
, on peut faire :
select(airports, name, everything())
8.4 La fonction arrange
La fonction arrange
réordonne les lignes d’un tableau selon une ou plusieurs colonnes. Ainsi, si on veut trier le tableau flights
selon le retard au départ croissant :
arrange(flights, dep_delay)
On peut trier selon plusieurs colonnes. Par exemple selon le mois, puis selon le retard au départ :
arrange(flights, month, dep_delay)
Si on veut trier selon une colonne par ordre décroissant, on lui applique la fonction desc()
:
arrange(flights, desc(dep_delay))
8.5 Enchaîner les fonctions avec le pipe
Quand on manipule un tableau de données, il est très fréquent d’enchaîner plusieurs opérations. On va par exemple extraire une sous-population avec filter
, sélectionner des colonnes avec select
puis trier selon une variable avec arrange
, etc.
Quand on veut enchaîner des opérations, on peut le faire de différentes manières. La première est d’effectuer toutes les opérations en une fois en les “emboîtant” :
arrange(select(filter(flights, dest == "LAX"), dep_delay, arr_delay), dep_delay)
Cette notation a plusieurs inconvénients :
- elle est peu lisible
- les opérations apparaissent dans l’ordre inverse de leur réalisation. Ici on effectue d’abord le
filter
, puis leselect
, puis learrange
, alors qu’à la lecture du code c’est learrange
qui apparaît en premier. - Il est difficile de voir quel paramètre se rapporte à quelle fonction
Une autre manière de faire est d’effectuer les opérations les unes après les autres, en stockant les résultats intermédiaires dans un objet temporaire :
<- filter(flights, dest == "LAX")
tmp <- select(tmp, dep_delay, arr_delay)
tmp arrange(tmp, dep_delay)
C’est nettement plus lisible, l’ordre des opérations est le bon, et les paramètres sont bien rattachés à leur fonction. Par contre, ça reste un peu “verbeux”, et on crée un objet temporaire tmp
dont on n’a pas réellement besoin.
Pour simplifier et améliorer encore la lisibilité du code, on va utiliser un nouvel opérateur, baptisé pipe8. Le pipe se note %>%
, et son fonctionnement est le suivant : si j’exécute expr %>% f
, alors le résultat de l’expression expr
, à gauche du pipe, sera passé comme premier argument à la fonction f
, à droite du pipe, ce qui revient à exécuter f(expr)
.
Ainsi les deux expressions suivantes sont rigoureusement équivalentes :
filter(flights, dest == "LAX")
%>% filter(dest == "LAX") flights
Ce qui est intéressant dans cette histoire, c’est qu’on va pouvoir enchaîner les pipes. Plutôt que d’écrire :
select(filter(flights, dest == "LAX"), dep_delay, arr_delay)
On va pouvoir faire :
%>%
flights filter(dest == "LAX") %>%
select(dep_delay, arr_delay)
Si la liste des fonctions enchaînées est longue, on peut les répartir sur plusieurs lignes à condition que l’opérateur %>%
soit en fin de ligne.
À chaque fois, le résultat de ce qui se trouve à gauche du pipe est passé comme premier argument à ce qui se trouve à droite : on part de l’objet flights
, qu’on passe comme premier argument à la fonction filter
, puis on passe le résultat de ce filter
comme premier argument du select
.
Le résultat final est le même avec les deux syntaxes, mais avec le pipe l’ordre des opérations correspond à l’ordre naturel de leur exécution, et on n’a pas eu besoin de créer d’objet intermédiaire. Évidemment, il est naturel de vouloir récupérer le résultat final d’un pipeline pour le stocker dans un objet. Par exemple, on peut stocker le résultat du pipeline dans un nouvel objet. L’utilisation du pipe n’est pas obligatoire, mais elle rend les scripts plus lisibles et plus rapides à saisir.
8.6 La fonction group_by
Un élément très important de dplyr
est la fonction group_by
. Elle permet de définir des groupes de lignes à partir des valeurs d’une ou plusieurs colonnes. Par exemple, on peut grouper les vols selon leur mois :
%>% group_by(month) flights
Par défaut ceci ne fait rien de visible, à part l’apparition d’une mention Groups
dans l’affichage du résultat. Mais à partir du moment où des groupes ont été définis, les fonctions comme slice
, mutate
ou summarise
vont en tenir compte lors de leurs opérations.
Par exemple, si on applique slice
à un tableau préalablement groupé, il va sélectionner les lignes aux positions indiquées pour chaque groupe. Ainsi la commande suivante affiche le premier vol de chaque mois, selon leur ordre d’apparition dans le tableau :
%>% group_by(month) %>% slice(1) flights
On peut grouper selon plusieurs variables à la fois, il suffit de les indiquer dans la clause du group_by
.
%>%
flights group_by(month, dest)
8.7 La fonction summarise
La fonction summarise
permet d’agréger les lignes du tableau en effectuant une opération “résumée” sur une ou plusieurs colonnes. Par exemple, si on souhaite connaître les retards moyens au départ et à l’arrivée pour l’ensemble des vols du tableau flights
:
%>%
flights summarise(retard_dep = mean(dep_delay, na.rm=TRUE),
retard_arr = mean(arr_delay, na.rm=TRUE))
Cette fonction est en général utilisée avec group_by
, puisqu’elle permet du coup d’agréger et résumer les lignes du tableau groupe par groupe. Si on souhaite calculer le délai maximum, le délai minimum et le délai moyen au départ pour chaque mois, on pourra faire :
%>%
flights group_by(month) %>%
summarise(max_delay = max(dep_delay, na.rm=TRUE),
min_delay = min(dep_delay, na.rm=TRUE),
mean_delay = mean(dep_delay, na.rm=TRUE),
nb = n())
summarise
dispose d’un opérateur spécial, n()
, qui retourne le nombre de lignes du groupe.
help(summarise)
8.8 Autres fonctions utiles de dplyr
8.8.1 La fonction rename
La fonction rename
permet de renommer des colonnes. On l’utilise en lui passant des paramètres de la forme nouveau_nom = ancien_nom
. Ainsi, si on veut renommer les colonnes lon
et lat
de airports
en longitude
et latitude
:
rename(airports, longitude = lon, latitude = lat)
Même si cela est déconseillé, si les noms de colonnes comportent des espaces ou des caractères spéciaux, on peut les entourer de guillemets ("
) ou de quotes inverses (`
) :
<- rename(flights,
tmp "retard départ" = dep_delay,
"retard arrivée" = arr_delay)
select(tmp, `retard départ`, `retard arrivée`)
8.8.2 La fonction mutate
La fonction mutate
permet de créer de nouvelles colonnes dans le tableau de données, en général à partir de variables existantes.
Par exemple, la table flights
contient la durée du vol en minutes. Si on veut créer une nouvelle variable duree_h
avec cette durée en heures, on peut faire :
<- mutate(flights, duree_h = air_time / 60)
flights select(flights, air_time, duree_h)
On peut créer plusieurs nouvelles colonnes en une seule commande, et les expressions successives peuvent prendre en compte les résultats des calculs précédents. L’exemple suivant convertit d’abord la durée en heures dans une variable duree_h
et la distance en kilomètres dans une variable distance_km
, puis utilise ces nouvelles colonnes pour calculer la vitesse en km/h.
<- mutate(flights,
flights duree_h = air_time / 60,
distance_km = distance / 0.62137,
vitesse = distance_km / duree_h)
select(flights, air_time, duree_h, distance, distance_km, vitesse)
À noter que mutate
est évidemment parfaitement compatible avec les fonctions de recodages comme case_when
.
<- mutate(flights,
flights type_retard = case_when(
> 0 & arr_delay > 0 ~ "Retard départ et arrivée",
dep_delay > 0 & arr_delay <= 0 ~ "Retard départ",
dep_delay <= 0 & arr_delay > 0 ~ "Retard arrivée",
dep_delay TRUE ~ "Aucun retard"))
8.8.3 La fonction distinct
La fonction distinct
filtre les lignes du tableau pour ne conserver que les lignes distinctes, en supprimant toutes les lignes en double.
%>%
flights select(day, month) %>%
distinct
On peut lui spécifier une liste de variables : dans ce cas, pour toutes les observations ayant des valeurs identiques pour les variables en question, distinct
ne conservera que la première d’entre elles.
%>%
flights distinct(month, day)
8.8.4 La fonction case_when
La fonction case_when
est une généralisation de la fonction if_else
(du package dplyr
) ou ifelse
(du package de base
) qui permet d’indiquer plusieurs tests et leurs valeurs associées.
<- mutate(flights,
flights type_retard = case_when(
> 0 & arr_delay > 0 ~ "Retard départ et arrivée",
dep_delay > 0 & arr_delay <= 0 ~ "Retard départ",
dep_delay <= 0 & arr_delay > 0 ~ "Retard arrivée",
dep_delay TRUE ~ "Aucun retard"))
select(flights, dep_delay, arr_delay , type_retard)
Le pipe a été introduit à l’origine par le package
magrittr
, et repris pardplyr
↩︎