Related to this question.
I'd like to build a custom pipe %W>%
that would silence warnings for one operation
library(magrittr)
data.frame(a= c(1,-1)) %W>% mutate(a=sqrt(a)) %>% cos
will be equivalent to :
w <- options()$warn
data.frame(a= c(1,-1)) %T>% {options(warn=-1)} %>%
mutate(a=sqrt(a)) %T>% {options(warn=w)} %>%
cos
These two tries don't work :
`%W>%` <- function(lhs,rhs){
w <- options()$warn
on.exit(options(warn=w))
options(warn=-1)
lhs %>% rhs
}
`%W>%` <- function(lhs,rhs){
lhs <- quo(lhs)
rhs <- quo(rhs)
w <- options()$warn
on.exit(options(warn=w))
options(warn=-1)
(!!lhs) %>% (!!rhs)
}
How can I rlang
this into something that works ?
I think I would approach it like this, by tweaking the magrittr pipes to include this new option. This way should be pretty robust.
First we need to insert a new option into magrittr's function is_pipe
by which it is determined whether a certain function is a pipe. We need it to recognise %W>%
new_is_pipe = function (pipe)
{
identical(pipe, quote(`%>%`)) || identical(pipe, quote(`%T>%`)) ||
identical(pipe, quote(`%W>%`)) ||
identical(pipe, quote(`%<>%`)) || identical(pipe, quote(`%$%`))
}
assignInNamespace("is_pipe", new_is_pipe, ns="magrittr", pos="package:magrittr")
`%W>%` = magrittr::`%>%`
We also need a new helper function that checks whether the pipe being processed is a %W>%
is_W = function(pipe) identical(pipe, quote(`%W>%`))
environment(is_W) = asNamespace('magrittr')
Finally, we need to put a new branch into magrittr:::wrap_function
which checks if this is a %W>%
pipe. If so, it inserts options(warn = -1)
and on.exit(options(warn = w)
into the body of the function call.
new_wrap_function = function (body, pipe, env)
{
w <- options()$warn
if (magrittr:::is_tee(pipe)) {
body <- call("{", body, quote(.))
}
else if (magrittr:::is_dollar(pipe)) {
body <- substitute(with(., b), list(b = body))
}
else if (is_W(pipe)) {
body <- as.call(c(as.name("{"), expression(options(warn=-1)), parse(text=paste0('on.exit(options(warn=', w, '))')), body))
}
eval(call("function", as.pairlist(alist(. = )), body), env, env)
}
assignInNamespace("wrap_function", new_wrap_function, ns="magrittr", pos="package:magrittr")
Testing this works:
data.frame(a= c(1,-1)) %W>% mutate(a=sqrt(a)) %>% cos
# a
# 1 0.5403023
# 2 NaN
compared to...
data.frame(a= c(1,-1)) %>% mutate(a=sqrt(a)) %>% cos
# a
# 1 0.5403023
# 2 NaN
# Warning message:
# In sqrt(a) : NaNs produced
Perhaps something like this with rlang
:
library(rlang)
library(magrittr)
`%W>%` <- function(lhs, rhs){
w <- options()$warn
on.exit(options(warn=w))
options(warn=-1)
lhs_quo = quo_name(enquo(lhs))
rhs_quo = quo_name(enquo(rhs))
pipe = paste(lhs_quo, "%>%", rhs_quo)
return(eval_tidy(parse_quosure(pipe)))
}
data.frame(a= c(1,-1)) %W>% mutate(a=sqrt(a)) %>% cos
Result:
a
1 0.5403023
2 NaN
Note:
You need enquo
instead of quo
because you are quoting the code that was supplied to lhs
and rhs
, not the literals lhs
and rhs
.
I couldn't figure out how to feed lhs_quo
/lhs
into rhs_quo
(which was a quosure
) before it was evaluated, neither can I evaluate rhs_quo
first (throws an error saying a
not found in mutate(a=sqrt(a))
)
The workaround that I came up with turns lhs
and rhs
into strings, pastes them with "%>%"
, parses the string to quosure
, then finally tidy evaluates the quosure
.
I'm not sure this solution works perfectly, but it's a start:
`%W>%` <- function(lhs, rhs) {
call <- substitute(`%>%`(lhs, rhs))
eval(withr::with_options(c("warn" = -1), eval(call)), parent.frame())
}
This seems to work for the following 2 examples:
> data.frame(a= c(1,-1)) %W>% mutate(a=sqrt(a)) %>% cos
a
1 0.5403023
2 NaN
> c(1,-1) %W>% sqrt()
[1] 1 NaN
Based on @dww's answer I created a package which integrates this pipe among others :
# devtools::install_github("moodymudskipper/mmpipe")
library(mmpipe)
library(dplyr)
data.frame(a= c(1,-1)) %W>% mutate(a=sqrt(a)) %>% cos
# a
# 1 0.5403023
# 2 NaN
The package offers also a way to define easily one own's pipe operators,
If we didn't already have %W>%
we could do :
add_pipe(`%W2>%`, substitute(
{options(warn = -1); on.exit(options(warn = w)); b},
list(w = options()$warn, b = body)))
where the second argument will create the new body of the function, and the variable body
is a call containing the body of the function before modification (in the following case it's quote(mutate(.a=sqrt(a)))
):
data.frame(a= c(1,-1)) %W2>% mutate(a=sqrt(a)) %>% cos
# a
# 1 0.5403023
# 2 NaN
More examples in the readme of mmpipe