在依赖于准引号的函数中使用列名

时间:2019-11-12 10:54:55

标签: r rlang quasiquotes

我正在编写一个自定义函数,该函数应该与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 package(v0.3.0)于2019-11-12创建

2 个答案:

答案 0 :(得分:1)

您要在此处使用enquoensym不能捕获引号环境,并且实际上试图将colnames(mtcars)[6]colnames(mtcars)[1]转换为符号本身,由于它们不是字符串,因此会产生错误。

如果使用enquo,我们将捕获报价环境并将其转换为要评估的法定人数。您可以使用它来检查它们各自在做什么:

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返回一个quasure,并将环境返回为全局。因此,如果我们使用enquo而不是ensym,则将评估quoresore,并为selectpull调用提供字符串值。

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])

您可以找到更聪明的人,比我自己更了解这点,并在此处解释差异:What is the difference between ensym and enquo when programming with dplyr?

答案 1 :(得分:1)

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

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

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

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

如果使用标准评估(SE)解释参数2和3,则在将其传递给"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