3

I'm trying to understand how |> works, and how it compares to the magrittr %>%. Consider the following code, which is rather unpleasant to look at / debug:

toy <- data.frame(a = c(1,2,3), type = c("apple", "pear", "orange"))

set.seed(1)
subset(toy, type == "apple")[sample(nrow(subset(toy, type == "apple")), 1),]
#>   a  type
#>   1 1 apple

The documentation of |> says:

Pipe notation allows a nested sequence of calls to be written in a way that may make the sequence of processing steps easier to follow.

Which makes me believe something like

toy |>
  subset(type == "apple") |>
  `[.data.frame`(sample(nrow(.), 1),)

is possible, but doesn't work, as the dot is meaningless here. Note, [.data.frame seems to bypass the [ restriction of the RHS of |>. I've tried to find the source code for |> by running *backtick*|>*backtick* in the console, but that results in Error: object '|>' not found.

Using the magrittr pipe, a placeholder . can be used like this:

library(magrittr)
toy %>%
  subset(type == "apple") %>%
  `[`(sample(nrow(.), 1),)
#>   a  type
#>   1 1 apple

Question

How to properly write nested calls using base R pipe |>?

Donald Seinen
  • 4,179
  • 5
  • 15
  • 40
  • `toy |> subset(type == "apple") |> {\(.) ``[.data.frame``(sample(nrow(.), 1),)}()` – Eyayaw Nov 06 '21 at 09:20
  • @Eyayaw Use of `{` results in an error: `toy |> {}` >> `{ is not supported in RHS of |>` – Donald Seinen Nov 06 '21 at 09:26
  • 1
    `toy |> subset(type == "apple") |> (function(x) {x[sample(nrow(x), 1), ]})()` – phiver Nov 06 '21 at 09:29
  • It works for me. Try this example: `mtcars|> subset(am == 1) |> {\(.) ``[.data.frame``(., sample(nrow(.), 1), )}()` – Eyayaw Nov 06 '21 at 09:31
  • 1
    There is no source code for `|>`. Unlike `%>%`, it is not an operator in that sense. It is handled completely by the parser. You can see this if you do something like `deparse(quote(toy |> subset(type == "apple")))`, which gives `"subset(toy, type == \"apple\")"`. – user2554330 Nov 06 '21 at 12:43
  • @user2554330 Ah, that's clearer than *"syntax transformation"* mentioned in the help file. So I shouldn't expect the placeholder `=>` to behave anything like magrittr's `.` placeholder (yet) if I want to refer to anything other than the top level in RHS. – Donald Seinen Nov 06 '21 at 14:59
  • base R native pipe `|>` now have a placeholder, `_`. See [here](https://stackoverflow.com/a/72004083/13460602). – Maël Apr 25 '22 at 18:35

2 Answers2

3
toy <- data.frame(a = c(1,2,3), type = c("apple", "pear", "orange"))

set.seed(1)

toy |> subset(type == "apple") |> 
(\(x) x[sample(nrow(x), 1), ])()
 a  type
1 1 apple

toy |> subset(type == "apple") |> 
(\(x) `[.data.frame`(x, sample(nrow(x), 1), ))()
 a  type
1 1 apple
Eyayaw
  • 1,033
  • 5
  • 10
  • Constructing and calling an anonymous function does work, thanks for the answer. Would it be correct to say "the base R pipe does not support placeholder arguments natively, but they can be constructed by formulating and calling anonymous functions"? Also - I'm a bit confused why `(` after `|>` does not throw an error. both `(` and `function` are not supported as RHS independently, but used together it works – Donald Seinen Nov 06 '21 at 10:03
  • 1
    Yes, as of now, the base pipe Op does not support a placeholder. There is an experimental placeholder option that you can turn on with `Sys.setenv`. Please look at [this](https://stackoverflow.com/a/67821421/9782217) SO answer. You can use `{` or `(` as a delimiter interchangeably. – Eyayaw Nov 06 '21 at 10:43
0

As of R version 4.2.0. an argument placeholder is available for the base pipe. From the R News page:

In a forward pipe |> expression it is now possible to use a named argument with the placeholder _ in the rhs call to specify where the lhs is to be inserted. The placeholder can only appear once on the rhs.

This however does not work for a nested function call like this.

As far as I know the canonical way to do this in base R is to use a lambda like this:

toy |>
  subset(type == "apple") |>
  (\(x){x[sample(nrow(x))]})()

I don't know if this is just an example or the code you want to use, if you want to have code that archives the same, just use the sample method for data.frames, that samples rows from a data.frame:

toy |> 
  subset(type=="apple") |> 
  sample(1)
snaut
  • 2,261
  • 18
  • 37