-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path04-Importation-Transformation.Rmd
executable file
·1106 lines (774 loc) · 83.7 KB
/
04-Importation-Transformation.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
# Traitement des données I {#import}
```{r setup, include=FALSE, echo=FALSE, message=FALSE, results='hide'}
SciViews::R
```
##### Objectifs {.unnumbered}
- Pouvoir importer des données depuis différents formats et différentes sources en utilisant la fonction `read()` de `SciViews::R`.
- Appréhender les types de variables et l'importance de les encoder convenablement dans R.
- Être capable de convertir des variables d'un type à l'autre, y compris par l'utilisation du découpage en classes pour passer de variable quantitative à qualitative.
- Comprendre comment remanier des variables, filtrer un tableau et le résumer pour en extraire l'information importante.
##### Prérequis {.unnumbered}
Le contenu du module \@ref(introvisu) doit être parfaitement maîtrisé. Il est également souhaitable, mais pas indispensable, de comprendre comment réaliser des graphiques dans R pour pouvoir comprendre le contenu de ce module.
## Importation des données
Il est possible d'encoder de très petits jeux de données directement dans R. La fonction `dtx_rows()` permet de le faire facilement. Notez que les noms des colonnes du tableau sont à rentrer sous forme de **formules** (`~var`), que chaque entrée est séparée par une virgule, et que les chaînes de caractères sont entourées de guillemets. Les espaces sont optionnels et peuvent être utilisés pour aligner les données afin que le tout soit plus lisible. Des commentaires peuvent être utilisés éventuellement en fin de ligne (un dièse `#` suivi du commentaire).
```{r}
small_dataset <- dtx_rows(
~treatment, ~dose, ~response,
"control", 0.5, 18.35,
"control", 1.0, 26.43, # This value needs to be double-checked
"control", 2.0, 51.08,
"test" , 0.5, 10.29,
"test" , 1.0, 19.92,
"test" , 2.0, 41.06)
# Print the table
small_dataset
```
Dans la plupart des cas, vous utiliserez ou collecterez des données stockées dans des formats divers : feuilles Excel, fichiers CSV ("comma-separated-values", un format standard d'encodage d'un tableau de données sous forme textuelle), formats spécifiques à divers logiciels statistiques comme SAS, Stata, Systat... Ces données peuvent être sur un disque local ou disponibles depuis un lien URL sur le net[^04-importation-transformation-1]. De nombreuses fonctions existent dans R pour importer toutes ces données. La fonction `read()` du package {data.io} est l'une des plus simples et conviviales d'entre-elles. Vous l'avez déjà utilisée, mais reprenons un exemple pour en discuter les détails.
[^04-importation-transformation-1]: R permet également d'interroger des bases de données spécialisées, mais nous n'aborderons ce sujet spécifique qu'au cours de Science des Données Biologiques II en dernière année de Bachelier.
```{r, warning=FALSE}
biometry <- read("biometry", package = "BioDataScience", lang = "fr")
tabularise$headtail(biometry)
```
Le jeu de données `biometry` est disponible dans le package R {BioDataScience} indiqué dans l'argument `package =`. Dans ce cas, il ne faut pas spécifier de chemin d'accès au fichier : R sait où le trouver tout seul. Il est également spécifié ici que la langue souhaitée est le français avec l'argument `lang = "fr"`. Le résultat de l'importation est assigné à la variable `biometry` (mais elle pourrait tout aussi bien porter un autre nom). Pour finir, visualisez le résultat de l'importation, ici en faisant un tableau des premières et dernières lignes avec `tabularise$headtail()`.
```{block2, type='note'}
**Visualisez toujours votre tableau de données juste après l'importation pour vérifier que l'opération s'est bien déroulée.** Vérifiez que les différentes colonnes ont été importées au bon format. Vous pouvez ouvrir l'onglet **Environnement** de RStudio et cliquer sur la petite icône bleue devant le nom de l'objet pour voir de quoi il est composé. *En particulier*,
- Les données numériques sont-elle bien comprises par R comme des nombres (`<num>`, `<dbl>` ou `<int>`) ?
- Les variables qualitatives ou semi-quantitatives sont parfois importées comme chaînes de caractères (`<chr>`) et doivent éventuellement être converties en variables de type **facteur** à l'aide de `as.factor()` ou **facteur ordonné** avec `as.ordered()`, voir plus loin.
L'impression du tableau de données est une façon de voir cela, mais il y en a bien d'autres : essayez `View(biometry)`, `glimpse(biometry)`, `str(biometry)`, `skimr::skim(biometry)`.
```
Avant d'importer vos données dans R, vous devez vous poser les deux questions suivantes :
- Où ces données sont-elles stockées ?
Vous venez d'importer des données depuis un package R. Vous pouvez également les lire depuis un fichier sur le disque ou en utilisant une URL pour les récupérer depuis le Web. Tous ces cas sont gérés par `read()` qui unifie donc de manière simple vos accès aux données.
- Quel est le format de vos données ?
Souvent ce format est renseigné par l'**extension** du fichier. Par exemple **.xlsx** pour un Microsoft Excel ou **.csv** pour du "comma-separated-value". Attention ! L'extension du fichier est cachée sous Windows, et parfois sous macOS. Visualisez vos fichiers dans l'onglet **Files** dans RStudio pour voir leurs noms complets avec les extensions. Pour l'instant, `read()` supporte `r NROW(getOption("read_write"))` formats de fichiers différents, mais cette liste est amenée à s'agrandir à l'avenir. Pour découvrir les formats supportés, et les fonctions d'importation spécifiques appelées à chaque fois, utilisez :
```{r}
getOption("read_write")
```
Par la suite, vous allez apprendre à importer vos données depuis différentes sources.
### Données sur le disque
Lorsque l'extension du fichier reflète le format des données, il vous suffit juste d'indiquer le chemin d'accès au fichier dans `read()`, en utilisant préférentiellement un chemin relatif. La plupart du temps, cela suffit pour importer correctement les données.
```{block, type='warning'}
**Très important :** le chemin d'accès à votre fichier peut s'écrire de manière absolue (chemin partant de la racine d'un disque sous Windows ou de la hiérarchie de dossiers sous macOS ou Linux) ou bien de manière relative (par rapport au dossier courant, la plupart du temps, le dossier du projet dans RStudio). **Vous devez autant que possible employer des chemins relatifs** pour que votre projet soit **portable**. Si vous avez du mal à déterminer le chemin relatif par rapport à vos données, le snippet `filerelchoose` vous sera très utile :
1. Assurez-vous que le chemin actif dans l'onglet **Console** est (A) le dossier racine de votre projet RStudio, ou si vous travaillez dans un fichier Quarto ou R Markdown, (B) le même que le répertoire contenant le fichier édité. Pour cela, utilisez l'entrée de menu RStudio `Session -> Définir le répertoire de travail -> Vers le répertoire du projet` (situation A) ou `Session -> Définir le répertoire de travail -> Vers l'emplacement du fichier source` (situation B).
2. Utilisez le snippet `filerelchoose` que vous activez dans une zone de code R (dans un script R, ou à l'intérieur d'un chunk dans un document Quarto/R Markdown). Entrez `file`, attendez que le menu contextuel de complétion apparaisse, sélectionnez `filerelchoose` dans la liste et tapez `Entrée`. Une boite de dialogue de sélection de fichier apparaît. Sélectionnez le fichier qui vous intéresse et ... `file` est **remplacé par le chemin relatif vers votre fichier** dans l'éditeur.
```
Les explications détaillées concernant l'organisation de vos projets dans RStudio pour qu'ils soient portables, ainsi que la gestion des chemins d'accès aux fichiers et les chemins relatifs sont détaillés dans l'annexe \@ref(prise), à la section \@ref(rs-projet). **C'est le moment de vérifier que vous avez bien compris et assimilé son contenu.**
##### À vous de jouer ! {.unnumbered}
`r h5p(38, height = 400, toc = "Chemins relatifs pour les fichiers")`
##### Pièges et astuces {.unnumbered}
- Si l'extension est incorrecte, vous pouvez forcer un format de fichier particulier à l'importation en l'indiquant dans l'appel à `read()` comme `read$<ext>()`. Par exemple, pour forcer l'importation d'un fichier de type "comma-separated-values" pour un fichier qui se nommerait `my_data.txt` et qui se situe dans le répertoire courant, vous écrirez `read$csv("my_data.txt")`.
- Si les données ne sont pas importées correctement, cela signifie que les arguments d'importation par défaut ne sont pas adaptés. Les arguments à spécifier sont différents d'un format à l'autre. Voyez d'abord la fonction appelée en interne par `read()`dans le tableau obtenu grâce à `getOption("read_write")`. Par exemple, pour un fichier `xlsx`, il s'agit de la fonction `readxl::read_excel()` qui est utilisée. Ensuite, voyez l'aide de cette dernière fonction pour en découvrir les différents arguments (`?readxl::read_excel`). Là, vous pourrez découvrir l'argument `sheet =` qui indique la feuille à importer depuis le fichier (première feuille par défaut), ou `range =` qui indique la plage de données dans la feuille à utiliser (par défaut, depuis la cellule A1 en haut à gauche jusqu'à la fin du tableau). Donc, si votre fichier `my_data.xlsx` contient les feuilles `sheet1`, `sheet2` et `sheet3`, et que les données qui vous intéressent sont dans la plage `C5:E34` de `sheet2`, vous pourrez écrire : `read("my_data.xlsx", sheet = "sheet2", range = "C5:E34")`.
### Données depuis Internet
Il existe différents logiciels qui permettent d'éditer des jeux de données en ligne et de les partager sur le Net. [Google Sheets](https://www.google.com/intl/fr_BE/sheets/about/) est l'un d'entre eux, tout comme [Excel Online](https://office.live.com/start/Excel.aspx). Des stockages spécifiques pour les données scientifiques existent aussi comme [Figshare](https://figshare.com) ou [Zenodo](https://zenodo.org). Ces sites permettent de partager facilement des jeux de données sur Internet.
```{block2, type='info'}
La science est de plus en plus ouverte, et les pratiques de **données ouvertes** (*Open Data*) de plus en plus fréquentes et même imposées par des programmes de recherche comme les [programmes européens](https://europa.eu/european-union/documents-publications/open-data_fr) ou le [FNRS](https://www.recherchescientifique.be/index.php?id=1628) en Belgique. Vous serez donc certainement amenés à accéder à des données depuis des dépôts spécialisés sur Internet.
Concentrez-vous sur les outils spécifiques à la gestion de ce type de données. il s'agit, en effet, d'une compétence clé qu'un bon scientifique des données se doit de maîtriser parfaitement. En recherchant à chaque fois la meilleure façon d'accéder à des données sur Internet, vous développerez cette compétence progressivement par la pratique... et **vous pourrez faire valoir un atout encore rare mais apprécié lors d'un entretien d'embauche plus tard.**
```
Voici un exemple de feuille de données Google Sheets : <https://docs.google.com/spreadsheets/d/1iEuGrMk4IcCkq7gMNzy04DkSaPeWH35Psb0E56KEQMw>. Il est possible d'importer ce genre de données **directement** dans R, mais il faut d'abord déterminer l'[URL à utiliser pour obtenir les données](https://www.labnol.org/internet/direct-links-for-google-drive/28356/) dans un format reconnu. Dans le cas de Google Sheets, il suffit d'indiquer que l'on souhaite exporter les données au format CSV en rajoutant `/export?format=csv` à la fin de l'URL.
Cette URL est très longue. Elle est peu pratique et par ailleurs, elle a toujours la même structure : `"https://docs.google.com/spreadsheets/d/{id}/export?format=csv"` avec `{id}` qui est l'identifiant unique de la feuille Google Sheets (ici `1iEuGrMk4IcCkq7gMNzy04DkSaPeWH35Psb0E56KEQMw`). Vous pouvez indiquer explicitement ceci dans votre code et profiter des capacités de remplacement de texte dans des chaînes de caractères de la fonction `glue::glue()` pour effectuer un travail impeccable.
```{r}
googlesheets_as_csv <- "https://docs.google.com/spreadsheets/d/{id}/export?format=csv"
coral_data_id <- "1iEuGrMk4IcCkq7gMNzy04DkSaPeWH35Psb0E56KEQMw"
(coral_url <- glue::glue(googlesheets_as_csv, id = coral_data_id))
```
Vous n'aurez alors plus qu'à lire les données depuis cette URL. N'oubliez pas non plus de spécifier à `read()` que les données sont à lire au format CSV en utilisant `read$csv()` :
```{r, warning=FALSE}
coral <- read$csv(coral_url)
tabularise$headtail(coral)
```
Lorsque vous travaillez sur des données issues d'une source externe, et donc susceptibles d'être modifiées ou même pire, de disparaître. Il est préférable d'enregistrer une **copie locale** de ces données dans votre projet (dans le sous-dossier `data` de préférence). Si vous voulez enregistrer une copie locale du fichier **non modifié** téléchargé depuis Internet, vous l'indiquez dans l'argument `cache_file =` de `read()` (pensez à utiliser autant que possible un chemin relatif). Ainsi, le code suivant ne téléchargera le fichier depuis Internet qu'une seule fois et en sauvera automatiquement une copie dans `data/coral.csv`. Ensuite, les exécutions suivantes du même code liront les données depuis la version du jeu de données sauvé en local dans `data/coral.csv`.
```{r, eval=FALSE}
(coral <- read$csv(coral_url, cache_file = "data/coral.csv"))
```
Faites bien attention que le dossier dans lequel vous souhaitez stocker une copie de vos données doit être présent sur votre disque. Dans le cas contraire, un message d'erreur sera généré et vous ne pourrez pas importer les données.
Vous pouvez aussi vous passer de cette fonctionnalité, mais importer vos données dans un script à part qui remanie aussi ces données à la suite. Vous pouvez alors terminer ce script en écrivant les données remaniées dans un dossier local. Si vous travaillez exclusivement avec R, l'un des meilleurs formats est RDS, un format natif qui conservera toutes les caractéristiques de votre objet, y compris sa classe, et d'éventuels attributs[^04-importation-transformation-2]. Par défaut, les données seront stockées non compressées, mais vous pourrez aussi décider de compresser avec les algorithmes `"gz"` (plus rapide et répandu), `"bz2"` (intermédiaire), ou `"xz"` (le plus efficace en taux de compression, mais aussi le plus lent et gourmand en ressources CPU). Par exemple, pour enregistrer les données avec compression `"gz"`, dans le sous-dossier `data` de votre projet, vous écrirez (le dossier `data` doit être préexistant) :
[^04-importation-transformation-2]: Si vous devez aussi accéder à vos données à partir d'autres langages comme Python, Java ou C++, utilisez un format commun reconnu par les différents logiciels. Le CSV fonctionne généralement bien, mais des formats binaires plus performants sont également disponibles. Parmi ces formats "inter-langages", gardez un œil sur [Apache Arrow](https://arrow.apache.org) qui offre des possibilités très intéressantes d'intercompatibilité de performance.
```{r, eval=FALSE}
write$rds(coral, file = "data/coral.rds", compress = "gz")
```
Cette instruction est valable si elle est exécutée depuis un script R dans la fenêtre **Console** où le répertoire actif est le dossier racine du projet. Pour une instruction dans un document Quarto ou R Markdown présent dans un sous-dossier du projet, vous écrirez `file = "../data/coral.rds"`. Dans les deux cas, vous pouvez aussi utiliser `file = here:here("data", "coral.rds")` qui fonctionnera dans tous les cas de figure (conseillé, donc). Cette forme est également utilisable pour `cache_file =` dans `read()`. Ensuite, vous pourrez simplement charger ces données plus loin depuis la version locale dans votre Quarto ou R Markdown comme ceci (cas d'un document Quarto dans un sous-dossier du projet, par exemple `docs`) :
```{r, eval=FALSE}
coral <- read("../data/coral.rds")
```
**Attention, ne supprimez jamais l'instruction permettant de retrouver vos données sur Internet** sous prétexte que vous avez maintenant une copie locale à disposition. C'est le lien, le fil conducteur vers les données originales. Vous pouvez soit mettre l'instruction en commentaire en ajoutant un dièse devant, soit soustraire le chunk de l'évaluation en indiquant `eval=FALSE` dans son entête. Faites-en de même avec l'instruction `write()`. Ainsi, le traitement de vos données commencera à l'instruction `read()` et vous partirez de la copie locale. Si jamais vous voulez effectuer une mise à jour depuis la source initiale, il sera toujours possible de dé-commenter les instructions, ou de passer le chunk à `eval=TRUE` temporairement (ou encore plus simplement, forcez l'exécution du chunk dans l'éditeur en cliquant sur la petite flèche verte en haut à gauche de ce chunk).
##### Pièges et astuces {.unnumbered}
- Comme il s'agit seulement d'une *copie* des données originelles, vous pouvez choisir de ne pas inclure le fichier `.rds` dans le système de gestion de version de Git. Il suffit d'ajouter une entrée `.rds` dans le fichier `.gitignore` à la racine de votre dépôt, et tous les fichiers avec cette extension seront ignorés. Notez toutefois que, si vous partagez votre projet sur GitHub, **les données locales n'y apparaîtront pas non plus.** D'une part, cela décharge le système de gestion de version, et d'autre part, les gros fichiers de données n'ont pas vraiment leur place dans GitHub. Cependant, soyez conscient que quelqu'un qui réalise un clone ou un fork de votre dépôt devra *d'abord* réimporter lui aussi localement les données avant de pouvoir travailler, ce qui implique de bien comprendre le mécanisme que vous avez mis en place. Documentez-le correctement, avec une note explicite dans le fichier `README.md`, par exemple.
- Les données originales ne sont peut-être pas présentées de la façon qui vous convient. Cela peut nécessiter un travail important de **préparation du tableau de données.** Au fur et à mesure que le ou les chunks d'importation/préparation des données augmentent en taille, ils deviennent de plus en plus gênants dans un document consacré à l'**analyse** de ces données. Si c'est le cas, vous avez deux options possibles :
1. Séparer votre Quarto/R Markdown en deux. Un premier document dédié à l'importation/préparation des données et un second qui se concentre sur l'analyse. Une bonne pratique consiste à numéroter les fichiers en tête pour qu'ils apparaissent par ordre logique lorsqu'ils sont listés par ordre alphabétique (`01import.qmd`, `02analysis.qmd`).
2. Effectuer le travail d'importation/préparation du tableau de données dans un script R. Dans le R Markdown, vous pouvez ajouter l'instruction (commentée ou placée dans un chunk `eval=FALSE`) pour "sourcer" ce script R afin de réimporter/retraiter vos données (ici, cas d'un fichier Quarto qui se situerait dans un sous-dossier docs) :
``` r
#source("../R/data-import.R")
```
Si le travail de préparation des données est lourd (et donc, prends beaucoup de temps) il peut être avantageux d'enregistrer localement la version **nettoyée** de vos données plutôt que la version originale. Mais alors, indiquez-le explicitement.
```{block, type='warning'}
Faites toujours la distinction entre **données brutes** et **données nettoyées**. Ne les mélangez jamais et documentez toujours de manière reproductible le processus qui mène des unes aux autres ! C'est tout aussi important que de garder un lien vers la source originale des données dans votre code et d'utiliser toujours des chemins relatifs vers vos fichiers pour une analyse portable et reproductible.
Nous vous conseillons de placer la version brute des données dans le sous-dossier `data/raw` et la version nettoyée dans le sous-dossier `data` de votre projet RStudio. Ainsi, la structure des dossiers est elle-même très suggestive.
```
##### À vous de jouer ! {.unnumbered}
`r h5p(39, height = 270, toc = "Rôle de .gitignore")`
### Données depuis un package
Les packages R comme {data.io}, {chart} ou encore {svFlow}, fournissent une série de fonctions supplémentaires. Certains d'entre eux proposent également des jeux de données. Ici aussi, `read()` permet de les récupérer, même si c'est la fonction `data()` qui est souvent utilisée à cet effet dans R. Comparons `read()` et `data()` dans le cas des données issues de packages R. Avec `data()`, vous n'assignez pas le jeu de données à un nom. Ce nom vous est **imposé** comme le nom initial du jeu de données :
```{r}
data("urchin_bio", package = "data.io")
```
Le jeu de données `urchin_bio` n'est pas véritablement chargé dans l'environnement utilisateur avec `data()`. Seulement une "promesse" de chargement (`Promise`) est enregistrée. Voyez dans l'onglet **Environnement** ce qui apparaît. Ce n'est qu'à la première utilisation du jeu de données que le tableau est véritablement chargé. Par exemple :
```{r}
head(urchin_bio)
```
Regardez à nouveau dans l'onglet **Environnement**. Ce coup-ci `urchin_bio` apparaît bien dans la section **Data** et l'icône en forme de petit tableau à la droite qui permet de le visualiser est enfin accessible.
La fonction `read()` permet de choisir librement le nom que nous souhaitons donner à notre jeu de données. Si nous voulons l'appeler `urchin` au lieu de `urchin_bio`, pas de problèmes. De plus, il est directement chargé et accessible dans l'onglet **Environnement** (en effet, si on utilise une instruction qui charge un jeu de données, c'est *très vraisemblablement* parce que l'on souhaite ensuite le manipuler depuis R, non ?)
```{r}
urchin <- read("urchin_bio", package = "data.io")
```
Nous avons déjà vu que `read()` donne accès également dans certains cas à des métadonnées (par exemple le label et les unités des jeux de données) dans différentes langues, ce que ne permet pas `data()`. Enfin, la syntaxe et la fonction utilisée sont pratiquement identiques pour charger des données depuis un fichier, depuis Internet ou depuis un package avec `read()`. C'est logique et facile à retenir. `data()` ne permet *que* de récupérer des données liées à un package R, et c'est tout ! **Pour toutes ces raisons, nous préférons utiliser ici `read()` à `data()`.**
#### Langue du jeu de données
La fonction `read()` est également capable de lire un fichier annexe permettant de rajouter des **métadonnées** (données complémentaires) à notre tableau, comme les **labels** et les **unités** des variables en différentes langues. Lorsque le jeu de données est importé avec la fonction `data()`, ces métadonnées ne sont pas employées.
```{r}
data("urchin_bio", package = "data.io")
# Visualisation des données
chart(urchin_bio, height ~ weight %col=% origin) +
geom_point()
```
Comparez ceci avec le même graphique, mais obtenu à partir de différentes versions du jeu de données `urchin_bio` importé à l'aide de `read()` avec des valeurs différentes pour l'argument `lang =`.
```{r}
urchin <- read("urchin_bio", package = "data.io") # langage par défaut défini dans SciViews::R()
urchin_en <- read("urchin_bio", package = "data.io", lang = "en")
urchin_fr <- read("urchin_bio", package = "data.io", lang = "fr")
urchin_FR <- read("urchin_bio", package = "data.io", lang = "FR")
```
Les différences dans les labels sont observables sur le graphique ci-dessous.
```{r}
a <- chart(urchin, height ~ weight %col=% origin) +
geom_point()
b <- chart(urchin_en, height ~ weight %col=% origin) +
geom_point()
c <- chart(urchin_fr, height ~ weight %col=% origin) +
geom_point()
d <- chart(urchin_FR, height ~ weight %col=% origin) +
geom_point()
combine_charts(list(a, b, c, d))
```
- `a` & `b` : l'argument `lang =` par défaut est `lang = "en"`. Il utilise les labels et unités en anglais avec les unités dans le système international.
- `c` : l'argument `lang = "fr"` utilise les labels et unités en français. Il laisse cependant les niveaux des variables facteurs en anglais (`Farm` et `Fishery`) afin d'éviter de devoir changer les instructions de manipulation des données qui feraient référence à ces niveaux.
- `d` : l'argument `lang = "FR"` ajoute les labels et unités en français. De plus, il traduit également les niveaux des variables facteurs (`Culture` et `Pêcherie`).
Il vous est conseillé d'employer l'argument `lang = "fr"` lors de vos différents travaux. La langue internationale en science est l'anglais et vous serez très certainement amené dans votre carrière scientifique à produire des documents en français et en anglais. L'utilisation de `lang = "fr"`rend le **même** code réutilisable sur la version française ou anglaise, contrairement à `lang = "FR"`. Observez les exemples ci-dessous.
```{r}
urchin_en %>.%
sfilter(., origin == "Farm") %>.%
head(.)
```
```{r}
urchin_fr %>.%
sfilter(., origin == "Farm") %>.%
head(.)
```
Pas d'adaptation nécessaire du code pour passer de `urchin_en` à `urchin_fr`.
```{r}
urchin_FR %>.%
sfilter(., origin == "Pêcherie") %>.%
head(.)
```
Le code a dû être modifié dans l'instruction `sfilter()` lors du passage à `urchin_FR` (`Farm` -\> `Pêcherie`). Bien évidemment, pour un rapport *plus formel* en français, **tout** doit être traduit en français et l'option `lang = "FR"` accompagnée d'une vérification et une adaptation éventuelle du code est à préférer dans ce cas précis.
##### À vous de jouer ! {.unnumbered}
`r h5p(40, height = 270, toc = "Importation de données depuis un package")`
## Types de variables
Lors de la réalisation de graphiques dans les modules précédents, vous avez compris que toutes les variables ne sont pas équivalentes. Certains graphiques sont plutôt destinés à des variables **qualitatives** (par exemple, graphique en barres), alors que d'autres représentent des données **quantitatives** comme le nuage de points.
```{r}
(biometry <- read("biometry", package = "BioDataScience", lang = "fr"))
```
La Figure \@ref(fig:two-boxplots) montre deux boites à moustaches parallèles différentes. Laquelle de ces deux représentations est incorrecte et pourquoi ?
```{r two-boxplots, fig.cap="Boites à moustaches parallèles de la taille (`height`) en fonction de A. une variable qualitative (`gender`) et B. une variable quantitative (`weight`) et couleur en fonction de gender`"}
a <- chart(biometry, height ~ gender %fill=% gender) +
geom_boxplot()
b <- chart(biometry, height ~ weight %fill=% gender) +
geom_boxplot()
combine_charts(list(a, b), common.legend = TRUE)
```
C'est la Figure \@ref(fig:two-boxplots)B qui est incorrecte car elle tente de représenter une variable quantitative numérique `height`sous forme de boites à moustaches parallèles en fonction d'une variable de découpage en sous-ensemble (`weight`) qui est elle-même une variable quantitative, ... alors qu'une variable qualitative telle que `gender` aurait dû être utilisée pour ce découpage (comme dans la Fig. \@ref(fig:two-boxplots)A). Dans le cas présent, R a bien voulu réaliser le graphique (avec juste un petit message d'avertissement), mais comment l'interpréter ? Dans d'autres situations, il vous renverra purement et simplement un message d'erreur.
Les jeux de données, lorsqu'ils sont bien encodés (**tableaux "cas par variables"**, en anglais on parlera de [tidy data](http://vita.had.co.nz/papers/tidy-data.html)), sont en fait un ensemble de variables en colonnes mesurées sur un ensemble d'individus en lignes. Vous avez à votre disposition plusieurs *types* de variables pour personnaliser le jeu de données. Deux catégories principales de variables existent, chacune avec deux sous-catégories :
- Les variables **quantitatives** sont issues de mesures quantitatives ou de dénombrements
- Les variables quantitatives **continues** sont représentées par des valeurs réelles (**double** dans R)
- Les variables quantitatives **discrètes** sont typiquement représentées par des entiers (**integer** dans R)
- Les variables **qualitatives** sont constituées d'un petit nombre de valeurs possibles (on parle des niveaux de la variables ou de leurs modalités)
- Les variables qualitatives **ordonnées** ont des niveaux qui peuvent être classés dans un ordre du plus petit au plus grand. elles sont typiquement représentées dans R par des objets **ordered**.
- Les variables qualitatives **non ordonnées** ont des niveaux qui ne peuvent être rangés et sont typiquement représentées par des objets **factor** en R
Il existe naturellement encore d'autres types de variables. Les dates sont représentées, par exemple, par des objets **Date**, les nombres complexes par **complex**, les données binaires par **raw**, etc.
La fonction `skimr::skim()` permet de visualiser la classe de la variable et bien plus encore. Elle fournit un résumé différent en fonction du type de la variable et propose, par exemple, un histogramme stylisé pour les variables numériques comme le montre le tableau ci-dessous.
```{r, echo=TRUE}
skimr::skim(biometry)
```
Avec une seule instruction, on obtient une quantité d'information sur notre jeu de données comme le nombre d'observations, le nombre de variables et un traitement spécifique pour chaque type de variable. Cette instruction permet de visualiser et d'appréhender le jeu de données mais ne doit généralement pas figurer tel quel dans un rapport final d'analyse (mais a toute sa place dans un bloc-notes, par exemple, où vous consignez vos différentes explorations comme dans un cahier de laboratoire).
##### À vous de jouer ! {.unnumbered}
`r h5p(41, height = 270, toc = "Types de variables")`
## Conversion de variables
Il est possible de convertir les variables seulement dans un sens : du plus détaillé au moins détaillé, c'est-à-dire, quantitatif continu -\> quantitatif discret -\> qualitatif ordonné -\> qualitatif non ordonné.
### Quantitatif continu à discret
R essaye de gommer autant que possible la distinction entre nombres **integer** et **double**, tous deux rassemblés en **numeric**. Si besoin, la conversion se fait automatiquement. En pratique, concentrez-vous essentiellement sur les objets **numeric** pour tout ce qui est quantitatif. Un nombre tel que `1` est considéré par R comme un **double** par défaut. Si vous vouliez expressément spécifier que c'est un entier, vous pouvez le faire en ajoutant un `L` majuscule derrière le nombre. Ainsi, `1L` est compris par R comme l'**entier** 1. Encore une fois, cette distinction *explicite* est rarement nécessaire dans R.
Si vous voulez arrondir des nombres, vous pouvez utiliser la fonction `round()` avec son argument `digits =` qui indique le nombre de chiffres à conserver derrière la virgule (0 par défaut, pour arrondir à l'entier le plus proche). Pour arrondir vers l'entier le plus proche vers le bas, utilisez `floor()` (plancher en anglais) et pour le plus proche vers le haut, employez `ceiling()` (plafond en anglais).
```{r}
(x <- seq(-1, 1, by = 0.1) + 0.01)
round(x)
round(x, digits = 1)
ceiling(x)
floor(x)
```
##### À vous de jouer ! {.unnumbered}
`r h5p(42, height = 270, toc = "Arrondir des nombres")`
### Quantitatif à qualitatif
Le traitement diffère selon le nombre de valeurs différentes rencontrées dans le jeu de données. Si une variable numérique contient en réalité un petit nombre de valeurs différentes, il suffit de convertir la *classe* de l'objet de **numeric** vers **factor** ou **ordered** pour obtenir une variable qualitative. Un exemple concret l'illustre ci-dessous. Si, par contre, le nombre de valeurs différentes est important (dizaines ou plus) alors il va falloir créer des regroupements. C'est le **découpage en classes** abordé plus loin.
Voici un jeu de données qui étudie l'allongement des dents chez le cobaye (cochon d'Inde) en fonction de la supplémentation alimentaire en acide ascorbique.
```{r}
tooth <- read("ToothGrowth", package = "datasets", lang = "fr")
```
Le jeu de données contient 60 observations effectuées sur des cochons d'Inde. Ces derniers reçoivent deux types de suppléments alimentaires : soit du jus d'orange (`OJ`), soit de la vitamine C (`VC`). Des lots différents reçoivent des doses différentes d'acide ascorbique par ces suppléments, soit 0.5, 1, ou 2 mg/j. Vous pouvez inspecter ces données rapidement avec la fonction `skim()`.
```{r}
skimr::skim(tooth)
```
La variable dose est encodée sous forme numérique alors que cette dernière ne contient que trois niveaux différents et devra être le plus souvent traitée comme une **variable qualitative ordonnée à trois niveaux. Vous devrez donc probablement réencoder cette variable en variable facteur.**
```{block2, type='note'}
Ce n'est pas le caractère quantitatif ou qualitatif du mécanisme sous-jacent mesuré qui détermine si la variable est quantitative ou qualitative, mais d'autres critères comme la précision avec laquelle la mesure a été effectuée. Par exemple, un anémomètre mesure la vitesse du vent sous forme de variable **quantitative** alors qu'une échelle approximative de type `vent nul`, `vent faible`, `vent moyen`, `vent fort` ou `tempête` basée sur l'observation des rides ou des vagues à la surface de la mer pourrait éventuellement convenir pour mesurer le même phénomène si une grande précision n'est pas nécessaire. Mais dans ce cas, la variable devra être traitée comme une variable **qualitative** ordonnée.
De même, un plan expérimental qui réduit volontairement les valeurs fixées dans une expérience, comme ici les doses journalières d'acide ascorbique, fera aussi basculer la variable en **qualitative**, et ce, quelle que soit la précision avec laquelle les valeurs sont mesurées par ailleurs. Un découpage en classes (voir ci-dessous) aura aussi le même effet de transformer une variable quantitative en variable qualitative ordonnée.
```
Indiquons à présent explicitement à R que la variable `dose` doit être considérée comme qualitative :
```{r}
tooth$dose <- as.factor(tooth$dose)
# Visualisation des données
skimr::skim(tooth)
```
Vous pouvez (et devez !) cependant aller encore plus loin, car la variable est en réalité qualitative **ordonnée**, et doit être représentée par un objet "facteur ordonné" (**ordered**) plutôt que **factor**. Il y a en effet, une progression dans les doses administrées. Lors de la conversion, R considère les différents niveaux par **ordre alphabétique** par défaut. Ici cela convient, mais ce n'est pas toujours le cas. Il vaut donc mieux spécifier explicitement l'*ordre* des niveaux dans l'argument optionnel `levels =`. Cela donne :
```{r}
tooth$dose <- ordered(tooth$dose, levels = c(0.5, 1, 2))
# Visualisation des données
skimr::skim(tooth)
```
Les fonctions `as.factor()` ou `factor()` et `as.ordered()` ou `ordered()` effectuent cette conversion de **character** ou **numeric** vers des objets **factor** ou **ordered**. Une variable facteur ordonnée sera alors reconnue comme telle par un ensemble de fonction dans R. Elle ne sera, de ce fait, pas traitée de la même manière qu'une variable facteur non ordonnée, ni même qu'une variable numérique. Soyez bien attentif à l'encodage correct des données dans R avant d'effectuer vos graphiques et vos analyses.
### Découpage en classes
La conversion d'une variable quantitative en une variable qualitative doit souvent passer par une réduction des niveaux en rassemblant les valeurs proches dans des **classes**. Vous avez déjà utilisé de manière implicite le découpage en classes lorsque vous avez réalisé des histogrammes. Si les histogrammes sont bi- ou multimodaux, un découpage se justifie. Par exemple, le jeu de données portant sur la biométrie humaine est typique d'un cas de distribution bimodale. En fait, ce sont des étudiants (ayant tous une vingtaine d'années) qui ont réalisé ces mesures. La plupart ont choisi de s'inclure dans l'échantillon, d'où un premier mode vers une vingtaine d'années. Ensuite, ils ont pu mesurer d'autres personnes, éventuellement dans leur entourage. Beaucoup ont demandé à leurs parents, ce qui résulte en un second mode vers la cinquantaine[^04-importation-transformation-3]. Donc, la distribution bimodale résulte plus de l'échantillonnage en lui-même que d'une réalité démographique ! Cela ne change cependant rien pour l'exercice.
[^04-importation-transformation-3]: Notez que ceci ne constitue **pas** un échantillonnage correct par rapport à la population générale du Hainaut pour plusieurs raisons. (1) toutes les tranches d'âges ne sont échantillonnées de manière équivalente pour les raisons évoquées, (2) des liens génétiques existent au sein des familles, ce qui résulte en une **non indépendance** des observations entre elles, et (3) seule une sous-population constituée de personnes fréquentant l'université et de leur entourage a été échantillonnée. Cependant, dans le cadre de l'exercice, nous accepterons ces biais, tout en étant conscients qu'ils existent.
```{r}
biometry <- read("biometry", package = "BioDataScience", lang = "fr")
chart(data = biometry, ~ age) +
geom_histogram(bins = 20) +
ylab("Effectifs")
```
Les **addins** de RStudio vont vous permettre de réaliser facilement un découpage du jeu de données en fonction de classes d'âges (bouton `Addins -> QUESTIONR -> Numeric range dividing`).
`r img("sdd1_05/addins_cut.gif")`
Vous spécifiez le découpage voulu dans une boite de dialogue sur base de l'histogramme et lorsque vous cliquez sur le bouton `Done`, le code R qui effectue ce découpage est inséré dans l'éditeur RStudio à l'endroit du curseur. La nouvelle variable facteur `age_rec` basée sur le découpage en classes sera ensuite utile pour faire ressortir de l'information supplémentaire en contrastant les individus plus jeunes et ceux plus âgés.
```{r}
# Instructions obtenues à partir de l'addins
biometry$age_rec <- cut(biometry$age, include.lowest = FALSE, right = TRUE,
breaks = c(14, 27, 90))
# Visualisation de la variable facteur obtenue
chart(biometry, formula = ~ age %fill=% age_rec) +
geom_histogram(bins = 20) +
ylab("Effectifs")
```
### Qualitatif ordonné ou non
Les données qualitatives sont souvent représentées par du texte (nom d'une couleur par exemple) et importées sous forme de chaînes de caractère (**character**) par défaut dans R à partir de la fonction `read()`. Vous devez les convertir de manière explicite à l'aide de `as.factor()`, `factor()`, `as.ordered()` ou `ordered()` par la suite. Voici un exemple :
```{r}
df <- dtx(
color = c("blue", "green", "blue", "red", "green"),
intensity = c("low", "low", "high", "mid", "high"))
df
# Conversion en factor (color) et ordered (intensity)
df$color <- factor(df$color,
levels = c("red", "green", "blue"))
df$intensity <- ordered(df$intensity,
levels = c("low", "mid", "high"))
df
# Information plus détaillée
str(df)
skimr::skim(df)
```
```{block2, type='warning'}
Les différents niveaux des variables **factor** ou **ordered** sont et doivent rester entièrement de votre responsabilité. Certains aspects anciens de R essayent de gérer cela pour vous, mais ces fonctions ou options (`StringsAsFactor =` par exemple) tendent heureusement à être remplacées par des versions moins assertives. De même, les niveaux ne sont **pas** réduits lorsque vous filtrez un tableau pour ne retenir que certains niveaux. Vous devez indiquer explicitement ensuite que vous voulez éliminer les niveaux vides du tableau avec la fonction `droplevels()`.
```
Le jeu de données `iris` contient des données relatives à trois espèces différentes (`table()` permet de compter le nombre d'observations pour chaque niveau d'une variable qualitative **factor** ou **ordered**) :
```{r}
iris <- read("iris", package = "datasets", lang = "fr")
table(iris$species)
```
Si nous restreignons le tableau aux 20 premiers individus, cela donne :
```{r}
iris20 <- iris[1:20, ]
table(iris20$species)
```
Nous voyons que le tableau réduit `iris20` ne contient des données que d'une seule espèce. Pourtant `table()` continue de lister les autres niveaux de la variable. Les niveaux connus sont aussi imprimés avec `levels()` :
```{r}
levels(iris20$species)
```
Dans le cas ici, nous souhaitons uniquement nous focaliser sur l'espèce *I. setosa*. Dès lors, l'utilisation de la fonction `droplevels()` permet de faire disparaître les autres niveaux de la variable `species`.
```{r}
iris20$species <- droplevels(iris20$species)
levels(iris20$species)
table(iris20$species)
```
## Remaniement des données
Dans le module \@ref(visu3), vous avez réalisé vos premiers remaniements de données dans le cadre des graphiques en barres. Nous ne nous sommes pas étendus sur les fonctions utilisées à cette occasion. Le **remaniement des données est une étape cruciale en analyse des données** et il faut en maîtriser au moins les principaux outils. Heureusement, il est déjà possible d'aller loin en combinant une petite dizaine d'outils simples. Les cinq principaux (les plus utilisés) dans l'approche [Tidyverse](https://www.tidyverse.org) utilisée ici sont :
- sélectionner des colonnes au sein d'un jeu de données avec `select()`/`sselect()`
- filtrer des lignes dans un jeu de données avec `filter()`/`sfilter()`
- calculer de nouvelles variables dans un jeu de données avec `mutate()`/`smutate()`
- regrouper les données au sein d'un tableau avec `group_by()`/`sgroup_by()`
- résumer les variables d'un jeu de données avec `summarise()`/`ssummarise()`
Ces outils provenant du package {dplyr} et de {collapse} et {svBase} pour les versions dont le nom commence par un "s" supplémentaire sont décrits en détails dans le [chapitre 4 de "R for Data Science (2e)"](https://r4ds.hadley.nz/data-transform.html). Nous allons nous familiariser avec ces outils en adoptant une approche pratique sur base de résolution d'exemples concrets.
```{r}
urchin <- read("urchin_bio", package = "data.io", lang = "fr")
urchin
```
### `select()`/`sselect()`
![](images/sdd1_04/select.gif)
Lors de l'utilisation de vos jeux de données, vous serez amené à réduire vos données en sous-tableau ne reprenant qu'un sous-ensemble des variables initiales. `select()` et `sselect()` effectuent cette opération[^04-importation-transformation-4] :
[^04-importation-transformation-4]: Voyez `?tidyselect::select_helpers` pour une panoplie de fonctions supplémentaires qui permettent une sélection "intelligente" des variables utilisables avec `select()`.
La fonction `select()` est une fonction du "tidyverse" (nous l'appellerons désormais une **fonction tidy**) qui a un comportement particulier dans R. Une fonction relativement équivalente, mais "non-tidy" est `sselect()`. Le préfixe "s" est là pour indiquer que c'est une fonction "speedy". Ces fonctions sont similaire à leur équivalents tidy, mais généralement plus rapides. Les fonctions tidy, ont cependant d'autres qualités. Notamment, elles fonctionneront aussi en grande partie sur des bases de données. Ces deux fonctions sont souvent interchangeables, mais pas toujours. **Dans `SciViews::R`, il est très important de faire la distinction entre les deux types de fonctions et leurs particularités.**
#### Fonctions "speedy"
Pour lister les fonctions speedy disponibles, vous pouvez faire :
```{r}
list_speedy_functions()
```
Vous pouvez constater que le nom de toutes ces fonctions est préfixé d'un "s". En fait, elles sont l'équivalent de fonctions du même nom, mais sans le préfixe "s" qui sont définies dans le tidyverse, voir les fonctions "tidy" ci-dessous. Ces fonctions speedy sont rendues disponibles dans `SciViews::R` afin d'accélérer le calcul sur de gros jeux de données. Vous ne devez pas prendre de précautions particulières pour les utiliser, si ce n'est de vous rappeler de **ne pas mélanger les fonctions speedy et tidy dans une même instruction R.**
Par exemple, si vous voulez utiliser `sselect` pour extraire du tableau `urchin` un sous-tableau qui ne reprend que les variables (= colonnes) `origin`, `height` et `skeleton`, vous utiliserez :
```{r}
urchin2 <- sselect(urchin, origin, height, skeleton)
head(urchin2)
```
Vous voyez que vous obtenez ici une `data.table` qui ne contient plus que trois colonnes.
À noter que la façon de sélectionner des colonnes dans un **data.frame** en R de base se fait différemment. R de base utilise l'opérateur `[` dans lequel vous spécifiez les lignes à conserver d'abord, puis, séparé par une virgule, les colonnes à conserver. Si vous n'indiquez rien devant la virgule vous conservez toutes les lignes, et si vous n'indiquez rien derrière la virgule vous conservez toutes les colonnes. Donc `urchin[ , ]` conservera le tableau entier. La sélection se fait à l'aide de nombre entiers qui indiquent l'**index** des lignes ou des colonnes à conserver. Si ce sont des nombres négatifs, ces lignes ou colonnes sont enlevées. Par exemple pour conserver les colonnes 1 et 3 de `urchin`, on écrira en R de base :
```{r}
urchin2 <- urchin[, c(1, 3)]
head(urchin2)
```
Au contraire, pour conserver tout sauf les colonnes 1 et 3, on écrira :
```{r}
urchin2 <- urchin[, c(-1,-3)] # ou -c(1, 3)
head(urchin2)
```
Enfin, pour être complet, vous pouvez aussi utiliser un vecteur de valeurs logiques pour sélectionner les lignes ou les colonnes à conserver.
#### Fonctions "tidy"
Pour lister les fonctions tidy disponibles, vous pouvez faire :
```{r}
list_tidy_functions()
```
Ces fonctions ont toutes un comportement commun et sont conçues pour travailler sur un tableau de données. Dans R, un tel tableau de données peut être encodé dans trois types d'objets :
- le **data.frame** est l'objet de R de base,
- le **tibble** est un data.frame particulier implémenté dans le tidyverse, et
- la **data.table** est implémentée dans le package {data.table} et permet de réaliser des remaniements de tableaux plus rapidement que les deux précédents.
Dans `SciViews::R`, l'objet **data.table** est utilisé par défaut. Les fonctions speedy sont écrites pour utiliser cet objet de manière native. Il existe une surcouche des fonctions tidy dans le package {dtplyr} qui fonctionne aussi sur des objets de classe `data.table`.
Les fonctions `select()` et `sselect()` permettent aussi de sélectionner des colonnes par leur position dans le tableau :
```{r}
urchin2 <- sselect(urchin, c(1, 4, 14))
head(urchin2)
```
Attention : la fonction `sselect()` n'est pas toujours utilisable pour remplacer la fonction `select()`. Par exemple, l'instruction `contains()` est utile pour sélectionner les variables dont le nom contient une chaîne de caractères. Mais cette forme n'est pas comprise par `sselect()`.
```{r}
urchin3 <- select(urchin, origin, contains("weight"))
head(urchin3)
```
Idem pour `ends_with()` et d'autres opérateurs du genre rassemblés dans la page d'aide `?select_helpers` :
```{r}
urchin4 <- select(urchin, ends_with("ht"))
head(urchin4)
```
### `filter()`/`sfilter()`
![](images/sdd1_04/filter.gif)
De même que toutes les colonnes d'un tableau ne sont pas forcément utiles, il est souvent nécessaire de sélectionner les lignes en fonction de critères particuliers pour restreindre l'analyse à une sous-population données, ou pour éliminer les cas qui ne correspondent pas à ce que vous voulez. `filter()` est une fonction tidy qui effectue ce travail. L'équivalent speedy est `sfilter()`. Repartons du jeu de données `urchin` simplifié à trois variables (`urchin2`).
```{r}
urchin2 <- sselect(urchin, origin, height, skeleton)
head(urchin2)
```
Si vous voulez sélectionner uniquement un niveau `"lvl"` d'une variable facteur `fact`, vous pouvez utiliser une **instruction de comparaison** "égal à" (`==`) : `fact == "lvl"`. Notez bien le **double** signe égal ici, et n'oubliez pas d'indiquer le niveau entre guillemets. De même, vous pouvez sélectionner tout **sauf** ce niveau avec l'opérateur "différent de" (`!=`). Les opérateurs "plus petit que" (`<`) ou "plus grand que" (`>`) fonctionnent sur les chaines de caractères selon une logique d'ordre alphabétique, donc, `"a" < "b"`[^04-importation-transformation-5].
[^04-importation-transformation-5]: L'ordre alphabétique qui fait également intervenir les caractères accentués diffère en fonction de la configuration du système (langue). L'état du système tel que vu par R pour le tri alphabétique est obtenu par `Sys.getlocale("LC_COLLATE")`. Dans la SciViews Box, ceci est **toujours** `"en_US.UTF-8"`, ceci afin de rendre le traitement reproductible d'un PC à l'autre, qu'il soit en anglais, français, espagnol, chinois, ou n'importe quelle autre langue.
| Comparaison | Opérateur | Exemple |
|:----------------------|:---------:|:----------------|
| Égal à | `==` | `fact == "lvl"` |
| Différent de | `!=` | `fact != "lvl"` |
| Plus grand que | `>` | `fact > "lvl"` |
| Plus grand ou égal à | `>=` | `fact >= "lvl"` |
| Plus petit que | `<` | `fact < "lvl"` |
| Plus petit ou égale à | `<=` | `fact <= "lvl"` |
Bien entendu, les comparaisons sont aussi possibles entre données numériques. Dans ce cas, vous **n'utilisez pas de guillemets**. Par exemple pour indiquer une condition telle qu'une variable numérique `x` doit être supérieure à 15, vous indiquerez `x > 15` et surtout pas `x > "15"` dans ce cas.
En version speedy, vous utiliserez `sfilter()` pour indiquer les *lignes* de votre jeu de données à conserver, la plupart du temps grâce à un test de condition. Ainsi pour conserver tous les oursins qui ne sont pas issus de la pêche, vous indiquerez :
```{r, echo=TRUE}
# Tous les oursins sauf ceux issus de la pêche
urchin_sub1 <- sfilter(urchin2, origin != "Fishery")
urchin_sub1
```
Vous pouvez aussi utiliser une variable numérique pour filtrer les données. Les comparaisons précédentes sont toujours applicables, sauf que cette fois vous faites porter la comparaison par rapport à une constante (ou par rapport à une autre variable numérique) et comme nous l'avons déjà expliqué, vous ne l'indiquez **pas** entre guillemets.
```{r}
# Oursins plus hauts que 20mm
urchin_sub2 <- sfilter(urchin2, height > 20)
urchin_sub2
```
Vous pouvez combiner différentes comparaisons avec les opérateurs "et" (`&`) et "ou" (`|`) :
```{r}
# Oursins plus hauts que 20 mm ET issus d'élevage ("Farm")
urchin_sub3 <- sfilter(urchin2, height > 20 & origin == "Farm")
urchin_sub3
```
Avec des variables facteurs composées de nombreux niveaux comme on peut en retrouver dans le jeu de données `zooplankton` du package {data.io}, vous pouvez être amené à sélectionner plusieurs niveaux. L'opérateur `%in%` permet d'indiquer que nous souhaitons garder tous les niveaux qui sont dans une liste. Il n'existe pas d'opérateur `%not_in%`, mais il suffit d'inverser le résultat en précédent l'instruction de `!` pour obtenir cet effet. Par exemple, `!letters %in% c("a", "d", "f")` conserve toutes les lettres *sauf* a, d et f. L'opérateur `!` est d'ailleurs utilisable avec toutes les comparaisons pour en inverser les effets. Ainsi, `!x == 1` est équivalent à `x != 1`.
```{r}
zooplankton <- read("zooplankton", package = "data.io", lang = "FR")
# Garde uniquement les copépodes (correspondant à 4 groupes distincts)
copepoda <- sfilter(zooplankton,
class %in% c("Calanoïde", "Cyclopoïde", "Harpacticoïde", "Poecilostomatoïde"))
sselect(copepoda, ecd:perimeter, class)
```
Enfin, la détection et l'élimination de lignes contenant des valeurs manquantes (encodées comme `NA`) est spéciale. En effet, vous ne pouvez pas écrire quelque chose comme `x == NA` car ceci se lit comme "x est égale à ... je ne sais pas quoi", ce qui renvoie à son tour `NA` pour toutes les comparaisons quelles qu'elles soient. Vous pouvez utiliser la fonction spécialement prévue pour ce test `is.na()`. Ainsi, `is.na(x)` effectue en réalité ce que vous voulez. Il peut être utilisé à l'intérieur de `filter()` ou `sfilter()`. Par exemple pour conserver toutes les lignes n'ayant pas de valeurs manquantes pour `skeleton`, vous écrirez `sfilter(urchin2, !is.na(skeleton))`. Vous noterez tout de même que ce n'est pas des plus lisibles. Il existe une fonction spécialement prévue pour débarrasser les tableaux des lignes contenant des valeurs manquantes : `drop_na()` ou `sdrop_na()`. Si vous spécifier des noms de colonnes (facultatifs), la fonction ira rechercher les valeurs manquantes uniquement dans ces colonnes-là, sinon, elle scrutera tout le tableau (mais faites très attention à ne pas utiliser inconsidérément cette fonction sans spécifier les colonnes : éliminer des individus sur base de valeurs manquantes dans des colonnes que vous n'utilisez pas ensuite est idiot).
```{r}
urchin_sub4 <- sdrop_na(urchin)
urchin_sub4
```
Pour terminer le filtrage des lignes de vos tableaux, il est utile d'être aussi capable de le faire en R de base. Nous avons déjà étudié l'opérateur `[` qui extrait un sous-ensemble d'un **data.frame** et nous avons dit que son premier argument sélectionne les lignes. Nous pouvons y indiquer aussi un vecteur de valeurs logiques issues d'une comparaison. Ainsi, l'équivalent en R de base de `urchin_sub2 <- sfilter(urchin2, height > 20)` est :
```{r}
urchin_sub2 <- urchin2[urchin2$height > 20, ]
head(urchin_sub2)
```
Notez la différence importante : en R de base, les variables du jeu de données `urchin2` ne sont pas accessibles juste avec leur nom. Il faut spécifier de manière complète "la variable `height` du jeu de données `urchin2`" qui s'écrit `urchin2$height`, sans quoi R vous dira qu'il ne connait pas l'objet `height`. Avec `filter()` ou `sfilter()`, vous pouvez utiliser directement le nom de la variable, sans spécifier le nom du jeu de données qui est connu car c'est le premier argument de la fonction.
Enfin, l'opérateur `[` permet de sélectionner des lignes **et** des colonnes en une seule opération, là ou les fonctions tidy ou speedy nécessitent un appel de `filter()`/`sfilter()` suivi d'un second appel de `select()`/`sselect()`. Donc, à partir du tableau complet `urchin`, pour sélectionner les six première lignes et les trois premières colonnes vous ferez en R de base :
```{r}
urchin_sub5 <- urchin[1:6, 1:3]
urchin_sub5
```
Dans ce cas-ci, R de base est bien plus concis que les fonctions tidy ou speedy. Pour obtenir la même chose, vous devez écrire :
```{r}
sfilter(urchin, 1:6) %>.%
sselect(., 1:3) ->
urchin_sub5
urchin_sub5
```
##### À vous de jouer ! {.unnumbered}
`r h5p(44, height = 270, toc = "Filtrer un tableau")`
### `mutate()`/`smutate()`
![](images/sdd1_04/mutate.gif)
La fonction tidy `mutate()` permet de calculer de nouvelles variables (si le nom fourni n'existe pas encore dans le jeu de donnée) ou écrase les variables existantes de même nom. La fonction speedy équivalente est `smutate()`. Repartons du jeu de données `urchin`. Pour calculer de nouvelles variables, vous pouvez employer :
- les opérateurs arithmétiques :
- addition : `+`
- soustraction : `-`
- multiplication : `*`
- division : `/`
- exposant : `^`
- modulo (reste lors d'une division entière) : `%%`
- division entière : `%/%`
```{r, echo=TRUE}
urchin <- smutate(urchin,
sum_skel = lantern + spines + test,
ratio = sum_skel / skeleton,
skeleton2 = skeleton^2)
sselect(urchin, skeleton:spines, sum_skel:skeleton2)
```
- les fonctions mathématiques :
- `ln()` ou `log()` (logarithme népérien), `lg()` ou `log10()` (logarithme en base 10)
- `ln1p()` ou `log1p()` (logarithme népérien de x + 1), ou `lg1p()` (logarithme en base 10 de x + 1)
- `exp()` (exponentielle, e^x^) et `expm1()` (e^x^ - 1)
- `sqrt()` (racine carrée)
- `sin()`, `cos()`, `tan()`
- ...
```{r, echo=TRUE}
urchin <- smutate(urchin,
skeleton_log = log(skeleton),
skeleton_sqrt = sqrt(skeleton))
sselect(urchin, skeleton, skeleton_log, skeleton_sqrt)
```
La fonction tidy `transmute()` ou `stransmute()` effectue la même opération, mais en plus, elle laisse tomber les variables d'origine pour ne garder *que* les nouvelles variables calculées.
En R de base, le calcul d'une nouvelle variable se fait comme ceci :
```{r, echo=TRUE}
urchin$log_skeleton <- log(urchin$skeleton)
sselect(urchin, skeleton, log_skeleton)
```
Remarquez encore une fois que vous devez qualifier complètement le nom de la variable à calculer, en indiquant le nom du jeu de données et le nom de la variable comme `urchin$skeleton`. Si vous voulez éliminer une variable du jeu de données en R de base, vous lui assignez `NULL` :
```{r, echo=TRUE}
urchin$log_skeleton <- NULL # Efface la variable log_skeleton dans urchin
```
Selon le contexte, la version tidy/speedy ou la version R de base sera la plus pratique.
##### À vous de jouer ! {.unnumbered}
`r h5p(43, height = 270, toc = "Calculer une nouvelle variable")`
### `group_by()`/`sgroup_by()`
![](images/sdd1_04/group_by.gif)
La fonction tidy `group_by()` ou la fonction speedy `sgroup_by()` ne change rien dans le tableau lui-même, mais ajoute une annotation qui indique que les calculs ultérieurs devront être effectués sur des sous-ensembles du tableau en parallèle. Ceci est surtout utile avec `summarise()`/`ssummarise()` (voir ci-dessous), mais aussi avec `mutate()`/`smutate()` pour faire des transformations par groupes. Pour annuler le regroupement, il suffit d'utiliser `ungroup()`/`sungroup()`.
À noter que toutes les fonctions qui collectent les résultats, à savoir `as_dtx()`, `collect_dtx()` et les assignations alternatives `%<-%` et `%->%` dégroupent également automatiquement les données. Ceci est voulu afin d'éviter que l'on oublie plus tard que l'objet a un regroupement enregistré et que vous ne réalisiez par la suite des calculs non voulus (par groupes alors que vous pensez travailler individu). **Il faut toujours préciser les groupes aussi proche que possible de leur utilisation, et dégrouper éventuellement à la fin lorsqu'on n'en a plus besoin.**
```{r}
urchin_by_orig <- sgroup_by(urchin, origin)
urchin_by_orig
```
Noter la ligne ci-dessus : `Grouped by: origin` qui indique quels regroupements sont actifs dans le tableau.
```{r}
# collect_dtx() ou %<-% / %->% dégroupent automatiquement
urchin_collected %<-% urchin_by_orig
urchin_collected
```
```{r}
# Excepté pour les commentaires, ces deux objets sont identiques
comment(urchin_collected) <- comment(urchin)
identical(urchin_collected, urchin)
```
### `summarise()`/`ssummarise()`
![](images/sdd1_04/summarise.gif)
Si vous voulez résumer vos données (calcul de la moyenne, médiane, etc.), vous pouvez réaliser ceci sur une variable en particulier avec les fonctions dédiées. Par exemple `mean(urchin$skeleton)` renvoie la masse moyenne de squelette pour tous les oursins (ce calcul donne `NA` dès qu'il y a des valeurs manquantes, mais l'argument `na.rm = TRUE` permet d'obtenir un résultat en ne tenant pas compte de ces données manquantes : `mean(urchin$skeleton, na.rm = TRUE)`). Cela devient vite laborieux s'il faut réitérer ce genre de calcul sur plusieurs variables du jeu de données, et assembler ensuite les résultats dans un petit tableau synthétique. D'autant plus, s'il faut séparer d'abord le jeu de données en sous-groupes pour faire ces calculs. La fonction tidy `summarise()`, ou son équivalent speedy `ssummarise()` reporte automatiquement ces calculs, en tenant compte des regroupements proposés via `group_by()`/`sgroup_by()`.
Si vous utilisez les fonctions speedy (commençant par un "s"), il est conseillé d'utiliser les fonction "fstat" (commençant par un "f") pour les calculs des moyennes, médianes, écarts types... Donc `fmean()`, `fmedian()`, `fsd()`... La combinaison des deux permet des accélérations substantielles des calculs. Cela ne vous apparaîtra pas avec les petits jeux de données utilisés au cours, mais lorsque vous traiterez des gros tableaux, le gain de vitesse peut être de dix fois, voire plus encore.
```{r, warning=FALSE}
tooth <- read("ToothGrowth", package = "datasets", lang = "fr")
tooth_summary <- ssummarise(tooth,
"moyenne" = fmean(len),
"minimum" = fmin(len),
"médiane" = fmedian(len),
"maximum" = fmax(len))
# Utiliser soit kable(), soit tabularise() pour afficher un tableau
#knitr::kable(tooth_summary, digits = 2,
# caption = "Allongement des dents chez des cochons d'Inde recevant de l'acide ascorbique.")
# Avec tabularise(), indiquer auto.labs = FALSE pour éviter une erreur !
tabularise(tooth_summary, auto.labs = FALSE)
```
Voici les mêmes calculs, mais effectués séparément pour les deux types de supplémentations alimentaires. Pour ce faire, nous allons **combiner** deux étapes en une : un `(s)group_by()` suivi d'un `(s)summarise()` (en fait, imbriqué dans ...).
```{r, warning=FALSE}
tooth_summary2 <- ssummarise(sgroup_by(tooth, supp),
"moyenne" = fmean(len),
"minimum" = fmin(len),
"médiane" = fmedian(len),
"maximum" = fmax(len))
# Avec tabularise(), indiquer auto.labs = FALSE pour éviter une erreur !
tabularise(tooth_summary2, auto.labs = FALSE)
```
##### Pièges et astuces {.unnumbered}
- Tout comme lors de réalisation d'une boite à moustaches, vous devez être particulièrement vigilant au nombre d'observation par sous-groupe. Pensez toujours à ajoutez à chaque tableau de résumé des données, le nombre d'observations par sous-groupe grâce à la fonction `fn()`.
```{r, warning=FALSE}
tooth_summary2 <- ssummarise(sgroup_by(tooth, supp),
"moyenne" = fmean(len),
"minimum" = fmin(len),
"médiane" = fmedian(len),
"maximum" = fmax(len),
"n" = fn(len))
#knitr::kable(tooth_summary2, digits = 2,
# caption = "Allongement des dents chez des cochons d'Inde en fonction du supplément jus d'orange (OJ) ou vitamine C (VC).")
tabularise(tooth_summary2, auto.labs = FALSE)
```
Si vous souhaitez connaitre le nombre d'observations **non manquantes** dans votre jeu de données, utilisez `fnobs()` au lieu de `fn()`. Vous pouvez naturellement utiliser les deux simultanément.
```{r, warning=FALSE}
urchin_skeleton_summary <- ssummarise(sgroup_by(urchin, origin),
"moyenne" = fmean(skeleton, na.rm = TRUE),
"n" = fn(skeleton), # Nombre total de cas
"n obs" = fnobs(skeleton)) # Nombre de cas hors valeurs manquantes
#knitr::kable(urchin_skeleton_summary, digits = 2,
# caption = "Moyenne, nombre de cas et nombre d'observations hors valeurs manquantes pour la masse du squelette (en g) d'oursins d'élevage et de pêcheries.")
tabularise(urchin_skeleton_summary, auto.labs = FALSE)
```
Dans le cas présent, le nombre d'observations utilisable est nettement inférieur au nombre total de cas dans le tableau. Cette distinction entre `fn()` et `fnobs()` est donc très importante. **Ne la perdez pas de vue.** De plus, si vous n'utilisez pas `na.rm = TRUE` dans les fonctions comme `mean()`/`fmean()`, `median()`/`fmedian()`... vous aurez des `NA` partout dans votre tableau résumé. D'un autre côté, soyez bien conscient qu'avec `na.rm = TRUE`, c'est le nombre d'observations hors valeurs manquantes qui est utilisé pour ces calculs !
Les fonctions `fn()` et `fnobs()` ne sont pas définies dans le tidyverse. Ce sont des fonctions d'une autre famille appelée dans `SciViews::R` des fonctions "fstat". Dans tidyverse, on vous fera utiliser la fonction `n()` sans argument qui est l'équivalent de `fn()`. Il n'y a pas d'équivalent direct de `fnobs()` et il faut ruser avec du code comme `sum(!is.na(skeleton))` pour obtenir ce résultat, ce qui n'est pas pratique. **La fonction `n()` n"est pas utilisable avec les fonctions speedy, et d'une manière générale, préférez-lui `fn()` qui est plus logique et qui fonctionne partout.**
- `summarise()` calcule ses variables dans le même environnement que le tableau de départ. Donc, si vous utiliser des noms de colonnes qui existent déjà, elles seraient écrasées par le résultat du calcul. Avec une **data.table**, ceci n'est pas permis (le code fonctionnera pourtant dans un autre contexte avec un **data.frame**, c'est pourquoi nous convertissons d'abord `tooth` avec `as_dtf()`). Voici un exemple concret :
```{r}
tooth_summary3 <- summarise(as_dtf(tooth),
"len" = mean(len), # Notez le même nom à gauche et à droite du = (len)
"len_sd" = sd(len))
knitr::kable(tooth_summary3, digits = 2,
caption = "Exemple de résumé des données erroné à cause de l'écrasement de la variable `len`.")
#tabularise(tooth_summary3, auto.labs = FALSE)
```
L'écart type est... `NA` ??? Pour comprendre ce qui s'est passé, il faut lire la transformation réalisée par `summarise()` ligne après ligne :
- la moyenne de la variable `len` est placée dans ... `len`. Donc ici, **nous écrasons la variable initiale** de 60 observations par un nombre unique : la moyenne,
- l'écart type de `len` est ensuite calculé. Attendez une minute, de quel `len` s'agit-il ici ? Et bien la dernière calculée, soit celle qui contient **une seule valeur**, la moyenne. Or, `sd()` nécessite au moins **deux** valeurs pour que l'écart type puisse être calculé, sinon, `NA` est renvoyé. C'est encore heureux ici, car nous aurions pu faire un calcul qui renvoie un résultat, ... mais qui n'est pas celui qu'on espère !
Conclusion : ne nommez **jamais** vos variables créées avec `summarise()` exactement comme les variables de votre tableau en entrée.
Toutefois, `ssummarise()` fonctionne différemment et n'a pas de problèmes dans ce cas-là :
```{r}
tooth_summary4 <- ssummarise(tooth,
"len" = fmean(len), # Notez le même nom à gauche et à droite du = (len)
"len_sd" = fsd(len))
knitr::kable(tooth_summary4, digits = 2,
caption = "Exemple de résumé des données correct avec `fsummarise()`.")
#tabularise(tooth_summary4, auto.labs = FALSE)
```
Faites bien attention : les couples de fonctions avec et sans "s" comme `select()`/`sselect()`, `mutate()`/`smutate()` ou `summarise()`/`ssummarise()` se ressemblent très fort au niveau de leur usage. On pourrait donc être tenté de croire qu'elles sont parfaitement interchangeables. La plupart du temps, c'est le cas. Cependant, elles fonctionnement radicalement différemment en interne et des différences existent, sont connues, et ne sont *pas* des bugs. Vérifiez toujours votre code !
#### Résumé avec les fonctions "fstat"
R propose de base une série de fonctions qui calculent des descripteurs statistiques classiques tels que la moyenne `mean()`, la médiane `median()`, la variance `var()`, etc. Toutes ces fonctions ont un argument `na.rm` qui prend la valeur `FALSE` par défaut. S'il y a une valeur manquante ou plus, le calcul renverra alors `NA`.
```{r}
vec <- c(5, 3, 8, NA, 4)
mean(vec)
```
Vous devez indiquer `na.rm = TRUE` si vous voulez quand même calculer la moyenne sur ce vecteur `vec` en utilisant uniquement les observations (non manquantes) :
```{r}
mean(vec, na.rm = TRUE)
```
Ces fonctions ne sont pas prévues pour travailler sur des **tableaux entiers**.
```{r, warning=TRUE}
mean(iris)
```
La fonction `mean()` (et les autres fonctions équivalentes) ne sont pas prévues pour travailler sur ce genre d'objet qui contient plusieurs colonnes. C'est pour cela que vous devez utiliser une construction plus complexe avec `summarise()` ou `ssummarise()`. Si vous voulez résumer les quatre variables numériques par la moyenne en fonction de `species`, vous devez faire (rappelez-vous que vous devez aussi utiliser d'autres noms que les noms de variables dans le tableau de départ) :
```{r}
ssummarise(sgroup_by(iris, species),
mean_sepal_length = mean(sepal_length, na.rm = TRUE),
mean_sepal_width = mean(sepal_width, na.rm = TRUE),
mean_petal_length = mean(petal_length, na.rm = TRUE),
mean_petal_width = mean(petal_width, na.rm = TRUE))
```
Tidyverse offre une astuce pour éviter de se répéter. Si vous devez appliquer la *même* fonction sur plusieurs variables, vous pouvez l'indiquer avec `across()` (aussi avec les fonctions speedy) comme ceci :
```{r}
ssummarise(sgroup_by(iris, species),
across(sepal_length:petal_width, fmean, na.rm = TRUE))
```
C'est déjà mieux, même si c'est moins lisible quant à ce que l'on veut obtenir comme tableau final. Dans le package {collapse}, et dans `SciViews::R` qui utilise ce package, il y a des fonctions de remplacement de `mean()`, `median()`... qui offrent plusieurs avantages. Ce sont les fonctions "fstat" que nous avons déjà utilisées. Nous allons maintenant détailler un peu plus ces fonctions.
Souvent, vous pourrez utiliser `fmean()` à la place de `mean()`, mais son comportement et ses possibilités sont très différentes :
- elle est plus rapide (appréciable pour les gros jeux de données), en fait "f", c'est pour fast !
- la valeur par défaut pour son argument `na.rm` est `TRUE`. Vous ne devez donc pas préciser `na.rm = TRUE` à tout bout de champ (mais restez bien attentif aux valeurs manquantes dans vos données !)
- elle fonctionne aussi sur des tableaux de données ne contenant que des variables numériques
- elle peut utiliser les regroupements effectués à l'aide de `sgroup_by()` (mais pas `group_by()`) et en tient compte à condition que toutes les autres variables non concernées par le regroupement soient numériques
Illustrons tout ceci :
```{r}
fmean(vec) # Pas besoin de préciser na.rm = TRUE, c'est la valeur pas défaut
```
Application directe sur un tableau, même avec regroupement :
```{r}
fmean(sgroup_by(iris, species))
```
Le tableau obtenu est très similaire à celui que nous avions calculé à l'aide de `ssummarise()` plus haut (à part que le nom des variables est conservé) et identique à celui obtenu en utilisant `across()`. Le calcul est toutefois bien plus rapide (vous ne devez pas comprendre le code ci-dessous, juste considérer que l'on détermine les performances de trois versions du même calcul : "tidy", "speedy" et "fstat") :
```{r}
iris2 <- datasets::iris
names(iris2) <- c("sepal_length", "sepal_width", "petal_length", "petal_width", "species")
bench::mark(
tidy = collect_dtf(summarise(group_by(iris2, species),
across(sepal_length:petal_width, mean, na.rm = TRUE))),
speedy = ssummarise(sgroup_by(iris2, species),
across(sepal_length:petal_width, fmean, na.rm = TRUE)),
fstat = fmean(sgroup_by(iris2, species)))
```
Examinez les colonnes **median** qui est le temps median et **mem_alloc** qui est la mémoire vive nécessaires pour faire ces calculs.
- Il a fallu 500 fois moins de temps à `fmean()` qu'à la version tidy avec `summarise()` et presque trois fois moins de temps que la version speedy avec `ssummarise()`. Vous comprenez maintenant pourquoi ces fonctions sont appelées "speedy" et "fast".
- Concernant l'utilisation de la mémoire vive, il a fallu neuf cent fois moins de mémoire vive à `fmean()` qu'à `summarise` et un petit peu moins qu'à `ssummarise()`.
**Par rapport aux petits jeux de données que nous utilisons dans le cadre de ce cours, la différence n'est pas très visible. Mais si vous travaillez plus tard avec des bien plus gros jeux de données, pensez à utiliser les fonctions speedy, ou mieux les fonctions fstat directement si vous le pouvez.**
Enfin, vous pouvez lister toutes les fonctions fstat disponibles comme ceci :
```{r}
list_fstat_functions()
```
Notez bien aussi les fonction `fn()` et `fnobs()` que nous avons vues pour énumérer les cas et les observations effectivement réalisées hors valeurs manquantes. `fna` compte les valeurs manquantes et est donc complémentaire à `fnobs()` (`fna(x) + fnobs(x) == fn(x)`) `fndistinct()` peut aussi être utile pour énumérer le nombre de valeurs différentes rencontrées (par exemple pour décider si cela est raisonnable de convertir directement une variable `numeric` ou `character` en `factor`). Vous devriez pouvoir déduire le rôle des autres fonctions via leur nom. `fvar()` calcule la variance alors que `fsd()` calcule l'écart type (*standard deviation* en anglais, d'où l'abréviation "sd") qui est la racine carrée de la variance.
##### Pour en savoir plus {.unnumbered}
- Le chapitre consacré à la [transformation des données de **R for Data Science seconde edition**](https://r4ds.hadley.nz/data-transform.html) présente le remaniement d'un tableau de données à l'aide des fonctions tidy différemment et propose des exemples et exercices complémentaires très utiles.
- La meilleure façon de se familiariser avec les "verbes" du **tidyverse** est de réaliser des transformations de données par soi-même. En cas de blocage, le site <https://stackoverflow.com> ou <https://phind.com> permet de chercher des solutions. Pour une recherche ciblée sur le langage R, précédez vos mots clés par "[R]" (R entre crochets). Par exemple, pour explorer diverses utilisations de la fonction `mutate()`, vous entrerez le texte de recherche suivant: "[R] mutate". Ne cherchez pas les fonctions speedy ou fstat, vous ne trouverez pas grand chose par contre. Adaptez les exemples trouvés avec les fonctions tidy, si nécessaire.
- N'oubliez pas les [aide-mémoires](https://posit.co/resources/cheatsheets/) de {dplyr} et de {tidyr} qui forment aussi une source d'inspiration utile pour vous guider vers les fonction (les "verbes") adéquats. Ensuite, allez voir l'aide en ligne de la fonction avec `?ma_fonction`.
## Chaînage des instructions
Le chaînage (ou *"pipe"* en anglais, prononcez "païpe") permet de combiner une suite d'instructions R. Il permet une représentation facilement lisible et compréhensible d'un traitement décomposé en plusieurs étapes simples de remaniement des données.
Différents opérateurs de chaînage existent dans R. Le [Tidyverse](https://www.tidyverse.org) a introduit un opérateur de chaînage **`%>%`** issu du package [{magrittr}](https://magrittr.tidyverse.org). Si nous sommes sensibles au clin d'œil fait ici à un artiste belge bien connu ("ceci n'est pas un pipe"), nous n'adhérons pas à ce choix pour des raisons multiples et plutôt techniques qui n'ont pas leur place dans ce document[^04-importation-transformation-6]. Nous vous présentons ici l'un des opérateurs de chaînage du package {svFlow} : **`%>.%`**. Le jeu de données sur la biométrie humaine est employé pour cette démonstration qui va comparer le remaniement d'un tableau de données avec et sans l'utilisation du chaînage.
[^04-importation-transformation-6]: Le lecteur intéressé pourra lire les différents articles suivants : [more pipes in R](http://www.win-vector.com/blog/2017/12/more-pipes-in-r/), y compris les liens qui s'y trouvent, permet de se faire une idée de la diversité des opérateurs de chaînage dans R et de leur historique. [Dot pipe](https://winvector.github.io/wrapr/articles/dot_pipe.html) présente l'opérateur `%.>%` du package {wrapr} très proche du nôtre et [in praise of syntactic sugar](http://www.win-vector.com/blog/2017/07/in-praise-of-syntactic-sugar/) explique ses avantages. Nous partageons l'idée que le "pipe de base" ne devrait pas modifier l'instruction de droite contrairement à ce que fait `%>%` de {magrittr}, et notre opérateur `%>.%` va en outre plus loin encore que `%.>%` dans la facilité de débogage du code chaîne.
```{r}
biometry <- read("biometry", package = "BioDataScience", lang = "fr")
```
Vous vous intéressez à l'indice de masse corporelle ou IMC (*BMI* en anglais) des individus de moins de 25 ans. Vous souhaitez représenter la moyenne, la médiane et le nombre d'observations de manière séparée pour les hommes et les femmes. Pour obtenir ces résultats, vous devez :
- calculer le BMI,
- filtrer le tableau pour ne retenir que les individus de moins de 25 ans,
- résumer les données afin d'obtenir la moyenne et la médiane par genre,
- afficher un tableau de données avec ces résultats.
Il est très clair ici que le traitement peut être décomposé en étapes plus simples. Cela apparaît naturellement rien que dans la description de ce qui doit être fait. Sans l'utilisation de l'opérateur de chaînage, deux approches sont possibles :
- Imbriquer les instructions les unes dans les autres (très difficile à lire et à déboguer) :
```{r}
knitr::kable(
ssummarise(
sgroup_by(
sfilter(
smutate(biometry, bmi = weight / (height/100)^2),
age <= 25),
gender),
mean = fmean(bmi),
median = fmedian(bmi),
number = fn(bmi)),
rows = NULL, digits = 1,
col = c("Genre", "Moyenne", "Médiane", "Observations"),
caption = "IMC d'hommes (M) et femmes (W) de 25 ans maximum."
)
```
- Passer par des variables intermédiaires (`biometry_25` et `biometry_tab`). Les instructions sont plus lisibles, mais les variables intermédiaires "polluent" inutilement l'environnement de travail (en tout cas, si elles ne servent plus par après) :
```{r}
biometry <- smutate(biometry, bmi = weight / (height/100)^2)
biometry_25 <- sfilter(biometry, age <= 25)
biometry_25 <- sgroup_by(biometry_25, gender)
biometry_tab <- ssummarise(biometry_25,
mean = fmean(bmi),
median = fmedian(bmi),
number = fn(bmi))
knitr::kable(biometry_tab, rows = NULL, digits = 1,
col = c("Genre", "Moyenne", "Médiane", "Observations"),
caption = "IMC d'hommes (M) et femmes (W) de 25 ans maximum.")
```
- Des trois approches, la version ci-dessous avec chaînage des opérations est la plus lisible et la plus pratique[^04-importation-transformation-7].
[^04-importation-transformation-7]: Le chaînage n'est cependant pas forcément plus facile à déboguer que la version avec variables intermédiaires. Le package {svFlow} propose la fonction `debug_flow()` à appeler directement après un plantage pour inspecter la dernière instruction qui a causé l'erreur, voir `?debug_flow`.
```{r}
biometry %>.%
smutate(., bmi = weight / (height/100)^2) %>.%
sfilter(., age <= 25) %>.%
sgroup_by(., gender) %>.%
ssummarise(.,
mean = fmean(bmi),