【问题标题】:Why do Tidyeval quotes fail in lamdas?为什么 Tidyeval 引号在 lambdas 中失败?
【发布时间】:2020-08-11 13:45:13
【问题描述】:

下面是一个简单的例子,说明如何使用引号来动态重命名 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)
} ()

Result= is_quosure(quo) 中的错误:找不到对象“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(quo) 中的错误:找不到对象“new_name”

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

【问题讨论】:

  • @akrun 我不确定你的意思,你能详细说明一下吗?
  • @akrun 谢谢,我纠正了错误,不幸的是这并没有解决问题。
  • @akrun 是的,'name' 的类型是字符,并且在运行 mutate 时包含值 "new_name_value" 。因此复制了其他示例。取消列出或将“名称”转换为角色没有区别。
  • 你可以使用rename_at t1 %>% mutate(new = map2(name, data, ~ {new_name <- .x; .y %>% rename_at(vars(old_name), ~ new_name)}))
  • @akrun 我已经简化了示例,以避免您建议的参数传递问题。

标签: r dplyr tidyverse tidyeval


【解决方案1】:

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

你的三个选项是

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

【讨论】:

    【解决方案2】:

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

    (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

    除了最简单的之外,所有的主体都是花括号表达式,即对 { 的调用:请参阅“示例”部分了解如何创建这样的调用。

    【讨论】:

    • 它似乎确实有效,但我不确定为什么。你能解释一下为什么这会有所不同吗?
    • @JasperJ。只是在function() 之后没有{。我通常使用带有() 而不是{} 的包装器,因为它有点主观偏好。此外,作为区分呼叫中的{} 和外部的一种方法
    • 有趣,尽管第二个示例仍然存在问题,尽管 a) 它已被包装在 () 中并且 b) { 放在函数声明之后。出了什么问题?
    猜你喜欢
    • 2015-04-13
    • 1970-01-01
    • 2021-04-06
    • 2011-11-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    • 2016-08-22
    • 1970-01-01
    相关资源
    最近更新 更多