【问题标题】:Use of tidyeval based non-standard evaluation in recode in right-hand side of mutate在 mutate 右侧的重新编码中使用基于 tidyeval 的非标准评估
【发布时间】:2020-02-09 06:31:15
【问题描述】:

考虑一个 tibble,其中每一列都是一个字符向量,可以取多个值——比如说“A”到“F”。

library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))

我希望创建一个以列名作为参数的函数,并重新编码该列,以便任何答案“A”变为 NA,否则 df 按原样返回。以这种方式设计它的原因是为了适应更广泛的管道,该管道使用给定的列执行一系列操作。

有很多方法可以做到这一点。但我有兴趣了解最好的惯用 tidy_eval/tidyverse 方法是什么。首先,问题名称需要在变异动词的左侧,因此我们适当地使用!!:= 运算符。但是,在右手边放什么?

fix_question <- function(df, question) {
    df %>% mutate(!!question := recode(... something goes here...))
}

fix_question(sample_df, "q1") # should produce a tibble whose first column is (NA, "B", "C")

我最初的想法是这会起作用:

df %>% mutate(!!question := recode(!!question, "A" = NA_character_))

当然,函数内部的 bang-bang 只返回文字字符串(例如“q1”)。我最终采用了一种感觉像是一条骇人听闻的路线来引用右侧的数据,使用基本的 R [[ 运算符并依赖 dplyr 的 . 构造,它有效,所以从某种意义上说我已经解决了我的根本问题:

df %>% mutate(!!question := recode(.[[question]], "A" = NA_character_))

我有兴趣从非常擅长 tidyeval 的人那里获得关于是否有更惯用的方法来执行此操作的反馈,希望看到一个有效的示例可以增强我对 tidyeval 函数集的更广泛理解。有什么想法吗?

【问题讨论】:

  • 谢谢,这是一个聪明的方法——我确实在我的代码的其他部分使用了函数式方法,并且可以考虑在这里也这样做。我知道有些人对 SO 上的代码风格谈话不屑一顾,但这么快就看到几种不同风格的答案对我来说非常有成果。
  • 结合这个问题的几个想法,我相信这是最简洁的版本,同时适用于q1(符号)和"q1"(字符串):df %&gt;% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)

标签: r dplyr rlang tidyeval nse


【解决方案1】:

您可以通过允许将重新编码的值向量作为参数输入来使函数更加灵活。例如:

library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))

fix_question <- function(df, question, recode.vec) {

  df %>% mutate({{question}} := recode({{question}}, !!!recode.vec))

}

fix_question(sample_df, q1, c(A=NA_character_, B="Was B"))
  q1    q2   
1 <NA>  B    
2 Was B B    
3 C     A

请注意,recode.vec!!! 是“未引用拼接”的。你可以看到这个例子在做什么,改编自Programming with dplyr vignette(搜索“splice”以查看相关示例)。请注意!!! 如何将重新编码值对“拼接”到recode 函数中,以便将它们用作recode 中的... 参数。

x = c("A", "B", "C")
args = c(A=NA_character_, B="Was B")

quo(recode(x, !!!args))

<quosure>
expr: ^recode(x, A = <chr: NA>, B = "Was B")
env:  global

如果您想在多个列上运行重新编码函数,您可以将其转换为只接受列名和重新编码向量的函数。这种方法似乎对管道更友好。

fix_question <- function(question, recode.vec) {

  recode({{question}}, !!!recode.vec)

}

sample_df %>% 
  mutate_at(vars(matches("q")), list(~fix_question(., c(A=NA_character_, B="Was B"))))
  q1    q2   
1 <NA>  Was B
2 Was B Was B
3 C     <NA>

或重新编码单个列:

sample_df %>% 
  mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))

【讨论】:

    【解决方案2】:

    如果您有rlang >= 0.4.0,您现在可以使用“curly curly”方法。

    感谢@eipi10的解释:

    这将quote-then-unquote的两步过程合二为一,所以{{question}}等价于!!enquo(question)

    fix_question <- function(df, question){
      df %>% mutate({{question}} := recode({{question}}, A = NA_character_))
    }
    
    fix_question(sample_df, q1)
    # # A tibble: 3 x 2
    #   q1    q2   
    #   <chr> <chr>
    # 1 NA    B    
    # 2 B     B    
    # 3 C     A    
    

    请注意,与ensym 方法不同,这不适用于角色名称。更糟糕的是,它会做错事,而不仅仅是给出错误。

    fix_question(sample_df, 'q1')
    
    # # A tibble: 3 x 2
    #   q1    q2   
    #   <chr> <chr>
    # 1 q1    B    
    # 2 q1    B    
    # 3 q1    A    
    

    【讨论】:

    • 我还没有养成“卷发”的习惯。你知道为什么这行得通,而 OP 看似相同的“砰砰”版本却没有吗?
    • 感谢您提及 curly-curly,我听说它即将推出。答案不适用于我安装的任何版本的 rlang/dplyr;我收到 LHS 错误。如果我用我的 LHS 替换 LHS 并引用 q1,我会遇到与上面相同的问题;如果我不引用 q1,我会收到错误消息。这可能是版本问题。
    • 是的 rlang 0.4.0 刚刚在 6 月底发布,所以如果你从那以后没有更新它,这对你不起作用
    • 我认为 bang-bang 不起作用,因为 question 在用于 dplyr 管道之前首先需要变成一个 quosure (question = enquo(question))。 {{question}} 等价于 !!enquo(question)
    • 第一个问题实例也需要 enquo 才能等效。
    【解决方案3】:

    这里,在:=的右边,我们可以指定sym转换为符号然后求值(!!)

    fix_question <- function(df, question) {
        df %>%
           mutate(!!question := recode(!! rlang::sym(question), "A" = NA_character_))
      }
    
    fix_question(sample_df, "q1") 
    # A tibble: 3 x 2
    #  q1    q2   
    #  <chr> <chr>
    #1 <NA>  B    
    #2 B     B    
    #3 C     A    
    

    ensymensymensym

    fix_question <- function(df, question) {
        question <- ensym(question)
        df %>%
           mutate(!!question := recode(!! question, "A" = NA_character_))
      }
    
    
    fix_question(sample_df, q1)
    # A tibble: 3 x 2
    #  q1    q2   
    #  <chr> <chr>
    #1 <NA>  B    
    #2 B     B    
    #3 C     A    
    
    fix_question(sample_df, "q1")
    # A tibble: 3 x 2
    #  q1    q2   
    #  <chr> <chr>
    #1 <NA>  B    
    #2 B     B    
    #3 C     A    
    

    【讨论】:

    • 我曾尝试使用一些 rlang 转换函数,但显然没有选择正确的函数,但您的方法有效——我认为我真的只需要在其中处理类型转换我的头。我的 !!question 不起作用,因为它从字面上评估字符串。你的工作,因为它首先将字符串转换为符号,然后评估符号,返回向量。我只是无法理解这是操作的顺序。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多