如何解决在自定义 dplyr 包装函数中使用带引号的变量
我的问题如下。我有一个在 foo
内工作的函数 dplyr::mutate
。此函数接受 tidyselect
语法。我想构建一个包装函数 bar
,它也应该支持 tidyselect
语法。我正在寻找一种干净的方式将 tidyselect
列从 bar
传递到 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 package (v0.3.0) 于 2021 年 4 月 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,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 package (v0.3.0) 于 2021 年 4 月 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,names(out))
out
}
bar <- function(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 package (v0.3.0) 于 2021 年 4 月 14 日创建
解决方法
也许我不理解用例,但是当您将列从 bar()
传递到 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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。