Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to evaluate a glue expression in a nested function?

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)

like image 578
dufei Avatar asked Mar 02 '23 09:03

dufei


2 Answers

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.

like image 141
Lionel Henry Avatar answered Mar 12 '23 08:03

Lionel Henry


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"))
like image 40
LMc Avatar answered Mar 12 '23 08:03

LMc