23  Pipes

23.1 Introduction

Note

In its basic form, a pipe allows writing: f(x) as x |> f() and, similarly, g(f(x)) as x |> f() |> g().

Figure 23.1: Illustration of pipes in R.

A pipe operator was first introduced to R by the magrittr package with the %>% symbol. Note that a number of other packages that endorse the use of pipes export the pipe operator as well.

Starting with R version 4.1 (May 2021), a native pipe operator is included with the |> symbol.

A pipe allows writing f(x) as x |> f() (native pipe) or x %>% f (magrittr).

Note that the native pipe requires parentheses, but magrittr works with or without them.

A pipe is often used to:

  • avoid multiple temporary assignments in a multistep procedure, or
  • as an alternative to nesting functions.

Some packages and developers promote its use, others discourage it. You should try and see if/when it suits your needs.

The following:

x <- f1(x)
x <- f2(x)
x <- f3(x)

is equivalent to:

x <- f3(f2(f1(x)))

is equivalent to:

x <- x |> f1() |> f2() |> f3()
iris[, -5] |>
  split(iris$Species) |>
  lapply(function(i) sapply(i, mean))
$setosa
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
       5.006        3.428        1.462        0.246 

$versicolor
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
       5.936        2.770        4.260        1.326 

$virginica
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
       6.588        2.974        5.552        2.026 

Pipes are used extensively in the tidyverse packages and many other third-party packages.
You can learn more about the magrittr pipe operator in the vignette.

Tip

In RStudio the keyboard shortcut for the pipe operator is Shift-Command-M (MacOS) or Ctrl-Shift-M (Windows).

23.2 Differences between native pipe and magrittr

Native pipe requires parenthesis (()) after function name, magrittr works with or without them. For example,

x <- rnorm(300)
x |> mean()
[1] 0.01181341

but this would fail:

x |> mean

while either works in magrittr:

[1] 0.01181341
x %>% mean
[1] 0.01181341

The native pipe passes its LHS to the first unnamed argument on the RHS. Alternatively, it can be passed to any named argument using an underscore (“_“) symbol.

On the other hand, magrittr allows using a period . to pipe to any position on the RHS. The native pipe workaround is using an anonymous function (can use the new shorter syntax \(x) instead of function(x)).

Example: Find the position of “r” in the latin alphabet

Here, we want to pass the LHS to the second argument of grep().

Using native pipe, we name the first argument pattern and the LHS is passed to the first unnamed argument, i.e. the second (which is x, the character vector where matches are looked for):

Native pipe or magrittr: pass to the first unnamed argument:

letters |> grep(pattern = "r")
[1] 18
letters %>% grep(pattern = "r")
[1] 18

Native pipe: pass to any named argument using an underscore (_) placeholder, but argument must be named:

letters |> grep("r", x = _)
[1] 18

magrittr: pass to any argument using a period (.) placeholder. Argument can be named or not (i.e. positional):

letters %>% grep("r", x = .)
[1] 18
letters %>% grep("r", .)
[1] 18

For demonstration purposes, here’s how you can achieve the same using the native pipe and an anonymous function.

# positional:
letters |> {\(x) grep("r", x)}()
[1] 18
# named:
letters |> {\(x) grep("r", x=x)}()
[1] 18