对多个参数使用 match.arg 时出错

Error in using match.arg for multiple arguments

我不熟悉使用 match.arg 在 R 函数中指定默认值。我对以下行为有疑问。

trial_func <- function(a=c("1","9","20"),b=c("12","3"),d=c("55","01")){
  a <- match.arg(a)
  b <- match.arg(b)
  d <- match.arg(d)
  list(a,b,d)
}
trial_func()
# [[1]]
# [1] "1"
# 
# [[2]]
# [1] "12"
# 
# [[3]]
# [1] "55"

当我尝试对每个单独的参数使用 match.arg 时,它按预期工作。但是当我尝试使用 lapply 来减少编写的代码时,会导致以下问题。

trial_func_apply <- function(a=c("1","9","20"),b=c("12","3"),d=c("55","01")){
  lapply(list(a,b,d), match.arg)
}
trial_func_apply()

Error in FUN(X[[i]], ...) : 'arg' must be of length 1

我是不是漏掉了什么?

稍作调查后,您需要传递字符向量为 NULL 的参数,即

trial_func_apply <- function(a=c("1","9","20"),b=c("12","3"),d=c("55","01")){
     lapply(list(a,b,d), function(i)match.arg(NULL, i))
 }

trial_func_apply()
#[[1]]
#[1] "1"

#[[2]]
#[1] "12"

#[[3]]
#[1] "55"

这是一个老问题,但我觉得这是一个很好的问题,所以我会尝试通过解释以下内容来提供广泛的解释:

  • 阅读 ?match.arg
  • 的相关文档
  • 使match.arg猜不出选项
  • 了解 match.arg 下面使用的 R 语言的三个特性。
  • 简化的match.arg实施
  • 使问题的 lapply 示例有效

match.arg 文档

用法告诉您 match.arg 需要您要匹配的所选选项 (arg) 和所有可能的 choices:

match.arg(arg, choices, several.ok = FALSE)

如果我们阅读 choices,我们会发现它经常会丢失,我们应该阅读更多的细节...... match.arg 怎么能在没有可能的选择的情况下工作,我们想知道?

choices: a character vector of candidate values, often missing, see ‘Details’.

也许“详细信息”部分提供了一些提示(粗体是我的):

Details:

In the one-argument form ‘match.arg(arg)’, the choices are obtained from a default setting for the formal argument ‘arg’ of the function from which ‘match.arg’ was called. (Since default argument matching will set ‘arg’ to ‘choices’, this is allowed as an exception to the ‘length one unless ‘several.ok’ is ‘TRUE’’ rule, and returns the first element.)

因此,如果您不指定 choices 参数,R 会做出一些努力来自动猜对它。要使 R 魔法起作用,必须满足几个条件:

  1. match.arg函数必须直接从带有参数
  2. 的函数中调用
  3. 要匹配的变量名必须是参数名

match.arg()可以被骗:

match.arg()猜不出选项:

dummy_fun1 <- function(x = c("a", "b"), y = "c") {
  # If you name your argument like another argument
  y <- x
  # The guessed choices will correspond to y (how could it know they were x?)
  wrong_choices <- match.arg(y)
}

dummy_fun1(x = "a")
# Error in match.arg(y) : 'arg' should be “c”

dummy_fun2 <- function(x = c("a", "b"), y = "c") {
  # If you name your argument differently
  z <- x
  # You don't get any guess:
  wrong_choices <- match.arg(z)
}

dummy_fun2(x="a")
#Error in match.arg(z) : 'arg' should be one of

match.arg 需要和使用的三个 R 语言特性

(1) 它使用non-standard求值得到变量名:

whats_the_var_name_called <- function(arg) {
  as.character(substitute(arg))
}

x <- 3
whats_the_var_name_called(x)
# "x"
y <- x
whats_the_var_name_called(y)
# "y"

(2) 它使用sys.function()得到调用函数:

this_function_returns_its_caller <- function() {  
  sys.function(1)
}

this_function_returns_itself <- function() {
  me <- this_function_returns_its_caller()
  message("This is the body of this_function_returns_itself")
  me
}

> this_function_returns_itself()
This is the body of this_function_returns_itself
function() {
  me <- this_function_returns_its_caller()
  message("This is the body of this_function_returns_itself")
  me
}

(3) 它使用 formals() 来获取可能的值:

a_function_with_default_values <- function(x=c("a", "b"), y = 3) {

}

formals(a_function_with_default_values)[["x"]]
#c("a", "b")

match.arg 是如何工作的?

结合这些东西,match.arg 使用 substitute() 获取 args 变量的名称,使用 sys.function() 获取调用函数,并使用 formals()在具有参数名称的调用函数上获取函数的默认值(选项):

get_choices <- function(arg, choices) {
  if (missing(choices)) {
    arg_name <- as.character(substitute(arg))
    caller_fun <- sys.function(1)
    choices_as_call <- formals(caller_fun)[[arg_name]]
    choices <- eval(choices_as_call)
  }
  choices
}

dummy_fun3 <- function(x = c("a", "b"), y = "c") {
  get_choices(x)
}
dummy_fun3()
#[1] "a" "b"

既然我们现在知道了用来获得选择的魔法,那么我们可以创建我们的 match.arg 实现:

my_match_arg <- function(arg, choices) {
  if (missing(choices)) {
    arg_name <- as.character(substitute(arg))
    caller_fun <- sys.function(1)
    choices_as_call <- formals(caller_fun)[[arg_name]]
    choices <- eval(choices_as_call)
  }
  # Really simple and cutting corners... but you get the idea:
  arg <- arg[1]
  if (! arg %in% choices) {
    stop("Wrong choice")
  }
  arg
}

dummy_fun4 <- function(x = c("a", "b"), y = "c") { 
  my_match_arg(x)
}

dummy_fun4(x="d")
# Error in my_match_arg(x) : Wrong choice
dummy_fun4(x="a")
# [1] "a"

这就是 match.arg 的工作原理。

为什么它在 lapply 下不起作用?如何解决?

要猜测 choices 参数,我们查看调用方参数。当我们在 lapply 调用中使用 match.arg() 时,调用者不是我们的函数,因此 match.arg 无法猜测 choices。我们可以手动获取选项并手动提供选项:

trial_func_apply <- function(a=c("1","9","20"),b=c("12","3"),d=c("55","01")){
  this_func <- sys.function()
  the_args <- formals(this_func)
  default_choices <- list(
    eval(the_args[["a"]]),
    eval(the_args[["b"]]),
    eval(the_args[["d"]])
  )
  # mapply instead of lapply because we have two lists we
  # want to apply match.arg to
  mapply(match.arg, list(a,b,d), default_choices)
}
trial_func_apply()
# [1] "1"  "12" "55"

请注意,我是在走捷径,没有定义所有评估应该发生的环境,因为在上面的示例中它们有效 as-is。可能有一些极端情况会使这个示例失败,所以不要在生产中使用它们。