5.5 Chaînage des instructions

Le chaînage (ou “pipe” en anglais) permet de combiner une suite d’instructions R. Il permet une représentation facilement lisible et compréhensible d’un traitement décomposé en plusieurs étapes simples de remaniement des données.

Différents opérateurs de chaînage existent dans R. Le Tidyverse et RStudio sont en faveur de l’adoption d’un opérateur de chaînage %>% issu du package magrittr. Si nous sommes sensibles au clin d’œil fait ici à un artiste belge bien connu (“ceci n’est pas un pipe”), nous n’adhérons pas à ce choix pour des raisons multiples et plutôt techniques qui n’ont pas leur place dans ce document22. Nous vous présentons ici l’un des opérateurs de chaînage du package flow : %>.%. Le jeu de données sur la biométrie humaine est employé pour cette démonstration qui va comparer le remaniement d’un tableau de données avec et sans l’utilisation du chaînage.

biometry <- read("biometry", package = "BioDataScience", lang = "fr")

Vous vous intéressez à l’indice de masse corporelle ou IMC (BMI en anglais) des individus de moins de 25 ans. Vous souhaitez représenter la moyenne, la médiane et le nombre d’observations de manière séparée pour les hommes et les femmes. Pour obtenir ces résultats vous devez :

  • calculer le BMI,
  • filtrer le tableau pour ne retenir que les individus de moins de 25 ans,
  • résumer les données afin d’obtenir la moyenne et la médiane par genre,
  • afficher un tableau de données avec ces résultats.

Il est très clair ici que le traitement peut être décomposé en étapes plus simples. Cela apparaît naturellement rien que dans la description de ce qui doit être fait. Sans l’utilisation de l’opérateur de chaînage, deux approches sont possibles :

  • Imbriquer les instructions les unes dans les autres (très difficile à lire et à déboguer) :
knitr::kable(
  summarise(
    group_by(
      filter(
        mutate(biometry, bmi = weight / (height/100)^2),
        age <= 25),
      gender),
    mean = mean(bmi), 
    median = median(bmi),
    number = n()),
  rows = NULL,  digits = 1,
  col = c("Genre", "Moyenne", "Médiane", "Observations"),
  caption = "IMC d'hommes (M) et femmes (W) de 25 ans maximum."
)
Tableau 5.4: IMC d’hommes (M) et femmes (W) de 25 ans maximum.
Genre Moyenne Médiane Observations
M 22.3 22.1 97
W 21.8 21.0 94
  • Passer par des variables intermédiaires (biometry_25 et biometry_tab). Les instructions sont plus lisibles, mais les variables intermédiaires “polluent” inutilement l’environnement de travail (en tout cas, si elles ne servent plus par après) :
biometry <- mutate(biometry, bmi = weight / (height/100)^2)
biometry_25 <- filter(biometry, age <= 25)
biometry_25 <- group_by(biometry_25, gender)
biometry_tab <- summarise(biometry_25,
  mean = mean(bmi), 
  median = median(bmi),
  number = n())
knitr::kable(biometry_tab, rows = NULL, digits = 1,
  col = c("Genre", "Moyenne", "Médiane", "Observations"),
  caption = "IMC d'hommes (M) et femmes (W) de 25 ans maximum.")
Tableau 5.5: IMC d’hommes (M) et femmes (W) de 25 ans maximum.
Genre Moyenne Médiane Observations
M 22.3 22.1 97
W 21.8 21.0 94
  • Des trois approches, la version ci-dessous avec chaînage des opérations est la plus lisible et la plus pratique23.
biometry %>.%
  mutate(., bmi = weight / (height/100)^2) %>.%
  filter(., age <= 25) %>.%
  group_by(., gender) %>.%
  summarise(.,
    mean = mean(bmi),
    median = median(bmi),
    number = n()) %>.%
  knitr::kable(., rows = NULL, digits = 1, 
    col = c("Genre", "Moyenne", "Médiane", "Observations"),
    caption = "IMC d'hommes (M) et femmes (W) de 25 ans maximum.")
Tableau 5.6: IMC d’hommes (M) et femmes (W) de 25 ans maximum.
Genre Moyenne Médiane Observations
M 22.3 22.1 97
W 21.8 21.0 94

Le pipe %>.% injecte le résultat précédent dans l’instruction suivante à travers l’objet . Ainsi, en seconde ligne mutate(.), . se réfère à biometry. A la ligne suivante, filter(.), le . se réfère au résultat issu de l’opération mutate(), et ainsi de suite. La logique d’enchaînement des opérations sur le résultat, à chaque fois, du calcul précédent est donc le fondement de cet opérateur “pipe”.

Le pipe permet d’éviter de répéter le nom des objets (version avec variables intermédiaires), ce qui alourdit inutilement le code et le rend moins agréable à la lecture. L’imbrication des fonctions dans la première version est catastrophique pour la compréhension du code car les arguments des fonctions de plus haut niveau sont repoussés loin. Par exemple, l’argument de l’appel à group_by() (gender) se retrouve quatre lignes plus loin. Et encore, nous avons pris soin d’indenter le code pour repérer sur un plan vertical qui appartient à qui, mais imaginez ce que cela donne si l’instruction est mise à plat sur une seule ligne ! Le code le plus clair à la lecture est définitivement celui avec chaînage des opérations. Or, un code plus lisible est plus compréhensible… et donc, moins bogué.

A vous de jouer

Maintenant que vous venez d’apprendre à importer correctement vos données, à les remanier avec quelques-uns des opérateurs les plus fréquents, et que vous savez chaîner vos instructions, il est temps de vous exercer sur un cas concret.

Une tâche individuelle vous est assignée via l’URL suivante :

Créez un rapport et effectuez les différents exercices en suivant les instructions qui sont dans le fichier README.md de ce dépôt GitHub Classroom.

Terminez ce module en vérifiant que vous en avez acquis les notions principales.

Ouvrez RStudio dans votre SciViews Box, puis exécutez l’instruction suivante dans la fenêtre console :

BioDataScience::run("05a_test")

Des challenges vous sont proposés afin d’améliorer vos compétences en remaniement de données. Ces derniers sont disponibles ici.

Pour en savoir plus

  1. Le lecteur intéressé pourra lire les différents articles suivants : more pipes in R, y compris les liens qui s’y trouvent, permet de se faire une idée de la diversité des opérateurs de chaînage dans R et de leur historique. Dot pipe présente l’opérateur %.>% du package wrapr très proche du nôtre et in praise of syntactic sugar explique ses avantages. Nous partageons l’idée que le “pipe de base” ne devrait pas modifier l’instruction de droite contrairement à ce que fait %>% de magrittr, et notre opérateur %>.% va en outre plus loin encore que %.>% dans la facilité de débogage du code chaîne.

  2. Le chaînage n’est cependant pas forcément plus facile à déboguer que la version avec variables intermédiaires. Le package flow propose la fonction debug_flow() à appeler directement après un plantage pour inspecter la dernière instruction qui a causé l’erreur, voir ?debug_flow.