Bonnes pratiques pour les projets statistiques

Une formation aux bonnes pratiques avec Git et R

Introduction

  • Version longue de la formation aux bonnes pratiques avec R et Git

Retour à la page d’accueil pour explorer les autres versions

Introduction

La notion de bonnes pratiques

  • Origine : communauté des développeurs logiciels
  • Constats :
    • le “code est plus souvent lu qu’écrit” (Guido Van Rossum)
    • la maintenance d’un code est très coûteuse
  • Conséquence : un ensemble de règles informelles, conventionnellement acceptées comme produisant des logiciels fiables, évolutifs et maintenables

Pourquoi s’intéresser aux bonnes pratiques ?


L’activité du statisticien / datascientist tend à se rapprocher de celle du développeur :

  • projets intenses en code
  • projets collaboratifs et de grande envergure
  • complexification des données et donc des infrastructures
  • déploiement d’applications pour valoriser les analyses

Bonnes pratiques et reproductibilité

Source : Peng R., Reproducible Research in Computational Science, Science (2011)

  • Une reproductibilité parfaite est coûteuse
  • Git est un standard atteignable et efficient

Note

Quel socle de bonnes pratiques pour les projets statistiques en R ?

Partie 1 : contrôle de version avec Git

Plan de la partie

1️⃣ Le contrôle de version : pourquoi faire ?

2️⃣ Le contrôle de version avec Git

3️⃣ Le travail collaboratif avec Git

I- Le contrôle de version : pourquoi faire ?

1️⃣ Archiver son code proprement

pour en finir avec ça :

1️⃣ Archiver son code proprement

ou ça :

1️⃣ Archiver son code proprement

ou encore ça :

prior <- read_csv(prior_path)
prior <- prior %>%
    select(id, proba_inter, proba_build, proba_rfl) %>%
    separate(id, into = c('nidt', 'grid_id'), sep = ":") %>%
    group_by(nidt) %>%
    mutate(
        proba_build = proba_build/sum(proba_build),
        proba_rfl = proba_rfl/sum(proba_rfl),
        ) %>%
    unite(col = "id", nidt, grid_id, sep = ":")

# Test
# prior_test <- prior %>%
#    mutate(
#        proba_inter = round(proba_inter, 4)
#        proba_build = round(proba_build, 4)
#        proba_rfl = round(proba_rfl, 4)
#    )

write_csv(prior_round, "~/prior.csv")

1️⃣ Archiver son code proprement

Pour arriver à ça :

Source : ThinkR

2️⃣ Voyager dans le temps (de votre projet)

3️⃣ Une collaboration simplifiée et efficace

Un modèle distribué

Source : specbee.com

3️⃣ Une collaboration simplifiée et efficace

Qui permet l’expérimentation en toute sécurité

Source : lutece.paris.fr

3️⃣ Une collaboration simplifiée et efficace

Quel que soit l’environnement de travail

3️⃣ Une collaboration simplifiée et efficace

Avec des outils pour faciliter la collaboration

4️⃣ Partager son code à un public large

Une vitrine pour les projets et l’organisation

En résumé

  • Construire et naviguer à travers l’historique de son projet
  • La collaboration rendue simple et efficace
  • Améliorer la reproductibilité de ses projets
  • Améliorer la visibilité de ses projets

II- Le contrôle de version avec Git

⚠️ Git est complexe

L’utilisation de Git nécessite certaines notions préalables:

  • Fonctionnement d’un filesystem
  • Connaissance basique du terminal Linux
  • Potentiellement, un grand nombre de commandes

Source : Ici

⚠️ Git est complexe

Mais

  • L’usage quotidien n’implique que quelques commandes
  • Lors de chaque action, Git renvoie un message qui indique s’il y a eu problème (il faut lire les messages de Git!)
  • Enormément de ressources disponibles sur internet
  • Des interfaces visuelles (ex: RStudio, Sublime Merge, VS Code) qui facilitent l’apprentissage
  • Un petit investissement individuel pour de gros gains collectifs

Concepts

Git, GitHub, GitLab… quelles différences ?

  • Git est un logiciel ;
  • Utilisation en ligne de commandes
  • Différentes interfaces graphiques (RStudio, VS Code…)

Concepts

Git, GitHub, GitLab… quelles différences ?

  • GitHub et GitLab sont des forges logicielles
  • Forge: espace d’archivage de code
  • Des fonctionalités supplémentaires : réseau social du code

Astuce

  • GitHub : utilisation pour les projets open-source
  • GitLab : utilisation pour les projets internes

Concepts

Dépôt local / dépôt distant (remote)

  • Par défaut, le dépôt distant porte l’alias origin

Concepts

Workflow local

Source : Git Documentation

Concepts

Workflow complet

Source : itnext.io

Commandes essentielles


Action Commande
Cloner un projet git clone [url-to-git-repo]
Afficher les changements git status
Retrouver l’URL du dépôt distant git remote -v

Commandes essentielles


Action Commande
Ajouter des changements à l’index de Git (stage fixes) Un seul fichier : git add <file-name>
Tous les fichiers déjà indexés : git add -u
Tous les fichiers ⚠️ : git add -A


Warning

La méthode git add -A peut amener à suivre les modifications de fichiers qui ne devraient pas l’être (par exemple, des données).

Il est recommandé de bien réfléchir avant de l’utiliser (ou d’avoir un bon .gitignore)

Commandes essentielles


Action Commande
Faire un commit git commit -m "message"
Pousser les changements locaux vers le dépôt distant (branche master) git push origin master
Récupérer les changements sur le dépôt distant (branche master) git pull origin master

Modes d’authentification

  • https
    • git clone https://github.com/username/projet.git
    • simple à utiliser
    • authentification username + password (ou token) à chaque push
  • ssh
    • git clone git@github.com:username/projet.git
    • (plus) complexe à initialiser
    • authentification automatique

Application 0

Préparation de l’environnement de travail

  1. Créer un compte GitHub
  2. Créer un nouveau dépôt privé sur GitHub
  3. Créer un compte sur le SSP Cloud
  4. Lancer un service RStudio. Dans l’onglet de configuration Git du service, fixer la durée du Cache pour le stockage des identifiants GitHub à une valeur suffisamment élevée
  5. Cloner le dépôt distant sur votre environnement local (ici, le RStudio du Datalab):
    • FileNew projectVersion ControlGit
  6. Générer un token (jeton d’authentification) sur GitHub
  7. Stocker le token sur le SSP Cloud (ou un gestionnaire de mot de passe) :
    • Mon Compte -> Services externes -> Jeton d'accès personnel GitHub
  8. Terminer la procédure de clonage en fournissant le nom d’utilisateur GitHub et le token

Question : qu’est ce qui différencie le projet cloné d’un projet quelconque ?

Application 1

Premiers commits

  1. Créer un dossier 📁 scripts
  2. Y créer les fichiers script1.R et script2.R, chacun contenant quelques commandes R de votre choix
  3. Ajouter ces fichiers à la zone de staging de Git en les cochant dans l’interface RStudio
  4. Effectuer un commit, auquel on donnera un message descriptif pertinent
  5. Supprimer le fichier script1.R et modifier le contenu du fichier script2.R
  6. Analyser ce qui se passe lorsque l’on coche ces fichiers dans l’interface RStudio
  7. Effectuer un nouveau commit pour ajouter ces modifications à l’historique
  8. Visualiser l’historique du projet à partir de l’interface graphique de RStudio

Question : à ce stade, le dépôt du projet sur GitHub (remote) a-t-il été modifié ?

Application 2

Interactions avec le dépôt distant

  1. Effectuer un push pour intégrer les changements locaux au projet distant
  2. Parcourir l’historique du projet sur GitHub
    1. Faire apparaître les différences entre deux versions consécutives du projet
    2. Afficher une version passée du projet

Bonnes pratiques

Que versionne-t-on ?

  • Essentiellement du code source
  • Pas d’outputs (fichiers .html, .pdf, modèles…)
  • Pas de données, d’informations locales ou sensibles

Note

Pour définir des règles qui évitent de committer tel ou tel fichier, on utilise un fichier nommé .gitignore.

Si on mélange du code et des éléments annexes (output, données…) dans un même dossier, il faut consacrer du temps à ce fichier.

Des modèles de .gitignore existent sur internet, par exemple celui-ci pour les projets .

N’hésitez pas à y ajouter des règles conservatrices (par exemple *.csv), comme cela est expliqué dans la documentation utilitR.

Bonnes pratiques

Format des commits

  • Fréquence
    • Aussi souvent que possible
    • Le lot de modifications doit “avoir du sens”
  • Messages
    • Courts et informatifs (comme un titre de mail)
    • Décrire le pourquoi plutôt que le comment dans le texte

Application 3

Le fichier .gitignore

Lors de la création du projet sur GitHub, nous avons demandé la création d’un fichier .gitignore, qui se situe à la racine du projet. Il spécifie l’ensemble des fichiers qui seront toujours exclus de l’indexation faite par Git.

  1. Exclure les fichiers de type *.pdf et *.html
  2. Créer un dossier data à la racine du projet et créer à l’intérieur de celui-ci un fichier data/raw.csv avec une ligne de données quelconque
  3. Ajouter au .gitignore le dossier data/
  4. Vérifier que toutes les règles ajoutées précédemment fonctionnent comme attendu

Question : que se passe-t-il lorsque l’on ajoute au .gitignore des fichiers qui ont déjà été commit sur le projet Git ?

III- Le travail collaboratif avec Git

Outils pour le travail collaboratif

  • L’éco-système Git facilite le travail collaboratif
    • Git : modèle des branches
    • GitHub / GitLab : Issues, Pull Requests, Forks
  • Ces outils ne remplacent pas une bonne définition de l’organisation du travail en équipe
    • Choix d’un workflow
    • Droits d’accès
    • Règles de contribution

Application 4

Synchronisation des dépôts

  1. Se mettre par groupes de 3/4 personnes:
    • Une personne aura la responsabilité d’être mainteneur
    • Deux à trois personnes seront développeurs
  2. Le mainteneur crée un dépôt sur Github. Il/Elle donne des droits au(x) développeur(s) du projet
  3. Créer une copie locale (clone) du projet sur son service RStudio du Datalab
  4. Créer un fichier <votre_nom>-<votre_prenom>.md. Écrire dedans trois phrases de son choix sans ponctuation ni majuscules, puis commit et push les modifications
  5. À ce stade, une seule personne (la plus rapide) devrait ne pas avoir rencontré de rejet du push. C’est normal ! Le premier ayant fait un push a modifié le dépôt commun ; les autres doivent intégrer ces modifications dans leur version locale (pull) avant d’avoir le droit de proposer un changement.
  6. Néanmoins, le pull renvoie également une erreur : Git ne parvient pas à résoudre la divergence d’historique. Essayons de comprendre le problème et les solutions possibles.

Divergence d’historiques : cas simple

  • Git résout le problème via un fast-forward merge
    • Le commit distant est rajouté à l’historique local
    • Les dépôts sont synchronisés

Divergence d’historiques : cas compliqué

  • Git ne peut pas résoudre de lui même la divergence
  • Deux stratégies possibles
    • Le merge
    • Le rebase

1ère possibilité : le merge

  • Git crée un commit de merge
    • Comportement par défaut (jusqu’à récemment)

2ème possibilité : le rebase (1/2)

  • Git effectue 3 étapes
    • Supprime temporairement le commit local
    • Réalise un fast-forward merge
    • Rajoute le commit local au bout de l’historique

2ème possibilité : le rebase (2/2)

  • Avantage : l’historique reste linéaire

Application 4 (suite)

Synchronisation des dépôts

  1. Dans notre cas, on peut utiliser rebase sans trop de risques. Comme l’interface RStudio ne permet pas (encore) de sélectionner la stratégie de pull, on va préciser la configuration voulue via une commande passée dans le terminal
    • git config pull.rebase true
  2. Pour ceux dont le push a été refusé, effectuer un pull des modifications distantes via l’interface RStudio
  3. Effectuer à nouveau un push de vos modifications locales
  4. Les derniers membres du groupe devront refaire les étapes précédentes, potentiellement plusieurs fois, pour pouvoir push leurs modifications locales

Question : que se serait-il passé si les différents membres du groupe avaient effectué leurs modifications sur un seul et même fichier ?

Application 5

Résoudre les conflits

  1. On se place dans la même configuration que dans l’application précédente : un mainteneur et deux/trois développeurs
  2. Le mainteneur modifie le contenu de son fichier, puis commit et push les modifications
  3. Sans faire de pull préalable, les développeurs modifient également le contenu du fichier du mainteneur, puis commit et push les modifications
  4. Le push est rejeté pour la même raison que dans l’application précédente : les dépôts ne sont plus synchronisés, il faut pull les changements distants au préalable. Mais cette fois, le pull est également rejeté : il y a un conflit entre l’historique du projet distant et celui du projet local. Git nous indique qu’il faut résoudre le conflit avant de pouvoir modifier l’historique du projet.
  5. Utiliser l’interface de RStudio pour résoudre le conflit, en choisissant la version du fichier que vous souhaitez conserver, puis commit/push les modifications
  6. Comme dans l’application précédente, seul le développeur le plus rapide parvient à push. Les autres doivent répéter l’opération.

Question : comment limiter au maximum la survenue des conflits d’historique ?

Le modèle des branches

Le modèle des branches

Exemple d’organisation : le GitHub flow

Description plus détaillée : ici

Application 6

Branches, issues et pull requests

  1. Sur GitHub, chaque personne ouvre une Issue sur le même dépôt que les applications précédentes, dans laquelle vous suggérez une modification à apporter à votre projet
  2. Créer une branche dont le nom indique la modification que vous allez apporter (ex : ajout-authentification)
  3. Effectuer un commit avec les modifications de votre choix, puis pousser les changements sur une nouvelle branche du dépôt distant
  4. Ouvrir une Pull Request (PR) pour proposer d’intégrer vos changements sur la branche principale du dépôt distant. Spécifier que l’acceptation de la Pull Request entraînera la fermeture automatique de l’Issue associée en écrivant dans le corps de la PR : close #NN est le numéro de l’Issue en question
  5. Chaque personne effectue finalement une review d’une PR d’un autre membre de l’équipe, suite à quoi les différentes PR peuvent être fusionnées

Question : quelle organisation pour merge dans la branche principale ?

Application 7

Utilisation de GitLab

Cette application vise à prendre en matin l’interface de GitLab, éventuellement dans le cadre de votre environnement interne de travail.

  1. Dans le cadre d’un GitLab interne, il est plus confortable d’utiliser l’authentification via clé ssh plutôt que l’authentification par token. Si vous n’en avez pas encore, générer une clé ssh et ajouter celle-ci à votre compte GitLab. Se référer pour cela à la documentation GitLab ou, idéalement, à la documentation de cette procédure sur votre environnement interne de travail.
  2. Explorer l’interface de GitLab pour retrouver les différents éléments que nous avons exploités sur l’interface de GitHub.
  3. Créer un projet dans son espace personnel sur GitLab et répéter les opérations des applications 1 et 2.

Application 8 (bonus)

Contribution à un projet open-source

  1. Sur GitHub, faire un fork du dépôt git-exo
  2. Créer une branche intitulée <votre_nom>-<votre_prénom>
  3. Effectuer un commit avec les modifications de votre choix, puis pousser les changements sur votre fork
  4. Ouvrir une Pull Request (PR) pour proposer d’intégrer vos changements sur la branche principale du dépôt git-exo
  5. Ouvrir une Issue dans laquelle vous mentionnez la Pull Request précédemment ouverte

Question : pourquoi, avec un fork, est-il très important de toujours effectuer une Pull Request à partir d’une branche différente de la branche principale ?

Ressources supplémentaires

Partie 2 : bonnes pratiques avec R

Plan de la partie

Améliorations graduelles dans l’échelle de la reproductibilité :

1️⃣ Qualité du code

2️⃣ Structure des projets

3️⃣ Formats de données

4️⃣ Environnements reproductibles

5️⃣ Pipelines de données

6️⃣ Publication reproductible

Application 0

Préparation de l’environnement de travail

  1. Si vous ne l’avez pas fait lors de la première partie de la formation, créer un compte sur GitHub et générer un token (jeton d’accès) d’authentification
  2. Créer un nouveau dépôt public sur GitHub
  3. Lancer un service RStudio. Dans l’onglet de configuration Git du service, fixer la durée du Cache pour le stockage des identifiants GitHub à une valeur suffisamment élevée
  4. Cloner le dépôt distant sur votre environnement local (ici, le RStudio du Datalab):
    • FileNew projectVersion ControlGit
  5. Créer un script get_data.R en copiant le contenu de ce fichier, puis l’exécuter
  6. Créer le script script.R dans votre dépôt en copiant le contenu de ce fichier
  7. Ajouter la règle “individu_reg.*” au fichier .gitignore. Que signifie-t-elle ?
  8. Commit/push les changements

I- Qualité du code

Enjeux

  • D’une vision utilitariste du code à une vision du code comme outil de communication
  • Favoriser la lisibilité et la maintenabilité
  • Faciliter la réutilisation
  • Assurer la transparence méthodologique

Principes généraux

  1. Adopter les standards communautaires
  1. Utiliser des fonctions
  1. Documenter son code
  1. Indiquer les packages utilisés afin d’éviter les conflits

1️⃣ Adopter les standards communautaires

“Good coding style is like correct punctuation: you can manage without it, butitsuremakesthingseasiertoread”

Tidyverse Style Guide

  • Respecter les conventions du langage dans lequel il est rédigé

1️⃣ Adopter les standards communautaires

Deux outils pratiques aident à respecter les standards :

  1. linter : programme qui vérifie que le code est formellement conforme à un certain guidestyle
    • signale problèmes formels, sans corriger
  1. formatter : programme qui reformate un code pour le rendre conforme à un certain guidestyle
    • modifie directement le code

Astuce

  • Exemples d’erreurs repérées par un linter :
    • lignes de code trop longues ou mal indentées, parenthèses non équilibrées, noms de fonctions mal construits…
  • Exemples d’erreurs non repérées par un linter :
    • fonctions mal utilisées, arguments mal spécifiés, structure du code incohérente, code insuffisamment documenté…

1️⃣ Adopter les standards communautaires


Dans le cas de :

  • le linter à utiliser est le package lintr
  • le formatter à utiliser est le package styler.
    • Existe également le package formatR.

2️⃣ Utiliser des fonctions

Règle d’or

Il faut utiliser une fonction dès qu’on utilise une même portion de code plus de deux fois (don’t repeat yourself (DRY))

  • Limite les risques d’erreurs liés aux copier/coller
  • Rend le code plus lisible et plus compact
  • Un seul endroit du code à modifier lorsqu’on souhaite modifier le traitement
  • Facilite la réutilisation et la documentation du code !

Règles pour écrire des fonctions pertinentes

  • Une tâche = une fonction
  • Une tâche complexe = un enchaînement de fonctions réalisant chacune une tâche simple
  • Limiter l’utilisation de variables globales.

3️⃣ Documenter son code

  • Grands principes :
    • Documenter le pourquoi plutôt que le comment
    • Privilégier l’auto-documentation via des nommages pertinents.

Comment bien documenter un script ?

  • Minimum 🚦 : commentaire au début du script pour décrire ce qu’il fait
  • Bien 👍 : commenter les parties “délicates” du code
  • Idéal 💪 : documenter ses fonctions avec la syntaxe roxygen2.

3️⃣ Documenter son code

Bien documenter… mais pas trop !

. . .

Bof :

# Utilise string si x est non manquant et non vide
if (!is.na(x) && nzchar(x)) {
  use_string(x)
}

. . .

Mieux !

x_is_not_empty_string <- (!is.na(x) && nzchar(x))
if (x_is_not_empty_string) {
  use_string(x)
}

. . . Voir la présentation de Maëlle Salmon sur le « Code beau ».

4️⃣ Pas d’ambiguïté sur les packages utilisés

  • Deux fonctions peuvent avoir le même nom dans des packages différents
  • R utilise par défaut la librairie chargée le plus récemment
  • Erreurs difficiles à repérer car il est nécessaire d’exécuter le code
  • Recommandation : indiquer explicitement le package : notation package::fonction()
    • Exemple : dplyr::filter()

Exemple

  • package1 et package2 contiennent chacun une fonction appelée superFonction.
  • Si package2 est chargé après package1, alors superFonction désigne par défaut la fonction de package2.
  • Mieux vaut noter package1::superFonction et package2::superFonction

Ressources supplémentaires


Application 1

Partie 1 : vérification du bon fonctionnement du code

Un code reproductible est avant toute chose un code fonctionnel ! Repérez les erreurs qui empêchent le script script.R de s’exécuter correctement, et les corriger.

Application 1

Partie 2 : premiers standards de qualité

  1. Installer les packages R lintr et styler.
  2. Définir le linter à utiliser comme étant de type tidyverse : lintr::use_lintr(type = "tidyverse")
  3. Diagnostiquer le script script.R : lintr::lint("script.R").
    • Comprenez-vous la nature des problèmes détectés par le linter?
  4. Appliquer le formatter au script.R : styler::style_file("script.R").
  5. Refaire tourner le linter. Il reste encore un certain nombre d’erreurs de formattage, car styler est un formatter peu intrusif.
  6. Regarder les différents problèmes repérés par le linter, et en corriger quelques uns (un pour chaque type de problème).

Application 1

Partie 3 : une meilleure gestion des packages utilisés

  1. Limiter les ambiguités sur les packages en utilisant la syntaxe package::fonction pour les packages rarement utilisés dans le script.
  2. L’installation des packages dans un script n’est pas une bonne pratique. Supprimer les instructions correspondantes.
  3. Importer le tidyverse au complet est rarement utile. N’importer à la place que les packages effectivement utilisés dans le script.

Application 1

Partie 4 : (auto-)documentation du code

  1. Déplacer les library pour les mettre tous ensemble au début du script.
  2. Le script n’est pas construit dans un ordre logique. Déplacer les parties pour adopter une structure plus logique :
    • Gestion de l’environnement -> Définition de fonctions -> Import des données -> Retraitement des données -> Statistiques descriptives -> Graphiques -> Modélisation
  3. Donner des titres aux parties/sous-parties en utilisant les standards de documentation reconnus par RStudio :
    • # TITRE NIVEAU 1 ------------ et ## TITRE NIVEAU 2 ==========

Application 1

Partie 5 : une meilleure gestion des secrets

  1. Repérer le jeton d’API dans le code. Retirer le jeton d’API du code et créer à la racine du projet un fichier YAML nommé secrets.yaml où vous écrivez ce secret sous la forme key: value. ⚠️ Attention : le package yaml impose la création d’une ligne vierge à la fin du fichier pour être valide.
  2. Dans script.R, importer ce YAML (avec yaml::read_yaml("secrets.yaml")) pour créer une variable api_token ayant cette valeur.
  3. Ajouter dans .gitignore le fichier secrets.yaml et indiquer dans le README.md de votre projet que les secrets sont stockés dans ce fichier. ⚠️ Attention : il ne faut pas committer secrets.yaml car le jeton d’API est personnel et secret!

Checkpoint

Bilan

  • Un code mal structuré
    • Limite la lisibilité du projet
    • Est très coûteux à maintenir (dette technique)

II- Structure des projets

Enjeux

  1. Favoriser la lisibilité et la maintenabilité

2 Construire des projets reproductibles

⚠️ A ne pas reproduire chez vous


├── report.Rmd
├── correlation.png
├── data.csv
├── data2.csv
├── fig1.png
├── figure 2 (copy).png
├── report.pdf
├── partial data.csv
├── script.R
└── script_final.R

Source : eliocamp.github.io

Principes généraux

  1. Utiliser les projets RStudio
  1. Organiser son projet en sous-dossiers
  1. Donner des noms pertinents aux fichiers
  1. Documenter son projet
  1. (Faire de son projet un package)

1️⃣ Utiliser les projets RStudio

  • Objectif : favoriser la reproductibilité
    • Tous les fichiers nécessaires au projet dans un même dossier
    • Le dossier contenant le projet RStudio est automatiquement utilisé comme working directory
    • Utilisation de chemins relatifs plutôt qu’absolus.
  • Bonus : en utilisant Git, on s’assure de toujours travailler dans un projet RStudio !

2️⃣ Organiser son projet en sous-dossiers

  • Objectif : adopter une structure arbitraire, mais lisible et cohérente
├── data
│   ├── raw
│   │   ├── data.csv
│   │   └── data2.csv
│   └── derived
│       └── partial data.csv
├── R
|   ├── script.R
│   ├── script_final.R
│   └── report.Rmd
└── output
    ├── fig1.png
    ├── figure 2 (copy).png
    ├── figure10.png
    ├── correlation.png
    └── report.pdf

3️⃣ Donner des noms pertinents aux fichiers

  • Objectif : auto-documenter son projet
├── data
│   ├── raw
│   │   ├── dpe_logement_202103.csv
│   │   └── dpe_logement_202003.csv
│   └── derived
│       └── dpe_logement_merged_preprocessed.csv
├── R
|   ├── preprocessing.R
│   ├── generate_plots.R
│   └── report.Rmd
└── output
    ├── histogram_energy_diagnostic.png
    ├── barplot_consumption_pcs.png
    ├── correlation_matrix.png
    └── report.pdf

4️⃣ Documenter son projet

  • Le fichier README.md, situé à la racine du projet, est à la fois la carte d’identité et la vitrine du projet
  • Idéalement, il contient :
    • Une présentation du contexte et des objectifs
    • Une description de son fonctionnement
    • Un guide de contribution (open-source)

5️⃣ Faire de son projet un package

  • Un package est la forme maximale de modularité
    • Contient des fonctions rangées dans des modules
    • Contient également de la documentation, des tests, des (méta-)données…
  • Avantages
    • Idéal pour favoriser la réutilisation du code
    • Des outils de développement : devtools et usethis
  • Inconvénients
    • Coût de maintenance élevé

Ressources supplémentaires


Application 2

Partie 1 : modularisation du projet

  1. Déplacer toutes les fonctions dans un fichier R/functions.R.
  2. Donner à la fonction fonction_de_stat_agregee un nom plus pertinent et des noms d’arguments plus transparents.
  3. Dans script.R, appeler en début de chaîne ces fonctions avec source("R/functions.R", encoding = "UTF-8").
  4. Documenter la fonction principale au format attendu par roxygen2.
  5. Ajouter les tests unitaires de la fonction comme exemples d’utilisation et les retirer de script.R.
  6. Tester le bon fonctionnement de script.R.

Application 2

Partie 2 : création d’un package

  1. Initialiser un package avec la fonction usethis::create_package()
  2. Placer dans le dossier R du package un module stat.R et y copier la fonction de statistique agrégée
  3. Charger le package avec la fonction devtools::load_all() et vérifier que la fonction marche correctement
  4. Remplir le fichier DESCRIPTION. En particulier, spécifier les dépendances nécessaires (Imports) et facultatives (Suggests)
  5. Construire la documentation du package avec la fonction devtools::document(). Où est-elle stockée et sous quel format ?
  6. Vérifier que la documentation de notre fonction est accessible avec ?ma_fonction
  7. (Facultatif) Initialiser un nouveau projet sur GitHub est y mettre le code du package. Vérifier que le package peut être installé en local avec la fonction devtools::install_github().

Checkpoint

III- Formats de données

Enjeux

  • Le choix d’un format de données répond à un arbitrage entre plusieurs critères :
    • Finalité (traitement, analyse, diffusion)
    • Public cible
    • Volumétrie

Recommandations

  • Eviter impérativement les formats de données adhérents à un langage (RDS, RData, fst, sas7bdat, etc.).
  • Deux formats à privilégier :
    • CSV : pour la plupart des usages courants
      • Avantage : non-compressé donc facilement lisible
      • Inconvénients : pas de gestion des méta-données, peu adapté aux données volumineuses
    • Parquet : pour le traitement de données volumineuses
      • Compressé et très performant en lecture/écriture
      • Gestion native des méta-données

Application 3

Manipulation des formats CSV et Parquet

  1. Effacer la base individu_reg.csv
  2. Modifier le script get_data.R pour écrire les données au format Parquet à l’aide de la fonction arrow::write_parquet
  3. Modifier la phase d’import dans script.R pour importer le fichier Parquet à l’aide de la fonction arrow::read_parquet

Checkpoint

IV- Environnements reproductibles

Expérience de pensée

  • Imaginons la situation suivante :

    • J’installe une version de R sur mon poste
    • Je développe un projet en installant les packages nécessaires
    • Une fois terminé, je passe au projet suivant, et ainsi de suite.
  • Quels problèmes puis-je rencontrer au fil des projets ?

  • Est-il facile de partager un de mes projets ?

Enjeux

  • Version de R fixe, celle de l’installation système
  • Conflits de version : différents projets peuvent requérir différentes versions d’un même package.
  • Reproductibilité limitée : difficile de dire quel projet nécessite quel package.
  • Portabilité limitée : difficile de préciser dans un fichier les dépendances spécifiques à un projet.

Des environnements reproductibles avec renv

  • renv permet de créer des environnements reproductibles
  • Isolation : chaque projet dispose de sa propre librairie de packages
  • Reproductibilité : renv enregistre les versions exactes des packages nécessaires au projet
  • Portabilité: un tiers peut exécuter le projet avec les mêmes spécifications

Utilisation de renv

  1. Initialisation (init) de l’environnement local du projet
  1. Développement du projet
  1. Enregistrement (snapshot) des versions des packages installés
  1. Restauration (restore) d’un environnement

1️⃣ Initialisation de l’environnement

  • renv::init() dans un projet RStudio crée :
    • Un dossier renv et le fichier .Rprofile : activation automatique de l’environnement
    • Le fichier renv.lock : versions des packages installés

2️⃣ Développement du projet

  • Une fois l’environnement initialisé, on développe le projet de manière habituelle :
    • Installations/suppressions/mises à jour de packages
    • Ecriture de scripts
  • renv::status() : indique les packages installés/supprimés par rapport au fichier renv.lock

3️⃣ Enregistrement de l’environnement

  • renv::snapshot() : enregistre les versions des packages installés dans le fichier renv.lock
  • Ne pas oublier de committer le fichier renv.lock!

4️⃣ Restauration de l’environnement

  • renv::restore() : installe/désinstalle les packages nécessaires pour arriver à l’état spécifié dans le fichier renv.lock
  • Portabilité : un tiers peut recréer un environnement avec les mêmes spécifications

Application 4

Partie 1 : prise en main de la librairie renv

  1. Installer le package renv

  2. Taper dans la console renv::init(), lire le message et accepter.

  3. Observer les nouveaux élements qui sont apparus dans le projet.

  4. Installer le package gt et observer le message dans la console.

  5. Dans la partie sur les statistiques descriptives d’âge, ajouter ce code :

    stats_age <- df %>%
      group_by(decennie = decennie_a_partir_annee(age)) %>%
      summarise(n())
    
    table_age <- gt::gt(stats_age) %>%
      gt::tab_header(
        title = "Distribution des âges dans notre population"
      ) %>%
      gt::fmt_number(
        columns = `n()`,
        sep_mark = " ",
        decimals = 0
      ) %>%
      gt::cols_label(
        decennie = "Tranche d'âge",
        `n()` = "Population"
      )

Application 4

Partie 2 : faire un snapshot de l’environnement

  1. Dans la console, faire renv::status() et observer le message.
  2. Effectuer renv::snapshot() pour intégrer les nouveaux packages au lockfile
  3. Faire un commit / push des fichiers script.R, .Rprofile, renv.lock et du dossier renv/.

Application 4

Partie 3 : test de la portabilité (facultatif)

  1. Ouvrir un nouveau service RStudio sur le SSPCloud
  2. Cloner votre dépôt
  3. Exécuter renv::restore() pour installer les packages nécessaires à l’exécution du projet
  4. Vérifier que le script.R s’exécute correctement dans ce nouvel environnement.

Checkpoint

Vers une reproductibilité optimale

  • Limites des environnements virtuels :
    • Les librairies système ne sont pas gérées
    • Lourdeur de la phase d’installation à chaque changement d’environnement
    • Peu adaptés à un environnement de production
  • La conteneurisation (ex : Docker) apporte la solution
  • Intuition : au lieu de distribuer la recette pour recréer l’environnement, distribuer directement une “machine” qui contient tout l’environnement nécessaire au projet

Ressources supplémentaires

V- Pipelines de données

Motivations

  • Une analyse de données ou une chaîne de production font intervenir des étapes standardisées

  • Ces étapes peuvent être formalisées sous forme d’un pipeline (direct acyclic graph)

Source

Motivations

  • Modéliser ces étapes sous forme de pipeline (direct acyclic graph) a plusieurs avantages :
  • Découplage des différentes étapes
  • Facilite la planification du traitement
  • Facilite la prise en main du projet par un tiers

Le package targets

  • targets est un framework de modélisation de pipelines spécifiquement dédié aux projets R.
  • Deux objectifs majeurs :
    1. Réduire le coût d’expérimentation en sauvegardant les résultats intermédiaires (targets)
    2. Garantir la reproductibilité de la chaîne en traçant les changements de ces targets

Méthode de travail avec targets

  • On développe dans des scripts :
    • Fonctions dans un ou plusieurs fichiers dédiés
    • Chaîne de production dans un fichier _targets.R
  • On gère l’exécution du pipeline directement dans la console
    • tar_visnetwork() pour inspecter la structure du pipeline
    • tar_make pour exécuter la chaine de production

Note

Quand on part du chaine de traitement dans un fichier déja existant (script.R par exemple), il faut faire la transition vers un fichier _targets.R dont la structure est particulière.

1️⃣ Un projet minimaliste

  • Structure de projet opinionated :
    • Long script script.R _targets.R
    • Chaîne de production: suite d’appels à des fonctions définies dans R/functions.R


├── _targets.R
├── data
│   ├── raw
│   │   └── data.csv
├── R
│   └── functions.R


  • Nouveau fichier: _targets.R
    • Contrôle le comportement de notre chaine de traitement
    • Enchaînement d’étapes de transformation de données selon une syntaxe particulière

2️⃣ Le fichier _targets.R

  • Le fichier _targets.R doit satisfaire plusieurs conditions :
    1. Charger le package targets
    2. Charger dans l’environnement les fonctions nécessaires
    3. Déclarer les packages nécessaires aux différentes étapes
    4. Définir le pipeline.
# _targets.R file
library(targets)
source("R/functions.R")
tar_option_set(packages = c("readr", "dplyr", "ggplot2"))
list(
  tar_target(file, "data.csv", format = "file"),
  tar_target(data, get_data(file)),
  tar_target(model, fit_model(data)),
  tar_target(plot, plot_model(model, data))
)

Note

Les fonctions get_data, fit_model et plot_model sont définies dans 📁 R/functions.R

3️⃣ Inspecter le pipeline

  • La fonction tar_visnetwork permet de visualiser le pipeline

3️⃣ Exécuter le pipeline

  • La fonction tar_make exécute le pipeline défini dans _targets.R

  • 💡 Lors des exécutions suivantes, targets saute automatiquement les étapes qui n’ont pas changé

Ressources supplémentaires

Application 5

Modélisation d’un projet sous forme de pipeline de données

  1. Installer le package targets.
  2. Créer un fichier _targets.R à la racine du dépôt, à partir de l’exemple dans la documentation.
  3. Ajouter _targets/ dans le .gitignore.
  4. Reporter les library dans le fichier R/functions.R et récupérer les noms des packages pour les mettre dans la section tar_option_set().
  5. Ajouter dans R/functions.R les fonctions read_yaml_secret et read_from_parquet:
read_yaml_secret <- function(path, key) {
  return(yaml::read_yaml(path)[[key]])
}
read_from_parquet <- function(path) {
  df <- arrow::read_parquet(
    path,
    col_select  = c(
      "region", "aemm", "aged", "anai", "catl", "cs1", "cs2", "cs3",
      "couple", "na38", "naf08", "pnai12", "sexe", "surf", "tp",
      "trans", "ur"
    )
  )
  return(df)
}

Application 5

Modélisation d’un projet sous forme de pipeline de données (suite)

  1. En extrayant du contenu de script.R, créer les deux premières cibles (targets) dans _targets.R:
    • Créer les targets file_token et file_data stockant les chemins de nos fichiers en entrée
    • ⚠️ Ne pas oublier l’argument format = 'file' dans tar_target
  2. Créer les deux cibles suivantes dans _targets.R:
    • Créer token et data à partir, respectivement, de read_yaml_secret et read_from_parquet.
    • Pour data, utiliser l’argument format = 'parquet' dans tar_target.
  3. Visualiser les relations entre objets de notre pipeline en utilisant dans la console R la fonction tar_visnetwork().
  4. Exécuter tar_make() dans la console et observer le changement d’état avec tar_visnetwork().

Application 5

Modélisation d’un projet sous forme de pipeline de données (suite)

  1. A partir des fonctions suivantes, créer deux nouvelles cibles (clean_data et table_age) qui automatisent la production du tableau gt
retraitement_donnees <- function(df){
  df <- df %>%
    mutate(aged = as.numeric(aged))
  df$sexe <- df$sexe %>%
    as.character() %>%
    fct_recode(Homme = "1", Femme = "2")
  return(df)
}


produce_table_age <- function(df){
  stats_age <- df %>%
    group_by(decennie = decennie_a_partir_annee(aged)) %>%
    summarise(n())
  
  table_age <- gt::gt(stats_age) %>%
    gt::tab_header(
      title = "Distribution des âges dans notre population"
    ) %>%
    gt::fmt_number(
      columns = `n()`,
      sep_mark = " ",
      decimals = 0
    ) %>%
    gt::cols_label(
      decennie = "Tranche d'âge",
      `n()` = "Population"
    )
  return(table_age)
}
  1. En console, visualiser l’état du pipeline avec tar_visnetwork puis exécuter tar_make(). Analyser la sortie dans la console.
  2. A l’aide de la fonction tar_read(table_age), visualiser la table des âges produite.
  3. Ajouter les commandes suivantes au sein de la fonction retraitement_donnees pour la modifier:
df <- df %>%
  mutate(
    surf = factor(surf, ordered = TRUE),
    cs1 = factor(cs1)
  )
  1. Depuis la console, inspecter le _pipeline avec tar_visnetwork puis exécuter tar_make() pour lancer le pipeline. Analyser la sortie.
  2. Si vous avez le temps, continuer à enrichir votre pipeline

:::

Checkpoint

VI- Publication reproductible

Enjeux

  • Produire des études reproductibles en intégrant le code et le texte dans un même document
  • La génération complète de l’étude est contenue dans un unique projet
  • Limiter les risques d’erreurs dues aux gestes manuels
  • Gestion native de différents formats pour le document final (pdf, html, odt, etc.)

R Markdown

  • R Markdown est un package R qui permet de lier
    • Du texte au format Markdown
    • Du code R qui peut être exécuté et dont les sorties peuvent être intégrées au texte
  • Dissociation du fond et de la forme du document
  • Un document est compilé en deux étapes
    • knit : le package knitr transforme le texte et les sorties R en un document Markdown standard
    • convert : le logiciel pandoc transforme le document .md en un format de sortie standard (html, pdf, etc.)

Quarto

  • Quarto est le successeur de R Markdown
  • Quarto supporte différents moteurs de calcul (knitr, Jupyter, Observable..) ce qui le rend nativement multi-langage (R, Python, JavaScript..)
  • Le fonctionnement des deux systèmes reste très proche

Anatomie d’un document reproductible

Ressources supplémentaires

Application 6

Introduction à la publication reproductible

  1. Cliquer sur File > New file > Quarto document:
    • Donner un titre et un.e auteur.e au rapport
    • Choisir HTML comme format de sortie
    • Décocher la case Use visual markdown editor
  2. Sauvegarder le fichier sous le nom report.qmd.

Application 6

Introduction à la publication reproductible (suite)

  1. Produire une courte note présentant le tableau des âges produit précédemment :
    • Un chunk R qui importe le package targets. Le contenu produit par ce chunk doit être caché, il est donc nécessaire d’utiliser une option
    • Une courte phrase d’introduction
    • Dans le fichier .qmd, importer l’objet table_age via tar_load et faire en sorte d’afficher ce tableau dans le document
  2. Générer le rapport avec le bouton Render et vérifier que l’output correspond à celui attendu.

Important

En supposant que ce document est à vocation d’une audience non technique, masquer le code ayant généré les sorties

Application 6

Introduction à la publication reproductible (suite)

  1. Dans script.R, nous avions créé un graphique p.png.
    • A partir des exemples de la documentation introduire cette figure
    • La faire précéder d’une phrase d’introduction.
    • Êtes-vous sûr que cette figure est à jour ?
  2. A partir de la fonction suivante à mettre dans R/functions.R, créer une nouvelle target graph_part_hommes dans _targets.R.
figure_part_homme_age <- function(df){
  p <- df %>%
    group_by(aged, sexe) %>%
    summarise(SH_sexe = n()) %>%
    group_by(aged) %>%
    mutate(SH_sexe = SH_sexe / sum(SH_sexe)) %>%
    filter(sexe == "Homme") %>%
    ggplot() +
    geom_bar(aes(x = aged, y = SH_sexe), stat = "identity") +
    geom_point(
      aes(x = aged, y = SH_sexe),
      stat = "identity", color = "red") +
    coord_cartesian(c(0, 100))
  return(p)
}
  1. Faire tourner le pipeline (tar_make)
  2. Pour être certain que votre note utilise la dernière version du pipeline, introduire un tar_make() dans votre document à la suite du library(targets)
  3. Cliquer sur le bouton Render pour mettre à jour votre note

Checkpoint

Ressources supplémentaires

  • Trouver de l’aide:

Conclusion

  • Les bonnes pratiques favorisent la reproductibilité et la réutilisation des projets statistiques
  • Des outils permettent d’appliquer les bonnes pratiques
  • Le coût est d’autant plus faible que l’on se place en amont du projet