Search code examples
rdplyrtidyversetidyeval

Why do Tidyeval quotes fail in lamdas?


Below is a simple example of how a quote is used to dynamically rename a tibble column.

quoteExample = function() {  
   new_name = quo("new_name_value"); 
   tibble(old_name=list(1,2,3)) %>% 
       rename( !! quo_name(new_name) := old_name) 
}

quoteExample()

Result= tibble(new_name_value=list(1,2,3))

Below the same simple example except this time in a lamda.

{function () 
   new_name = quo("new_name_value"); 
   tibble(old_name=list(1,2,3)) %>% 
       rename( !! quo_name(new_name) := old_name)
} ()

Result= Error in is_quosure(quo) : object 'new_name' not found

Why do quotes fail in a lamda but not in a named function? Where does this difference come from? Am I doing something wrong?

EDIT: The example above has been solved by Akrun, but below is another example that fails although the suggested solution has been applied:

df = tibble(data=list(tibble(old_name= c(1,2,3))))

df %>% 
   mutate(data = map(data, (function(d){
      new_name = quo("new_value")
      d %>% rename( !! quo_name(new_name) := old_name)
    })))

Result: Error in is_quosure(quo) : object 'new_name' not found

Is this failing because of another issue?


Solution

  • This is basically the same issue as the one here. The main cause is the !! operator forcing immediate evaluation of its argument, before the anonymous function environment is created. In your case, !! quo_name(new_name) attempts to find the definition of new_name relative to the expression as a whole (i.e., the entire mutate(...) expression). Since new_name is defined in the expression itself, you end up with a circular dependency that results in "object not found" error.

    You three options are

    1) Pull your lambda out into a standalone function to ensure its environment is created first, thus having all variables in that environment properly initialized before the !! operator forces their evaluation:

    f <- function(d) {
        new_name = sym("new_value")
        d %>% rename(!!new_name := old_name)
    }
    
    df %>% mutate(data = map(data, f))
    

    2) Define new_name outside the expression that attempts to force its evaluation with !!

    new_name = sym("new_value")
    df %>%
        mutate(data = map(data, function(d) {d %>% rename(!!new_name := old_name)}))
    

    3) Rewrite your expression such that it doesn't use the !! operator to evaluate variables that have not been initialized yet (new_name in this case):

    df %>%
       mutate(data = map(data, function(d) {
         new_name = "new_value"
         do.call( partial(rename, d), set_names(syms("old_name"), new_name) )
       }))
    

    SIDE NOTE: You will notice that I replaced your quo() calls with sym(). The function quo() captures an expression together with its environment. Since the string literal "new_value" will always evaluate to the same value, there is no need to tag along its environment. In general, the proper verb for capturing column names as symbols is sym().