17  Ceci n’est pas qu’un opérateur : %>% et magrittr

17.1 L’opérateur pipe %>%

L’opérateur pipe permet de passer, de gauche à droite, le résultat d’une fonction à une seconde fonction, puis à une troisième. Ce pipe est détaillé dans un chapitre mais sa compréhension devrait être intuitive.

Comparez par exemple ces deux lignes pourtant strictement équivalentes :

{r} library(magrittr) plot(sqrt(sample(seq(1:100), 6))) # road to burnout seq(1:100) %>% sample(6) %>% sqrt() %>% plot() # let’s breathe

Les packages du R moderne, en premier lieu ceux du tidyverse en ont fait une idée centrale de leur design et il est peu dire que nous autres mortel·le·s en profitons tous les jours.

Il est peu dire que cet opérateur1 a révolutionné R, lorsqu’il y a été importé, d’abord dans le package magrittr sous sa forme %>%. Il est désormais inclus dans le R de “base” sous sa forme |> mais nous n’utiliserons que la version historique %>%, que je trouve plus lisible, plus facile à taper (<Maj> + <Ctrl/Cmd> + <M> dans RStudio) et parce que les années aidant, je deviens conservateur.

L’idée du pipe est issue de la composition de fonctions en mathématiques. Plutôt que d’écrire :

h(g(f(x))) on peut déplier cet emboitement de fonctions et écrire (h ∘ g ∘ f)(x)

En langage R, plutôt que d’écrire h(g(f(x))) on écrira : x %>% f() %>% g() %>% h()2. Cette écriture est non seulement plus lisible mais elle se lit également de gauche à droite, dans le sens conventionnel de notre partie du monde.

17.2 %>% vs |>

D’abord introduit par le package magrittr le forward pipe est désormais dans le R “de base” depuis la version 4.1.0.

Le pipe |> tend désormais à être préféré à %>% comme on le lira sur le (blog du tidyverse)[, très complet)[https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/]. Ce dernier mérite néanmoins, avec ses autres copains de magrittr, d’être détaillé.

Si la vignette de magrittr est très bien faite (vignette("magrittr"), j’en livre ici une introduction rapide.

17.3 %>%

Dans les grandes lignes, %>% et |> fonctionnent de la même façon. J’ai tendance à continuer d’utiliser %>% qui est chargé automatiquement par le tidyverse. Pour les autres opérateurs, ou si vous n’utilisez pas le tidyverse vous pouvez en disposer avec :

── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   4.0.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Attaching package: 'magrittr'


The following object is masked from 'package:purrr':

    set_names


The following object is masked from 'package:tidyr':

    extract

Venons en aux faits.

runif(100) %>% mean()
[1] 0.5606005

17.4 Le . pour customiser le forward

Par défaut, %>% injecte ce qui sort de la fonction à sa gauche comme premier argument de la fonction à sa droite.

La plupart du temps, notamment avec le tidyverse, cela fonctionne à merveille. Mais pensez à plot ou à lm, l’argument data est le second, pas le premier (qui est une formula).

maggrittr prévoir ce cas et vous pouvez spécifier un atterrissage alternatif avec .:

iris %>% 
  as_tibble() %>% 
  select(pl=Petal.Length, pw=Petal.Width) %>% 
  lm(pw~pl, data=.)

Call:
lm(formula = pw ~ pl, data = .)

Coefficients:
(Intercept)           pl  
    -0.3631       0.4158  

17.5 %T>%

Le “tee” pipe est lui utile quand l’une des fonctions est un cul de sac, typiquement un print ou un plot.

L’idée est, dans un seul pipe, de plotter (ou printer) mais de continuer avec l’objet de départ. En d’autres termes de créer en un point du pipe, une bifurcation dont l’une des branche est un cul de sac, et l’autre continue.

Un exemple vaut mille mots :

iris %>% 
  as_tibble() %>% 
  select(pl=Petal.Length, pw=Petal.Width) %T>% 
  print() %>% # here we print
  plot(pw~pl, data=.) # and we come back to the object returned by `select`
# A tibble: 150 × 2
      pl    pw
   <dbl> <dbl>
 1   1.4   0.2
 2   1.4   0.2
 3   1.3   0.2
 4   1.5   0.2
 5   1.4   0.2
 6   1.7   0.4
 7   1.4   0.3
 8   1.5   0.2
 9   1.4   0.2
10   1.5   0.1
# ℹ 140 more rows

17.6 `%$%

Cette variante a pour but d’“exposer” le nom des éléments d’une liste, souvent à des fins d’extraction :

iris %>% 
  as_tibble() %>% 
  slice(1:5) %>% 
  select(pl=Petal.Length, pw=Petal.Width) %$% pl
[1] 1.4 1.4 1.3 1.5 1.4

Vous pouvez remplacer avantageusement ce pipe par dplyr::pull qui est quelque part encore plus conforme à l’esprit “dplyr/tidyverse” :

iris %>% 
  as_tibble() %>% 
  slice(1:5) %>% 
  select(pl=Petal.Length, pw=Petal.Width) %>% 
  pull(pw)
[1] 0.2 0.2 0.2 0.2 0.2

17.7 %<>%

Dernier opérateur de cette liste, un peu passé de mode mais que je mentionne ici par souci de complétude. Cet opérateur pipe part d’un objet, lui fait faire tout le chemin au sein d’un pipe et réassigne l’objet de départ avec l’objet retourné par le pipe :

x <- iris %>% as_tibble()
x
# A tibble: 150 × 5
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl> <fct>  
 1          5.1         3.5          1.4         0.2 setosa 
 2          4.9         3            1.4         0.2 setosa 
 3          4.7         3.2          1.3         0.2 setosa 
 4          4.6         3.1          1.5         0.2 setosa 
 5          5           3.6          1.4         0.2 setosa 
 6          5.4         3.9          1.7         0.4 setosa 
 7          4.6         3.4          1.4         0.3 setosa 
 8          5           3.4          1.5         0.2 setosa 
 9          4.4         2.9          1.4         0.2 setosa 
10          4.9         3.1          1.5         0.1 setosa 
# ℹ 140 more rows
x %<>% select(1:2)
x
# A tibble: 150 × 2
   Sepal.Length Sepal.Width
          <dbl>       <dbl>
 1          5.1         3.5
 2          4.9         3  
 3          4.7         3.2
 4          4.6         3.1
 5          5           3.6
 6          5.4         3.9
 7          4.6         3.4
 8          5           3.4
 9          4.4         2.9
10          4.9         3.1
# ℹ 140 more rows

Cet opérateur est critique pour un motif recevable : l’assignation est une opération tellement importante qu’elle devrait être plus visible qu’un seul caractère (%>% versus %<>%).


  1. À prononcer à l’anglaise hein : “payeupeu”↩︎

  2. Vous pouvez également omettre les parenthèses si vos fonctions sont passées sans argument.↩︎

❤ Placé dans le domaine public par Vincent Bonhomme