Créer un package R en quelques minutes

Tags : Ressources

Fabriquer un package R en quelques minutes

Dans ces quelques lignes je vais vous expliquer comment concevoir votre propre package R. Ne partez pas ! Il n’y a pas d’appréhension à avoir, c’est relativement facile à faire et très rapide avec un peu d’habitude

La création d’un package R en cinq étapes

A partir d’un copier-coller, une fonction s’impose et à partir d’une fonction, il faut faire un package (en sachant qu’un package, comme tout projet de développement, se doit d’être versionné via git).

Le package n’est pas le Saint Graal, mais plutôt la brique de base dans R. Réaliser un package c’est rapide et facile (une fois que l’on a les idées claires).

Le package permet de contenir un ensemble de fonctions, leur documentation, les éventuelles dépendances à d’autres packages mais aussi des applications Shiny, des tests unitaires…

Le package ne sert pas qu’à diffuser des fonctions, il peut aussi servir de template de travail au quotidien pour vos analyses…

TL;DR
On vous a fait une video 😉

Étape 1 : Création du projet Rstudio

Grâce à Rstudio et des packages tels que {devtools}, {usethis} et {roxygen2}, concevoir un package revient à cliquer sur File > New Project > New Directory > R package using devtools.

Assurez-vous d’avoir bien installé les packages suivants :

install.packages(c("devtools", "usethis", "roxygen2"))

La boîte de dialogue vous invite à choisir un nom pour votre package. Quelques règles majeures : pas d’espace, pas d’underscore, ne pas commencer par un chiffre et, par pitié, en minuscule (les majuscules ça ennuie tout le monde). Jeu de mot avec “ar” (R) en option.

Vous pouvez utiliser le package {available} pour vérifier la disponibilité et les éventuelles traductions (en anglais) du nom de votre package available::available("monpackage")

RStudio s’ouvre alors sur un nouveau projet avec un nouveau dossier contenant une arborescence dont il suffit de retenir deux choses :

  • le fichier DESCRIPTION qu’il faut remplir à la main en remplacant les valeurs pré-remplies (nom, licence, etc)
  • le dossier R dans lequel il faudra placer ses (belles) fonctions

Et c’est tout.

A titre d’information : le .gitignore permet d’indiquer les fichiers/dossiers que l’on ne souhaite pas versionner sur git, le .Rbuildignore permet de lister les fichiers/dossiers qui ne doivent pas être pris en compte par le package, c’est le cas du fichier en .Rproj, par exemple, qui indique qu’il s’agit d’un projet Rstudio. Le fichier NAMESPACE est évoqué plus loin dans cet article.

En passant, rendez-vous dans le menu Build > Configure Build Tools > Cliquez sur configure et cochez Build & Reload (qui un jour s’appelera Install and Restart j’imagine, comme ailleurs dans Rstudio)

Étape 2 : le fichier DESCRIPTION

Ouvrir le fichier et l’éditer.
Vous pouvez voir ce fichier comme un texte à trou dans lequel il faut entrer votre nom, prénom, décrire ce que fait le package, etc. Vous devez choisir une licence (GPL-3, MIT). Ni plus, ni moins pour le moment.

On passe donc de :

Package: monpackage
Title: What the Package Does (one line, title case)
Version: 0.0.0.9000
[email protected]: person("First", "Last", email = "[email protected]", role = c("aut", "cre"))
Description: What the package does (one paragraph).
Depends: R (>= 3.4.3)
License: What license is it under?
Encoding: UTF-8
LazyData: true

à

Package: monpackage
Title: Simplement un Package de demonstration
Version: 0.0.0.9000
[email protected]: person("Vincent", "Guyader", email = "vince[email protected]", role = c("aut", "cre"))
Description: Package qui ne sert a rien de particulier.
Depends: R (>= 3.4.3)
License: GPL-3
Encoding: UTF-8
LazyData: true

 

Vous noterez que je n’ai pas mis d’accents pour éviter les problèmes d’encodage. On pourrait tout écrire dans un français correct mais cela pose toujours des soucis à un moment ou un autre. Dans les faits, on écrit plutôt en anglais de toutes façons…

Une fois ce fichier complété, cliquez sur check dans l’onglet build en haut à droite dans Rstudio.

Une cinquantaine de tests seront alors réalisés. Dans la mesure où votre package est vide, il est essentiel d’avoir 0 erreur, 0 warning et 0 note (c’est ce que l’on visera tout le temps ! On cherche a faire quelque chose dans les règles de l’art…) .

Ce bouton check est votre nouveau meilleur ennemi ami, n’hésitez pas à en abuser pour détecter toute erreur que vous pourriez faire (fichier au mauvais endroit, fonction sans documentation, etc). N’attendez pas la fin de votre package pour l’utiliser : après chaque étape, on passe à la caisse au check !

Si vous avez des erreurs, c’est grave. Des warnings, c’est gênant. Des notes, ce n’est pas si grave. Mais essayez vraiment d’avoir 0 à chaque check.

Déchiffrer les erreurs du check est un art en soi, cependant on retombe souvent sur les mêmes. Pour vous aider, rendez-vous sur google, stackoverflow ou la section commentaires de cet article.

Une fois que c’est fait, sachez que vous n’aurez plus besoin d’éditer le fichier DESCRIPTION à la main, ce sont les packages {devtools} ou encore {usethis} qui s’en chargeront.

Étape 3 : Création de fonctions

Vous pouvez maintenant commencer à soigner vos fonctions et les ranger consciencieusement dans le dossier R de l’arborescence, dans des fichiers portant l’extension .R.

Soigner vos fonctions signifie qu’il est strictement interdit d’utiliser setwd(), source() et autre library() de tout poil. setwd() et source() sont de mauvaises pratiques et library() et require() seront gérées de façon bien plus rigoureuse au niveau du package.

Rien ne vous y oblige, mais il est plutôt pratique et recommandé de créer un nouveau fichier pour chaque nouvelle fonction, surtout si vous travaillez à plusieurs.

Créons, par exemple, une fonction moyenne() qui permet de calculer la moyenne d’un vecteur en supprimant automatiquement les données manquantes. Nous l’enregistrons dans un fichier R/moyenne.R

moyenne <- function(x){
  x <- x %>% na.omit()
  sum(x)/length(x)
}

Recommandation

Avant la création de la première fonction, créez dès à présent une vignette qui expliquera comment utiliser votre package. Vous pouvez utiliser la commande usethis::use_vignette("comment-utiliser-mon-package")

Cette commande ouvre un fichier .Rmd.

  • Faîtes le ménage et ajoutez-y le chargement de votre package library(monpackage) dans le premier chunk.
  • Ajoutez un titre et décrivez la première fonctionnalité de votre package
  • Ajoutez un chunk avec un exemple d’utilisation et créez le code qui permet de faire ce que vous avez décrit
  • Transformez-le en fonction.
  • Dès que ça marche comme vous le voulez, déplacez cette fonction dans un fichier .R dans le dossier R tel que décrit ci-dessus
  • Gardez l’exemple dans votre vignette avec l’appel de votre fonction.

De cette manière, vous créez la documentation en même temps que votre package. Plus de perte de temps ! Et votre package peut-être testé par n’importe qui à tous les stades de son développement.

C’est aussi lors de cette étape que vous pouvez (devez?) écrire les tests qui permettent de vérifier que votre fonction sortira toujours le même type de résultat, quelques soient les modifications des autres fonctions ou les modifications à venir des dépendances que vous utilisez.

Étape 4 : Documentation et NAMESPACE

En cliquant sur check à l’étape 2, vous avez, (sans vous en rendre compte ?) fabriqué un dossier man dans l’arborescence. Mais pour l’instant il est vide…

Le fichier NAMESPACE comme le dossier man ne seront pas le centre de notre attention. Le NAMESPACE sert à définir comment notre package interagit avec le monde extérieur (importer d’autres fonctions, d’autres packages, exporter telle ou telle fonction) et le dossier man contient la documentation des fonctions. En pratique ils seront générés et mis à jour automatiquement grâce à {roxygen2}, en cliquant sur le bouton check.

Afin que le NAMESPACE et man soient gérés automatiquement, nous allons adopter un système de balises spécifiques pour documenter le code R. Des commentaires un peu particuliers vont commencer, non pas par #, mais par #'

#' moyenne d’un vecteur
#' Une fonction pour faire une moyenne en enlevant les valeurs manquantes
#'
#' @param x un vecteur numerique
#'
#' @return la fonction renvoie la moyenne d'un vecteur
#' @import magrittr
#' @importFrom stats na.omit
#' moyenne(c(4,5))
#' @export
moyenne <- function(x){
  x <- x %>% na.omit()
  sum(x)/length(x)
}

Les plus ninjas malins d’entre nous aiment à cliquer entre les {} d’une fonction pour utiliser le raccourci clavier Ctrl + Alt + Shift + R. Celui-ci génère directement un template de documentation. On peut aussi aller dans Code > Insert Roxygen Skeleton :

La fonction moyenne ici en exemple utilise le %>%, il faut donc lier notre package au package {magrittr} qui contient cet opérateur. De même la façon na.omit vient du package {stats}. Notre package dépend donc de {magrittr} et de {stats}. Un utilisateur qui ne disposerait pas de {magrittr} ou {stats} ne pourrait pas utiliser notre fonction !

C’est @importFrom qui permet d’aller chercher dans le package {stats} la fonction na.omit utilisée, et @import va importer l’intégralité des fonctions de {magrittr}.

Exit donc, les library() et autre require(), @importFrom et @import sont la clé. @import importe TOUT le package tandis que @importFrom permet de n’importer qu’un ensemble de fonctions. C’est @importFrom qui sera privilégié car c’est le niveau le plus fin.

MAIS cela ne suffit pas. Il faut aussi modifier le fichier DESCRIPTION pour y ajouter la dépendance aux deux packages {magrittr} et {stats} dans la rubrique Imports. En l’état, un check renverra des warnings/erreurs. (vous pouvez tester !)

Cependant, modifier le fichier DESCRIPTION à la main, c’est vivre dangereusement. Un simple espace au mauvais endroit et votre package va générer des erreurs. Nous allons donc privilégier l’usage de {devtools} (les plus avertis utiliseront {usethis}). Les instructions suivantes (que vous pouvez taper, pour l’instant, directement dans la console) vont ajouter les dépendances au bon endroit :

devtools::use_package("stats")
devtools::use_package("magrittr")

Le fichier DESCRIPTION ressemble maintenant à ceci :

Package: monpackage
Title: Simplement un Package de demonstration
Version: 0.0.0.9000
[email protected]: person("Vincent", "Guyader", email = "[email protected]", role = c("aut", "cre"))
Description: Package qui ne sert a rien de particulier.
Depends: R (>= 3.4.3)
License: GPL-3
Encoding: UTF-8
LazyData: true
Imports: stats,
    magrittr

Bravo ! vous avez un package ! Faites un check puis cliquez dans l’onglet build sur Install and Restart et admirez votre travail. R redémarre et lance votre package (on notera le library(monpackage) lancé pour nous dans la console). La fonction moyenne est utilisable et taper ?moyenne revient à interroger R sur l’aide de la fonction : une page de documentation du plus bel effet est ouverte dans l’onglet help.

Si vous avez compris ces quelques étapes, vous avez fait le plus difficile.

Bonnes pratiques

Afin de s’assurer de la reproductibilité de votre travail, il est recommandé de créer un fichier nommé devtools_history.R, qui n’a rien à faire dans le dossier R, mais contiendra tous les appels à {devtools} (ou {usethis}) que nous allons faire. Ce fichier est là uniquement pour les archives, pour conserver une trace. Il ne sera pas exécuté automatiquement, ni lu, mais c’est une bonne pratique d’en avoir un. Cela évite de taper directement des instructions dans la console. On pourra aussi y lire ce qu’ont fait nos collaborateurs pour construire le package.

Nous vous proposons de mettre ce fichier à la racine de votre package. Cependant, si vous faites cela, votre check retournera un warning car il aura détecté un fichier qui n’a rien à faire à cet endroit. Nous allons donc ajouter ce fichier dans la liste du .Rbuildignore grâce à une instruction… qu’il faudra taper dans devtools_history.R

Ainsi le fichier devtools_history.R de notre package devient :

devtools::use_package('magrittr')
devtools::use_package('stats')
usethis::use_build_ignore("devtools_history.R")

Pensez bien à faire les CTRL + Entrée qui vont bien pour exécuter ces lignes, elles ne seront pas automatiquement lancées.

Étape 5 : Partager son travail

Dans l’onglet Build se trouve un bouton more qui vous permet de construire votre package source (Build Source Package).

Cela génère un fichier compressé en tar.gz (ici monpackage_0.0.0.9000.tar.gz) que vous pouvez partager avec qui veut… voire l’envoyer au CRAN pour le rendre disponible à la communauté.

Et voilà, vous avez les bases. Exercez-vous ! Cela vaut vraiment le coup.

Si votre objectif est, à terme, de concevoir une application shiny, un nouvel article super détaillé arrive très vite.

Pour aller plus loin, et vraiment aller au bout du concept, voici les points que nous aborderons dans un autre article :

  • le travail avec Github
  • la documentation générale du package
  • l’ajout d’un jeu de données
  • les tests unitaires
  • le code coverage
  • la création de vignette
  • la mise en ligne sur le CRAN

Si vous avez des questions ou remarques, n’hésitez pas à poster vos commentaires au bas de cette page.


À propos de l'auteur

Vincent Guyader

Vincent Guyader

Codeur fou, formateur et expert logiciel R


Commentaires


À lire également