我的问题如下。我有一个foo
在里面工作的函数dplyr::mutate
。此函数接受tidyselect
语法。我想构建一个bar
也应该支持tidyselect
语法的包装函数。我正在寻找一种干净的方式将tidyselect
ed 列从bar
to 传递给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 创建