16

I am trying to learn the piping function (%>%).
When trying to convert from this line of code to another line it does not work.

---- R code -- original version -----

set.seed(1014)
replicate(6,sample(1:8))
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    3    7    4    5    1
[2,]    2    8    4    2    4    2
[3,]    5    4    8    5    8    5
[4,]    3    1    2    1    1    7
[5,]    4    6    3    7    7    3
[6,]    6    5    1    3    3    8
[7,]    8    7    5    8    6    6
[8,]    7    2    6    6    2    4

---- R code - recoded with the pipe ----

> sample(1:8) %>%  replicate(6,.)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    7    7    7    7    7    7
[2,]    3    3    3    3    3    3
[3,]    2    2    2    2    2    2
[4,]    1    1    1    1    1    1
[5,]    5    5    5    5    5    5
[6,]    4    4    4    4    4    4
[7,]    8    8    8    8    8    8
[8,]    6    6    6    6    6    6

Notice that when using pipes, the sampling does not work giving me the same vector across.

phage
  • 584
  • 3
  • 17
  • 5
    In the first code, the replicate is executing the sample function 6 time. In the second code, you are given the output to the sample function and replicating that output 6 times. – student Apr 07 '17 at 16:26

2 Answers2

21

That's to be expected. replicate expects an expression, but when using the pipe operator as is you just paste the result of the call to sample() to replicate. So you get 6 times the same result.

You have to use quote() to pass the expression to replicate instead of the result, but you shouldn't forget to evaluate each of the repetitions of that expression.

quote(sample(c(1:10,-99),6,rep=TRUE)) %>% 
  replicate(6, .) %>%
  sapply(eval)

Gives:

    [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    5    2   10   10    9    2
[2,]    4    3    1    3  -99    1
[3,]   10    2    3    8    2    4
[4,]  -99    1    6    2   10    3
[5,]    8  -99    1    9    4    6
[6,]    4   10    8    1  -99    8

What happens here:

  • the piping sends and expression to replicate without evaluating it.
  • replicate replicates that expression and returns a list with 6 times that expression but without evaluating it.
  • sapply(eval) goes through the list and executes each expression in that list.

In your previous question (i.e. when using data.frame), you could have done eg:

quote(sample(c(1:10,-99),6,rep=TRUE)) %>% 
  replicate(6, .) %>%
  data.frame

Now the function data.frame would force the expressions to be executed, but you also end up with terrible variable names, i.e. the expression itself.

If you want to learn more about the issues here, you'll have to dive into what is called "lazy evaluation" and how that is dealt with exactly by the pipe operator. But in all honesty, I really don't see any advantage of using the pipe operator in this case. It's not even more readable.

As per Frank's comment: You can use a mixture of piping and nesting of functions to avoid the sapply. But for that, you have to contain the nested functions inside a code block or the pipe operator won't process it correctly:

quote(sample(c(1:10,-99),6,rep=TRUE)) %>% {
  replicate(6, eval(.)) }

Very interesting, but imho not really useful...

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • Thank you so much. That makes so much sense. Just trying to learn this stuff. Yeah, may not make worth it to use pipes here but I was just learning. – phage Apr 07 '17 at 16:29
  • I don't think sapply is needed: `quote(sample(c(1:10,-99),6,rep=TRUE)) %>% { replicate(6, eval(.)) }` – Frank Apr 07 '17 at 16:37
  • 1
    @Frank forget my previous comment, I missed the `{}` and the fact `eval()` is now nested inside `replicate`. That works indeed, but this is half piping, half not, so it makes even less sense. Thank you for mentioning it though, it's an interesting case. And yet another example as to why that pipe operator is a can of worms I'm reluctant to open. I added your proposal to the post though. – Joris Meys Apr 07 '17 at 16:41
  • 4
    Ok, in my opinion, it's still piping, just not to the first argument of `replicate`. The magrittr rules for "guessing" when to pass `.` to the first argument are annoying, yeah. And also, for simulations, I wouldn't use pipes, ran a benchmark here (under "speed and pipes"): http://franknarf1.github.io/r-tutorial/_book/work.html#simulations – Frank Apr 07 '17 at 16:52
0

As of R 4.1.0, a new pipe |> is available which does work with replicate.

library(magrittr)
set.seed(123)
runif(1) %>%
  replicate(n = 4)
# 0.2875775 0.2875775 0.2875775 0.2875775
runif(1) |>
  replicate(n = 4)
# 0.7883051 0.4089769 0.8830174 0.9404673
LBogaardt
  • 402
  • 1
  • 4
  • 25