I am new to using match.arg
for default value specification in R functions. And I have a query regarding the below behavior.
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"
When I try using match.arg
for each individual argument, it works as expected. But when I try to use an lapply to reduce the code written, it causes the below issue.
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
Am I missing something here?
After investigating a bit, you need to pass the argument that your character vector is NULL, ie
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"
It's an old question, but I feel it's a great one, so I will try to provide extensive explanation for it by explaining the following:
?match.arg
match.arg
fail to guess the choicesmatch.arg
uses underneath.match.arg
implementationlapply
example of the question workmatch.arg
documentation The usage tells you that match.arg
needs the selected option you want to match ( arg
) and all the possible choices
:
match.arg(arg, choices, several.ok = FALSE)
If we read choices
, we see that it can often be missing, and we should read more in the details... How could match.arg
work without having the possible choices, we wonder?
choices: a character vector of candidate values, often missing, see 'Details'.
Maybe the Details section gives some hints (bold is mine):
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.)
So, if you don't specify the choices
argument, R will make a bit of effort to guess it right automagically. For the R magic to work, several conditions must be fulfilled:
match.arg
function must be called directly from the function with the argumentmatch.arg()
can be tricked: Let's make match.arg()
fail to guess the choices:
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
(1) It uses non-standard evaluation to get the name of the variable:
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) It uses sys.function()
to get the caller 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) It uses formals()
to get the possible values:
a_function_with_default_values <- function(x=c("a", "b"), y = 3) {
}
formals(a_function_with_default_values)[["x"]]
#c("a", "b")
match.arg
work? Combining these things, match.arg
uses substitute()
to get the name of the args variable, it uses sys.function()
to get the caller function, and it uses formals()
on the caller function with the argument name to get the default values of the function (the choices):
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"
Since we now know the magic used to get the choices, so we can create our match.arg
implementation:
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"
And that's how match.arg
works.
lapply
? How to fix it? To guess the choices
argument, we look at the caller argument. When we use match.arg()
inside an lapply call, the caller is not our function, so match.arg
fails to guess the choices
. We can get the choices manually and provide the choices manually:
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"
Please note that I am cutting corners by not defining the environments where all the evals should happen, because in the examples above they work as-is. There may be some corner cases that make this examples to fail, so don't use them in production.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.