3

我的问题如下。我有一个foo在里面工作的函数dplyr::mutate。此函数接受tidyselect语法。我想构建一个bar也应该支持tidyselect语法的包装函数。我正在寻找一种干净的方式将tidyselected 列从barto 传递给foo. 听起来很容易,但问题是foo需要接受将被引用的裸用户输入,并且还需要接受来自包装函数的已引用列。

那么让我们来看看这个问题:

library(dplyr)

myiris <- as_tibble(iris)

# this is a minimal function supporting tidyselect
# its a toy function, which just returns the tidyselected columns 

foo <- function(cols){
  data <- cur_data()
  vars <- tidyselect::eval_select(rlang::enquo(cols),  data)
  out <- data[, vars]
  
  names(out) <- paste0("new_", names(out))
  out
}

# the function is working:
myiris %>%
  mutate(foo(c(Sepal.Length)))
#> # A tibble: 150 x 6
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species new_Sepal.Length
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>              <dbl>
#>  1          5.1         3.5          1.4         0.2 setosa               5.1
#>  2          4.9         3            1.4         0.2 setosa               4.9
#>  3          4.7         3.2          1.3         0.2 setosa               4.7
#>  4          4.6         3.1          1.5         0.2 setosa               4.6
#>  5          5           3.6          1.4         0.2 setosa               5  
#>  6          5.4         3.9          1.7         0.4 setosa               5.4
#>  7          4.6         3.4          1.4         0.3 setosa               4.6
#>  8          5           3.4          1.5         0.2 setosa               5  
#>  9          4.4         2.9          1.4         0.2 setosa               4.4
#> 10          4.9         3.1          1.5         0.1 setosa               4.9
#> # … with 140 more rows

# this is a wrapper function around `foo`
bar <- function(df, .cols) {
  .cols <- rlang::enquo(.cols)
  mutate(df, foo(.cols))
}

# this will throw an error
myiris %>%
  bar(Sepal.Length)

#> Note: Using an external vector in selections is ambiguous.
#> ℹ Use `all_of(.cols)` instead of `.cols` to silence this message.
#> ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
#> This message is displayed once per session.
#> Error: Problem with `mutate()` input `..1`.
#> x Must subset columns with a valid subscript vector.
#> x Subscript has the wrong type `quosure/formula`.
#> ℹ It must be numeric or character.
#> ℹ Input `..1` is `foo(.cols)`.

reprex 包(v0.3.0)于 2021-04-14 创建

上述方法不起作用是完全有道理的。对我来说,如何以干净一致的方式处理这个问题并不明显。

下面我展示了我尝试过的方法以及我想出了什么样的平庸解决方法。

我认为我可以做的是:检查列是否已被引用,如果没有enquote。然而,这似乎是不可能的。一旦未引用的列用于任何类型的操作,它们将被评估和更改。必须作为第一enquo件事发生。但如果它首先发生,我无法检查它们是否已经被引用。

# we would need to check in foo
# if cols is already quoted or not
# but this seems not to be possible
# since `cols` changes, once it is used / touched

foo <- function(cols){
  data <- cur_data()
  if (!rlang::is_quosure(cols)) {
    cols <- enquo(cols)
  }
  vars <- tidyselect::eval_select(cols, data)
  out <- data[, vars]
  
  names(out) <- paste0("new_", names(out))
  out
}

# not working
iris %>%
  mutate(foo(c(Sepal.Length)))
#> Error: Problem with `mutate()` input `..1`.
#> x Must subset columns with a valid subscript vector.
#> x Can't convert from <double> to <integer> due to loss of precision.
#> ℹ Input `..1` is `foo(c(Sepal.Length))`.

reprex 包(v0.3.0)于 2021-04-14 创建

目前我正在使用一种我不太喜欢的解决方法。我使用省略号...foo以便可以使用不需要记录的附加参数调用它。现在foo可以用flag参数调用,在这种情况下foo知道不必引用列。

但是,我认为这不是一个干净的解决方案。我更喜欢某种在未引用时引用的函数,或者在将列名传递给时恢复列名环境的函数bar

另一种可能的解决方案是首先评估其中的列,bar然后将列名称作为字符串粘贴到foo. 我没有尝试过,它应该可以工作,因为 tidyselect 接受字符串,但是我想避免评估列名,bar因为它看起来不是很有性能。

欢迎任何其他想法。

# workaround using `...`
foo <- function(cols, ...){
  
  dots <- rlang::list2(...)
  if (is.null(dots$flag)) {
    cols <- enquo(cols)
  }
  
  data <- cur_data()
  vars <- tidyselect::eval_select(cols, data)
  out <- data[, vars]
  
  names(out) <- paste0("new_", names(out))
  out
}

bar <- function(df, .cols) {
  .cols <- rlang::enquo(.cols)
  mutate(df, foo(.cols, flag = TRUE))
}


# working
myiris %>%
  mutate(foo(c(Sepal.Length)))

#> # A tibble: 150 x 6
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species new_Sepal.Length
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>              <dbl>
#>  1          5.1         3.5          1.4         0.2 setosa               5.1
#>  2          4.9         3            1.4         0.2 setosa               4.9
#>  3          4.7         3.2          1.3         0.2 setosa               4.7
#>  4          4.6         3.1          1.5         0.2 setosa               4.6
#>  5          5           3.6          1.4         0.2 setosa               5  
#>  6          5.4         3.9          1.7         0.4 setosa               5.4
#>  7          4.6         3.4          1.4         0.3 setosa               4.6
#>  8          5           3.4          1.5         0.2 setosa               5  
#>  9          4.4         2.9          1.4         0.2 setosa               4.4
#> 10          4.9         3.1          1.5         0.1 setosa               4.9
#> # … with 140 more rows

# working
myiris %>%
  bar(Sepal.Length)

#> # A tibble: 150 x 6
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species new_Sepal.Length
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>              <dbl>
#>  1          5.1         3.5          1.4         0.2 setosa               5.1
#>  2          4.9         3            1.4         0.2 setosa               4.9
#>  3          4.7         3.2          1.3         0.2 setosa               4.7
#>  4          4.6         3.1          1.5         0.2 setosa               4.6
#>  5          5           3.6          1.4         0.2 setosa               5  
#>  6          5.4         3.9          1.7         0.4 setosa               5.4
#>  7          4.6         3.4          1.4         0.3 setosa               4.6
#>  8          5           3.4          1.5         0.2 setosa               5  
#>  9          4.4         2.9          1.4         0.2 setosa               4.4
#> 10          4.9         3.1          1.5         0.1 setosa               4.9
#> # … with 140 more rows

reprex 包(v0.3.0)于 2021-04-14 创建

4

1 回答 1

1

也许我不理解用例,但是为什么在将列从bar()to传递时必须引用它们foo()?如果您取消引用输入,一切都会按预期工作:

bar <- function(df, .cols) {
  .cols <- rlang::enquo(.cols)
  mutate(df, foo(!!.cols))      # <--- unquote before passing to foo()
}

# Or alternatively
bar <- function(df, .cols) {mutate(df, foo( {{.cols}} ))}

myiris %>%
  bar(Sepal.Length)             # works
于 2021-04-15T14:33:23.127 回答