I'm trying to nest a function that glues together two strings within a function that uses the combined string to name a column of a dataframe. However, the problem seems to be that the glue expression is not evaluated to a string early enough. Can (and should) I force the expression to be evaluated before it is being passed on as an argument to another function?
library(tidyverse)
# define inner function
add_prefix <- function(string) {
x <- glue::glue("prefix_{string}")
return(as.character(x))
}
# define outer function
mod_mtcars <- function(df, name) {
df %>%
mutate({{ name }} := mpg ^ 2)
}
mod_mtcars(mtcars, add_prefix("foo"))
#> Error: The LHS of `:=` must be a string or a symbol
# alternative outer function with explicit enquoting
mod_mtcars2 <- function(df, name) {
name <- ensym(name)
df %>%
mutate(!!name := mpg ^ 2)
}
mod_mtcars2(mtcars, add_prefix("foo"))
#> Error: Only strings can be converted to symbols
Created on 2021-10-30 by the reprex package (v2.0.1)
CodePudding user response:
name
is not a symbol. Try:
mod_mtcars <- function(df, name) {
name <- sym(name)
df %>%
mutate({{ name }} := mpg ^ 2)
}
mod_mtcars(mtcars, add_prefix("foo"))
Even better, tidy eval now supports glue strings so you could simplify with:
# don't need add_prefix()
mod_mtcars <- function(df, name){
df %>%
mutate("prefix_{{name}}" := mpg ^ 2)
}
mod_mtcars(mtcars, foo)
Lastly, the curly-curly operator tunnels your expression and you're passing it a string. If you keep your current functions use the bang-bang operator instead:
mod_mtcars <- function(df, name){
df %>%
mutate(!! name := mpg ^ 2)
}
mod_mtcars(mtcars, add_prefix("foo"))
CodePudding user response:
We may need to escape the input (keeping the OP's original function unchanged)
mod_mtcars(mtcars, !!add_prefix("foo"))
-output
mpg cyl disp hp drat wt qsec vs am gear carb prefix_foo
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4 441.00
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4 441.00
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1 519.84
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1 457.96
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2 349.69
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1 327.61
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4 204.49
...
CodePudding user response:
First of all, the glue string syntax is now preferred over embracing directly in the LHS. So prefer "{{ var }}" := expr
to {{ var }} := expr
. In a future version of rlang (next year) we'll make it possible to use glue strings with =
. At that point, :=
will be pretty much superseded. We went with :=
to allow !!
injection on the LHS before glue support was added.
Second, your problem is that you're using {{
instead of simple injection. {{
is for injecting the expression supplied as argument, not the value of the expression. Use normal glue interpolation with "{"
to inject the value instead:
mod_mtcars <- function(df, name) {
df %>%
mutate("{name}" := mpg ^ 2)
}
PS: Your !!
version had a similar problem. Because you used ensym()
on the argument, you were defusing the expression supplied as argument instead of using the value. But ensym()
requires the expression to be a simple name and you supplied a full computation, causing an error. You can fix it like this:
mod_mtcars2 <- function(df, name) {
df %>%
mutate(!!name := mpg ^ 2)
}
But glue syntax is now preferred.