August 07, 2014

A quiz on magrittr: How many scores can you get?

Here is a quiz on magrittr and you may check if you are really good at using it. Since the CRAN version currently does not support nested ., it won't be interesting to make a quiz on that version. All the following examples are using the latest development version on GitHub. You can do the same test with the CRAN version if you like.

Consider the following function:

f <- function(x,y = "nothing") {
  cat("x = ", x, "\n")
  cat("y = ", y, "\n")
}

Now answer what is going to be evaluated for the following expressions:

library(magrittr)
# (0) Example
# only pipe to . like f(1:10)
1:10 %>% f(.) 
x =  1 2 3 4 5 6 7 8 9 10 
y =  nothing 
# (1)
1:10 %>% f(1)
# (2)
1:10 %>% f(.,.)
# (3)
1:10 %>% f(length(.)) 
# (4)
1:10 %>% f(min(.),max(.))
# (5)
1:10 %>% f(c(.,1),c(1,.))

Are you getting confused? Can you predict what is going to happen? If you feel quite confident or only a blink will tell you how the code will be run, just go ahead.

Consider the following function:

g <- function(x,y,z = "z") {
  cat("x = ", x, "\n")
  cat("y = ", y, "\n")
  cat("z = ", z, "\n")
}

Predict what is going to happen.

1:10 %>% g(.,2)
1:10 %>% g(1,length(.))
1:10 %>% g(1,(.))
1:10 %>% g(1,.)
1:10 %>% g(mean(.),length(.))

You may type or just copy-paste them in your console and see whether you get correct answers. If you are correct for all, you are really good at using it!

Now let's see what happens with the code above. For f(x,y):

f <- function(x,y = "nothing") {
  cat("x = ", x, "\n")
  cat("y = ", y, "\n")
}
# only pipe to .
# as if (1:10)
1:10 %>% f(.) 
x =  1 2 3 4 5 6 7 8 9 10 
y =  nothing 
# pipe to first argument
# as if f(1:10,1)
1:10 %>% f(1) 
x =  1 2 3 4 5 6 7 8 9 10 
y =  1 
# only pipe to .
# as if f(1:10,1:10)
1:10 %>% f(.,.) 
x =  1 2 3 4 5 6 7 8 9 10 
y =  1 2 3 4 5 6 7 8 9 10 
# pipe to first argument and .
# as if f(1:10,length(1:10))
1:10 %>% f(length(.)) 
x =  1 2 3 4 5 6 7 8 9 10 
y =  10 
# try to pipe to first-argument but does not work
# as if f(1:10,min(.),max(.))
1:10 %>% f(min(.),max(.))
Error: unused argument (max(.))
# try to pipe to first-argument but does not work
# as if f(1:10,c(.,1),c(1,.))
1:10 %>% f(c(.,1),c(1,.))
Error: unused argument (c(1, .))

Then consider what happens with g(x,y,z)

g <- function(x,y,z = "z") {
  cat("x = ", x, "\n")
  cat("y = ", y, "\n")
  cat("z = ", z, "\n")
}
# pipe to .
# as if g(1:10,2)
1:10 %>% g(.,2)
x =  1 2 3 4 5 6 7 8 9 10 
y =  2 
z =  z 
# pipe to first argument and .
# as if g(1:10,1,length(1:10))
1:10 %>% g(1,length(.))
x =  1 2 3 4 5 6 7 8 9 10 
y =  1 
z =  10 
# pipe to first argument
# as if g(1:10,1,(1:10))
1:10 %>% g(1,(.))
x =  1 2 3 4 5 6 7 8 9 10 
y =  1 
z =  1 2 3 4 5 6 7 8 9 10 
# pipe to .
# as if g(1,1:10)
1:10 %>% g(1,.)
x =  1 
y =  1 2 3 4 5 6 7 8 9 10 
z =  z 

How many scores do you get? What if you don't want to pipe the object to the first argument but simply want to evaluate g(mean(1:10),length(1:10))?

# pipe to first argument
# as if g(1:10,mean(1:10),length(1:10))
1:10 %>% g(mean(.),length(.))
x =  1 2 3 4 5 6 7 8 9 10 
y =  5.5 
z =  10 

A solution is to use lambda() or l() enclosed.

1:10 %>% (l(. ~ g(mean(.),length(.))))
x =  5.5 
y =  10 
z =  z 

In many cases, %>% fits the situation but sometimes it may not do as what we believe it will. And that is why pipeR is created. It gives user the full control of how the object will be piped, then all the problems above simply do not exist. You will feel pretty intuitive and confident to use the operator %>>% because it behaves based on a set of simple and intuitive rules:

  1. Give it a function call or name, it will always pipe to the first argument
  2. If that's not what you want, enclose your expression within {} or ()
  3. In () you can also name the symbol by yourself like (x -> f(x)).

Therefore, with pipeR the code won't be ambiguous anymore, and you can read and write pipelines that are easy to read and understand what exactly is going to happen.

Here are some examples:

  • Pipe to first argument and .? Give a function name or call.
library(pipeR)
1:10 %>>% f(1)
x =  1 2 3 4 5 6 7 8 9 10 
y =  1 
  • Only want to pipe to .? Enclose your expression.
1:10 %>>% ( f(.) )
x =  1 2 3 4 5 6 7 8 9 10 
y =  nothing 
1:10 %>>% ( f(.,.) )
x =  1 2 3 4 5 6 7 8 9 10 
y =  1 2 3 4 5 6 7 8 9 10 
1:10 %>>% ( f(mean(.), length(.)) )
x =  5.5 
y =  10 
  • Name the symbol by yourself? Use lambda expression.
1:10 %>>% (x -> f(mean(x),length(x)))
x =  5.5 
y =  10 

You will feel that you have full power and confidence to control its behavior and read/write code that brings no ambiguity. See the project page of pipeR.

comments powered by Disqus