20 décembre 2021
Trois fois le même code : écrire une fonction.
Trois fonctions qui se complètent : écrire un package.
Standardisation du code.
Vérifications.
Documentation obligatoire.
devtools et roxygen2 simplifient le travail.
A quoi va servir le package ?
Consolidation de sa recherche : le package rassemble et organise des méthodes. Exemples : entropart, ade4.
Outil d’intérêt général. Exemples : EcoFoG, vegan, spatstat, ggplot2.
Un package ne doit traiter qu’un sujet. Si nécessaire, écrire plusieurs packages.
Package multiple :
des fonctions : Double(x)
, Triple(x)
et Multiple(x, n)
… pour calculer des multiples.
des fonctions comme FuzzyDouble(x)
pour calculer des multiples avec un bruit.
une représentation graphique du multiple en fonction de x
(une droite).
Le package est créé dans le dossier indiqué.
devtools::create(path = "multiple")
Le dossier final a le nom du package.
Ouvrir le projet du package.
Etudier les fichiers créés.
Package: multiple Title: Compute multiples Version: 0.0.0.9000 Authors@R: person("First", "Last", email = "first.last@example.com", role = c("aut", "cre")) Description: Efficiently calculate multiples of numbers, following Me et al. (2018). Depends: R (>= 3.4.3) License: GNU General Public License Encoding: UTF-8 LazyData: true
Code dans des fichiers .R
Organisation libre, les fichiers ne seront pas dans le package.
Choix :
un fichier project.R
pour le code commun à tout le package
un fichier R par groupe de fonctions, ici : Double.R
, …
Code commun
#' multiple #' #' Exercise package #' #' @name multiple #' @docType package #' @import ggplot2 NULL
L’importation de l’espace de noms de ggplot2 est inutile, c’est une erreur volontaire.
Première version de Double(x)
Double <- function(number) { return(2 * number) }
Exécuter le code pour charger la fonction en mémoire.
Tester.
Insert Roxygen sleleton :
#' Double #' #' Compute the Double value of a vector #' #' The Double is calculated by multiplying each value by 2. #' #' @param number A numeric vector #' #' @return A vector containing the Double values. #' @export #' #' @examples #' Double(runif(3))
Double()
est exportée.
Build Source package : crée multiple_0.0.0.9000.tar.gz avec sa documentation.
Etudier le contenu de man
Charger le package :
Nettoyer l’environnement (fonctions en mémoire)
Install and Restart
?multiple
Check
(...) checking package dependencies ... ERROR Namespace dependency not required: 'ggplot2' (...) R CMD check results 1 error | 0 warnings | 0 notes
Corriger toutes les erreurs, avertissements et notes.
Problème : Double()
transforme les entiers en réels.
str(Double(1L))
## num 2
Solution :
str(1L * 2L)
## int 2
Double <- function(x, ...) { UseMethod("Double") } Double.default <- function(x, ...) { return(2 * x) } Double.integer <- function(x, ...) { return(2L * x) }
Exécuter et tester.
str(Double(2L))
## int 4
Remarquer : le respect de la signature, obligatoire.
Un seul fichier d’aide : @name
et @rdname
#' Double (...) #' @param x A vector #' @param ... Unused (...) #' @name Double #NULL #' @rdname Double #' export Double <- function(x, ...) { UseMethod("Double") }
Les méthodes S3 doivent être déclarées, pas exportées mais Roxygen exige @export
pour la déclaration.
#' @rdname Double #' @method Double default #' @export Double.default <- function(x) { return(2*x) } #' @rdname Double #' @method Double integer #' @export Double.integer <- function(x) { return(2L*x) }
Build Source package, Install and Restart puis ?Double
Double.default()
peut revevoir un objet non numérique.
Double.default <- function(x) { # Input check if (!is.numeric(x)) stop("Double requires a numeric object") # Compute and return return(2 * x) }
Tester après Install and Restart (Oxygénation inutile).
Que se passe-t-il si x
est une matrice ?
#' @param x An object
Objectif : écrire une fonction double, avec un terme d’erreur normal, qui retourne un data.frame avec x
et son double, facile à dessiner.
La fonction va dans un nouveau fichier : FuzzyDouble.R
Ecrire la fonction et la tester en la sourçant.
FuzzyDouble <- function(x, mean = 0, sd = 1) { # Double x and add normal error y <- 2 * x + stats::rnorm(n = length(x), mean = mean, sd = sd) # Make a data.frame fuzzydouble <- data.frame(x = x, y = y) # Make it a FuzzyMultiple object class(fuzzydouble) <- c("FuzzyDouble", class(fuzzydouble)) return(fuzzydouble) }
Remarquer :
stats::rnorm()
; Classe ;
Commentaires
Ne pas oublier les tests !
FuzzyDouble <- function(x, mean = 0, sd = 1) { # Input check if (!is.numeric(x)) stop("Double requires a numeric object") if (!is.numeric(mean)) stop("The mean noise must be numeric") if (!is.numeric(sd)) stop("The standard deviation of the noise must be numeric") if (length(mean) > 1 | length(sd) > 1) stop("The mean and standard deviation of the noise must be of length 1") if (sd < 0) stop("The standard deviation of the noise must be positive") # (...) }
#' FuzzyDouble #' #' Fuzzy double of a numeric object. #' #' Doubles an object with a random noise: a Gaussian error drawn #' by \code{\link{rnorm}}. #' #' @param x A numeric object #' @param mean The mean noise. Default is 0. #' @param sd The standard deviation of the noise. Default is 1. #' #' @return a \code{FuzzyDouble} object which is a data.frame with #' columns \code{x} for the input and \code{y} for the output. #' #' @seealso \code{\link{plot.FuzzyDouble}}, #' \code{\link{autoplot.FuzzyDouble}} #' @export
Remarquer : le lien vers rnorm
, @seealso
.
Nettoyer l’environnement
Build Source package
Install and Restart
?FuzzyDouble
Ecrire un méthode plot
pour FuzzyDouble
plot.FuzzyDouble <- function(x, xlab = "x", ylab = "Double", ..., LineCol = "red") { # xy standard plot graphics::plot(x$x, x$y, xlab = xlab, ylab = ylab, ...) # Add the regression line graphics::lines(x$x, 2 * x$x, col = LineCol) }
Remarquer :
...
et le passage de xlab
et ylab
Ecrire un méthode autoplot
pour FuzzyDouble
autoplot.FuzzyDouble <- function(object, xlab = "x", ylab = "Double", ..., LineCol = "red") { # ggplot thePlot <- ggplot2::ggplot(data = object, ggplot2::aes_(x = ~x, y = ~y)) + ggplot2::geom_point() + ggplot2::labs(x = xlab, y = ylab) + ggplot2::geom_line(ggplot2::aes_(y = ~2 * x), colour = LineCol) return(thePlot) }
#' Plot FuzzyDouble #' #' Plot a FuzzyDouble object #' #' @param x The \code{\link{FuzzyDouble}} object #' @param xlab The X-axis label #' @param ylab The Y-axis label #' @param ... Extra parameters passed to \code{\link{plot}} #' @param LineCol The color of the line representing $y=2x$ #' #' @importFrom graphics plot #' @method plot FuzzyDouble #' @export #' #' @examples #' plot(FuzzyDouble(1:10))
Remarquer : la formule mathématique, la déclaration de la méthode S3 et l’importation du générique.
#' Plot FuzzyDouble #' #' Plot a FuzzyDouble object with ggplot2 #' #' @inheritParams plot.FuzzyDouble #' @param object The \code{\link{FuzzyDouble}} object #' @param ... Extra parameters passed to \code{\link{autoplot}} #' #' @return A \code{\link{ggplot}} object. #' #' @importFrom ggplot2 autoplot #' @method autoplot FuzzyDouble #' @export #' #' @examples #' autoplot(FuzzyDouble(1:10))
Remarquer : l’héritage des paramètres, sauf object
. et ...
Check détecte que les packages stats, graphics et ggplpot2 manquent dans DESCRIPTION
.
Corriger :
Depends: R (>= 3.4.3), graphics, ggplot2 Imports: stats
Dans DESCRIPTION
:
Imports: stats comme plyr. Standard.
Depends: graphics comme reshape à cause des génériques.
Toute fonction d’un package doit être appelée explicitement: graphics::plot()
Ne pas importer ces fonctions dans l’espace de nom (@importFrom
).
Leurs packages doivent être déclarés Imports dans DESCRIPTION
.
Les fonctions publiques du package doivent être exportées (@export
), y compris les génériques.
Les méthodes S3 ne sont pas exportées mais obligatoirement déclarées (@method
).
Attention : Roxygen2 ne les déclare que si @export
est ajouté.
Conséquence : les génériques doivent être importés (@importFrom
) et leur package déclaré Depends dans DESCRIPTION
pour être exportés.
Etudier NAMESPACE
La fonction Multiple(x, n)
avec un paramètre supplémentaire.
Double(x)
pourrait appeler Multiple(x, 2)
et lui sous-traiter les vérifications et les calculs.
Une classe Multiple
dont n
pourrait être un attribut, ses méthodes plot
et autoplot
…
Des données peuvent être intégrées au package dans un ou des fichiers RData.
Créer un dossier /data
La fonction use_data()
crée les fichiers:
MyData <- 1:100 devtools::use_data(MyData)
Dans Project.R
:
#' My Data #' #' A useless dataset. #' #' @format A numeric vector #' @source \url{http://www.ecofog.gf/} "MyData"
Suivre le développement du package.
Collaborer.
Le rendre accessible sur GitHub.
Bénéficier des outils de GitHub : intégration continue, couverture du code.
A partir d’un projet RStudio existant :
Passer le projet sous contrôle de version :
Tools /Version Control /Project Setup…
Sélectionner Git.
Créer un dépôt sur GitHub, récupérer son URL : https://github.com/MonCompte/MonDepot.git
Dans le Terminal de RStudio, exécuter :
git remote add origin https://github.com/MonCompte/MonDepot.git git push -u origin master
Tirer ;
Modifier ;
Vérifier : Check pour valider le package.
Livrer ;
Pousser.
Standard R :
Majeure.Mineure-Patch.Développement
Dans DESCRIPTION
:
0.0.0.9000
Version de développement à partir de 9000, seulement entre deux versions CRAN:
0.1-0
Fichier Markdown pour lister les avancées du projet.
Exemple :
# EcoFoG 1.2-1 ## Correction de bug * Modèle Présentation : tricotage Beamer impossible en absence de bout de code. Ajout de `\usepackage{fancyvrb}` dans `EcoFoGBeamer.tex`. ## Améliorations * _.gitignore_ dans tous les modèles.
Tirer ;
Modifier ;
Vérifier : Check pour valider le package.
Mettre à jour la version dans DESCRIPTION
et compléter NEWS.md
Livrer ;
Pousser.
GitHub Actions vérifie le package par un Check à chaque livraison.
codecov.io mesure la proportion du code exécutée par GitHub Actions :
Ouvrir un compte sur codecov.io
Ajouter le dépôt GitHub du package.
Fichier de paramétrage pas à pas.
Déclaration du flux de travail :
on: push: branches: - master name: R-CMD-check
Déclenchement et nom.
Déclaration du flux de travail :
jobs: R-CMD-check: runs-on: macOS-latest env: GITHUB_PAT: ${{ secrets.GH_PAT }} steps: - (...)
Liste des jobs (un seul suffit ici), OS, environnement.
Étapes : installation.
steps: - uses: actions/checkout@v2 - uses: r-lib/actions/setup-r@v1 - name: Install pandoc uses: r-lib/actions/setup-pandoc@v1
Les actions évitent d’écrire des scripts.
Étape : installation des dépendances.
- name: Install dependencies run: | options(pkgType = "binary") options(install.packages.check.source = "no") install.packages(c("remotes", "rcmdcheck", "covr", "pkgdown")) remotes::install_deps(dependencies = TRUE) shell: Rscript {0}
Le script est du code R.
Étapes : vérification du package.
- name: Check run: rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "warning", check_dir = "check") shell: Rscript {0} - name: Test coverage run: covr::codecov(type="all") shell: Rscript {0}
Exécution des vérifications et du calcul de la couverture.
Étapes : documentation.
- name: Install package run: R CMD INSTALL . - name: Pkgdown run: | git config --local user.email "actions@github.com" git config --local user.name "GitHub Actions" Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)'
Installation du package et déploiement du site pkgdown (traité plus loin).
Dans README.md
(package EricMarcon/SpatDiv):
![stability-wip](https://img.shields.io/badge/ stability-work_in_progress-lightgrey.svg) ![R-CMD-check] (https://github.com/EricMarcon/entropart/workflows/R-CMD-check/badge.svg) ![codecov](https://codecov.io/github/EricMarcon/SpatDiv/ branch/master/graphs/badge.svg)] (https://codecov.io/github/EricMarcon/SpatDiv)
D’autres badges sur shields.io.
Les vignettes sont la documentation d’un package.
Standard de fait : pkgdown.
# Run once to configure package to use pkgdown usethis::use_pkgdown() # Run to build the website pkgdown::build_site()
Créer un fichier _pkgdown.yml
.
Déclarer l’emplacement du site à créer :
url: https://GitHubID.github.io/Projet/
Exécuter :
pkgdown::build_site()
pour une construction locale dans docs/
.
Dans vignettes
, un fichier au nom du package .Rmd
.
Appelé par vignette("package")
Ecrite en R Markdown.
Doit contenir un lien vers le site pkgdown.
En-tête :
--- title: "NomPackage" subtitle: "Description en une ligne" bibliography: fichier.bib output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to NomPackage} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} ---
Le code C++ est très rapide mais plus compliqué à écrire et déboguer.
A utiliser seulement en cas de besoin. Exemple : dbmss
La parallélisation permet d’éxécuter des tâches longues sur plusieurs processeurs ou ordinateurs.
Le code est plus complexe.
La synchronisation des tâches consomme des ressources.
A utiliser seulement pour des tâches longues.
Démarrage :
devtools::use_rcpp()
Dans Project.R
:
#' @useDynLib multiple, .registration = TRUE #' @importFrom Rcpp sourceCpp
Le code est dans /src
. Créer un fichier C++ :
#include <Rcpp.h> using namespace Rcpp; //' timesTwo //' //' Multiplies by 2 //' //' @param x An integer //' @export // [[Rcpp::export]] int timesTwo(int x) { return x * 2; }
Remarquer : la documentation pour Roxygen, dont @export
et la directive d’export pour Rcpp, sans apostrophe après les //
Le package Rcpp compile le code C++, en fait un programme exécutable (librairie dll).
Il crée une fonction R du même nom que la fonction C (étudier R/RcppExports.R
).
La librairie dll est déclarée dans NAMESPACE
et la fonction R est exportée.
Nombreuses techniques disponibles.
Une très simple pour le code R dans le package parallel
parallel::mclapply
remplace lapply()
Sans effet sous Windows.
Package Rcppparallel
Fonctionnement similaire à Rcppp mais code beaucoup plus complexe.
Extrêmement efficace, y compris sous Windows.
Problèmes avec CRAN.
Tester le code pour vérifier :
sa syntaxe (le code non exécuté n’est pas vérifié par Check);
ses résultats.
Principes :
exécuter tout le code (couverture = 100%) ;
comparer les résultats entre eux ou à des références.
Package testthat
devtools::use_testthat()
Crée les dossiers et modifie DESCRIPTION
.
Suggests: testthat
Ajouter les fichiers de tests dans tests/testthat
Leur nom doit commencer par test
Double.R
:
testthat::context("Double") # Random integer vector x <- rpois(10, lambda = 100) testthat::test_that("Default and integer methods give same result", { testthat::skip_on_cran() testthat::expect_equal(Double(x), Double(as.numeric(x))) })
## Test passed 🥳
testthat::test_that("Double(integer) is integer", { testthat::skip_on_cran() testthat::expect_is(Double(x), "integer") })
## Test passed 😸
Tester tout le code :
les tests négatifs :
les cas rares.
Utiliser codecov.io pour voir le code non couvert.
Un package sur CRAN peut être utilisé par tous.
Il peut être publié : Methods in Ecology and Evolution, … , The R Journal.
L’auteur aura des retours d’autres utilisateurs.
Le responsable du package (role = c("aut", "cre")
) doit répondre aux sollicitations de CRAN.
Le package ne doit générer aucun avertissement, et normalement aucune note.
La vérification sur CRAN est plus exigente :
plusieurs plateformes ;
des tests du code C.
Vérification OK localement et sur GitHub Actions
Construire la source du package avec la dernière version de R.
Vérifier le package sur The r-hub builder : nombreuses plateformes.
Vérifier que la version est à correcte :
patch = 0 sauf si c’est un patch.
pas de version de développement.
Soumission sur CRAN.
En cas de rejet, corriger et resoumettre en incrémentant le patch.
En cas de publication, ajouter un fichier inst/CITATION
Exemple de entropart
citation(package = "entropart")
## ## To cite entropart in publications use: ## ## Eric Marcon, Bruno Herault (2015). ## entropart: An R Package to Measure and ## Partition Diversity. Journal of ## Statistical Software, 67(8), 1-26. ## doi:10.18637/jss.v067.i08 ## ## A BibTeX entry for LaTeX users is ## ## @Article{, ## title = {{entropart}: An {R} Package to Measure and Partition Diversity}, ## author = {Eric Marcon and Bruno H{\'e}rault}, ## journal = {Journal of Statistical Software}, ## year = {2015}, ## volume = {67}, ## number = {8}, ## pages = {1--26}, ## doi = {10.18637/jss.v067.i08}, ## }