Implementing the Native R Pipe (|>) and Underscore Placeholder
Learn how R's native pipe (|>) and underscore placeholder work, how they differ from magrittr's %>%, and how to migrate production pipelines safely.
19 Mar 2026, 07:16 UTC

Moving Beyond magrittr: The Native R Pipe
For years, R developers relied on the magrittr package's %>% operator to build readable data pipelines. Since R 4.1.0, a native pipe operator |> has been built into the language. The useful takeaway: |> is a syntax-level transformation, not a function call. It removes the magrittr dependency entirely, adds no runtime overhead, and produces cleaner call stacks in traceback() because the parser rewrites the pipeline before evaluation.
How the Native Pipe Works
The expression x |> f() is parsed by R as f(x). The result of the left-hand side is passed as the first argument of the right-hand side call. Because this rewriting happens at parse time, chaining multiple operations costs nothing extra at runtime, and the code runs in minimal environments — containers, CI runners, or Rscript on servers — with zero package dependencies.
Routing Values with the Underscore Placeholder
R 4.2.0 added the underscore _ placeholder, which lets you pipe a value into a named argument other than the first position. This matters for functions where the data object is not the first argument, such as aggregate() or lm().
# Requires R >= 4.2.0
# Mean MPG by cylinder count, using only cars with mpg > 20
mtcars |>
subset(mpg > 20) |>
aggregate(mpg ~ cyl, data = _, FUN = mean)
Here the result of subset() is routed into the data argument of aggregate(). Without the placeholder, it would land in the first position — the formula argument — and fail with a type error.
Native Pipe vs. magrittr
| Feature | Native Pipe (|>) |
magrittr (%>%) |
|---|---|---|
| Implementation | Parser-level rewrite | Function-based evaluation |
| Dependencies | None (base R) | Requires magrittr package |
| Placeholder | _ (strict rules) |
. (flexible, reusable, nestable) |
| Debugging | Flat, desugared call stack | Deep, nested call stack |
| Extras | None | %$%, %T>%, tee and exposition pipes |
Strict Limits of the Underscore
The _ is a syntactic marker, not a variable. Three constraints commonly trip up migrations from magrittr:
- Named arguments only: the placeholder must appear as a named argument (e.g.,
data = _), not positionally. - Single use: it may appear at most once per call.
- No expressions:
x |> sum(_ + 1)is a parse error, not a runtime error. Wrap the logic in an anonymous function instead:x |> (\(y) sum(y + 1))().
Note also that |> evaluates eagerly left-to-right; it does not alter the lazy evaluation semantics of the functions being called.
Migration Risks and Verification
A common mistake is a mechanical find-and-replace of %>% with |>. This silently breaks any pipeline that uses the . placeholder inside nested expressions (e.g., paste0(., "x")) or references the piped value more than once. Mixing both pipes in one project is legal but hurts readability and confuses linters — pick one per project.
Run the following in an R console (no special permissions needed) to verify your environment before migrating:
# 1. Confirm R >= 4.2.0 for placeholder support
R.version.string
# 2. Basic piping — expect 15
1:5 |> sum()
# 3. Valid placeholder use — expect 3
list(a = 1, b = 2) |> with(sum(unlist(_)))
# 4. Invalid nested placeholder — expect a parse error mentioning '_'
1:3 |> sum(_ + 1)
Before committing a migration, run the original magrittr pipeline and the rewritten |> version on identical data and compare outputs with identical() or all.equal(). If the target environment's R version is unknown or older than 4.1.0, keep magrittr::%>% and declare magrittr in your package's DESCRIPTION file — code using |> or _ fails to parse on older R, so it will not even load.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.