基于tidyeval的非标准评估在mutate右侧重新编码中的使用


13

考虑一个小标题,其中每一列都是可以采用许多值的字符向量-假设是“ A”到“ F”。

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

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

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

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

但是,当然,函数内部的爆炸只是返回文字字符串(例如“ q1”)。我最终采取了一种感觉很拙劣的方法,即使用基本R [[运算符并依靠.dplyr 的构造来在右侧引用数据,并且它起作用了,因此从某种意义上说,我已经解决了我的根本问题:

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

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


谢谢,这是一个聪明的方法-我在代码的其他部分中确实使用了功能性方法,并且也可能考虑过在此处进行操作。我知道有些人对SO的代码风格不屑一顾,但是这么快看到几种不同风格的答案对我来说是非常富有成果的。
亚伦

1
结合这个问题的几个想法,我认为这是最简洁的版本,可同时使用q1(符号)和"q1"(字符串):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov,

Answers:


6

在此处,:=我们可以在的右侧指定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    

适用于带引号和不带引号的输入的更好方法是 ensym

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    

2
我曾尝试使用一些rlang转换函数,但是显然没有选择正确的函数,但是您的方法可行-我认为我真的只需要在脑海中进行类型转换。我的!!问题不起作用,因为它按字面意义计算字符串。您的工作原理是,它首先将字符串转换为符号,然后对符号求值,并返回矢量。我只是无法包扎那是操作的顺序。再次感谢。
亚伦

8

如果rlang> = 0.4.0,则可以立即使用“卷曲卷曲”方法。

感谢@ eipi10的解释:

这将报价,然后取消报价的两步过​​程合并为一个步骤,因此{{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    

2
我还没有养成“卷曲的”习惯。您知道为什么这样做有效,而OP的看似完全相同的“ bang bang”版本却没有吗?
卡米尔,

感谢您提到卷曲,我听说即将到来。答案对于我安装的任何版本的rlang / dplyr均无效。LHS出现错误。如果我用LHS代替LHS并引用q1,则会遇到与上面相同的问题。如果我不引用q1,则会出现错误。这可能是版本问题。
亚伦

1
是的,rlang 0.4.0刚刚于6月底发布,因此,如果您从那以后没有对其进行更新,那么它将对您不起作用
IceCreamToucan

2
我认为爆炸式爆炸是行不通的,因为在用于dplyr管道之前,question首先需要将其转换为quosure(question = enquo(question))。{{question}}等同于!!enquo(question)
eipi10

2
您也需要为第一个问题的实例感到相等。
IceCreamToucan,

7

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

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是“未加引号” !!!。您可以看到此示例的内容,该示例改编自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")))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.