Pipe en R : histoire, usages et différences
Découvrez l’histoire du pipe en R, de magrittr à l’opérateur natif |> : différences, exemples concrets et critères pour choisir le bon outil dans vos projets.

Du plombier à R : comment le pipe a changé l'écriture du code
Le pipe est devenu un réflexe pour beaucoup de gens qui écrivent du code en R. L'idée est simple : au lieu d'imbriquer des appels de fonctions les uns dans les autres, on enchaîne les opérations dans l'ordre où elles se font. Résultat : la lecture devient plus fluide. On voit d'abord les données entrer, puis passer à l'étape suivante.
Et non, cette façon d'écrire n'est pas sortie de nulle part avec R. Elle a été proposée, testée et discutée par des utilisateurs bien avant d'être intégrée au langage. Plusieurs paquets ont joué un rôle dans cette histoire, notamment dplyr, magrittr, pipeR et wrapr. En 2021, R a finalement adopté son propre opérateur, |>. Depuis, la syntaxe native a aussi évolué : le caractère _ peut notamment servir de marqueur dans certains appels.
Comprendre cette évolution ne sert pas qu'à faire joli dans une frise historique. Ça aide vraiment à choisir entre |> et %>%, à repérer les différences qui comptent, et surtout à éviter les migrations automatiques qui finissent par rendre un script moins lisible. Je vous propose donc de passer par les étapes importantes, puis par des règles pratiques pour écrire et maintenir des chaînes de traitement en R.
Pourquoi le pipe a changé la lecture du code R
Un pipe transmet le résultat d'une expression à une autre fonction. Dans R, l'opérateur natif |> passe par défaut l'objet situé à gauche comme premier argument de la fonction à droite.
1:10 |>
mean()
On obtient la même valeur que dans mean(1:10). La vraie différence, c'est la façon dont on lit le code. Avec une imbrication classique, il faut « commencer » au niveau le plus profond :
filter(
summarise(
group_by(donnees, region),
moyenne = mean(montant, na.rm = TRUE)
),
moyenne > 100
)
Avec une chaîne, les étapes reviennent dans l'ordre du traitement :
donnees |>
group_by(region) |>
summarise(moyenne = mean(montant, na.rm = TRUE)) |>
filter(moyenne > 100)
Dans le second exemple, on suit le trajet de l'objet : données brutes, regroupement, calcul, filtrage. Pour un script d'analyse, cette lecture séquentielle aide à comprendre plus vite, et à en discuter plus facilement avec d'autres.
Un gain de lisibilité, mais pas une baguette magique
Le pipe marche très bien quand chaque étape transforme l'objet, et que l'étape suivante consomme ce résultat. C'est fréquent dans le nettoyage de données, les analyses statistiques, ou la préparation d'un dataset pour un rapport.
Mais il ne garantit pas, automatiquement, un code plus clair. Une chaîne trop longue peut masquer des hypothèses, des effets de bord, ou une étape dont le rôle devient flou. Si vous devez remonter dix opérations pour savoir ce qu'une fonction reçoit, le pipe n'aide plus : il encombre.
En pratique, je découpe dès qu'une étape mérite un nom métier, qu'elle est réutilisée ailleurs, ou qu'elle devient pénible à tester. Un pipe sert à organiser, pas à tout faire tenir sur une seule ligne « par principe ».
Pourquoi l'histoire des opérateurs compte encore
|> et %>% se ressemblent, mais ils ne suivent pas exactement les mêmes règles. Une migration faite trop vite peut casser du code (ou, pire, changer le sens d'un appel). À l'inverse, garder %>% dans un projet qui dépend déjà de magrittr peut être une décision tout à fait rationnelle.
Le bon critère n'est donc pas « le plus récent ». Il faut regarder la version de R visée, les dépendances du projet, ce que votre code utilise concrètement dans magrittr, et le coût réel de maintenance.
De Stack Overflow à l'opérateur natif
L'histoire documentée du pipe en R commence sur Stack Overflow. Le 17 janvier 2012, une question cherche à reproduire l'opérateur de chaînage de F#, avec une syntaxe du type donnees |> foo |> bar. Ben Bolker propose alors une première version de %>%, très simple :
"%>%" <- function(x, f) do.call(f, list(x))
pi %>% sin %>% cos
Cette réponse porte déjà l'idée essentielle : transmettre le résultat à l'étape suivante. En revanche, cette première version ne couvre pas toutes les fonctionnalités qu'on associe ensuite aux utilisateurs de magrittr.
Les premières expérimentations et l'arrivée de dplyr
En 2012, Peter Meilstrup crée le paquet ptools, qui contient notamment une fonction chain. Plus tard, le paquet change de nom pour devenir vadr. À l'automne de la même année, Hadley Wickham commence le développement de dplyr, dans la continuité de plyr.
Dans dplyr, le chaînage arrive en 2013. Une première fonction chain permet déjà de mettre en ordre les transformations. Le paquet introduit ensuite l'opérateur %.%, pour relier les étapes. Cette notation est remplacée plus tard par %>%, associée au paquet magrittr.
L'intérêt saute aux yeux dans un exemple d'analyse de retards de vols. Au lieu d'imbriquer group_by(), select(), summarise() et filter(), on peut écrire une transformation par ligne :
hflights %>%
group_by(Year, Month, DayofMonth) %>%
select(Year:DayofMonth, ArrDelay, DepDelay) %>%
summarise(
arr = mean(ArrDelay, na.rm = TRUE),
dep = mean(DepDelay, na.rm = TRUE)
) %>%
filter(arr > 30 | dep > 30)
La logique statistique ne change pas, mais le traitement devient plus visible. C'est une des raisons pour lesquelles le pipe s'est vite répandu dans les exemples dplyr et, plus largement, dans l'écosystème tidyverse.
Magrittr, pipeR et les autres variantes
À la fin de 2013, Stefan Bache reprend l'idée du pipe et propose une implémentation plus poussée. Le paquet (d'abord appelé plumbr, puis renommé magrittr) rend une fonctionnalité particulièrement utile : le point . permet d'indiquer où placer l'objet transmis. Avec %>%, le résultat à gauche devient souvent le premier argument, mais on peut le déplacer dans l'appel.
penguins %>%
rpart(species ~ ., method = "class", data = .) %>%
prp(extra = 4)
D'autres paquets ont exploré des besoins différents. pipeR, publié en 2014, propose %>>% et plusieurs façons de transmettre (ou de conserver) des résultats intermédiaires. Le paquet wrapr introduit ensuite l'opérateur %.>%, qui permet notamment de garder l'objet de gauche dans l'environnement sous le nom .. Ces variantes montrent une tension classique : plus un opérateur est flexible, plus il impose au lecteur d'apprendre ses règles.
Magrittr a aussi développé d'autres opérateurs, comme %T>%, %$% et %<>%. Ils répondent à des usages précis (agir sans remplacer le résultat principal, ou exposer des colonnes dans une expression). Ils ne sont pas équivalents au pipe de base.
L'intégration dans R de base
L'ajout d'un pipe à R de base se discute pendant plusieurs années. Lionel Henry étudie cette possibilité dès 2016, notamment parce qu'un opérateur natif pourrait améliorer la gestion des erreurs et éviter de dépendre d'un paquet externe pour une construction très courante.
Les discussions reprennent publiquement sur la liste r-devel en 2019. En juillet 2020, Luke Tierney et Martin Mächler évoquent le développement d'un pipe natif lors de leur présentation à UseR! 2020. L'opérateur |> est finalement intégré à R 4.1.0, publiée le 18 mai 2021.
Cette intégration ne veut pas dire que magrittr « disparaît ». Elle donne plutôt un choix : un opérateur natif intégré au langage, ou une solution plus riche déjà utilisée depuis longtemps dans de nombreux projets. Et depuis, la syntaxe native a continué à évoluer : avec R 4.2.0, le marqueur _ arrive pour transmettre la valeur dans un argument nommé. La documentation de l'opérateur pipe de R détaille les règles à jour.
Comparer |> et %>% en pratique
Sur les cas simples, les deux opérateurs donnent le même résultat :
1:10 %>% mean()
1:10 |> mean()
Mais ils ne sont pas interchangeables dans tous les scripts. Les différences principales concernent l'appel de fonction à droite, la façon dont la valeur transmise est placée, et les fonctionnalités spécifiques à magrittr.
Avec le pipe natif, écrivez un appel de fonction
|> attend un appel à droite. Du coup, on écrit mean() plutôt que mean :
1:10 |> mean()
Avec magrittr, on peut omettre les parenthèses dans certains cas :
1:10 %>% mean
Cette souplesse peut sembler pratique, mais elle ajoute une règle de plus à retenir. Pour du code partagé ou maintenu longtemps, les appels explicites avec parenthèses sont souvent plus faciles à repérer.
Le point de magrittr et le marqueur _
Avec magrittr, le point . sert à placer l'objet transmis à l'endroit voulu :
penguins %>%
rpart(species ~ ., method = "class", data = .)
Attention au double sens : dans species ~ ., le point fait partie de la formule et signifie « toutes les autres variables ». Dans data = ., le point est le marqueur de magrittr. Même symbole, rôles différents : c'est utile, mais ça surprend au début.
Le pipe natif ne reprend pas ce « remplacement » du point dans les formules. Pour transmettre une valeur à un argument nommé, R utilise _ :
penguins |>
rpart(species ~ ., method = "class", data = _)
Et là aussi, ce n'est pas une simple substitution de caractères. Les contraintes autour de _ ne sont pas celles du point de magrittr. Donc évitez de migrer mécaniquement . → _ : vérifiez d'abord dans la documentation de R sur le pipe, surtout si votre code utilise des cas moins courants.
Une fonction anonyme avec |>
Autre option : entourer l'appel dans une fonction anonyme. La notation abrégée \(x) est disponible depuis R 4.1.0 :
penguins |>
(\(x) rpart(species ~ ., method = "class", data = x))() |>
prp(extra = 4)
L'objet transmis devient alors l'argument x, que vous placez où il faut. C'est clair et explicite, et ça ne dépend pas de magrittr. Par contre, c'est plus verbeux, et ça demande un peu plus d'attention.
Dans du code récent, le marqueur _ pour un argument nommé est souvent plus direct. La fonction anonyme reste utile quand la transformation ne se résume pas à un argument nommé, ou quand vous avez besoin de faire plusieurs étapes « locales » avant de renvoyer un résultat.
Les cas où le pipe se prête moins
Le pipe est surtout confortable quand l'objet transformé devient un argument d'une fonction. Il l'est moins quand la valeur doit alimenter plusieurs arguments, ou plusieurs branches de calcul. Dans ce cas, une variable intermédiaire nommée peut vraiment clarifier :
modele <- rpart(species ~ ., data = penguins, method = "class")
resume_modele <- summary(modele)
Et si la transformation correspond à une étape métier réutilisable, une fonction dédiée fait souvent mieux que empiler des pipes. Le but, c'est de rendre les dépendances visibles, pas de maximiser le nombre d'opérateurs dans un fichier.
Choisir et utiliser un pipe dans un projet
Le choix entre |> et %>% dépend moins d'une préférence personnelle que du contexte. Dans un nouveau projet qui vise une version récente de R et n'a pas besoin de fonctionnalités magrittr, |> est souvent un choix simple : pas de dépendance en plus, et la syntaxe fait partie du langage.
Dans une application existante qui vit déjà avec dplyr et magrittr, continuer avec %>% peut être parfaitement cohérent. Remplacer juste l'opérateur, sans autre effort, apporte rarement un gain suffisant pour justifier une réécriture. Là, compteront surtout le risque, la qualité des tests, et le temps de revue.
Une grille de décision simple
Avant de trancher, vérifiez ces quatre points :
- La version minimale de R.
|>nécessite R 4.1.0 ou plus._arrive avec R 4.2.0. Si vos environnements sont plus anciens, c'est décisif. - Les fonctionnalités utilisées. Si le code dépend de
%T>%,%$%,%<>%ou du placement souple de.dans des appels, magrittr garde un intérêt concret. - Les conventions de l'équipe. Dans un dépôt partagé, la cohérence aide aux revues et évite des discussions sans fin. Une convention écrite bat une migration « au feeling ».
- Le coût de maintenance. Est-ce qu'un changement de pipe introduit vraiment du risque ou une complexité supplémentaire ? Si non, gardez le code déjà testé et lisible.
Des règles de lecture qui restent vraies
Quelques habitudes suffisent pour éviter la plupart des problèmes :
- Mettez une opération importante par ligne quand une chaîne a plusieurs étapes.
- Donnez un nom aux résultats intermédiaires qui ont du sens métier ou d'analyse.
- Évitez de mélanger
|>et%>%dans le même traitement, sauf raison claire. - Gardez les dépendances explicites :
|>n'exige pas magrittr, maisgroup_by()etsummarise()viennent bien de dplyr. - Testez les chaînes après migration, surtout quand les données ne sont pas le premier argument.
- Vérifiez la version de R en production, pas seulement celle de la machine de dev.
Pour aller plus loin, vous pouvez consulter la documentation officielle de R, les guides de dplyr, et l'historique du pipe magrittr. Pour le contexte historique, l'article d'Adolfo Álvarez, « Plumbers, chains, and famous painters », retrace les premières étapes jusqu'à l'arrivée de |>.
Conclusion : choisissez la syntaxe qui simplifie le projet
Le pipe en R est le résultat d'années d'expérimentations de la communauté : des premières discussions sur Stack Overflow à l'adoption de |> dans R de base, en passant par dplyr et magrittr. L'objectif reste le même : écrire une suite de transformations de façon lisible, dans l'ordre où elles s'exécutent.
Pour un nouveau projet compatible avec les versions récentes de R, partez souvent sur |>. Gardez %>% quand ses fonctionnalités vous servent vraiment, ou quand le projet s'appuie déjà dessus. Et dans tous les cas : évitez les migrations « cosmétiques ». La meilleure syntaxe est celle que votre équipe peut comprendre, tester et maintenir sans friction.
Si vous révisez un script aujourd'hui, commencez par vérifier la version minimale de R et repérez les usages de .. Ensuite seulement, vous pourrez décider si le pipe natif rend réellement le code plus simple - ou si votre configuration actuelle reste le meilleur choix.