对多个参数使用 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 魔法起作用,必须满足几个条件:
match.arg
函数必须直接从带有参数 的函数中调用
- 要匹配的变量名必须是参数名
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。可能有一些极端情况会使这个示例失败,所以不要在生产中使用它们。
我不熟悉使用 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 魔法起作用,必须满足几个条件:
match.arg
函数必须直接从带有参数 的函数中调用
- 要匹配的变量名必须是参数名
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。可能有一些极端情况会使这个示例失败,所以不要在生产中使用它们。