0

我正在编写一个自定义函数,预计可以同时使用unquoted"quoted"输入。我可以使用rlang. 但是当"quoted"使用colnames.

关于如何解决这个问题的任何想法?

library(tidyverse)

# function
cor_foo <- function(data, x1, x2) {
  x1 <- rlang::ensym(x1)
  x2 <- rlang::ensym(x2)

  df <- dplyr::select(data, {{x1}}, {{x2}})

  cor(df %>% dplyr::pull({{x1}}), df %>% dplyr::pull({{x2}}))
}

# works
cor_foo(mtcars, wt, mpg)
#> [1] -0.8676594

# works
cor_foo(mtcars, "wt", "mpg")
#> [1] -0.8676594

# checking strings that will be passed to the function as arguments
colnames(mtcars)[1]
#> [1] "mpg"
colnames(mtcars)[6]
#> [1] "wt"

# doesn't work with these inputs
cor_foo(mtcars, colnames(mtcars)[6], colnames(mtcars)[1])
#> Error: Only strings can be converted to symbols

reprex 包(v0.3.0)于 2019 年 11 月 12 日创建

4

2 回答 2

1

您正在尝试混合标准和非标准评估,这几乎总是会导致模棱两可的行为。考虑以下数据变体:

X <- mtcars %>% mutate(`colnames(mtcars)[6]` = 1:n(), `colnames(mtcars)[1]` = 1:n())

在这种情况下,您的函数应该返回什么?

cor_foo(X, colnames(mtcars)[6], colnames(mtcars)[1])

如果参数 2 和 3 使用标准评估 (SE) 进行解释,则它们应解析为字符串,然后"mpg"再传"wt"递给cor_foo. 另一方面,如果参数 2 和 3 旨在遵循非标准评估 (NSE),则应将它们视为已包含列名的未评估表达式。

我的建议是致力于 SE 或 NSE。rlang::ensym()通过同时使用字符串和符号来连接两者。但是,它不适用于任意表达式,因为这些表达式是否已经包含列名或需要评估以获得列名是不明确的。

可能为您提供所需行为的解决方案是ensym()代替enquo(). 请注意,它{{.}}是 的简写!!enquo(.),因此您可以简单地删除以下ensym行:

cor_foo <- function(data, x1, x2) {
  df <- dplyr::select(data, {{x1}}, {{x2}})
  cor(df %>% dplyr::pull({{x1}}), df %>% dplyr::pull({{x2}}))
}

cor_foo(X, "mpg", "wt")
# [1] -0.8676594
cor_foo(X, mpg, wt)
# [1] -0.8676594
cor_foo(X, colnames(mtcars)[6], colnames(mtcars)[1])
# [1] -0.8676594
cor_foo(X, `colnames(mtcars)[6]`, `colnames(mtcars)[1]`)
# [1] 1

请注意,这是对 NSE 解释的承诺,用户必须使用它!!来强制对表达式进行就地评估:

cyl <- colnames(mtcars)[1]     # Effectively cyl <- "mpg"
cor_foo(X, cyl, wt)
# [1] 0.7824958
cor_foo(X, !!cyl, wt)
# [1] -0.8676594
于 2019-11-12T17:38:55.520 回答
1

你想在enquo这里使用。ensym不捕获引用环境,实际上试图将其colnames(mtcars)[6]转换colnames(mtcars)[1]为符号本身,这会产生错误,因为它们不是字符串。

如果我们使用enquo,我们会捕获引用环境并将其转换为要评估的 quosore。您可以使用它来检查每个人在做什么:

cor_sym <- function(data, x1) {
  x1 <- rlang::ensym(x1)
  x1
}

cor_sym(mtcars, colnames(mtcars)[6])

# Run traceback on the error

cor_quo <- function(data, x1) {
  x1 <- rlang::enquo(x1)
  x1
}

cor_quo(mtcars, colnames(mtcars)[6])

您将看到它cor_quo正在返回一个 quosure 并将环境作为全局返回。因此,如果我们使用enquo而不是ensym,则会评估 quosore 并为selectandpull调用提供字符串值。

cor_foo <- function(data, x1, x2) {
  x1 <- rlang::enquo(x1)
  x2 <- rlang::enquo(x2)
  df <- dplyr::select(data, {{x1}}, {{x2}})
  cor(df %>% dplyr::pull({{x1}}), df %>% dplyr::pull({{x2}}))
}

cor_foo(mtcars, colnames(mtcars)[6], colnames(mtcars)[1])

您可以找到比我更了解这一点的更聪明的人在这里解释差异:使用 dplyr 编程时 ensym 和 enquo 有什么区别?

于 2019-11-12T11:21:08.370 回答