library(dplyr)
library(microbenchmark)

R et le chaînage d’opérations

Le chaînage d’opérations est disponible depuis plusieurs années via le pipe %>% de {{maggritr}} (exporté dans et utilisé via {{dplyr}}). R 4.1.+ a introduit un opérateur de pipe natif |>.

Cet opérateur séquentiel fonctionne de la même manière que le %>% usuel.

10:15 %>% # Pour le vecteur 10:15
  sqrt() %>% # On prend la racine carrée, puis
  mean() %>% # On calcule la moyenne
  round(digits = 2) # On arrondit
## [1] 3.53
# Maintenant, avec le pipe "natif"
10:15 |> # Pour le vecteur 10:15
  sqrt() |> # On prend la racine carrée, puis
  mean() |> # On calcule la moyenne
  round(digits = 2) # On arrondit
## [1] 3.53

Différences entre le %>% et le |>

Une première question naturelle est “Quelle est la différence?”. La différence fondamentale est que %>% est une fonction qui s’écrit %>%(LHS, RHS)RHS est une fonction et LHS est une expression R quelconque, alors que le |> est un simple raccourci syntaxique , i.e. a |> f() est interprété exactement comme f(a).

On peut s’en rendre compte en examinant deux expressions construites avec |> et %>%

quote(2 %>% sqrt()) ## pas de modifications
## 2 %>% sqrt()
quote(2 |> sqrt()) ## reinterprétation comme sqrt(2)
## sqrt(2)

Cette simplification fait que ce dernier est plus efficace, comme illustré sur notre suite d’opérations simples:

microbenchmark(native = {10:15 |>
    sqrt() |> 
    mean() |> 
    round(digits = 2)},
    magrittr = {10:15 %>%
        sqrt() %>%
        mean() %>%
        round(digits = 2)}) %>% 
  summary()

Cependant, en pratique, dans l’usage classique impliquant des tableaux de données, ce gain est négligeable au vu des différentes manipulations (notamment les copies des tableaux) effectuées.

library(dplyr) # Pour la manipulation de data.frame
microbenchmark(native = iris |>
                 select_if(is.numeric) |>
                 mutate_all(sqrt) |> 
                 summarise_all(mean) |>
                 summarise_all(round, digits = 2),
               magritr = iris %>% 
                 select_if(is.numeric) %>% 
                 mutate_all(sqrt) %>%  
                 summarise_all(mean) %>% 
                 summarise_all(round, digits = 2)) %>% 
  summary()

Cette subtilité peut induire des comportements insidieux, notamment dans la manipulation d’expressions de R, où l’on préferera sans doute le |> natif qui reproduit exactement le R de base.

Par exemple, si on reproduire le code suivant de manière séquentielle:

quote(print("Hello"))
## print("Hello")

on peut procéder “naturellement” avec le pipe natif:

"Hello" |> 
  print() |> 
  quote()
## print("Hello")

Cependant, cette syntaxe ne fonctionnerait pas avec le pipe %>%

"Hello" %>% 
  print() %>% 
  quote()
## .

Différence syntaxique

A noter une différence importante qui est la manière de gérer l’argument, c’est à dire la position de l’argument LHS dans la fonction RHS.

Typiquement, pour écrire:

lm(Sepal.Length ~ Sepal.Width, data = iris[iris$Species == "setosa",])

la version tidy avec %>% est

iris %>% 
  filter(Species == "setosa") %>% 
  lm(Sepal.Length ~ Sepal.Width, data = .)

où on a utilisé le . pour spécifier “ce qu’il y avait avant”, ce qui a permis de spécifier comme argument data le résultat des traitements précédents.

Cette possibilité existe dans le pipe natif, mais seulement dans la version R 4.2.+. Et on utilisera le placeholder _ au lieu de ..

iris |> 
  filter(Species == "setosa") |> 
  lm(Sepal.Length ~ Sepal.Width, data = _)

Ecriture de fonctions

En ce qui concerne l’écriture de fonctions, les deux expressions ci-dessous sont équivalentes :

function(x) x + 1 
## function(x) x + 1
\(x) x + 1
## \(x) x + 1

Cette nouvelle écriture, permet principalement d’économiser le nombre de caractères.

En combinant avec le pipe natif, on obtiendrait :

mtcars |> 
   (\(x) lm(mpg ~ disp, data = x))() 
## 
## Call:
## lm(formula = mpg ~ disp, data = x)
## 
## Coefficients:
## (Intercept)         disp  
##    29.59985     -0.04122

A la place de :

mtcars |> 
   (function(x) lm(mpg ~ disp, data = x))()
## 
## Call:
## lm(formula = mpg ~ disp, data = x)
## 
## Coefficients:
## (Intercept)         disp  
##    29.59985     -0.04122

et de la formule suivante utilisant le pipe %>% :

mtcars %>% 
   lm(mpg ~ disp, data = .)
## 
## Call:
## lm(formula = mpg ~ disp, data = .)
## 
## Coefficients:
## (Intercept)         disp  
##    29.59985     -0.04122

Pour une discussion exhaustive sur les différences entre les deux pipes, on peut consulter la discussion StackOverflow sur le sujet.

Nouveautés dans RStudio

L’IDE RStudio fait l’objet d’un travail de développement intense. Sur les derniers mois, quelques évolutions notables concernent:

Apprentissage de git

L’apprentissage de git, notamment la gestion des branches, est souvent considéré comme complexe malgré la profusion de ressources existantes (par exemple: git manual). Des interfaces ludiques existent pour (re)-découvrir et s’entraîner à la gestion des branches. Nous avons exploré Learning branching.