1

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?

JasperJ
  • 1,192
  • 1
  • 12
  • 23
  • @akrun I'm unsure what you mean, could you elaborate? – JasperJ Apr 27 '20 at 21:09
  • @akrun Thanks, I corrected the mistake, unfortunately this doesn't solve the issue. – JasperJ Apr 27 '20 at 21:35
  • @akrun Yes, type of 'name' is character and when running mutate contains the value "new_name_value" . And therefore replicates the other examples. Unlisting or casting 'name' to a character makes no difference. – JasperJ Apr 27 '20 at 21:42
  • 1
    You could use `rename_at` `t1 %>% mutate(new = map2(name, data, ~ {new_name <- .x; .y %>% rename_at(vars(old_name), ~ new_name)}))` – akrun Apr 27 '20 at 21:45
  • @akrun I've simplified the example to avoid problems with paramter passing as you suggested. – JasperJ Apr 27 '20 at 21:46
  • I guess it is an issue within the `mutate/map` because `map(df$data, function(d) {new_name <- quo('new_value'); d %>% rename(!!quo_name(new_name) := old_name)})` works or `lapply(df$data, function(d) {new_name <- quo('new_value'); d %>% rename(!!quo_name(new_name) := old_name)})` works – akrun Apr 27 '20 at 21:54
  • @akrun that does satisfy me for now as a workaround. Many thanks for you help! – JasperJ Apr 27 '20 at 21:54
  • Not clear whether it is a bug or not in finding the environment of the objects created – akrun Apr 27 '20 at 21:56

2 Answers2

2

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().

Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74
1

If we make it self-contained with a () or with {} it should work

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

# A tibble: 3 x 1
#  new_name_value
#  <list>        
#1 <dbl [1]>     
#2 <dbl [1]>     
#3 <dbl [1]>  

If the anonymous function contains only a single expression, it is not needed to use {}, but if it have more one line of expression, we wrap with {}. According to ?body

The bodies of all but the simplest are braced expressions, that is calls to {: see the ‘Examples’ section for how to create such a call.

akrun
  • 874,273
  • 37
  • 540
  • 662
  • It does seem to work, yet I'm unsure why. Could you explain why this makes a difference? – JasperJ Apr 27 '20 at 21:12
  • 1
    @JasperJ. It is just that you didn't had the `{` after the `function()`. I generally use the wrapper with `()` instead of `{}` as it is a bit of subjective preference. Also, as a means to differentiate the `{}` within the call from the outside – akrun Apr 27 '20 at 21:14
  • Interesting, although the issue remains with the second example although a) it has been wrapped in () and b) { was placed after the function declaration. What is going wrong? – JasperJ Apr 27 '20 at 21:27