brcopeland
brcopeland

Reputation: 51

Side Effects of Calling a Function in R with do.call?

I'm trying to use the train function in the caret package, the code for which is here. Here's a very simple example of it working properly:

train(y ~ ., data=X, na.action=na.fail)
Error in na.fail.default(list(y = c(1L, 1L, 2L, 1L, 2L, 1L, 1L, 2L, 1L,  :
  missing values in object

It is expected in this case that this will fail as my data does have missing values, but the actual problem I'm trying to address is getting the same behavior, i.e. getting to this point of failure, with the following code:

lst <- list(form=y ~ ., data=X, na.action=na.fail)
do.call(train, lst)
Error in as.character(call_obj$na.action) :
  cannot coerce type 'closure' to vector of type 'character'

I can see that this is failing in the function check_na_conflict linked here, but I'm not getting why it works in the first case and not the second.My understanding of do.call is that it should simply convert the values in the list into arguments to pass to train, but evidently that is not the case? And if not is there some other proper way to call a function like this with a list of arguments?

Edit: A simpler example is the following:

> f <- function(na.missing) {
              m <- match.call()
              print(paste("na.missing =", m$na.missing))
          }

> f(na.omit)
[1] "na.missing = na.omit"
> lst <- list(na.missing=na.omit)
> do.call(f, lst)
Error in paste("na.missing =", m$na.missing) :
  cannot coerce type 'closure' to vector of type 'character'

I was under the impression that do.call would simply call f with argument na.missing=na.omit, which works when calling directly. Is there a way to get this to work properly with do.call? The reason I want to use do.call in the first place is I want to apply the original function to a list of lists of arguments.

Upvotes: 1

Views: 199

Answers (1)

SamR
SamR

Reputation: 20260

What you are describing is because of your use of match.call(), rather than do.call(). The purpose of match.call() is:

To record the call for later re-use: for example most model-fitting functions record the call as element call of the list they return. Here the default expand.dots = TRUE is appropriate.

Without using match.call()

Your example does not seem to require match.call(), in which case, it's easier to directly use the function passed as an argument.

Let's define a simple function which takes a vector, x, and a function, na_missing_fun to index the missing values. It will return the vector without the missing values:

f <- function(x, na_missing_fun) {

    x_not_na  <- x[na_missing_fun(x)] 
    return(x_not_na)

}

We can call this as follows:

x  <- c(1,NA,3,NA,5)

f(x, na.omit) # 1 3 5

Similarly, using do.call(), we can do:

do.call(f, c(list(x), na_missing_fun = na.omit)) # 1 3 5

If we want to apply this function to a list of vectors, we can use lapply():

lapply(list(x,x,x), f, na_missing_fun = na.omit)
# [[1]]
# [1] 1 3 5

# [[2]]
# [1] 1 3 5

# [[3]]
# [1] 1 3 5

With match.call()

If there is a reason that you have to use match.call(), you can do it but you will need to eval() the argument as in line 947 of the caret code that you posted. That is because m$na_missing_fun is of class name, rather than class function.

f <- function(x, na_missing_fun) {
    
    m  <- match.call()
    fun  <-  eval.parent(m$na_missing_fun)
    x_not_na  <- x[na_missing_fun(x)] 
    return(x_not_na)

}

You can then use the function f exactly as above:

f(x, na.omit) # 1 3 5

do.call(f, c(list(x), na_missing_fun = na.omit)) # 1 3 5 
lapply(list(x,x,x), f, na_missing_fun = na.omit) # List of 3x c(1, 3, 5) as above

Upvotes: 1

Related Questions