I've been studying tidyeval
semantics from a number of sources, but I'm getting a result I can't explain.
I'm using mutate_at
and case_when
to transform some variables by (1) retrieving their names using quotation, (2) modifying their names using gsub
, and (3) referencing the data associated with the modified names.
In my minimal example, I'm creating foo$c
as a transformation of foo$b
which is meant to simply take on the value from foo$a
. Steps (1) and (2) seem to be straightforward:
library(tidyverse)
library(rlang)
foo <- data.frame(a = 1, b = 2)
foo %>%
mutate_at(vars(c = b),
funs(case_when(
TRUE ~ gsub("b", "a", as_name(quo(.)))
)))
#> a b c
#> 1 1 2 a
foo$c
contains the correct name of the variable we want to look at. I understand I need to transform the string into a symbol
using sym()
and then evaluate it. If I was using a simple mutate()
, !!
and sym()
work fine:
foo %>%
mutate(c := !!sym(gsub("b", "a", as_name(quo(b)))))
#> a b c
#> 1 1 2 1
But when I do this inside the mutate_at(case_when())
I do not get the correct result:
foo %>%
mutate_at(vars(c = b),
funs(case_when(
TRUE ~ !!sym(gsub("b", "a", as_name(quo(.))))
)))
#> a b c
#> 1 1 2 2
To see what's going on I made a simple printing function. Without !!
it looks from the printout as though gsub()
and sym()
are both producing the intended results:
look <- function(x) {
print(x)
print(typeof(x))
return(x)
}
foo %>%
mutate_at(vars(c = b),
funs(case_when(
TRUE ~ look(sym(look(gsub("b", "a", as_name(quo(.))))))
)))
#> [1] "a"
#> [1] "character"
#> a
#> [1] "symbol"
#> Error in mutate_impl(.data, dots): Evaluation error: object of type 'symbol' is not subsettable.
Once I put !!
in front, the printout seems to show that we're getting a different result for gsub()
and sym()
:
foo %>%
mutate_at(vars(c = b),
funs(case_when(
TRUE ~ !!(look(sym(look(gsub("b", "a", as_name(quo(.)))))))
)))
#> [1] "."
#> [1] "character"
#> .
#> [1] "symbol"
#> [1] "."
#> [1] "character"
#> .
#> [1] "symbol"
#> a b c
#> 1 1 2 2
I don't understand how adding !!
can change the result from the nested sym(gsub())
. Adding a new operation to the end shouldn't change the prior/interior result. I've read that !!
is "not a function call, but a syntactic operation" but I don't fully appreciate that distinction or how that could change the result.
Using eval_tidy
instead of !!
seems to work fine, though I can't explain why:
foo %>%
mutate_at(vars(c = b),
funs(case_when(
TRUE ~ eval_tidy(look(sym(look(gsub("b", "a", as_name(quo(.)))))))
)))
#> [1] "a"
#> [1] "character"
#> a
#> [1] "symbol"
#> a b c
#> 1 1 2 1