您如何使用非标准评估指定函数中可能的参数状态?

How do you specify of possible arguments states in functions using non-standard evaluation?

我正在学习使用 tidy 求值和非标准求值进行编程,并且一直在尝试找出如何限制函数中参数的可能状态。

例如给定一个数据集:

set.seed(123)
data <- data_frame(GROUP_ONE = sample(LETTERS[1:3], 10, replace = TRUE), 
                   GROUP_TWO = sample(letters[4:6], 10, replace = TRUE), 
                   result = rnorm(10))

我可以创建一个函数,它有一个参数,我用它来使用 quosure 对数据进行分组:

my_function <- function(data, group = GROUP_ONE){

  require(dplyr)
  require(magrittr)

  group <- enquo(group)

  result <- data %>% 
    group_by(!!group) %>% 
    summarise(mean=mean(result))

  return(result)
}

这就是我想要的

my_function(data)

# A tibble: 3 x 2
  GROUP_ONE       mean
      <chr>      <dbl>
1         A  1.5054975
2         B  0.2817966
3         C -0.5129904

我可以提供一个不同的组:

my_function(data, group = GROUP_TWO)

# A tibble: 3 x 2
  GROUP_TWO       mean
      <chr>      <dbl>
1         d -0.3308130
2         e  0.2352483
3         f  0.7347437

但是,我无法按数据中不存在的列进行分组。

例如

 my_function(data, group = GROUP_THREE)

Error in grouped_df_impl(data, unname(vars), drop) : Column GROUP_THREE is unknown

我想在函数的开头添加一个步骤,以便在组参数不是 GROUP_ONE 或 GROUP_TWO

时函数停止并显示自定义错误消息

类似于:

if(!group %in% c(~GROUP_ONE, ~GROUP_TWO)) stop("CUSTOM ERROR MESSAGE")

除非这不起作用,因为您显然不能将 quosures 放入向量中。应该可以以某种方式将 quosure 转换为字符串并具有字符串向量,但我不知道如何实现。

这是怎么做到的?

我认为您需要 quo_name(来自 dplyrrlang),它将带引号的符号转换为字符串:

my_function <- function(data, group = GROUP_ONE){

    require(dplyr)
    require(magrittr)

    group <- enquo(group)

    if(!quo_name(group) %in% names(data)) stop("CUSTOM ERROR MESSAGE")

    result <- data %>% 
        group_by(!!group) %>% 
        summarise(mean=mean(result))

    return(result)
}

# > my_function(data, GROUP_THREE)
# Error in my_function(data, GROUP_THREE) : CUSTOM ERROR MESSAGE

编辑

正如 lionel 在评论中指出的:除了 quo_name,还有许多其他替代方案,包括来自 rlang.[=18= 的基础 R as.characteras_string ]

quo_name() 用于将任意表达式转换为文本,因此不适合检查符号。

如果您只需要符号,并且这些符号应该只表示数据帧列,则不需要 quosures。在这种情况下你可以用enexpr()来捕获(在下一个版本的rlang中会有ensym()):

group <- enexpr(group)
stopifnot(is_symbol(group))  # Or some custom error

然后转成字符串进行校验:

as_string(group) %in% names

然后您可以像取消引号一样取消对符号的引用。

df %>% group_by(!! group)

或者,如果您需要 quosures,您可以检查包含的表达式:

expr <- get_expr(quo)
is_symbol(expr) && as_string(expr) %in% names

那应该是首选 UI,因为 group_by() 具有变异语义,因此您可以这样做:df %>% group_by(as.factor(col))。这也意味着尝试提供自定义错误消息是没有希望的,除非您想捕获错误,解析它以确保它是 "symbol not found" 错误,然后重新抛出另一个错误。