Packages R: développer un package

De Wiki ISIG
Aller à la navigation Aller à la recherche

Développer un package R avec RStudio

Ce tutoriel présente une démarche simple pour créer un package R moderne, documenté et testé. Il s'appuie sur les packages devtools, usethis, roxygen2 et testthat, qui constituent aujourd'hui l'écosystème de référence pour le développement de packages.

Prérequis

Installer les principaux outils de développement :

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

Il est également recommandé d'utiliser une version récente de R et RStudio.

1. Créer le package

Dans RStudio :

File > New Project > New Directory > R Package

Choisir :

  • le nom du package (sans espaces ni caractères spéciaux)
  • le dossier de création

RStudio crée automatiquement toute l'architecture du package.

On obtient notamment :

monpackage/

├── DESCRIPTION
├── NAMESPACE
├── R/
├── man/
└── monpackage.Rproj

Les rôles des principaux dossiers sont :

Élément Rôle
R/ Contient toutes les fonctions du package
man/ Documentation générée automatiquement
DESCRIPTION Métadonnées et dépendances
NAMESPACE Fonctions exportées et imports (généré automatiquement)

Les fichiers NAMESPACE et man/ ne doivent généralement pas être modifiés à la main lorsqu'on utilise roxygen2.

2. Initialiser le package

Plusieurs outils peuvent être ajoutés immédiatement grâce à usethis.

Initialiser Git

usethis::use_git()

Éventuellement :

usethis::use_github()

pour créer directement un dépôt GitHub.

Ajouter une licence

Par exemple :

usethis::use_mit_license()

ou

usethis::use_gpl3_license()

Ajouter un README

usethis::use_readme_rmd()

3. Écrire des fonctions

Toutes les fonctions doivent être placées dans le dossier

R/

Par exemple :

R/calcul.R

contenant

addition <- function(x, y) {
  x + y
}

Le nom du fichier est libre.

En revanche, les fonctions doivent toujours être dans le dossier R/.

4. Documenter avec Roxygen

Chaque fonction est précédée d'un bloc de commentaires.

Exemple :

#' Addition de deux nombres
#'
#' Additionne deux valeurs numériques.
#'
#' @param x premier nombre
#' @param y second nombre
#'
#' @return Une valeur numérique.
#'
#' @export
addition <- function(x, y) {
  x + y
}

La documentation est ensuite générée automatiquement.

Dans RStudio :

Build > More > Document

ou

devtools::document()

Cette commande met à jour :

  • le dossier man/
  • le fichier NAMESPACE

Ces fichiers ne doivent normalement jamais être édités manuellement.

5. Gérer les dépendances

Éviter de modifier directement le fichier DESCRIPTION.

Préférer :

usethis::use_package("sf")

qui ajoute automatiquement

Imports:
    sf

Pour une dépendance utilisée uniquement pendant le développement :

usethis::use_package("testthat", type = "Suggests")

Les types de dépendances les plus fréquents sont :

Type Utilisation
Imports Package nécessaire au fonctionnement
Suggests Package utilisé uniquement pour les tests ou des fonctionnalités optionnelles
Depends Rarement utilisé aujourd'hui

6. Charger le package pendant le développement

Pendant le développement, il n'est pas nécessaire de réinstaller le package après chaque modification.

Utiliser :

devtools::load_all()

Cette commande :

  • charge toutes les fonctions ;
  • recharge les données ;
  • recharge les fichiers R modifiés ;
  • permet de tester immédiatement les nouvelles fonctions.

C'est la commande utilisée en permanence pendant le développement.

7. Différence entre load_all() et le bouton Build

Ces deux actions ont des objectifs très différents.

devtools::load_all()

À utiliser pendant le développement.

Il simule un package installé mais sans construire réellement le package.

C'est très rapide.

Idéal pour :

  • tester une fonction ;
  • lancer une application Shiny ;
  • faire du débogage.

Build

Le bouton Build réalise une véritable construction du package.

Selon l'action choisie, RStudio peut :

  • documenter le package ;
  • construire le fichier .tar.gz ;
  • installer le package ;
  • vérifier (R CMD check) qu'il respecte les standards CRAN.

Cette étape est plus lente mais permet de détecter des erreurs qui ne sont pas visibles avec load_all().

En pratique :

  • load_all() est utilisé des dizaines de fois par jour ;
  • Build est utilisé avant un commit important ou avant une diffusion.

8. Tester avec testthat

Initialiser les tests :

usethis::use_testthat()

Créer un premier fichier de test :

usethis::use_test("addition")

On obtient :

tests/testthat/test-addition.R

Exemple :

test_that("addition fonctionne", {

  expect_equal(addition(2,3),5)

})

Exécuter tous les tests :

devtools::test()

Les tests permettent de vérifier que les fonctions continuent à produire les résultats attendus après des modifications.

9. Vérifier le package

Avant toute diffusion :

devtools::check()

Cette commande lance l'équivalent de :

R CMD check

Elle détecte notamment :

  • erreurs ;
  • avertissements ;
  • notes ;
  • documentation incomplète ;
  • dépendances oubliées.

Un package destiné au partage devrait idéalement être exempt d'erreurs et d'avertissements.

10. Cas particulier : package contenant une application Shiny

Un package peut également contenir une application Shiny.

L'organisation est généralement la suivante :

R/
    app.R
    server.R
    ui.R
    modules/
    utils.R

La fonction principale est par exemple :

run_app <- function() {

  shiny::shinyApp(
    ui = app_ui(),
    server = app_server
  )

}

Pendant le développement :

devtools::load_all()

run_app()

L'application utilise alors directement les fonctions du package en cours de développement.

Cette approche présente plusieurs avantages :

  • les fonctions sont réutilisables ;
  • les tests peuvent porter sur la logique métier indépendamment de Shiny ;
  • le code est mieux organisé ;
  • il devient facile de documenter et partager les fonctions.

Le package golem automatise une grande partie de cette architecture et constitue aujourd'hui une référence pour le développement d'applications Shiny complexes.

Bonnes pratiques

  • Une fonction = une responsabilité.
  • Documenter chaque fonction.
  • Écrire des tests dès que possible.
  • Utiliser usethis plutôt que modifier les fichiers système à la main.
  • Utiliser load_all() pendant le développement.
  • Lancer régulièrement devtools::check().
  • Versionner le projet avec Git.

En suivant cette démarche, on obtient un package facile à maintenir, documenté, testable et partageable.