通过整洁的评估来改变变量(符号或字符串)

mutating variables (symbol or string) with tidy evaluation

我想在我的函数中使用 tidy 求值,同时改变输入的变量,但似乎无法使函数正常工作。

我都读过 and ,他们似乎建议需要 不同的解决方案 基于符号还是字符串作为争论。但我更喜欢一个适用于这两个输入的问题的通用解决方案。

有办法吗?

函数

library(magrittr)

foo <- function(data, x) {
  # I know this is one solution to solve my problem
  # But I want to avoid it because it creates problem if the function is to be used within a loop
  # x <- rlang::ensym(x)
  
  df <- tibble::as_tibble(dplyr::select(data, {{ x }}))
  
  df %>% dplyr::mutate(.data = ., {{ x }} := droplevels(as.factor({{ x }})))
}

按预期工作

foo(mtcars, am)
#> # A tibble: 32 x 1
#>    am   
#>    <fct>
#>  1 1    
#>  2 1    
#>  3 1    
#>  4 0    
#>  5 0    
#>  6 0    
#>  7 0    
#>  8 0    
#>  9 0    
#> 10 0    
#> # ... with 22 more rows

没有按预期工作

foo(mtcars, "am")
#> # A tibble: 32 x 1
#>    am   
#>    <fct>
#>  1 am   
#>  2 am   
#>  3 am   
#>  4 am   
#>  5 am   
#>  6 am   
#>  7 am   
#>  8 am   
#>  9 am   
#> 10 am   
#> # ... with 22 more rows

P.S。如果您对可能的解决方案在循环中引起的问题感到好奇,这里有一个代表:

library(magrittr)
col.name <- colnames(mtcars)
foo <- function(data, x) {
  x <- rlang::ensym(x)
  df <- tibble::as_tibble(dplyr::select(data, {{ x }}))
  df %>% dplyr::mutate(.data = ., {{ x }} := droplevels(as.factor({{ x }})))
}

for (i in 3:length(mtcars)) {
  foo(mtcars, col.name[i])
}
#> Error: Only strings can be converted to symbols

也许试试 !!:

#Function
col.name <- colnames(mtcars)
foo <- function(data, x) {
  x <- rlang::ensym(x)
  df <- tibble::as_tibble(dplyr::select(data, {{ x }}))
  df %>% dplyr::mutate(.data = ., {{ x }} := droplevels(as.factor({{ x }})))
}
#Loop
for (i in 3:length(mtcars)) {
  print(foo(mtcars, !!col.name[i]))
}

如果您想与循环一起使用,该函数也适用于 foo(mtcars, !!'am')foo(mtcars, am)

通常不建议尝试让您的函数与屏蔽列和列名兼容,因为这不是 tidyverse 函数设计的工作方式。

首先选择是要进行操作还是选择。如果选择后者,则可以使用 all_of() 等 tidyselect 功能来与字符向量兼容。在所有 dplyr 动词中进行选择的一种简单方法是使用 across().

在这种情况下,您也开始使用 select(),因此我们可以只使用 across(everything())。一般来说,你可以做 across({{ x }}).

foo <- function(data, x) {
  data %>%
    as_tibble() %>%
    select({{ x }}) %>%
    # Could also remove the `select()` step and pass `{{ x }}` to across()
    mutate(across(everything(), ~ droplevels(as.factor(.))))
}

foo(mtcars, am)

由于您已将 x 转发到选择上下文,您的函数现在支持所有 tidyselect 功能。如果需要传递字符向量或字符串,只需使用 all_of()。调整您的示例:

nms <- names(mtcars)

for (i in 3:length(mtcars)) {
  foo(mtcars, all_of(nms[i]))
}