Research compendium

Objectif

L’objectif de cet exercice est de créer un research compendium, c.-à-d. un dossier de travail dont la structure est dérivée de celle d’un package . Vous allez découvrir les fichiers importants que nous vous recommandons d’ajouter à un projet de recherche. Vous allez aussi apprendre à écrire et documenter des fonctions .

Ce research compendium servira de base de travail tout au long de la formation.

NB. Cet exercice s’inspire du workshop proposé par Anna Krystalli.

Afin de nous assister dans la création de la structure de notre dossier de travail, nous allons utiliser le package rcompendium, développé dans le cadre de cette formation. Il permet d’automatiser la création des fichiers/répertoires spécifiques à un compendium/package .

Préambule

Installez le package rcompendium depuis le CRAN :

## Installation de 'rcompendium' ----
install.packages("rcompendium")

## Chargement du package -----
library("rcompendium")

  Si vous rencontrez des difficultés à installer le package, lisez attentivement le README.

Une fois le package installé, vous devez exécuter la fonction set_credentials() afin de stocker localement vos informations personnelles (prénom, nom, email, ORCID, protocole de communication avec GitHub). Ces informations permettront de remplir automatiquement certains fichiers.

  Cette fonction n’est à utiliser qu’une seule fois.

## Stockage de vos informations ----
set_credentials(given    = "Jane",
                family   = "Doe", 
                email    = "jane.doe@mail.me", 
                orcid    = "0000-0000-0000-0000", 
                protocol = "ssh")

Ces informations ont été copiées dans le presse-papier. Collez son contenu dans le fichier ~/.Rprofile (ouvert dans RStudio par cette fonction). Ce fichier est lu à chaque ouverture de et son contenu sera accessible aux fonctions du package rcompendium.

Redémarrez la session et vérifiez que vos informations personnelles sont bien accessibles.

## Vérification (après redémarrage de R) ----
options()$"email"
# [1] "jane.doe@mail.me"

Finalement, vérifiez que vous avez bien suivi les instructions pour configurer git en exécutant la commande gh::gh_whoami(). Vous devriez voir s’afficher :

{
  "name": "Jane Doe",
  "login": "jdoe",
  "html_url": "https://github.com/jdoe",
  "scopes": "repo, workflow",
  "token": "ghp_...ZZ9z"
} 

Projet RStudio

Créez un nouveau projet RStudio : File > New Project > New Directory > New Project

  • Choisissez un nom pour votre projet (sans signe de ponctuation)
  • Sélectionnez l’emplacement où le nouveau projet sera créé
  • Décochez toutes les autres cases et validez
Bonne pratique #1

Toujours travailler dans un Projet RStudio. Cela présente l’avantage de simplifier les chemins d’accès aux fichiers, notamment avec le package here et sa fonction here(). Les chemins d’accès seront toujours construits par rapport au dossier contenant le fichier .Rproj (racine du projet). On parle de chemin relatif. N’utilisez plus jamais la fonction setwd().

Fichier DESCRIPTION

Le fichier DESCRIPTION décrit les métadonnées du projet (titre, auteur, description, dépendances requises, etc.). C’est un des éléments essentiels d’un package . Ici, nous allons le détourner pour l’utiliser dans le cadre d’un compendium afin de bénéficier des outils de développement de packages . Ajoutons ce fichier avec la fonction add_description() de rcompendium.

## Ajout d'un fichier DESCRIPTION ----
add_description()

Comme vous le voyez, le fichier DESCRIPTION a été pré-rempli avec vos informations personnelles. Vous éditerez les champs Title et Description plus loin.

Bonne pratique #2

Toujours ajouter un fichier DESCRIPTION à la racine du projet. En plus de la description du projet, il permet de lister les packages dont le projet dépend (tags Imports, Depends et Remotes). Avec ce fichier, plus besoin d’utiliser les fonctions install.packages() et library(). Elles seront remplacées respectivement par remotes::install_deps() et devtools::load_all().

Choix d’une Licence

Tout matériel partagé en ligne doit disposé d’une licence qui décrit ce qu’il est possible de faire avec. Ainsi, nous vous recommandons d’ajouter dès le début du projet une licence. Pour savoir quelle licence est la plus appropriée à votre projet, rendez-vous sur cette page : https://choosealicense.com.

Ajoutons la licence GPL-2 à notre projet avec la fonction add_license() de rcompendium.

## Ajout d'une licence ----
add_license(license = "GPL-2")

Notez qu’un nouveau fichier a été créé : LICENSE.md. Celui-ci détaille le contenu de la license choisie et sera lu par GitHub. Regardez aussi le contenu du fichier DESCRIPTION : la section License a été mise à jour.

Bonne pratique #3

Toujours ajouter une LICENSE à un projet qui sera rendu public. Visitez le site Choose a License pour choisir la plus appropriée à votre projet.

  Si aucune licence n’est renseignée, votre projet est soumis aux règles de la No License : aucune permission n’est accordée. En d’autres termes, personne ne peut rien faire avec votre projet (pas d’utilisation, pas de modification, pas de partage, etc.).

Ajout des répertoires

La prochaine étape consiste en la création de sous-répertoires, chacun ayant un rôle précis. Pour cela, utilisez la fonction add_compendium() de rcompendium.

## Ajout de sous-répertoires ----
add_compendium(compendium = c("data", "analyses", "R", "figures", "outputs"))
Bonne pratique #4

Un bon Research compendium sera composé de différents sous-répertoires, chacun destiné à accueillir un certain type de fichiers. Par ex., le dossier data/ contiendra toutes les données brutes nécessaires au projet. Le dossier outputs/ contiendra tous les résultats générés (hors figures). Le dossier figures/ contiendra toutes les figures produites par les analyses. Le dossier R/ ne contiendra que des fonctions (et leurs documentations). Le dossier analyses/ contiendra des scripts (ou des fichiers .Rmd et/ou .qmd) qui appeleront les fonctions . Cette structure peut bien sûr être adaptée selon les besoins et la complexité du projet.

Implémentation des fonctions

Nous voilà fin prêt à coder !

Le dépôt GitHub https://github.com/rdatatoolbox/datarepo contient les données que nous utiliserons tout au long de la formation. Celles-ci proviennent de deux bases de données : PanTHERIA et WWF WildFinder. Lisez attentivement le README pour plus de détails.

Objectif : écrire 1, 2 ou 4 fonctions , qui vont permettre de télécharger les quatre fichiers de données hébergées sur le dépôt GitHub mentionné ci-dessus. Les fichiers seront enregistrés dans le dossier data/ et le sous-dossier spécifique à leurs bases de données (pantheria/ ou wildfinder/).

Utilisez la fonction usethis::use_r() pour créer le/les fichiers .R dans le dossier R/.

Utilisez les fonctions dir.create(), here::here() et utils::download.file().

Proposition de fonction (essayer de ne pas regarder)

Code
dl_pantheria_data <- function(overwrite = FALSE) {
  
  ## Destination ---- 
  path <- here::here("data", "pantheria")
  
  ## File name ----
  filename <- "PanTHERIA_1-0_WR05_Aug2008.txt"
  
  ## GitHub URL ----
  url <- paste0("https://raw.githubusercontent.com/rdatatoolbox/datarepo/main/",
                "data/pantheria/")
  
  if (file.exists(file.path(path, filename)) && !overwrite) {
    
    ## Check if exists locally ----
    message("The filename already exists. Use 'overwrite = TRUE' to replace it")
    
  } else {
    
    ## Create destination folder ----
    dir.create(path, showWarnings = FALSE, recursive = TRUE)
    
    ## Download file ----
    utils::download.file(url      = paste0(url, filename),
                         destfile = file.path(path, filename),
                         mode     = "wb")
  }

  invisible(NULL) 
}
Bonne pratique #5

Ecrivez des fonctions : on parle de Factorisation de code. Cela rendra votre code plus clair et plus facilement réutilisable. Placez toujours vos fonctions dans le dossier R/. Si vous utilisez des fonctions de dépendances externes, priviligiez cette écriture : package::fonction().

Documentation

Maintenant, documentez votre/vos fonctions. C’est essentiel ! Pour cela, ajoutez un entête roxygen2 à vos fonctions. Cette syntaxe permet de documenter efficacement toute fonction .

Cette entête devra contenir (a minima) un titre, une description de chaque argument et le retour de la fonction.

Proposition de documentation (essayer de ne pas regarder)

Code
#' Download PanTHERIA dataset
#'
#' @description 
#' This function downloads the PanTHERIA dataset (text file) hosted on the 
#' GitHub repository <https://github.com/rdatatoolbox/datarepo/>. The file
#' won't be downloaded if already exists locally (except if `overwrite = TRUE`).
#' The file `PanTHERIA_1-0_WR05_Aug2008.txt` will be stored in 
#' `data/pantheria/`. This folder will be created if required.
#' 
#' @param overwrite a `logical`. If `TRUE`, the file will be downloaded again 
#'   and the previous version will be replaced.
#'
#' @return No return value.
#' 
#' @export

dl_pantheria_data <- function(overwrite = FALSE) { ... }

Optionnel : transpilez vos entêtes roxygen2 en fichiers .Rd, seuls fichiers acceptés par pour documenter des fonctions. Ces fichiers .Rd seront stockés dans le dossier man/.

## Génération de la doc ----
devtools::document()

L’aide de votre fonction est maintenant accessible via ?nom_fonction.

Bonne pratique #6

Pensez aux autres (et au vous du futur) : documentez toujours votre code. Un code sans documentation est inutile. Utilisez les entêtes roxygen2 pour documenter vos fonctions , de simples commentaires pour documenter du code et des README pour tout le reste.

Ajout des dépendances

Nos fonctions contiennent des dépendances à deux packages externes : utils et here. Nous devons ajouter ces dépendances au fichier DESCRIPTION. Pour cela, nous allons utiliser la fonction add_dependencies() de rcompendium.

## Ajout de dépendances ----
add_dependencies(compendium = ".")

Regardez le contenu du fichier DESCRIPTION. Par défaut, les packages requis sont listés sous le tag Imports. Ainsi, pour utiliser une fonction externe, il faudra l’appeler par package::fonction(). Si vous remplacer le tag Imports par Depends, l’utilisation de la fonction devtools::load_all() (voir plus bas) aura le même effet qu’un library() et vous pourrez utiliser une fonction externe par fonction().

La fonction add_dependencies() va scanner chaque fichier .R, .Rmd et .qmd du projet et détecter les packages externes utilisés pour les ajouter automatiquement au fichier DESCRIPTION.

Bonne pratique #7

Listez toujours les packages requis dans le fichier DESCRIPTION. Ainsi, vous centralisez la liste des packages requis en un seul endroit et vous pourrez utiliser les fonctions remotes::install_deps() et devtools::load_all().

Chargement du projet

Maintenant que notre compendium contient les éléments clés d’un package , c.-à-d. un fichier DESCRIPTION et un répertoire R/, nous pouvons utiliser les outils de développement des packages pour réaliser deux tâches.

  1. Les packages requis peuvent être installés (ou mis à jour) à l’aide de la fonction remotes::install_deps(). Pour être plus reproductible, vous pouvez désactiver les mises à jour en ajoutant l’argument upgrade = "never". Cette fonction vient remplacer la fonction install.packages(). Elle va lire le fichier DESCRIPTION pour récupérer la liste des packages requis. Il est donc important d’utiliser régulièrement la fonction add_dependencies() pour tenir ce fichier à jour.

  2. Les fonctions stockées dans le dossier R/ peuvent être chargées avec la fonction devtools::load_all(). Cette fonction vient remplacer la fonction source() (et library() si les packages requis sont listés sous le tag Depends dans le fichier DESCRIPTION). Ce qui est pratique dans le cas où on doit charger de nombreux fonctions . Après chaque modification d’une fonction , n’oubliez pas d’exécuter la fonction devtools::load_all().

Essayez ces deux fonctions.

## Installation des packages manquants ----
remotes::install_deps(upgrade = "never")

## Chargement des packages et fonctions R ----
devtools::load_all()
Bonne pratique #8

Avec un fichier DESCRIPTION (listant les dépendances requises) et un dossier R/, vous pouvez utiliser les fonctions remotes::install_deps() (installation/mise à jour des packages) et devtools::load_all() (chargement du projet) au lieu de install.packages(), library() et source().

Ajout d’un make.R

Afin d’automatiser notre projet, nous allons créer un script à la racine du projet. Nous l’appelerons, par convention, make.R. Celui-ci aura deux objectifs : 1) mettre en place le projet et 2) exécuter le projet. L’idée est de n’exécuter que ce script. Utilisez la fonction add_makefile() de rcompendium et lisez le contenu du nouveau fichier créé.

## Ajout d'un makefile ----
add_makefile()
Bonne pratique #9

Bien que non essentiel, un fichier make.R placé à la racine du projet permet de facilement mettre en place le projet (installation et chargement des packages requis et des fonctions ) et d’exécuter les différentes analyses de manière séquentielle (en sourçant les scripts qui appellent eux-même les fonctions ). C’est la porte d’entrée des analyses.

Appel aux fonctions

Jusqu’à présent, nous n’avons fait que définir des fonctions , mais nous ne les avons pas exécutées. Nous allons créer notre premier script dans le dossier analyses/. Celui-ci aura pour objectif d’appeler les fonctions définies précédemment pour télécharger les données.

Créez un nouveau script comme suit et éditez-le:

## Ajout d'un script R ----
utils::file.edit(here::here("analyses", "download-data.R"))

Proposition de contenu (essayer de ne pas regarder)

Code
# Download project raw data
#
# This script will download the PanTHERIA and WWF WildFinder datasets. The
# four files will be stored in `data/`.
# 
# All functions used in the script have been developed for this project
# and can be found in the folder R/.
#
# Jane Doe <jane.doe@mail.me>
# 2023/11/09

## Download PanTHERIA database ----

dl_pantheria_data(overwrite = FALSE)


## Download WWF WildFinder database ----

# ...

N.B. Nous verrons plus tard comment transformer ce script en un fichier Quarto (.qmd).

Finalement, ajoutez une ligne dans le fichier make.R qui permettra d’exécuter ce script . Utilisez les fonctions source() et here::here() pour cela.

Pour charger le projet et lancer les analyses, il suffit d’exécuter ce fichier make.R.

Bonne pratique #10

Le dossier analyses/ contient les scripts qui appellent les fonctions stockées dans le dossier R/. Il peut être ignoré dans le cas de simples analyses. Le code de l’analyse devra alors se trouver dans le make.R. A contrario, dans le cas d’analyses complexes, n’hésitez pas à multiplier les scripts (plutôt que d’avoir un seul gros script).

Ajout d’un README

Plus tard, vous verrez comment envoyer ce projet sur GitHub. Pour l’instant, nous allons légèrement anticiper les cours suivants, et ajouter un README à notre compendium. Ce sera la vitrine du projet. Les rôles d’un README sont : 1) de présenter le projet, 2) d’expliquer son contenu, et 3) d’expliquer comment l’installer et l’utiliser.

Pour cela, vous allez ajouter un README.Rmd (fichier R Markdown) à la racine de votre projet avec la fonction add_readme_rmd() de rcompendium.

## Ajout d'un README ----
add_readme_rmd(type = "compendium")

Adaptez son contenu et n’oubliez pas de transpiler ce fichier .Rmd en un fichier .md (fichier Markdown lu par GitHub). Utilisez le bouton Knit de RStudio ou :

## Conversion du README.Rmd en README.md ----
rmarkdown::render("README.Rmd")

  N’oubliez pas aussi d’éditer les sections Title et Description du fichier DESCRIPTION.

Bonne pratique #11

Ajoutez un README à votre projet afin d’aider l’utilisateur à comprendre votre projet.

Félicitation ! Votre research compendium est maintenant fonctionnel.

Bonus

Ecrivez une fonction qui va télécharger le fichier README.md du dépôt https://github.com/rdatatoolbox/datarepo/ et enregistrez-le dans le dossier data/. Vous ajouterez ainsi la description des données à votre projet.

Proposition de fonction (essayer de ne pas regarder)

Code
#' Download data description (README)
#'
#' @description 
#' This function downloads the `README.md` of the GitHub repository
#' <https://github.com/rdatatoolbox/datarepo/> providing data description.
#' The file won't be downloaded if already exists locally (except if 
#' `overwrite = TRUE`).
#' The `README.md` will be stored in `data/`.
#' 
#' @param overwrite a logical. If `TRUE`, the file will be downloaded again and
#'   the previous version will be erased.
#'
#' @return No return value.
#' 
#' @export

dl_data_descr <- function(overwrite = FALSE) {
  
  ## Destination location ---- 
  path <- here::here("data")
  
  ## File name ----
  filename <- "README.md"
  
  ## GitHub URL ----
  url <- "https://raw.githubusercontent.com/rdatatoolbox/datarepo/main/"
  
  if (file.exists(file.path(path, filename)) && !overwrite) {
    
    ## Check if exists locally ----
    message("The filename already exists. Use 'overwrite = TRUE' to replace it")
    
  } else {
    
    ## Create destination folder ----
    dir.create(path, showWarnings = FALSE, recursive = TRUE)
    
    ## Download file ----
    utils::download.file(url      = paste0(url, filename),
                         destfile = file.path(path, filename),
                         mode     = "wb")
  }

  invisible(NULL) 
}
La fonction new_compendium()

L’ensemble de ces étapes peut être réalisé avec une seule fonction : new_compendium() de rcompendium.
N’oubliez pas de créer un nouveau projet RStudio avant si vous voulez l’utiliser.