forked from juba/tidyverse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
10-dplyr.Rmd
1522 lines (1028 loc) · 45.4 KB
/
10-dplyr.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
output: html_document
editor_options:
chunk_output_type: console
---
# Manipuler les données avec `dplyr` {#dplyr}
`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, sous formes de verbes, pour la plupart des opérations de ce type. 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.
`dplyr` part du principe que les données sont organisées selon le modèle des *tidy data* (voir la section \@ref(tidydata)). 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 \@ref(tibbles)).
## Préparation
`dplyr` fait partie du coeur du *tidyverse*, elle est donc chargée automatiquement avec :
```{r message=FALSE, warning=FALSE, cache = FALSE}
library(tidyverse)
```
On peut également la charger individuellement avec :
```{r}
library(dplyr)
```
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...
- `airports` contient des informations sur les aéroports
- `airlines` contient des données sur les compagnies aériennes
On va charger les trois tables du jeu de données :
```{r cache = FALSE}
library(nycflights13)
## Chargement des trois tables
data(flights)
data(airports)
data(airlines)
```
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` :
```{r}
slice(airports, 345)
```
Si on veut sélectionner les 5 premières lignes :
```{r}
slice(airports, 1:5)
```
### `filter` {#filter}
`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` (vrai) sont conservées. Pour plus d'informations sur les tests et leur syntaxe, voir la section \@ref(tests).
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 :
```{r}
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 :
```{r}
filter(flights, dep_delay >= 10 & dep_delay <= 15)
```
Si on passe plusieurs arguments à `filter`, celui-ci rajoute automatiquement une condition *et* entre les conditions. La commande précédente peut donc être écrite de la manière suivante, avec le même résultat :
```{r eval = FALSE}
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 :
```{r}
filter(flights, distance == max(distance))
```
### `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 :
```{r}
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 :
```{r}
select(airports, -lat, -lon)
```
`select` comprend toute une série de fonctions facilitant la sélection de colonnes multiples. Par exemple, `starts_with`, `ends_width`, `contains` ou `matches` permettent d'exprimer des conditions sur les noms de variables :
```{r}
select(flights, starts_with("dep_"))
```
La syntaxe `colonne1:colonne2` permet de sélectionner toutes les colonnes situées entre `colonne1` et `colonne2` incluses^[À 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.] :
```{r}
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 :
```{r}
select(airports, name, everything())
```
Une variante de `select` est `rename`^[Il est également possible de renommer des colonnes directement avec `select`, avec la même syntaxe que pour `rename`.], qui 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` :
```{r}
rename(airports, longitude = lon, latitude = lat)
```
Si les noms de colonnes comportent des espaces ou des caractères spéciaux, on peut les entourer de guillemets (`"`) ou de quotes inverses (`` ` ``) :
```{r}
tmp <- rename(flights,
"retard départ" = dep_delay,
"retard arrivée" = arr_delay)
select(tmp, `retard départ`, `retard arrivée`)
```
### `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 :
```{r}
arrange(flights, dep_delay)
```
On peut trier selon plusieurs colonnes. Par exemple selon le mois, puis selon le retard au départ :
```{r}
arrange(flights, month, dep_delay)
```
Si on veut trier selon une colonne par ordre décroissant, on lui applique la fonction `desc()` :
```{r}
arrange(flights, desc(dep_delay))
```
Combiné avec `slice`, `arrange` permet par exemple de sélectionner les trois vols ayant eu le plus de retard :
```{r}
tmp <- arrange(flights, desc(dep_delay))
slice(tmp, 1:3)
```
### `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 `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 :
```{r}
flights <- mutate(flights, duree_h = air_time / 60)
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.
```{r}
flights <- mutate(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 vues dans le chapitre \@ref(vectorfactor) sur les recodages : `fct_recode`, `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` :
```{r, eval=FALSE}
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"))
```
Ensuite, il permet aussi d'intégrer ces recodages dans un *pipeline* de traitement de données, concept présenté dans la section suivante.
## Enchaîner les opérations avec le *pipe* {#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" :
```{r eval=FALSE}
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 le `select`, puis le `arrange`, alors qu'à la lecture du code c'est le `arrange` 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 :
```{r eval=FALSE}
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é *pipe*^[Le *pipe* a été introduit à l'origine par l'extension `magrittr`, et repris par `dplyr`]. 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 :
```{r eval=FALSE}
filter(flights, dest == "LAX")
```
```{r eval=FALSE}
flights %>% filter(dest == "LAX")
```
Ce qui est intéressant dans cette histoire, c'est qu'on va pouvoir enchaîner les *pipes*. Plutôt que d'écrire :
```{r eval=FALSE}
select(filter(flights, dest == "LAX"), dep_delay, arr_delay)
```
On va pouvoir faire :
```{r eval=FALSE}
flights %>% filter(dest == "LAX") %>% select(dep_delay, arr_delay)
```
À 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 :
```{r, eval=FALSE}
flights %>%
filter(dest == "LAX") %>%
select(dep_delay, arr_delay) %>%
arrange(dep_delay)
```
```{block type='rmdnote'}
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 :
```{r}
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 :
```{r}
delay_la <- flights %>%
filter(dest == "LAX") %>%
select(dep_delay, arr_delay)
```
est équivalente à :
```{r}
delay_la <- (flights %>% filter(dest == "LAX") %>% select(dep_delay, arr_delay))
```
```{block type='rmdnote'}
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 :
```{r}
flights %>% group_by(month)
```
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 :
```{r}
flights %>% group_by(month) %>% slice(1)
```
Idem pour `mutate` : les opérations appliquées lors du calcul des valeurs des nouvelles colonnes sont appliquées groupe de lignes par groupe de lignes. Dans l'exemple suivant, on ajoute une nouvelle colonne qui contient le retard moyen *pour chaque compagnie aérienne*. Cette valeur est donc différente d'une compagnie à une autre, mais identique pour tous les vols d'une même compagnie :
```{r}
flights %>%
group_by(carrier) %>%
mutate(mean_delay_carrier = mean(dep_delay, na.rm = TRUE)) %>%
select(dep_delay, month, mean_delay_carrier)
```
Ceci peut permettre, par exemple, de déterminer si un retard donné est supérieur ou inférieur au retard médian de la compagnie :
```{r}
flights %>%
group_by(carrier) %>%
mutate(median_delay = median(dep_delay, na.rm = TRUE),
delay_carrier = if_else(dep_delay > median_delay,
"Supérieur", "Inférieur ou égal")) %>%
select(dep_delay, month, median_delay, delay_carrier)
```
`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* :
```{r}
flights %>%
group_by(month) %>%
filter(dep_delay == max(dep_delay, na.rm = TRUE))
```
```{block type='rmdimportant'}
**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 :
```{r}
flights %>%
group_by(month) %>%
arrange(desc(dep_delay))
```
```{r}
flights %>%
group_by(month) %>%
arrange(desc(dep_delay), .by_group = TRUE)
```
### `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` :
```{r}
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 :
```{r}
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))
```
`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 :
```{r}
flights %>%
group_by(dest) %>%
summarise(nb = n())
```
`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 :
```{r}
flights %>%
count(dest)
```
Depuis la version 0.8 de `dplyr`, lorsque la variable de groupage est un facteur et que certaines valeurs du facteur ne sont pas présentes dans le tableau, l'argument `.drop = FALSE` de `group_by` permet de conserver ces niveaux dans le résultat d'une opération groupée.
Par exemple, si on transforme la variable `origin` en facteur pour conserver la liste de ses modalités, et on ne garde que les vols à destination de San Francisco (code `SFO`) :
```{r}
ff <- flights %>%
mutate(origin = factor(origin)) %>%
filter(dest == "SFO")
```
Par défaut, si on compte le nombre de vols selon l'aéroport de départ, La Guardia n'apparaît pas car il ne compte aucun vol :
```{r}
ff %>%
group_by(origin) %>%
summarise(n = n())
```
Si on souhaite faire apparaître cette information dans la sortie du `summarise`, on peut ajouter l'argument `.drop = FALSE` au `group_by` :
```{r}
ff %>%
group_by(origin, .drop = FALSE) %>%
summarise(n = n())
```
Cet argument fonctionne aussi avec `count` :
```{r}
ff %>%
count(origin, .drop = FALSE)
```
### Grouper selon plusieurs variables
On peut grouper selon plusieurs variables à la fois, il suffit de les indiquer dans la clause du `group_by`. Le *pipeline* suivant le nombre de vols pour chaque mois et pour chaque destination, et trie le résultat par nombre de vols décroissant :
```{r}
flights %>%
group_by(month, dest) %>%
summarise(nb = n()) %>%
arrange(desc(nb))
```
On peut également utiliser `count` sur plusieurs variables. Les commandes suivantes comptent le nombre de vols pour chaque couple aéroport de départ / aéroport d'arrivée, et trie le résultat par nombre de vols décroissant :
```{r}
flights %>%
count(origin, dest) %>%
arrange(desc(n))
```
On peut utiliser plusieurs opérations de groupage dans le même *pipeline*. Ainsi, si on souhaite déterminer le triplet compagnie aérienne / aéroport de départ / aéroport d'arrivée 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, compagnie, aéroports d'origine et d'arrivée 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 :
```{r}
flights %>%
group_by(month, carrier, origin, dest) %>%
summarise(nb = n()) %>%
group_by(month) %>%
filter(nb == max(nb))
```
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` :
```{r}
flights %>%
group_by(month, origin, dest) %>%
summarise(nb = n())
```
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 :
```{r}
flights %>%
group_by(month, dest) %>%
summarise(nb = n()) %>%
mutate(pourcentage = nb / sum(nb) * 100)
```
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 :
```{r}
flights %>%
group_by(month, dest) %>%
summarise(nb = n()) %>%
ungroup() %>%
mutate(pourcentage = nb / sum(nb) * 100)
```
À noter que `count`, par contre, renvoie un tableau non groupé :
```{r}
flights %>%
count(month, dest)
```
## Autres fonctions utiles
`dplyr` contient beaucoup d'autres fonctions utiles pour la manipulation de données.
### `sample_n`, `sample_frac`
Ces verbes 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` :
```{r}
airports %>% sample_n(5)
```
Si on veut tirer au hasard 10% des lignes de `flights` :
```{r}
flights %>% sample_frac(0.1)
```
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`).
```{r}
lead(1:5)
lag(1:5)
```
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 :
```{r}
flights %>%
mutate(dep_delay_prev = lag(dep_delay),
dep_delay_diff = dep_delay - dep_delay_prev) %>%
select(dep_delay_prev, dep_delay, dep_delay_diff)
```
### `tally`
`tally` est une fonction qui permet de compter le nombre d'observations d'un groupe :
```{r}
flights %>%
group_by(month, origin, dest) %>%
tally
```
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))` :
```{r}
flights %>%
group_by(month, origin, dest) %>%
tally %>%
tally
```
Et ainsi de suite :
```{r}
flights %>%
group_by(month, origin, dest) %>%
tally %>%
tally %>%
tally
```
### `distinct`
`distinct` filtre les lignes du tableau pour ne conserver que les lignes distinctes, en supprimant toutes les lignes en double.
```{r}
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.
```{r}
flights %>%
distinct(month, day)
```
L'option `.keep_all` permet, dans l'opération précédente, de conserver l'ensemble des colonnes du tableau :
```{r}
flights %>%
distinct(month, day, .keep_all = TRUE)
```
## Tables multiples
Le jeu de données `nycflights13` est un exemple de données réparties en plusieurs tables. Ici on en a trois : les informations sur les vols dans `flights`, celles sur les aéroports dans `airports` et celles sur les compagnies aériennes dans `airlines`.
`dplyr` propose différentes fonctions permettant de travailler avec des données structurées de cette manière.
### Concaténation : `bind_rows` et `bind_cols`
Les fonctions `bind_rows` et `bind_cols` permettent d'ajouter des lignes (respectivement des colonnes) à une table à partir d'une ou plusieurs autres tables.
L'exemple suivant (certes très artificiel) montre l'utilisation de `bind_rows`. On commence par créer trois tableaux `t1`, `t2` et `t3` :
```{r}
t1 <- airports %>%
select(faa, name, lat, lon) %>%
slice(1:2)
t1
```
```{r}
t2 <- airports %>%
select(faa, name, lat, lon) %>%
slice(5:6)
t2
```
```{r}
t3 <- airports %>%
select(faa, name) %>%
slice(100:101)
t3
```
On concaténe ensuite les trois tables avec `bind_rows` :
```{r}
bind_rows(t1, t2, t3)
```
On remarquera que si des colonnes sont manquantes pour certaines tables, comme les colonnes `lat` et `lon` de `t3`, des `NA` sont automatiquement insérées.
Il peut être utile, quand on concatène des lignes, de garder une trace du tableau d'origine de chacune des lignes dans le tableau final. C'est possible grâce à l'argument `.id` de `bind_rows`. On passe à cet argument le nom d'une colonne qui contiendra l'indicateur d'origine des lignes :
```{r}
bind_rows(t1, t2, t3, .id = "source")
```
Par défaut la colonne `.id` ne contient qu'un nombre, différent pour chaque tableau. On peut lui spécifier des valeurs plus explicites en "nommant" les tables dans `bind_rows` de la manière suivante :
```{r}
bind_rows(table1 = t1, table2 = t2, table3 = t3, .id = "source")
```
`bind_cols` permet de concaténer des colonnes et fonctionne de manière similaire :
```{r}
t1 <- flights %>% slice(1:5) %>% select(dep_delay, dep_time)
t2 <- flights %>% slice(1:5) %>% select(origin, dest)
t3 <- flights %>% slice(1:5) %>% select(arr_delay, arr_time)
bind_cols(t1, t2, t3)
```
À noter que `bind_cols` associe les lignes uniquement *par position*. Les lignes des différents tableaux associés doivent donc correspondre (et leur nombre doit être identique). Pour associer des tables *par valeur*, on doit utiliser des jointures.
### Jointures
#### Clés implicites
Très souvent, les données relatives à une analyse sont réparties dans plusieurs tables différentes. Dans notre exemple, on peut voir que la table `flights` contient seulement le code de la compagnie aérienne du vol dans la variable `carrier` :
```{r}
flights %>% select(carrier)
```
Et que par ailleurs la table `airlines` contient une information supplémentaire relative à ces compagnies, à savoir le nom complet.
```{r}
airlines
```
Il est donc naturel de vouloir associer les deux, en l'occurrence pour ajouter les noms complets des compagnies à la table `flights`. Dans ce cas on va faire une *jointure* : les lignes d'une table seront associées à une autre en se basant non pas sur leur position, mais sur les valeurs d'une ou plusieurs colonnes. Ces colonnes sont appelées des *clés*.
Pour faire une jointure de ce type, on va utiliser la fonction `left_join` :
```{r eval = FALSE}
left_join(flights, airlines)
```
Pour faciliter la lecture, on va afficher seulement certaines colonnes du résultat :
```{r}
left_join(flights, airlines) %>%
select(month, day, carrier, name)
```
On voit que la table résultat est bien la fusion des deux tables d'origine selon les valeurs des deux colonnes clés `carrier`. On est parti de la table `flights`, et pour chaque ligne on a ajouté les colonnes de `airlines` pour lesquelles la valeur de `carrier` est la même. On a donc bien une nouvelle colonne `name` dans notre table résultat, avec le nom complet de la compagnie aérienne.
```{block type='rmdnote'}
À noter qu'on peut tout à fait utiliser le *pipe* avec les fonctions de jointure :
`flights %>% left_join(airlines)`.
```
Nous sommes ici dans le cas le plus simple concernant les clés de jointure : les deux clés sont uniques et portent le même nom dans les deux tables. Par défaut, si on ne lui spécifie pas explicitement les clés, `dplyr` fusionne en utilisant l'ensemble des colonnes communes aux deux tables. On peut d'ailleurs voir dans cet exemple qu'un message a été affiché précisant que la jointure s'est faite sur la variable `carrier`.
#### Clés explicites
La table `airports`, elle, contient des informations supplémentaires sur les aéroports : nom complet, altitude, position géographique, etc. Chaque aéroport est identifié par un code contenu dans la colonne `faa`.
Si on regarde la table `flights`, on voit que le code d'identification des aéroports apparaît à deux endroits différents : pour l'aéroport de départ dans la colonne `origin`, et pour celui d'arrivée dans la colonne `dest`. On a donc deux clés de jointure possibles, et qui portent un nom différent de la clé de `airports`.
On va commencer par fusionner les données concernant l'aéroport de départ. Pour simplifier l'affichage des résultats, on va se contenter d'un sous-ensemble des deux tables :
```{r}
flights_ex <- flights %>% select(month, day, origin, dest)
airports_ex <- airports %>% select(faa, alt, name)
```
Si on se contente d'un `left_join` comme à l'étape précédente, on obtient un message d'erreur car aucune colonne commune ne peut être identifiée comme clé de jointure :
```{r error=TRUE}
left_join(flights_ex, airports_ex)
```
On doit donc spécifier explicitement les clés avec l'argument `by` de `left_join`. Ici la clé est nommée `origin` dans la première table, et `faa` dans la seconde. La syntaxe est donc la suivante :
```{r}
left_join(flights_ex, airports_ex, by = c("origin" = "faa"))
```
On constate que les deux nouvelles colonnes `name` et `alt` contiennent bien les données correspondant à l'aéroport de départ.
On va stocker le résultat de cette jointure dans la table `flights_ex` :
```{r}
flights_ex <- flights_ex %>%
left_join(airports_ex, by = c("origin" = "faa"))
```
Supposons qu'on souhaite maintenant fusionner à nouveau les informations de la table `airports`, mais cette fois pour les aéroports d'arrivée de notre nouvelle table `flights_ex`. Les deux clés sont donc désormais `dest` dans la première table, et `faa` dans la deuxième. La syntaxe est donc la suivante :
```{r}
left_join(flights_ex, airports_ex, by=c("dest" = "faa"))
```
Cela fonctionne, les informations de l'aéroport d'arrivée ont bien été ajoutées, mais on constate que les colonnes ont été renommées. En effet, ici les deux tables fusionnées contenaient toutes les deux des colonnes `name` et `alt`. Comme on ne peut pas avoir deux colonnes avec le même nom dans un tableau, `dplyr` a renommé les colonnes de la première table en `name.x` et `alt.x`, et celles de la deuxième en `name.y` et `alt.y`.
C'est pratique, mais pas forcément très parlant. On pourrait renommer manuellement les colonnes avec `rename` avant de faire la jointure pour avoir des intitulés plus explicites, mais on peut aussi utiliser l'argument `suffix` de `left_join`, qui permet d'indiquer les suffixes à ajouter aux colonnes. Ainsi, on peut faire :
```{r}
left_join(flights_ex, airports_ex,
by = c("dest" = "faa"),
suffix = c("_depart", "_arrivee"))
```
On obtient ainsi directement des noms de colonnes nettement plus clairs.
### Types de jointures
Jusqu'à présent nous avons utilisé la fonction `left_join`, mais il existe plusieurs types de jointures.
Partons de deux tables d'exemple, `personnes` et `voitures` :
```{r}
personnes <- tibble(nom = c("Sylvie", "Sylvie", "Monique", "Gunter", "Rayan", "Rayan"),
voiture = c("Twingo", "Ferrari", "Scenic", "Lada", "Twingo", "Clio"))
```
```{r, echo=FALSE, eval=TRUE}
kable(personnes)
```
```{r}
voitures <- tibble(voiture = c("Twingo", "Ferrari", "Clio", "Lada", "208"),
vitesse = c("140", "280", "160", "85", "160"))
```
```{r, echo=FALSE, eval=TRUE}
kable(voitures)
```
#### left_join
Si on fait un `left_join` de `voitures` sur `personnes` :
```{r, echo=TRUE, eval=FALSE}
left_join(personnes, voitures)
```
```{r, echo=FALSE, eval=TRUE}
kable(left_join(personnes, voitures))
```
On voit que chaque ligne de `personnes` est bien présente, et qu'on lui a ajouté une ligne de `voitures` correspondante si elle existe. Dans le cas du `Scenic`, il n'y a avait pas de ligne dans `voitures`, donc `vitesse` a été mise à `NA`. Dans le cas de `208`, présente dans `voitures` mais pas dans `personnes`, la ligne n'apparaît pas.
Si on fait un `left_join` cette fois de `personnes` sur `voitures`, c'est l'inverse :
```{r, echo=TRUE, eval=FALSE}
left_join(voitures, personnes)
```
```{r, echo=FALSE, eval=TRUE}
kable(left_join(voitures, personnes))
```
La ligne `208` est là, mais `nom` est à `NA`. Par contre `Monique` est absente. Et on remarquera que la ligne `Twingo`, présente deux fois dans `personnes`, a été dupliquée pour être associée aux deux lignes de données de `Sylvie` et `Rayan`.
En résumé, quand on fait un `left_join(x, y)`, toutes les lignes de `x` sont présentes, et dupliquées si nécessaire quand elles apparaissent plusieurs fois dans `y`. Les lignes de `y` non présentes dans `x` disparaissent. Les lignes de `x` non présentes dans `y` se voient attribuer des `NA` pour les nouvelles colonnes.
Intuitivement, on pourrait considérer que `left_join(x, y)` signifie "ramener l'information de la table `y` sur la table `x`".
En général, `left_join` sera le type de jointures le plus fréquemment utilisé.
#### right_join
La jointure `right_join` est l'exacte symétrique de `left_join`, c'est-à dire que `right_join(x, y)` est équivalent à `left_join(y, x)` :
```{r, echo=TRUE, eval=FALSE}
right_join(personnes, voitures)
```
```{r, echo=FALSE, eval=TRUE}
kable(right_join(personnes, voitures))
```
#### inner_join
Dans le cas de `inner_join(x, y)`, seules les lignes présentes à la fois dans `x` et `y` sont conservées (et si nécessaire dupliquées) dans la table résultat :
```{r, echo=TRUE, eval=FALSE}
inner_join(personnes, voitures)
```
```{r, echo=FALSE, eval=TRUE}
kable(inner_join(personnes, voitures))
```
Ici la ligne `208` est absente, ainsi que la ligne `Monique`, qui dans le cas d'un `left_join` avait été conservée et s'était vue attribuer une `vitesse` à `NA`.
#### full_join
Dans le cas de `full_join(x, y)`, toutes les lignes de `x` et toutes les lignes de `y` sont conservées (avec des `NA` ajoutés si nécessaire) même si elles sont absentes de l'autre table :
```{r, echo=TRUE, eval=FALSE}
full_join(personnes, voitures)
```
```{r, echo=FALSE, eval=TRUE}
kable(full_join(personnes, voitures))
```
#### semi_join et anti_join
`semi_join` et `anti_join` sont des jointures *filtrantes*, c'est-à-dire qu'elles sélectionnent les lignes de `x` sans ajouter les colonnes de `y`.
Ainsi, `semi_join` ne conservera que les lignes de `x` pour lesquelles une ligne de `y` existe également, et supprimera les autres. Dans notre exemple, la ligne `Monique` est donc supprimée :
```{r, echo=TRUE, eval=FALSE}
semi_join(personnes, voitures)
```
```{r, echo=FALSE, eval=TRUE}
kable(semi_join(personnes, voitures))
```
Un `anti_join` fait l'inverse, il ne conserve que les lignes de `x` absentes de `y`. Dans notre exemple, on ne garde donc que la ligne `Monique` :
```{r, echo=TRUE, eval=FALSE}
anti_join(personnes, voitures)
```
```{r, echo=FALSE, eval=TRUE}
kable(anti_join(personnes, voitures))
```
## 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](http://r4ds.had.co.nz/transform.html) pour les manipulations
- [Relational data](http://r4ds.had.co.nz/relational-data.html) pour les tables multiples
Le [site de l'extension](http://dplyr.tidyverse.org/) comprend une [liste des fonctions](http://dplyr.tidyverse.org/reference/index.html) et les pages d'aide associées, mais aussi une [introduction](http://dplyr.tidyverse.org/articles/dplyr.html) au package et plusieurs articles dont un spécifiquement sur les [jointures](http://dplyr.tidyverse.org/articles/two-table.html).
Enfin, 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*.
## Exercices
On commence par charger les extensions et les données nécessaires.
```{r echo=TRUE, message=FALSE, warning=FALSE}
library(tidyverse)
library(nycflights13)
data(flights)
data(airports)
data(airlines)
```
### Les verbes de base de `dplyr`
**Exercice 1.1**
Sélectionner les lignes 100 à 105 du tableau des vols (`flights`).
```{r echo=FALSE}
slice(flights, 100:105)
```
<div class="solution">
\iffalse
```{r eval=FALSE}
slice(flights, 100:105)
```
\fi
</div>
**Exercice 1.2**
Sélectionnez les vols du mois de juillet (variable `month`).
```{r echo = FALSE}
filter(flights, month == 7)
```
<div class="solution">
\iffalse
```{r eval=FALSE}
filter(flights, month == 7)
```
\fi
</div>
Sélectionnez les vols avec un retard à l'arrivée (variable `arr_delay`) compris entre 5 et 15 minutes.
```{r echo = FALSE}
filter(flights, arr_delay >= 5 & arr_delay <= 15)
```
<div class="solution">
\iffalse
```{r eval=FALSE}
filter(flights, arr_delay >= 5 & arr_delay <= 15)
```
\fi
</div>
Sélectionnez les vols des compagnies Delta, United et American (codes `DL`, `UA` et `AA`).
```{r echo=FALSE}
filter(flights, carrier %in% c("AA", "DL", "UA"))
```
<div class="solution">
\iffalse
```{r eval=FALSE}
filter(flights, carrier %in% c("DL", "UA", "AA"))
```
\fi
</div>