为什么 Tidyeval 引用在 lambda 中失败?

Why do Tidyeval quotes fail in lamdas?

下面是一个简单示例,说明如何使用引号动态重命名 tibble 列。

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

quoteExample()

结果= tibble(new_name_value=list(1,2,3))

除了这次在 lamda 中,下面是同一个简单示例。

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

结果= is_quosure 中的错误(现状):未找到对象 'new_name'

为什么引号在 lamda 中失败但在命名函数中却没有?这种差异从何而来?难道我做错了什么?

编辑:上面的例子已经被 Akrun 解决了,但是下面是另一个例子,尽管已经应用了建议的解决方案,但还是失败了:

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)
    })))

结果:is_quosure 中的错误(现状):未找到对象 'new_name'

失败是因为另一个问题吗?

如果我们用 (){} 使其独立,它应该可以工作

(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]>  

如果匿名函数只包含一个expression,则不需要使用{},但如果表达式多一行,则用{}换行.根据?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.

这与 基本上是同一个问题。主要原因是 !! 运算符强制立即评估其参数, 创建匿名函数环境之前。在您的情况下, !! quo_name(new_name) 尝试找到 new_name 相对于整个表达式(即整个 mutate(...) 表达式)的定义。由于 new_name 是在表达式本身中定义的,因此您最终会遇到导致 "object not found" 错误的循环依赖。

你的三个选择是

1) 将您的 lambda 拉出到一个独立的函数中,以确保首先创建它的环境,从而在 !! 运算符强制其评估之前正确初始化该环境中的所有变量:

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

df %>% mutate(data = map(data, f))

2) 在试图用 !!

强制计算的表达式外定义 new_name
new_name = sym("new_value")
df %>%
    mutate(data = map(data, function(d) {d %>% rename(!!new_name := old_name)}))

3) 重写您的表达式,使其不使用 !! 运算符来评估尚未初始化的变量(在本例中为 new_name):

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

旁注:您会注意到我用 sym() 替换了您的 quo() 调用。函数 quo() 捕获表达式及其环境。由于字符串文字 "new_value" 将始终评估为相同的值,因此无需标记其环境。通常,将列名捕获为符号的正确动词是 sym().