Manipuler les données avec dplyr
La version originale de ce chapitre a été écrite par Julien Barnier dans le cadre de son Introduction à R et au tidyverse.
dplyr
est une extension facilitant le traitement et la manipulation de données contenues dans une ou plusieurs tables (qu’il s’agisse de data frame ou de tibble). Elle propose une syntaxe claire et cohérente, sous formes de verbes, pour la plupart des opérations de ce type.
Par ailleurs, les fonctions de dplyr
sont en général plus rapides que leur équivalent sous R de base, elles permettent donc de traiter des données de grande dimension1.
dplyr
part du principe que les données sont tidy (voir la section consacrée aux tidy data). Les fonctions de l’extension peuvent s’appliquer à des tableaux de type data.frame
ou tibble
, et elles retournent systématiquement un tibble
(voir la section dédiée).
Préparation
dplyr
fait partie du coeur du tidyverse, elle est donc chargée automatiquement avec :
On peut également la charger individuellement avec :
Dans ce qui suit on va utiliser les données du 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…airports
contient des informations sur les aéroportsairlines
contient des données sur les compagnies aériennes
On va charger les trois tables du jeu de données :
library(nycflights13)
## Chargement des trois tables du jeu de données
data(flights)
data(airports)
data(airlines)
Normalement trois objets correspondant aux trois tables ont dû apparaître dans votre environnement.
Les verbes de dplyr
La manipulation de données avec dplyr
se fait en utilisant un nombre réduit de verbes, qui correspondent chacun à une action différente appliquée à un tableau de données.
slice
Le verbe 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
:
# A tibble: 1 x 8
faa name lat lon alt tz dst tzone
<chr> <chr> <dbl> <dbl> <int> <dbl> <chr> <chr>
1 CYF Chefornak Airport 60.1 -164. 40 -9. A America/Anchorage
Si on veut sélectionner les 5 premières lignes :
# A tibble: 5 x 8
faa name lat lon alt tz dst tzone
<chr> <chr> <dbl> <dbl> <int> <dbl> <chr> <chr>
1 04G Lansdowne Airport 41.1 -80.6 1044 -5. A America/Ne~
2 06A Moton Field Municipal Airport 32.5 -85.7 264 -6. A America/Ch~
3 06C Schaumburg Regional 42.0 -88.1 801 -6. A America/Ch~
4 06N Randall Airport 41.4 -74.4 523 -5. A America/Ne~
5 09J Jekyll Island Airport 31.1 -81.4 11 -5. A America/Ne~
filter
filter
sélectionne des lignes d’un tableau de données selon une condition. On lui passe en paramètre un test, et seules les lignes pour lesquelles ce test renvoit TRUE
(vrai) 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 :
# A tibble: 27,004 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 517 515 2. 830 819
2 2013 1 1 533 529 4. 850 830
3 2013 1 1 542 540 2. 923 850
4 2013 1 1 544 545 -1. 1004 1022
5 2013 1 1 554 600 -6. 812 837
6 2013 1 1 554 558 -4. 740 728
7 2013 1 1 555 600 -5. 913 854
8 2013 1 1 557 600 -3. 709 723
9 2013 1 1 557 600 -3. 838 846
10 2013 1 1 558 600 -2. 753 745
# ... with 26,994 more rows, and 11 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Si on veut uniquement les vols avec un retard au départ (variable dep_delay) compris entre 10 et 15 minutes :
# A tibble: 14,919 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 611 600 11. 945 931
2 2013 1 1 623 610 13. 920 915
3 2013 1 1 743 730 13. 1107 1100
4 2013 1 1 743 730 13. 1059 1056
5 2013 1 1 851 840 11. 1215 1206
6 2013 1 1 912 900 12. 1241 1220
7 2013 1 1 914 900 14. 1058 1043
8 2013 1 1 920 905 15. 1039 1025
9 2013 1 1 1011 1001 10. 1133 1128
10 2013 1 1 1112 1100 12. 1440 1438
# ... with 14,909 more rows, and 11 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Si on passe plusieurs arguments à filter
, celui-ci rajoute automatiquement une condition et entre les conditions. La ligne ci-dessus peut donc également être écrite de la manière suivante, avec le même résultat :
Enfin, on peut également placer des fonctions dans les tests, qui nous permettent par exemple de sélectionner les vols avec la plus grande distance :
# A tibble: 342 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 857 900 -3. 1516 1530
2 2013 1 2 909 900 9. 1525 1530
3 2013 1 3 914 900 14. 1504 1530
4 2013 1 4 900 900 0. 1516 1530
5 2013 1 5 858 900 -2. 1519 1530
6 2013 1 6 1019 900 79. 1558 1530
7 2013 1 7 1042 900 102. 1620 1530
8 2013 1 8 901 900 1. 1504 1530
9 2013 1 9 641 900 1301. 1242 1530
10 2013 1 10 859 900 -1. 1449 1530
# ... with 332 more rows, and 11 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
select et rename
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 :
# A tibble: 1,458 x 2
lat lon
<dbl> <dbl>
1 41.1 -80.6
2 32.5 -85.7
3 42.0 -88.1
4 41.4 -74.4
5 31.1 -81.4
6 36.4 -82.2
7 41.5 -84.5
8 42.9 -76.8
9 39.8 -76.6
10 48.1 -123.
# ... with 1,448 more rows
Si on fait précéder le nom d’un -
, la colonne est éliminée plutôt que sélectionnée :
# A tibble: 1,458 x 6
faa name alt tz dst tzone
<chr> <chr> <int> <dbl> <chr> <chr>
1 04G Lansdowne Airport 1044 -5. A America/New_York
2 06A Moton Field Municipal Airport 264 -6. A America/Chicago
3 06C Schaumburg Regional 801 -6. A America/Chicago
4 06N Randall Airport 523 -5. A America/New_York
5 09J Jekyll Island Airport 11 -5. A America/New_York
6 0A9 Elizabethton Municipal Airport 1593 -5. A America/New_York
7 0G6 Williams County Airport 730 -5. A America/New_York
8 0G7 Finger Lakes Regional Airport 492 -5. A America/New_York
9 0P2 Shoestring Aviation Airfield 1000 -5. U America/New_York
10 0S9 Jefferson County Intl 108 -8. A America/Los_Angeles
# ... with 1,448 more rows
select
comprend toute une série de fonctions facilitant la sélection de multiples colonnes. Par exemple, starts_with
, ends_width
, contains
ou matches
permettent d’exprimer des conditions sur les noms de variables :
# A tibble: 336,776 x 2
dep_time dep_delay
<int> <dbl>
1 517 2.
2 533 4.
3 542 2.
4 544 -1.
5 554 -6.
6 554 -4.
7 555 -5.
8 557 -3.
9 557 -3.
10 558 -2.
# ... with 336,766 more rows
La syntaxe colonne1:colonne2
permet de sélectionner toutes les colonnes situées entre colonne1 et colonne2 incluses2 :
# A tibble: 336,776 x 3
year month day
<int> <int> <int>
1 2013 1 1
2 2013 1 1
3 2013 1 1
4 2013 1 1
5 2013 1 1
6 2013 1 1
7 2013 1 1
8 2013 1 1
9 2013 1 1
10 2013 1 1
# ... with 336,766 more rows
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 :
# A tibble: 1,458 x 8
name faa lat lon alt tz dst tzone
<chr> <chr> <dbl> <dbl> <int> <dbl> <chr> <chr>
1 Lansdowne Airport 04G 41.1 -80.6 1044 -5. A America~
2 Moton Field Municipal Airport 06A 32.5 -85.7 264 -6. A America~
3 Schaumburg Regional 06C 42.0 -88.1 801 -6. A America~
4 Randall Airport 06N 41.4 -74.4 523 -5. A America~
5 Jekyll Island Airport 09J 31.1 -81.4 11 -5. A America~
6 Elizabethton Municipal Airport 0A9 36.4 -82.2 1593 -5. A America~
7 Williams County Airport 0G6 41.5 -84.5 730 -5. A America~
8 Finger Lakes Regional Airport 0G7 42.9 -76.8 492 -5. A America~
9 Shoestring Aviation Airfield 0P2 39.8 -76.6 1000 -5. U America~
10 Jefferson County Intl 0S9 48.1 -123. 108 -8. A America~
# ... with 1,448 more rows
Une variante de select
est rename
3, qui permet de renommer facilement 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 :
# A tibble: 1,458 x 8
faa name latitude longitude alt tz dst tzone
<chr> <chr> <dbl> <dbl> <int> <dbl> <chr> <chr>
1 04G Lansdowne Airport 41.1 -80.6 1044 -5. A America/New~
2 06A Moton Field Municip~ 32.5 -85.7 264 -6. A America/Chi~
3 06C Schaumburg Regional 42.0 -88.1 801 -6. A America/Chi~
4 06N Randall Airport 41.4 -74.4 523 -5. A America/New~
5 09J Jekyll Island Airpo~ 31.1 -81.4 11 -5. A America/New~
6 0A9 Elizabethton Munici~ 36.4 -82.2 1593 -5. A America/New~
7 0G6 Williams County Air~ 41.5 -84.5 730 -5. A America/New~
8 0G7 Finger Lakes Region~ 42.9 -76.8 492 -5. A America/New~
9 0P2 Shoestring Aviation~ 39.8 -76.6 1000 -5. U America/New~
10 0S9 Jefferson County In~ 48.1 -123. 108 -8. A America/Los~
# ... with 1,448 more rows
Si les noms de colonnes comportent des espaces ou des caractères spéciaux, on peut les entourer de guillemets ("
) ou de quotes inverses (`
) :
tmp <- rename(flights,
"retard départ" = dep_delay,
"retard arrivée" = arr_delay)
select(tmp, `retard départ`, `retard arrivée`)
# A tibble: 336,776 x 2
`retard départ` `retard arrivée`
<dbl> <dbl>
1 2. 11.
2 4. 20.
3 2. 33.
4 -1. -18.
5 -6. -25.
6 -4. 12.
7 -5. 19.
8 -3. -14.
9 -3. -8.
10 -2. 8.
# ... with 336,766 more rows
arrange
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 :
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 12 7 2040 2123 -43. 40 2352
2 2013 2 3 2022 2055 -33. 2240 2338
3 2013 11 10 1408 1440 -32. 1549 1559
4 2013 1 11 1900 1930 -30. 2233 2243
5 2013 1 29 1703 1730 -27. 1947 1957
6 2013 8 9 729 755 -26. 1002 955
7 2013 10 23 1907 1932 -25. 2143 2143
8 2013 3 30 2030 2055 -25. 2213 2250
9 2013 3 2 1431 1455 -24. 1601 1631
10 2013 5 5 934 958 -24. 1225 1309
# ... with 336,766 more rows, and 11 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
On peut trier selon plusieurs colonnes. Par exemple selon le mois, puis selon le retard au départ :
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 11 1900 1930 -30. 2233 2243
2 2013 1 29 1703 1730 -27. 1947 1957
3 2013 1 12 1354 1416 -22. 1606 1650
4 2013 1 21 2137 2159 -22. 2232 2316
5 2013 1 20 704 725 -21. 1025 1035
6 2013 1 12 2050 2110 -20. 2310 2355
7 2013 1 12 2134 2154 -20. 4 50
8 2013 1 14 2050 2110 -20. 2329 2355
9 2013 1 4 2140 2159 -19. 2241 2316
10 2013 1 11 1947 2005 -18. 2209 2230
# ... with 336,766 more rows, and 11 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Si on veut trier selon une colonne par ordre décroissant, on lui applique la fonction desc()
:
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 9 641 900 1301. 1242 1530
2 2013 6 15 1432 1935 1137. 1607 2120
3 2013 1 10 1121 1635 1126. 1239 1810
4 2013 9 20 1139 1845 1014. 1457 2210
5 2013 7 22 845 1600 1005. 1044 1815
6 2013 4 10 1100 1900 960. 1342 2211
7 2013 3 17 2321 810 911. 135 1020
8 2013 6 27 959 1900 899. 1236 2226
9 2013 7 22 2257 759 898. 121 1026
10 2013 12 5 756 1700 896. 1058 2020
# ... with 336,766 more rows, and 11 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Combiné avec slice
, arrange
permet par exemple de sélectionner les trois vols ayant eu le plus de retard :
# A tibble: 3 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 9 641 900 1301. 1242 1530
2 2013 6 15 1432 1935 1137. 1607 2120
3 2013 1 10 1121 1635 1126. 1239 1810
# ... with 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
# tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>
mutate
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 airports
contient l’altitude de l’aéroport en pieds. Si on veut créer une nouvelle variable alt_m avec l’altitude en mètres, on peut faire :
# A tibble: 1,458 x 3
name alt alt_m
<chr> <int> <dbl>
1 Lansdowne Airport 1044 318.
2 Moton Field Municipal Airport 264 80.5
3 Schaumburg Regional 801 244.
4 Randall Airport 523 159.
5 Jekyll Island Airport 11 3.35
6 Elizabethton Municipal Airport 1593 486.
7 Williams County Airport 730 223.
8 Finger Lakes Regional Airport 492 150.
9 Shoestring Aviation Airfield 1000 305.
10 Jefferson County Intl 108 32.9
# ... with 1,448 more rows
On peut créer plusieurs nouvelles colonnes en une seule fois, et les expressions successives peuvent prendre en compte les résultats des calculs précédents. L’exemple suivant convertit d’abord la distance en kilomètres dans une variable distance_km, puis utilise cette nouvelle colonne pour calculer la vitesse en km/h.
flights <- mutate(flights,
distance_km = distance / 0.62137,
vitesse = distance_km / air_time * 60)
select(flights, distance, distance_km, vitesse)
# A tibble: 336,776 x 3
distance distance_km vitesse
<dbl> <dbl> <dbl>
1 1400. 2253. 596.
2 1416. 2279. 602.
3 1089. 1753. 657.
4 1576. 2536. 832.
5 762. 1226. 634.
6 719. 1157. 463.
7 1065. 1714. 651.
8 229. 369. 417.
9 944. 1519. 651.
10 733. 1180. 513.
# ... with 336,766 more rows
À noter que mutate
est évidemment parfaitement compatible avec les fonctions vues dans le chapitre sur les recodages : fonctions de forcats
, if_else
, case_when
…
L’avantage d’utiliser mutate
est double. D’abord il permet d’éviter d’avoir à saisir le nom du tableau de données dans les conditions d’un if_else
ou d’un case_when
:
flights <- mutate(flights,
type_retard = case_when(
dep_delay > 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",
TRUE ~ "Aucun retard"))
Utiliser mutate
pour les recodages permet aussi de les intégrer dans un pipeline de traitement de données, concept présenté dans la section suivante.
Enchaîner les opérations 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 filtrer pour extraire une sous-population, sélectionner des colonnes puis trier selon une variable.
Dans ce cas on peut le faire de deux manières différentes. La première est d’effectuer toutes les opérations en une fois en les emboîtant
:
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 :
tmp <- filter(flights, dest == "LAX")
tmp <- select(tmp, dep_delay, arr_delay)
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é pipe4. 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 :
Ce qui est intéressant dans cette histoire, c’est qu’on va pouvoir enchaîner les pipes. Plutôt que d’écrire :
On va pouvoir faire :
À 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.
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 :
On appelle une suite d’instructions de ce type un pipeline.
É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 ci-dessus dans un nouveau tableau delay_la
de la manière suivante :
delay_la <- flights %>%
filter(dest == "LAX") %>%
select(dep_delay, arr_delay) %>%
arrange(dep_delay)
Dans ce cas, delay_la
contiendra le tableau final, obtenu après application des trois instructions filter
, select
et arrange
.
Cette notation n’est pas forcément très intuitive au départ. Il faut bien comprendre que c’est le résultat final, une fois application de toutes les opérations du pipeline, qui est renvoyé et stocké dans l’objet en début de ligne.
Une manière de le comprendre peut être de voir que la notation suivante :
est équivalente à :
L’utilisation du pipe n’est pas obligatoire, mais elle rend les scripts plus lisibles et plus rapides à saisir. On l’utilisera donc dans ce qui suit.
Opérations groupées
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 :
# A tibble: 336,776 x 21
# Groups: month [12]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 517 515 2. 830 819
2 2013 1 1 533 529 4. 850 830
3 2013 1 1 542 540 2. 923 850
4 2013 1 1 544 545 -1. 1004 1022
5 2013 1 1 554 600 -6. 812 837
6 2013 1 1 554 558 -4. 740 728
7 2013 1 1 555 600 -5. 913 854
8 2013 1 1 557 600 -3. 709 723
9 2013 1 1 557 600 -3. 838 846
10 2013 1 1 558 600 -2. 753 745
# ... with 336,766 more rows, and 13 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
# distance_km <dbl>, vitesse <dbl>
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 verbes 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 :
# A tibble: 12 x 21
# Groups: month [12]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 517 515 2. 830 819
2 2013 2 1 456 500 -4. 652 648
3 2013 3 1 4 2159 125. 318 56
4 2013 4 1 454 500 -6. 636 640
5 2013 5 1 9 1655 434. 308 2020
6 2013 6 1 2 2359 3. 341 350
7 2013 7 1 1 2029 212. 236 2359
8 2013 8 1 12 2130 162. 257 14
9 2013 9 1 9 2359 10. 343 340
10 2013 10 1 447 500 -13. 614 648
11 2013 11 1 5 2359 6. 352 345
12 2013 12 1 13 2359 14. 446 445
# ... with 13 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
# tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>, distance_km <dbl>,
# vitesse <dbl>
Idem pour mutate
: les opérations appliquées lors du calcul des valeurs des nouvelles colonnes sont aplliquée groupe de lignes par groupe de lignes. Dans l’exemple suivant, on ajoute une nouvelle colonne qui contient le retard moyen du mois correspondant :
flights %>%
group_by(month) %>%
mutate(mean_delay_month = mean(dep_delay, na.rm = TRUE)) %>%
select(dep_delay, month, mean_delay_month)
# A tibble: 336,776 x 3
# Groups: month [12]
dep_delay month mean_delay_month
<dbl> <int> <dbl>
1 2. 1 10.0
2 4. 1 10.0
3 2. 1 10.0
4 -1. 1 10.0
5 -6. 1 10.0
6 -4. 1 10.0
7 -5. 1 10.0
8 -3. 1 10.0
9 -3. 1 10.0
10 -2. 1 10.0
# ... with 336,766 more rows
Ceci peut permettre, par exemple, de déterminer si un retard donné est supérieur ou inférieur au retard moyen du mois en cours.
group_by
peut aussi être utile avec filter
, par exemple pour sélectionner les vols avec le retard au départ le plus important pour chaque mois :
# A tibble: 12 x 21
# Groups: month [12]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 9 641 900 1301. 1242 1530
2 2013 10 14 2042 900 702. 2255 1127
3 2013 11 3 603 1645 798. 829 1913
4 2013 12 5 756 1700 896. 1058 2020
5 2013 2 10 2243 830 853. 100 1106
6 2013 3 17 2321 810 911. 135 1020
7 2013 4 10 1100 1900 960. 1342 2211
8 2013 5 3 1133 2055 878. 1250 2215
9 2013 6 15 1432 1935 1137. 1607 2120
10 2013 7 22 845 1600 1005. 1044 1815
11 2013 8 8 2334 1454 520. 120 1710
12 2013 9 20 1139 1845 1014. 1457 2210
# ... with 13 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
# tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>, distance_km <dbl>,
# vitesse <dbl>
Attention : la clause group_by
marche pour les verbes déjà vus précédemment, sauf pour arrange
, qui par défaut trie la table sans tenir compte des groupes. Pour obtenir un tri par groupe, il faut lui ajouter l’argument .by_group = TRUE
.
On peut voir la différence en comparant les deux résultats suivants :
# A tibble: 336,776 x 21
# Groups: month [12]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 9 641 900 1301. 1242 1530
2 2013 6 15 1432 1935 1137. 1607 2120
3 2013 1 10 1121 1635 1126. 1239 1810
4 2013 9 20 1139 1845 1014. 1457 2210
5 2013 7 22 845 1600 1005. 1044 1815
6 2013 4 10 1100 1900 960. 1342 2211
7 2013 3 17 2321 810 911. 135 1020
8 2013 6 27 959 1900 899. 1236 2226
9 2013 7 22 2257 759 898. 121 1026
10 2013 12 5 756 1700 896. 1058 2020
# ... with 336,766 more rows, and 13 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
# distance_km <dbl>, vitesse <dbl>
# A tibble: 336,776 x 21
# Groups: month [12]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 9 641 900 1301. 1242 1530
2 2013 1 10 1121 1635 1126. 1239 1810
3 2013 1 1 848 1835 853. 1001 1950
4 2013 1 13 1809 810 599. 2054 1042
5 2013 1 16 1622 800 502. 1911 1054
6 2013 1 23 1551 753 478. 1812 1006
7 2013 1 10 1525 900 385. 1713 1039
8 2013 1 1 2343 1724 379. 314 1938
9 2013 1 2 2131 1512 379. 2340 1741
10 2013 1 7 2021 1415 366. 2332 1724
# ... with 336,766 more rows, and 13 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
# distance_km <dbl>, vitesse <dbl>
summarise et count
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))
# A tibble: 1 x 2
retard_dep retard_arr
<dbl> <dbl>
1 12.6 6.90
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))
# A tibble: 12 x 4
month max_delay min_delay mean_delay
<int> <dbl> <dbl> <dbl>
1 1 1301. -30. 10.0
2 2 853. -33. 10.8
3 3 911. -25. 13.2
4 4 960. -21. 13.9
5 5 878. -24. 13.0
6 6 1137. -21. 20.8
7 7 1005. -22. 21.7
8 8 520. -26. 12.6
9 9 1014. -24. 6.72
10 10 702. -25. 6.24
11 11 798. -32. 5.44
12 12 896. -43. 16.6
summarise
dispose d’un opérateur spécial, n()
, qui retourne le nombre de lignes du groupe. Ainsi si on veut le nombre de vols par destination, on peut utiliser :
# A tibble: 105 x 2
dest nb
<chr> <int>
1 ABQ 254
2 ACK 265
3 ALB 439
4 ANC 8
5 ATL 17215
6 AUS 2439
7 AVL 275
8 BDL 443
9 BGR 375
10 BHM 297
# ... with 95 more rows
n()
peut aussi être utilisée avec filter
et mutate
.
À noter que quand on veut compter le nombre de lignes par groupe, on peut utiliser directement la fonction count
. Ainsi le code suivant est identique au précédent :
# A tibble: 105 x 2
dest n
<chr> <int>
1 ABQ 254
2 ACK 265
3 ALB 439
4 ANC 8
5 ATL 17215
6 AUS 2439
7 AVL 275
8 BDL 443
9 BGR 375
10 BHM 297
# ... with 95 more rows
Grouper selon plusieurs variables
On peut grouper selon plusieurs variables à la fois, il suffit de les indiquer dans la clause du group_by
:
# A tibble: 1,113 x 3
# Groups: month [12]
month dest nb
<int> <chr> <int>
1 8 ORD 1604
2 10 ORD 1604
3 5 ORD 1582
4 9 ORD 1582
5 7 ORD 1573
6 6 ORD 1547
7 7 ATL 1511
8 8 ATL 1507
9 8 LAX 1505
10 7 LAX 1500
# ... with 1,103 more rows
On peut également compter selon plusieurs variables :
# A tibble: 224 x 3
origin dest n
<chr> <chr> <int>
1 JFK LAX 11262
2 LGA ATL 10263
3 LGA ORD 8857
4 JFK SFO 8204
5 LGA CLT 6168
6 EWR ORD 6100
7 JFK BOS 5898
8 LGA MIA 5781
9 JFK MCO 5464
10 EWR BOS 5327
# ... with 214 more rows
On peut utiliser plusieurs opérations de groupage dans le même pipeline. Ainsi, si on souhaite déterminer le couple origine/destination ayant le plus grand nombre de vols selon le mois de l’année, on devra procéder en deux étapes :
- d’abord grouper selon mois, origine et destination pour calculer le nombre de vols
- puis grouper uniquement selon le mois pour sélectionner la ligne avec la valeur maximale.
Au final, on obtient le code suivant :
flights %>%
group_by(month, origin, dest) %>%
summarise(nb = n()) %>%
group_by(month) %>%
filter(nb == max(nb))
# A tibble: 12 x 4
# Groups: month [12]
month origin dest nb
<int> <chr> <chr> <int>
1 1 JFK LAX 937
2 2 JFK LAX 834
3 3 JFK LAX 960
4 4 JFK LAX 935
5 5 JFK LAX 960
6 6 JFK LAX 928
7 7 JFK LAX 985
8 8 JFK LAX 979
9 9 JFK LAX 925
10 10 JFK LAX 965
11 11 JFK LAX 907
12 12 JFK LAX 947
Lorsqu’on effectue un group_by
suivi d’un summarise
, le tableau résultat est automatiquement dégroupé de la dernière variable de regroupement. Ainsi le tableau généré par le code suivant est groupé par month et origin :
# A tibble: 2,313 x 4
# Groups: month, origin [?]
month origin dest nb
<int> <chr> <chr> <int>
1 1 EWR ALB 64
2 1 EWR ATL 362
3 1 EWR AUS 51
4 1 EWR AVL 2
5 1 EWR BDL 37
6 1 EWR BNA 111
7 1 EWR BOS 430
8 1 EWR BQN 31
9 1 EWR BTV 100
10 1 EWR BUF 119
# ... with 2,303 more rows
Cela peut permettre “d’enchaîner” les opérations groupées. Dans l’exemple suivant on calcule le pourcentage des trajets pour chaque destination par rapport à tous les trajets du mois :
flights %>%
group_by(month, dest) %>%
summarise(nb = n()) %>%
mutate(pourcentage = nb / sum(nb) * 100)
# A tibble: 1,113 x 4
# Groups: month [12]
month dest nb pourcentage
<int> <chr> <int> <dbl>
1 1 ALB 64 0.237
2 1 ATL 1396 5.17
3 1 AUS 169 0.626
4 1 AVL 2 0.00741
5 1 BDL 37 0.137
6 1 BHM 25 0.0926
7 1 BNA 399 1.48
8 1 BOS 1245 4.61
9 1 BQN 93 0.344
10 1 BTV 223 0.826
# ... with 1,103 more rows
On peut à tout moment “dégrouper” un tableau à l’aide de ungroup
. Ce serait par exemple nécessaire, dans l’exemple précédent, si on voulait calculer le pourcentage sur le nombre total de vols plutôt que sur le nombre de vols par mois :
flights %>%
group_by(month, dest) %>%
summarise(nb = n()) %>%
ungroup() %>%
mutate(pourcentage = nb / sum(nb) * 100)
# A tibble: 1,113 x 4
month dest nb pourcentage
<int> <chr> <int> <dbl>
1 1 ALB 64 0.0190
2 1 ATL 1396 0.415
3 1 AUS 169 0.0502
4 1 AVL 2 0.000594
5 1 BDL 37 0.0110
6 1 BHM 25 0.00742
7 1 BNA 399 0.118
8 1 BOS 1245 0.370
9 1 BQN 93 0.0276
10 1 BTV 223 0.0662
# ... with 1,103 more rows
À noter que count
, par contre, renvoit un tableau non groupé :
# A tibble: 1,113 x 3
month dest n
<int> <chr> <int>
1 1 ALB 64
2 1 ATL 1396
3 1 AUS 169
4 1 AVL 2
5 1 BDL 37
6 1 BHM 25
7 1 BNA 399
8 1 BOS 1245
9 1 BQN 93
10 1 BTV 223
# ... with 1,103 more rows
Autres fonctions utiles
dplyr
contient beaucoup d’autres fonctions utiles pour la manipulation de données.
sample_n et sample_frac
sample_n
et sample_frac
permettent de sélectionner un nombre de lignes ou une fraction des lignes d’un tableau aléatoirement. Ainsi si on veut choisir 5 lignes au hasard dans le tableau airports
:
# A tibble: 5 x 9
faa name lat lon alt tz dst tzone alt_m
<chr> <chr> <dbl> <dbl> <int> <dbl> <chr> <chr> <dbl>
1 BLI Bellingham Intl 48.8 -123. 170 -8. A Amer~ 51.8
2 SIK Sikeston Memorial Municipal 36.9 -89.6 315 -6. A Amer~ 96.0
3 ZRA Atlantic City Rail Terminal 39.4 -74.4 8 -5. A Amer~ 2.44
4 ANI Aniak Airport 61.6 -160. 88 -9. A Amer~ 26.8
5 GGG East Texas Rgnl 32.4 -94.7 365 -6. A Amer~ 111.
Si on veut tirer au hasard 10% des lignes de flights
:
# A tibble: 33,678 x 21
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 8 25 650 700 -10. 918 947
2 2013 10 8 654 700 -6. 928 1003
3 2013 10 15 1455 1459 -4. 1722 1737
4 2013 1 9 1403 1335 28. 1655 1633
5 2013 7 12 1053 1100 -7. 1342 1349
6 2013 7 19 957 1000 -3. 1248 1319
7 2013 1 1 1305 1315 -10. 1523 1520
8 2013 10 28 1505 1452 13. 1754 1757
9 2013 7 27 1443 1450 -7. 1634 1633
10 2013 11 16 855 900 -5. 1155 1212
# ... with 33,668 more rows, and 13 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
# distance_km <dbl>, vitesse <dbl>
Ces fonctions sont utiles notamment pour faire de “l’échantillonnage” en tirant au hasard un certain nombre d’observations du tableau.
lead et lag
lead
et lag
permettent de décaler les observations d’une variable d’un cran vers l’arrière (pour lead
) ou vers l’avant (pour lag
).
[1] 2 3 4 5 NA
[1] NA 1 2 3 4
Ceci peut être utile pour des données de type “séries temporelles”. Par exemple, on peut facilement calculer l’écart entre le retard au départ de chaque vol et celui du vol précédent :
flights %>%
mutate(dep_delay_prev = lead(dep_delay),
dep_delay_diff = dep_delay - dep_delay_prev) %>%
select(dep_delay_prev, dep_delay, dep_delay_diff)
# A tibble: 336,776 x 3
dep_delay_prev dep_delay dep_delay_diff
<dbl> <dbl> <dbl>
1 4. 2. -2.
2 2. 4. 2.
3 -1. 2. 3.
4 -6. -1. 5.
5 -4. -6. -2.
6 -5. -4. 1.
7 -3. -5. -2.
8 -3. -3. 0.
9 -2. -3. -1.
10 -2. -2. 0.
# ... with 336,766 more rows
tally
tally
est une fonction qui permet de compter le nombre d’observations d’un groupe :
# A tibble: 2,313 x 4
# Groups: month, origin [?]
month origin dest n
<int> <chr> <chr> <int>
1 1 EWR ALB 64
2 1 EWR ATL 362
3 1 EWR AUS 51
4 1 EWR AVL 2
5 1 EWR BDL 37
6 1 EWR BNA 111
7 1 EWR BOS 430
8 1 EWR BQN 31
9 1 EWR BTV 100
10 1 EWR BUF 119
# ... with 2,303 more rows
Lors de son premier appel, elle sera équivalente à un summarise(n = n())
ou à un count()
. Là où la fonction est intelligente, c’est que si on l’appelle plusieurs fois successivement, elle prendra en compte l’existence d’un n
déjà calculé et effectuera automatiquement un summarise(n = sum(n))
:
Using `n` as weighting variable
# A tibble: 36 x 3
# Groups: month [?]
month origin nn
<int> <chr> <int>
1 1 EWR 9893
2 1 JFK 9161
3 1 LGA 7950
4 2 EWR 9107
5 2 JFK 8421
6 2 LGA 7423
7 3 EWR 10420
8 3 JFK 9697
9 3 LGA 8717
10 4 EWR 10531
# ... with 26 more rows
distinct
distinct
filtre les lignes du tableau pour ne conserver que les lignes distinctes, en supprimant toutes les lignes en double.
# A tibble: 365 x 2
day month
<int> <int>
1 1 1
2 2 1
3 3 1
4 4 1
5 5 1
6 6 1
7 7 1
8 8 1
9 9 1
10 10 1
# ... with 355 more rows
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.
# A tibble: 365 x 2
month day
<int> <int>
1 1 1
2 1 2
3 1 3
4 1 4
5 1 5
6 1 6
7 1 7
8 1 8
9 1 9
10 1 10
# ... with 355 more rows
L’option .keep_all
permet, dans l’opération précédente, de conserver l’ensemble des colonnes du tableau :
# A tibble: 365 x 21
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 517 515 2. 830 819
2 2013 1 2 42 2359 43. 518 442
3 2013 1 3 32 2359 33. 504 442
4 2013 1 4 25 2359 26. 505 442
5 2013 1 5 14 2359 15. 503 445
6 2013 1 6 16 2359 17. 451 442
7 2013 1 7 49 2359 50. 531 444
8 2013 1 8 454 500 -6. 625 648
9 2013 1 9 2 2359 3. 432 444
10 2013 1 10 3 2359 4. 426 437
# ... with 355 more rows, and 13 more variables: arr_delay <dbl>,
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
# distance_km <dbl>, vitesse <dbl>
Ressources
Toutes les ressources ci-dessous sont en anglais…
Le livre R for data science, librement accessible en ligne, contient plusieurs chapitres très complets sur la manipulation des données, notamment :
- Data transformation pour les manipulations
- Relational data pour les tables multiples
Le site de l’extension comprend une liste des fonctions et les pages d’aide associées, mais aussi une introduction au package et plusieurs articles dont un spécifiquement sur les jointures.
Une “antisèche” très synthétique est également accessible depuis RStudio, en allant dans le menu Help puis Cheatsheets et Data Transformation with dplyr.
Enfin, on trouvera des exercices dans l’Introduction à R et au tidyverse de Julien Barnier.
dplyr et data.table
Pour ceux travaillant également avec l’extension data.table
, il est possible de concilier tibble et data.table avec l’extension dtplyr
et sa fonction tbl_dt
.
[1] "tbl_dt" "tbl" "data.table" "data.frame"
Le tableau de données est à la fois compatible avec data.table
(et notamment sa syntaxe particulière des crochets) et les verbes de dplyr
.
Pour décrouvrir data.table
, voir le chapitre dédié.
dplyr et survey
L’extension srvyr
vise à permettre d’utiliser les verbes de dplyr
avec les plans d’échantillonnage complexe définis avec survey
. Le fonctionnement de cette extension est expliqué dans une vignette dédiée : https://cran.r-project.org/web/packages/srvyr/vignettes/srvyr-vs-survey.html.
Elles sont cependant moins rapides que les fonctions de
data.table
, voir le chapitre dédié↩À noter que cette opération est un peu plus “fragile” que les autres, car si l’ordre des colonnes change elle peut renvoyer un résultat différent.↩
Il est également possible de renommer des colonnes directement avec
select
, avec la même syntaxe que pourrename
.↩Le pipe a été introduit à l’origine par l’extension
magrittr
, et repris pardplyr
↩