-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy path05-PackageR.Rmd
1179 lines (904 loc) · 47.3 KB
/
05-PackageR.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
# Package {#chap-package}
\toc{1}
Les packages de R permettent d'étendre les fonctionnalités du logiciel par du code fourni par la communauté des développeurs.
Ils sont la clé du succès de R parce qu'ils permettent de diffuser rapidement de nouvelles méthodes issues de la recherche ou d'ajouter de nouveaux outils qui peuvent devenir des standards, comme le **tidyverse**.
Il est utile de produire un package quand on a écrit des nouvelles fonctions qui forment un ensemble cohérent.
Un package à usage personnel ou limité à une équipe de travail est simple à mettre en place et le temps gagné en utilisant facilement la version à jour de chaque fonction amortit très rapidement le temps consacré à la fabrication du package.
Ce type de package a vocation à être hébergé sur GitHub.
Des packages à usage plus large, qui fournissent par exemple le code correspondant à une méthode publiée, sont placés dans le dépôt CRAN, d'où ils pourront être installés par la commande standard `install.packages()`.
CRAN effectue des vérifications poussées du code et n'accepte que les packages passant sans aucun avertissement sa batterie de tests.
Ils doivent respecter la politique[^501] du dépôt.
[^501]: https://cran.r-project.org/web/packages/policies.html
La documentation pour la création de packages est abondante.
L'ouvrage de référence est celui de @Wickham2015, à consulter en tant que référence.
L'approche utilisée ici consiste à créer un premier package très rapidement pour comprendre que la démarche est assez simple.
Il sera ensuite enrichi des éléments nécessaires à un package diffusé à d'autres utilisateurs que son concepteur: une documentation complète et des tests de bon fonctionnement notamment.
## Premier package
Cette introduction reprend les recommandations du blog *Créer un package en quelques minutes*[^502] de ThinkR.
[^502]: https://thinkr.fr/creer-package-r-quelques-minutes/
### Création
Les packages ont une organisation stricte dans une structure de fichiers et de répertoires figée.
Il est possible de créer cette structure manuellement mais des packages spécialisés peuvent s'en charger:
* **usethis** automatise la création des dossiers;
* **roxygen2** permet d'automatiser la documentation obligatoire des packages;
* **devtools** est la boîte à outils du développeur, permettant notamment de construire et tester les packages;
Les trois sont à installer en premier lieu:
```{r install_pkg, eval=FALSE}
install.packages(c("usethis", "roxygen2", "devtools"))
```
Le package à créer sera un projet RStudio.
Dans le menu des projets, sélectionner "New Project > New Directory > R package using devtools...", choisir le nom du projet et son dossier parent.
Le package s'appellera **multiple**, dans le dossier `%LOCALAPPDATA%\ProjetsR` en suivant les recommandations de la section \@ref(sec:solution-dossiers).
Le nom du package doit respecter les contraintes des noms de projets: pas de caractères spéciaux, pas d'espaces...
Il doit aussi être évocateur de l'objet du package.
Si le package doit être diffusé, toute sa documentation sera rédigée en Anglais, y compris son nom.
La structure minimale est crée:
* un fichier `DESCRIPTION` qui indique que le dossier contient un package et précise au minimum son nom;
* un fichier `NAMESPACE` qui déclare comment le package intervient dans la gestion des noms des objets de R (son contenu sera mis à jour par **roxygen2**);
* un dossier `R` qui contient le code des fonctions offertes par le package (vide à ce stade).
Le package peut être testé tout de suite: dans la fenêtre *Build* de RStudio, cliquer sur "Install and Restart" construit le package et le charge dans R, après avoir redémarré le programme pour éviter tout conflit.
Dans la fenêtre *Packages*, **multiple** est maintenant visible.
Il est chargé, mais ne contient rien.
### Première fonction
#### Fichiers
Les fonctions sont placées dans un ou plusieurs fichier `.R` dans le dossier `R`.
L'organisation de ces fichiers est libre.
Pour cet exemple, un fichier du nom de chaque fonction sera créé.
Des fichiers regroupant les fonctions similaires ou un seul fichier contenant tout le code sont des choix possibles.
Le choix fait ici est le suivant:
* un fichier qui contiendra le code commun à tout le package: `package.R`;
* un fichier commun à toutes les fonctions: `fonctions.R`.
#### Création
La première fonction, `double()`, est créée et enregistrée dans le fichier `fonctions.R`:
```{r double}
double <- function(number) {
return(2*number)
}
```
A ce stade, la fonction est interne au package et n'est pas accessible depuis l'environnement de travail.
Pour s'en persuader, construire le package (*Install and Restart*) et vérifier le bon fonctionnement de la fonction:
```{r double2, eval=FALSE}
double(2)
```
Le résultat est un vecteur composé de deux 0 parce que la fonction appelée est un homonyme du package **base** (voir sa documentation en tapant `?double`):
```{r base_double}
base::double(2)
```
Pour que la fonction de notre package soit visible, elle doit être *exportée* en la déclarant dans le fichier `NAMESPACE`.
C'est le travail de **roxygen2** qui gère en même temps la documentation de chaque fonction.
Pour l'activer, placer le curseur dans la fonction et appeler le menu "Code > Insert Roxygen Skeleton".
Des commentaires sont ajoutés avant la fonction:
```{r double_f, eval=FALSE}
#' Title
#'
#' @param number
#'
#' @return
#' @export
#'
#' @examples
double <- function(number) {
return(2*number)
}
```
Les commentaires à destination de **roxygen2** commencent par `#'`:
* la première ligne contient le titre de la fonction, c'est-à-dire un descriptif très court: son nom en général;
* la ligne suivante (séparée par un saut de ligne) peut contenir sa description (rubrique *Description* dans l'aide);
* la suivante (après un autre saut de ligne) peut contenir plus d'informations (rubrique *Details* dans l'aide);
* les arguments de la fonction sont décrits par les lignes `@param`;
* `@return` décrit le résultat de la fonction;
* `@export` déclare que la fonction est exportée: elle sera donc utilisable dans l'environnement de travail;
* des exemples peuvent être ajoutés.
La documentation doit être complétée:
```{r double_roxy, eval=FALSE}
#' double
#'
#' Double value of numbers.
#'
#' Calculate the double values of numbers.
#'
#' @param number a numeric vector.
#'
#' @return A vector of the same length as `number` containing the
#' transformed values.
#' @export
#'
#' @examples
#' double(2)
#' double(1:4)
double <- function(number) {
return(2*number)
}
```
Ne pas hésiter à s'inspirer de l'aide de fonctions existantes pour respecter les standards de R (ici: `?log`):
* penser que les fonctions sont normalement vectorielles: `number` est par défaut un vecteur, pas un scalaire;
* certains éléments commencent par une majuscule et se terminent par un point parce que ce sont des paragraphes dans le fichier d'aide;
* le titre n'a pas de point final;
* la description des paramètres ne commence pas par une majuscule.
La prise en compte des changements dans la documentation nécessitent d'appeler la fonction `roxygenize()`.
Dans la fenêtre *Build*, le menu "More > Document" permet de le faire.
Ensuite, construire le package (*Install and Restart*) et vérifier le résultat en exécutant la fonction et en affichant son aide:
```{r double_help, eval=FALSE}
double(2)
?double
```
Il est possible d'automatiser la mise à jour de la documentation à chaque construction du package par le menu "Build > Configure Build Tools...": cliquer sur "Configure" et cocher la case "Automatically reoxygenize when running Install and Restart".
C'est un choix efficace pour un petit package mais pénalisant quand le temps de mise à jour de la documentation s'allonge avec la complexité du package. La reconstruction du package est le plus souvent utilisée pour tester des modifications du code: sa rapidité est essentielle.
La documentation pour **roxygen2** supporte le format Markdown[^508].
[^508]: https://roxygen2.r-lib.org/articles/markdown.html
A ce stade, le package est fonctionnel: il contient une fonction et un début de documentation.
Il est temps de lancer une vérification de son code: dans la fenêtre *Build*, cliquer sur "Check" ou utiliser la commande `devtools::check()`.
L'opération *réoxygène* le package (met à jour sa documentation), effectue un grand nombre de tests et renvoie la liste des erreurs, avertissements et notes détectées.
L'objectif est toujours de n'avoir aucune alerte: elles doivent être traitées immédiatement.
Par exemple, le retour suivant est un avertissement sur la non-conformité de la licence déclarée:
```
> checking DESCRIPTION meta-information ... WARNING
Non-standard license specification:
`use_gpl3_license()`
Standardizable: FALSE
0 errors v | 1 warning x | 0 notes v
Erreur : R CMD check found WARNINGs
```
Pour la corriger, mettre à jour, exécuter la commande de mise à jour de la licence, en commençant par votre nom:
```{r use_gpl3_license, eval=FALSE}
options(usethis.full_name = "Eric Marcon")
usethis::use_gpl3_license()
```
La liste des licences valides est fournie par R[^503].
[^503]: https://svn.r-project.org/R/trunk/share/licenses/license.db
Après correction, relancer les tests jusqu'à la disparition des alertes.
### Contrôle de source {#sec:package-cds}
Il est temps de placer le code sous contrôle de source.
Activer le contrôle de source dans les options du projet (figure \@ref(fig:git-Project)).
Redémarrer RStudio à la demande.
Créer un dépôt sur GitHub et y pousser le dépôt local, comme expliqué dans le chapitre \@ref(chap-git).
Créer le fichier `README.md`:
```
# multiple
An R package to compute mutiple of numbers.
```
Le développement du package est ponctué par de nombreux commits à chaque modification et une publication (push) à chaque étape, validée par une incrémentation du numéro de version.
### package.R
Le fichier `package.R` est destiné à recevoir le code R et surtout les commentaires pour **roxygen2** qui concernent l'ensemble du package.
Ce fichier peut aussi être nommé `multiple-package.R`, en préfixant son nom par celui du package, par souci de compatibilité avec **usethis**.
Il peut d'ailleurs être créé sous ce nom par la commande:
```{r use_package_doc, eval=FALSE}
usethis::use_package_doc()
```
Le premier bloc de commentaire fournira l'aide du package (`?multiple`).
```
#' @keywords internal
"_PACKAGE"
```
Le mot-clé "_PACKAGE" indique que la documentation du package doit être produite.
Elle pourrait être écrite dans le bloc, avec une syntaxe identique à celle des fonctions, mais son contenu par défaut est celui du champ `Description` du fichier `DESCRIPTION`.
Le mot clé `internal` masque la documentation du package dans le sommaire de son aide.
La documentation est mise à jour par la commande `roxygen2::roxygenise()`.
Après reconstruction du package, vérifier que l'aide est est apparue: `?multiple`.
## Organisation du package
### Fichier DESCRIPTION {#sec:package-description}
Le fichier doit être complété:
```
Package: multiple
Title: Calculate multiples of numbers
Version: 0.0.0.9000
Authors@R:
person(given = "Eric",
family = "Marcon",
role = c("aut", "cre"),
email = "[email protected]",
comment = c(ORCID = "0000-0002-5249-321X"))
Description: Simple computation of multiples of numbers,
including fast algorithms for integers.
License: GPL-3
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1
```
Le nom du package est figé et ne doit pas être modifié.
Son titre doit décrire en une ligne à quoi il sert.
Le titre est affiché dans la fenêtre *Packages* à côté des noms des packages.
La version doit respecter les conventions:
* Le premier nombre est la version majeure, 0 tant que le package n'est pas stable puis 1.
La version majeure ne change que si le package n'est plus compatible avec ses versions précédentes, ce qui oblige les utilisateurs à modifier leur code.
* Le deuxième est la version mineure, incrémentée quand des fonctionnalités nouvelles sont ajoutées.
* Le troisième est la version de correction: 0 à l'origine, incrémentée à chaque correction de code sans nouvelle fonctionnalité.
* Le quatrième est réservé au développement, et commence à 9000.
Il est incrémenté à chaque version instable et disparaît quand une nouvelle version stable (*release*) est produite.
Exemple: une correction de bug sur la version 1.3.0 produit la version 1.3.1.
Les versions de développement suivantes (instables, non destinées à l'usage en production) sont 1.3.1.9000 puis 1.3.1.9001, etc.
Le numéro de version doit être mis à jour à chaque fois que le package est poussé sur GitHub.
Quand le développement est stabilisé, la nouvelle version, destinée à être utilisée en production, est 1.3.2 si elle n'apporte pas de nouvelle fonctionnalité ou 1.4.0 dans le cas contraire.
La description des auteurs est assez lourde mais simple à comprendre.
Les identifiants Orcid des auteurs académiques peuvent être utilisés.
Si le package a plusieurs auteurs, ils sont placés dans une fonction `c()`: `c(person(...), person(...))` pour deux auteurs.
Dans ce cas, il faut préciser le rôle de chacun:
* "cre" pour le créateur du package;
* "aut" pour un auteur parmi les autres;
* "ctb" pour un contributeur, qui peut avoir signalé un bug ou fourni un peu de code.
La description du package en un paragraphe permet de donner plus d'informations.
La licence précise la façon dont le package peut être utilisé et modifié.
GPL-3 est une bonne valeur par défaut, mais d'autres choix sont possibles[^504].
[^504]: https://r-pkgs.org/description.html#description-license
L'option `LazyData` signifie que les données d'exemples fournies avec le package peuvent être utilisées sans les appeler au préalable par la fonction `data()`: c'est le standard actuel.
Enfin, les deux dernières lignes sont gérées par **roxygen2**.
### Fichier NEWS.md
Le fichier `NEWS.md` contient l'historique du package.
Les nouvelles versions sont ajoutées en haut du fichier.
Créer une première version du fichier:
```
# multiple 0.0.0.9000
## New features
* Initial version of the package
```
Les titres de premier niveau doivent contenir le nom du package et sa version.
Les titres de niveau 2 sont libres, mais contiennent en général des rubriques comme "New features" et "Bug Fixes".
Pour ne pas multiplier les versions décrites, il est conseillé de modifier la version en cours et de compléter la documentation jusqu'au changement de version de correction (troisième nombre).
Ensuite, l'entrée correspondant à cette version reste figée et une nouvelle entrée est ajoutée.
## Vignette
Une vignette est indispensable pour documenter correctement le package:
```{r use_vignette, eval=FALSE}
usethis::use_vignette("multiple")
```
Le fichier `multiple.Rmd` est créé dans le dossier `vignettes`.
Ajouter un sous-titre dans son entête: la description courte du package:
```
title: "multiple"
subtitle: "Multiples of numbers"
```
Le reste de l'entête permet à R de construire la vignette à partir de code R Markdown.
Le corps de la vignette contient par défaut du code R pour déclarer les options de présentation des bouts de code et le chargement du package.
Une introduction à l'utilisation du package doit être écrite dans ce document, en R Markdown.
Pendant le développement du package, la vignette peut être construite manuellement en exécutant:
```{r build_vignettes, eval=FALSE}
devtools::build_vignettes("multiple")
```
Les fichiers produits sont placés dans `doc/`: ouvrir le fichier `.html` pour contrôler le résultat.
RStudio ne crée pas la vignette du package quand la commande "Install and Restart" de la fenêtre Build est appelée.
Pour une installation complète, deux solutions sont possibles:
- Construire le fichier source du package ("Build > More > Build Source Package") puis l'installer ("Packages > Install > Install from > Package Archive file").
Le fichier source se trouve à côté de celui du projet.
- Pousser le code du package sur GitHub puis exécuter:
```{r install_github_5, eval=FALSE}
remotes::install_github("multiple", build_vignettes = TRUE)
```
La vignette peut ensuite être affichée par la commande:
```{r vignette_multiple, eval=FALSE}
vignette("multiple")
```
## pkgdown
Le package **pkgdown** permet de créer un site d'accompagnement du package[^505], qui reprend le fichier `README.md` comme page d'accueil, la vignette dans une rubrique "Get Started", l'ensemble des fichiers d'aide avec leurs exemples exécutés (section "Reference"), le fichier `NEWS.md` pour un historique du package (section "Changelog") et des informations du fichier `DESCRIPTION`.
[^505]: Exemple: https://EricMarcon.github.io/entropart/
Créer le site avec **usethis**:
```{r use_pkgdown, eval=FALSE}
usethis::use_pkgdown()
```
Construire ensuite le site.
Cette commande sera exécutée à nouveau à chaque changement de version du package:
```{r build_site_5, eval=FALSE}
pkgdown::build_site()
```
Le site est placé dans le dossier `docs`.
Ouvrir le fichier `index.htm` avec un navigateur web pour le visualiser.
Dès que le projet sera poussé sur GitHub, activer les pages du dépôt pour que le site soit visible en ligne (voir section \@ref(sec:github-pages)).
**pkgdown** place le site dans le dossier `docs`.
Ajouter l'adresse des pages GitHub dans une nouvelle ligne du fichier `DESCRIPTION`:
```
URL: https://GitHubID.github.io/multiple
```
L'ajouter aussi dans le fichier `_pkgdown.yml` qui a été créé vide, ainsi que l'option suivante:
```
url: https://GitHubID.github.io/multiple
development:
mode: auto
```
**pkgdown** place le site dans le dossier `docs/dev` si le site d'une version stable (à trois nombres) du package existe dans `docs` et que la version en cours est une version de développement (à quatre nombres).
De cette façon, les utilisateurs d'une version de production du package ont accès au site sans qu'il soit perturbé par les versions de développement.
Le site peut être enrichi de plusieurs façons:
* En ajoutant des articles au format R Markdown dans le dossier `vignettes/articles`.
La vignette ne peut pas mobiliser d'importantes ressources de calcul pour présenter des exemples parce qu'elle est construite en même temps que le package.
Les articles sont générés par **pkgdown**, indépendamment, et peuvent donc être plus ambitieux;
* En améliorant sa présentation (regroupement des fonctions par thèmes, ajout de badges, d'un sticker[^512]...): se référer à la vignette de **pkgdown**.
[^512]: L'application Shiny **hexmake** permet de créer facilement un sticker: https://connect.thinkr.fr/hexmake/
Pour enrichir la documentation du package, il est possible d'utiliser un fichier `README.Rmd` au format R Markdown, à tricoter pour créer le `README.md` standard de GitHub, utilisé comme page d'accueil du site **pkgdown**, qui peut de cette façon présenter des exemples d'utilisation du code.
La démarche est détaillée dans *R Packages*[^513].
La complexité ajoutée est à comparer au gain obtenu: une page d'accueil simple (sans code) avec des liens vers la vignette et les articles est plus simple à mettre en œuvre.
[^513]: https://r-pkgs.org/release.html?q=readme#readme-rmd
## Code spécifique aux packages
### Importation de fonctions
Créons une nouvelle fonction dans `fonctions.R` qui ajoute un bruit aléatoire à la valeur double:
```{r fuzzydouble}
fuzzydouble <- function(number, sd=1) {
return(2*number + rnorm(length(number), 0, sd))
}
```
Le bruit est tiré dans une loi normale centrée d'écart-type `sd` et ajouté à la valeur calculée.
`rnorm()` est une fonction du package **stats**.
Même si le package est systématiquement chargé par R, le package d'appartenance de la fonction doit obligatoirement être déclaré: les seules exceptions sont les fonctions du package **base**.
Le package **stats** doit d'abord être déclaré dans `DESCRIPTION` qui contient une instruction `Imports:`.
Tous les packages utilisés par le code de **multiple** seront listés, séparés par des virgules.
```
Imports: stats
```
Cette "importation" signifie simplement que le package **stats** doit être chargé, mais pas nécessairement attaché (voir section \@ref(sec:environnements)), pour que **multiple** fonctionne.
Ensuite, la fonction `rnorm()` doit être trouvable dans l'environnement du package **multiple**.
Il y a plusieurs façons de remplir cette obligation.
D'abord, le commentaire suivant pourrait être fourni pour **roxygen2**:
```{r stats}
#' @import stats
```
Tout l'espace de nom du package **stats** serait attaché et accessible au package **multiple**.
Ce n'est pas une bonne pratique parce qu'elle multiplie les risques de conflits de noms (voir section \@ref(sec:environnements)).
Notons que la notion d'importation utilisée ici est différente de celle de `DESCRIPTION`, bien qu'elles aient le même nom.
Il est préférable d'importer uniquement la fonction `rnorm()` en la déclarant dans la documentation de la fonction:
```{r rnorm}
#' @importFrom stats rnorm
```
Ce n'est pas une pratique idéale non plus parce que l'origine de la fonction n'apparaîtrait pas clairement dans le code du package.
La bonne pratique est de ne rien importer (au sens de **roxygen2**) et de qualifier systématiquement les fonctions d'autres packages avec la syntaxe `package::fonction()`.
C'est la solution retenue ici parce que la directive `@importFrom` importerait la fonction dans tout le package **multiple**, pas seulement dans la fonction `fuzzydouble()`, au risque de créer des effets de bord (modifier le comportement d'une autre fonction du package qui n'assumerait pas l'importation de `rnorm()`).
Finalement, le code de la fonction est le suivant:
```{r fuzzydouble_roxy, eval=FALSE}
#' fuzzydouble
#'
#' Double value of numbers with an error
#'
#' Calculate the double values of numbers
#' and add a random error to the result.
#'
#' @param number a numeric vector.
#' @param sd the standard deviation of the Gaussian error added.
#'
#' @return A vector of the same length as `number`
#' containing the transformed values.
#' @export
#'
#' @examples
#' fuzzydouble(2)
#' fuzzydouble(1:4)
fuzzydouble <- function(number, sd=1) {
return(2*number + stats::rnorm(length(number), 0, sd))
}
```
### Méthodes S3
Les méthodes S3 sont présentées en section \@ref(sec:S3).
#### Classes
Les objets appartiennent à des classes:
```{r class}
# Classe d'un nombre
class(2)
# Classe d'une fonction
class(sum)
```
En plus des classes de base, les développeurs peuvent en créer d'autres.
#### Méthodes
L'intérêt de créer de nouvelles classes est de leur adapter des méthodes existantes, le cas le plus courant étant `plot()`.
Il s'agit d'une méthode générique, c'est-à-dire un modèle de fonction, sans code, à décliner selon la classe d'objet à traiter.
```{r plot}
plot
```
Il existe dans R de nombreuses déclinaisons de `plot` qui sont des fonctions dont le nom est de la forme `plot.class()`.
**stats** fournit une fonction `plot.lm()` pour créer une figure à partir d'un modèle linéaire.
De nombreux packages créent des classes adaptées à leurs objets et proposent une méthode `plot` pour chaque classe.
Les fonctions peuvent être listées:
```{r methods}
# Quelques fonctions plot()
head(methods(plot))
# Nombre total
length(methods(plot))
```
Inversement, les méthodes disponibles pour une classe peuvent être affichées:
```{r}
methods(class = "lm")
```
La méthode `print` est utilisée pour afficher tout objet (elle est implicite quand on saisit seulement le nom d'un objet):
```{r print}
my_lm <- lm(dist~speed, data=cars)
# Equivalent de "> my_lm"
print(my_lm)
```
La méthode `summary` affiche un résumé lisible de l'objet:
```{r summary}
summary(my_lm)
```
Les autres méthodes ont été créées spécifiquement pour les besoins du package **stats**.
#### Attribution d'un objet à une classe
Pour qu'un objet appartient à une classe, il suffit de le déclarer:
```{r MyClass}
x <- 1
class(x) <- "MyClass"
class(x)
```
Une façon plus élégante de le faire est d'ajouter la nouvelle classe à l'ensemble des classes auquel l'objet appartient déjà:
```{r MyClass2}
y <- 1
class(y) <- c("MyClass", class(y))
class(y)
```
Il n'y a aucune vérification de cohérence entre la structure réelle de l'objet et une structure de la classe qui serait déclarée ailleurs: le développeur doit s'assurer que les méthodes trouveront bien les bonnes données dans les objets qui déclarent lui appartenir.
Dans le cas contraire, des erreurs se produisent:
```{r tryCatch}
class(y) <- "lm"
tryCatch(print(y), error= function(e) print(e))
```
### En pratique
#### Création d'une méthode générique
De nouvelles méthodes génériques peuvent être créées et déclinées selon les classes.
A titre d'exemple, créons une méthode générique `triple` qui calculera le triple des valeurs dans le package **multiple**, déclinée en deux fonctions distinctes: une pour les entiers et une pour les réels.
Les calculs sur les nombres entiers plus rapides que ceux sur les réels, ce qui justifie l'effort d'écrire deux versions du code.
```{r UseMethod}
# Méthode générique
triple <- function (x, ...) {
UseMethod("triple")
}
```
La méthode générique ne contient pas de code au-delà de sa déclaration.
Sa signature (c'est-à-dire l'ensemble de ses arguments) est importante parce que les fonctions dérivées de cette méthode devront obligatoirement avoir les mêmes arguments dans le même ordre et pourront seulement ajouter des arguments supplémentaires avant `...` (qui est obligatoire).
Comme la nature du premier argument dépendra de la classe de chaque objet, l'usage est de l'appeler `x`.
La méthode est déclinée en deux fonctions:
```{r triple, tidy=FALSE}
triple.integer<- function (x, ...){
return(x * 3L)
}
triple.numeric<- function (x, ...){
return(x * 3.0)
}
```
Dans sa version entière, `x` est multiplié par `3L`, le suffixe `L` signifiant que 3 doit être compris comme un entier.
Dans sa version réelle, 3 peut être noté `3.0` pour montrer clairement qu'il s'agit d'un réel.
Sous R, `3` sans autre précision est compris comme un réel.
Le choix de la fonction dépend de la classe de l'objet passé en argument.
```{r triple.x}
# Argument entier
class(2L)
# Résultat entier par la fonction triple.integer
class(triple(2L))
# Argument réel
class(2)
# Résultat réel par la fonction triple.numeric
class(triple(2))
# Performance
microbenchmark::microbenchmark(triple.integer(2L), triple.numeric(2), triple(2L))
```
La mesure des performances par le package **microbenchmark** ne montre pas de différence entre les fonctions `triple.integer()` et `triple.numeric` comme attendu parce que le temps consacré au calcul lui-même est négligeable en comparaison du temps d'appel de la fonction.
La méthode générique consomme beaucoup plus de temps que les calculs très simples ici.
R teste en effet l'existence de fonctions correspondant à la classe de l'objet passé en argument aux méthodes génériques.
Comme un objet peut appartenir à plusieurs classes, il recherche une fonction adaptée à la première classe, puis aux classes suivantes successivement.
Cette recherche prend beaucoup de temps et justifie de réserver l'usage de méthodes génériques à la lisibilité du code plutôt qu'à une recherche de performance: l'intérêt des méthodes génériques est de fournir à l'utilisateur du code une seule fonction pour un objectif donné (`plot` pour réaliser une figure) quelles que soient les données à traiter.
#### Création d'une classe
Dans un package, on créera des classes si les résultats des fonctions le justifient: structure de liste et identification de la classe à un objet ("lm" est la classe des modèles linéaires).
Pour toute classe créée, les méthodes `print`, `summary` et `plot` (si une représentation graphique est possible) doivent être écrites.
Ecrivons une fonction `multiple()` dont le résultat sera un objet d'une nouvelle classe, "multiple", qui sera une liste mémorisant les valeurs à multiplier, le multiplicateur et le résultat.
```{r multiple}
multiple <- function(number, times=1) {
# Calculate the multiples
y <- number * times
# Save in a list
result <- list(x=number, y=y, times=times)
# Set the class
class(result) <- c("multiple", class(result))
return(result)
}
# Classe du résultat
my_multiple <- multiple(1:3, 2)
class(my_multiple)
```
L'appel à la fonction `multiple()` renvoie un objet de classe "multiple", qui est aussi de classe "list".
En absence de fonction `print.multiple()`, R cherche la fonction `print.list()` qui n'existe pas et se rabat sur la fonction `print.default()`:
```{r my_multiple}
my_multiple
```
La fonction `print.multiple` doit donc être écrite pour un affichage lisible, limité au résultat:
```{r print.multiple}
print.multiple <- function(x, ...) {
print.default(x$y)
}
# Nouvel affichage
my_multiple
```
Les détails peuvent être présentés dans la fonction `summary`:
```{r summary.multiple}
summary.multiple <- function(object, ...) {
print.default(object$x)
cat("multiplied by", object$times, "is:\n")
print.default(object$y)
}
# Nouvel affichage
summary(my_multiple)
```
Enfin, une fonction `plot` et une fonction `autoplot` complètent l'ensemble:
```{r plot.multiple, tidy=FALSE}
plot.multiple <- function(x, y, ...) {
plot.default(y=x$y, x=x$x, type = "p",
main = paste("Multiplication by", x$times), ...)
}
autoplot.multiple <- function(object, ...) {
data.frame(x = object$x, y = object$y) %>%
ggplot2::ggplot() +
ggplot2::geom_point(ggplot2::aes(x = .data$x, y = .data$y)) +
ggplot2::labs(title = paste("Multiplication by",
object$times))
}
plot(my_multiple)
autoplot(my_multiple)
```
Pour des raisons techniques liées à l'évaluation non conventionnelle dans le tidyverse, les noms de variables utilisées par `aes()` doivent être préfixées par `.data$` dans les packages et `rlang::.data` doit être importé.
Dans le cas contraire, la vérification du package renvoie une note indiquant que les variables `x` et `y`, utilisées par les arguments de `aes()` n'ont pas été déclarées et n'existent peut-être pas dans l'environnement local (voir section \@ref(sec:environnements)).
#### Documentation
Les méthodes génériques et les fonctions qui les déclinent doivent être documentées comme n'importe quelle autre fonction.
La gestion de l'espace des noms est un peu plus complexe:
- Les méthodes génériques doivent être exportées:
```
#' @export
```
- Les fonctions dérivées de méthodes génériques ne doivent pas être exportées mais être déclarées comme méthodes, avec le nom de la méthode générique et la classe traitée.
**roxygen2** demande qu'une directive d'exportation soit ajoutée mais ne l'applique pas (comme il se doit) dans le fichier `NAMESPACE` qui est utilisé par R:
```
#' @method plot multiple
#' @export
```
- Depuis la version 3 de **roxygen2**, la déclaration ` @method` est inutile tant que le nom de la fonction est décomposable sans ambiguïté, comme `plot.multiple`: `@export` suffit.
Si le nom de la fonction dérivée comporte plusieurs points, **roxygen2** peut ne pas détecter automatiquement le générique et l'objet et ` @method` doit être maintenu.
- Les fonctions dérivées de méthodes génériques venant d'un autre package nécessitent d'importer la méthode générique, sauf si elle est fournie par **base** (`print` est fourni par **base** et n'est donc pas concerné):
```
#' @importFrom graphics plot
#' @importFrom ggplot2 autoplot
```
- Les génériques importés de cette manière doivent être réexportés par une directive à placer par exemple juste après le code de la fonction dérivée:
```
#' @export
graphics::plot
#' @export
ggplot2::autoplot
```
- **roxygen2** crée automatiquement un fichier d'aide `reexports.Rd` dans lequel se trouve un lien vers la documentation originale des génériques réexportés.
Dans `DESCRIPTION`, le package d'origine de chaque générique doit être listé dans la directive `Imports:`:
```
Imports: ggplot2, graphics
```
Enfin, l'importation de fonctions du tidyverse nécessite aussi quelques précautions:
- le package **tidyverse** est réservé à l'usage interactif de R: il n'est pas question de l'importer dans `DESCRIPTION` parce que ses dépendances peuvent changer et aboutir à des résultats imprévisibles.
Le package **magrittr** fournit les tuyaux, principalement `%>%`.
Le package **rlang** fournit l'objet `.data` présenté ci-dessous.
Il doivent être importés dans `DESCRIPTION`.
```
Imports: magrittr, rlang, stats
```
- Comme il n'est pas possible de préfixer les `%>%` par le nom du package, il faut importer la fonction en utilisant les délimiteurs prévus pour les fonctions dont le nom contient des caractères spéciaux:
```{r importFrom1}
#' @importFrom magrittr `%>%`
```
- Les fonctions du tidyverse qui utilisent des noms de colonnes de tibbles ou dataframes génèrent des avertissements au moment de la vérification du package parce que ces noms sont confondus avec des noms de variables non définies.
Pour éviter cette confusions, l'objet `.data` du package **rlang** est utilisé (par exemple dans `aes()` vu plus haut).
Il doit être importé:
```{r importFrom2}
#' @importFrom rlang .data
```
Finalement, le code complet est le suivant:
```{r multiple_roxy, tidy=FALSE}
#' Multiplication of a numeric vector
#'
#' @param number a numeric vector
#' @param times a number to multiply
#'
#' @return an object of class `multiple`
#' @export
#'
#' @examples
#' multiple(1:2,3)
multiple <- function(number, times = 1) {
# Calculate the multiples
y <- number * times
# Save in a list
result <- list(x = number, y = y, times = times)
# Set the class
class(result) <- c("multiple", class(result))
return(result)
}
#' Print objects of class multiple
#'
#' @param x an object of class `multiple`.
#' @param ... further arguments passed to the generic method.
#'
#' @export
#'
#' @examples
#' print(multiple(2,3))
print.multiple <- function(x, ...) {
print.default(x$y)
}
#' Summarize objects of class multiple
#'
#' @param object an object of class `multiple`.
#' @param ... further arguments passed to the generic method.
#'
#' @export
#'
#' @examples
#' summary(multiple(2,3))
summary.multiple <- function(object, ...) {
print.default(object$x)
cat("multiplied by", object$times, "is:\n")
print.default(object$y)
}
#' Plot objects of class multiple
#'
#' @param x a vector of numbers
#' @param y a vector of multiplied numbers
#' @param ... further arguments passed to the generic method.
#'
#' @importFrom graphics plot
#' @export
#'
#' @examples
#' plot(multiple(2,3))
plot.multiple <- function(x, y, ...) {
plot.default(y=x$y, x=x$x, type = "p",
main = paste("Multiplication by", x$times), ...)
}
#' @export
graphics::plot
#' autoplot
#'
#' ggplot of the `multiple` objects.
#'
#' @param object an object of class `multiple`.
#' @param ... ignored.
#'
#' @return a `ggplot` object
#' @importFrom ggplot2 autoplot
#' @importFrom magrittr `%>%`
#' @importFrom rlang .data
#' @export
#'
#' @examples
#' autoplot(multiple(2,3))
autoplot.multiple <- function(object, ...) {
data.frame(x = object$x, y = object$y) %>%
ggplot2::ggplot() +
ggplot2::geom_point(ggplot2::aes(x = .data$x, y = .data$y)) +
ggplot2::labs(title = paste("Multiplication by",
object$times))
}
#' @export
ggplot2::autoplot
```
### Code C++
L'utilisation de code C++ a été vue en section \@ref(sec:cpp).
Pour intégrer ces fonctions dans un packages, il faut respecter les règles suivantes:
- les fichiers `.cpp` contenant le code sont placés dans le dossier `/src` du projet;
- le code est commenté pour **roxygen2** de la même façon que les fonctions R, mais avec le marqueur de commentaire du langage C:
```{Rcpp Rcpp_timesTwo, eval=FALSE}
#include <Rcpp.h>
using namespace Rcpp;
//' timesTwo
//'
//' Calculates the double of a value.
//'
//' @param x A numeric vector.
//' @export
// [[Rcpp::export]]
NumericVector timesTwo(NumericVector x) {
return x * 2;
}
```
- dans `DESCRIPTION`, importer les packages.
**Rcpp**, et **RcppParallel** si du code parallélisé est utilisé (supprimer ses références sinon), doivent être déclarés dans `LinkingTo`:
```
Imports: Rcpp, RcppParallel
LinkingTo: Rcpp, RcppParallel
```
- les commentaires pour **roxygen2** doivent être ajoutés à `package.R` ("multiple" est le nom du package):
```{r useDynLib}
#' @importFrom Rcpp sourceCpp
#' @importFrom RcppParallel RcppParallelLibs
#' @useDynLib multiple, .registration = TRUE
```
- les fichiers de travail de C++ sont exclus du contrôle de source dans `.gitignore`:
```
# C binaries
src/*.o
src/*.so
src/*.dll
```
Ces modifications sont en partie effectuées automatiquement, pour **Rcpp** seulement, par **usethis**, mais l'insertion manuelle du code est plus rapide et fiable: ne pas utiliser cette commande.
```{r use_rcpp, eval=FALSE}
# usethis::use_rcpp()
```
La construction du package entraînera la compilation du code: les Rtools sont donc indispensables.
### Package bien rangé
Tout package moderne doit être compatible avec le tidyverse, ce qui nécessite peu d'efforts:
* pour permettre l'utilisation de pipelines, l'argument principal des fonctions doit être le premier;
* les fonctions qui transforment des données doivent accepter un dataframe ou un tibble comme premier argument et retourner un objet du même format;
* les méthodes `plot()` doivent être doublées de méthodes `autoplot()` avec les mêmes arguments qui produisent le même graphique avec **ggplot2**.
## Bibliographie
La documentation d'un package fait appel à des références bibliographiques.
Elles peuvent être gérées automatiquement avec **Rdpack** et **roxygen2**.
Les références utilisées dans les fichiers R Markdown (vignette, site produit par **pkgdown**) ne sont pas concernées.
### Préparation
Les références bibliographiques doivent être placées dans un fichier BibTeX `REFERENCES.bib` placé dans le dossier `inst`.
Ce dossier contient des fichiers qui seront placés à la racine du dossier du package quand il sera installé.
Ajouter la ligne suivante à `DESCRIPTION`:
```
RdMacros: Rdpack
```
Ajouter aussi le package `Rdpack` à la liste des packages importés:
```
Imports: magrittr, stats, Rcpp, Rdpack
```
Enfin, importer la fonction `reprompt()` de **Rdpack** en ajoutant les lignes suivantes à la documentation pour **roxygen2** dans `package.R`:
```{r importFrom3}
#' @importFrom Rdpack reprompt
```
### Citations
Les références sont citées par la commande `\insertCite{key}{package}` dans la documentation destinée à **roxygen2**.
`package` est le nom du package dans lequel le fichier `REFERENCES.bib` doit être cherché: ce sera normalement le package en cours, mais les références d'autres packages sont accessibles, à la seule condition qu'ils utilisent **Rdpack**.
`key` est l'identifiant de la référence dans le fichier.
Exemple[^507]: documentation du package **divent** hébergé sur GitHub, fichier `.R` du package:
```{r Citations}
#' divent
#'
#' Measures of Diversity and Entropy
#'
#' This package is a reboot of the **entropart** package \insertCite{Marcon2014c}{divent}.
#'
#' @importFrom Rdpack reprompt
#'
#' @references
#' \insertAllCited{}
"_PACKAGE"
```
[^507]: Package **divent** sur GitHub: https://github.com/EricMarcon/divent/blob/master/R/package.R
La référence citée se trouve dans `inst/REFERENCES.bib`:
```
@Article{Marcon2014c,
author = {Marcon, Eric and Herault, Bruno},
title = {entropart, an R Package to Partition
Diversity},
journal = {Journal of Statistical Software},
year = {2015},
volume = {67},
number = {8},
pages = {1--26},
}
```
Les citations sont entre parenthèses.
Pour placer le nom de l'auteur hors de la parenthèse, ajouter la déclaration `;textual`:
```
\insertCite{Marcon2014c;textual}{divent}
```
Pour citer plusieurs références (forcément du même package), les séparer par des virgules.
A la fin de la documentation d'un objet utilisant des citations, ajouter systématiquement une liste des références:
```{r insertAllCited}