4

Giving a classification function ranger, I want to get all the parameters with their default values from its definition using formals. Then, I want to change some default values and use them as parameter with do.call

library(ranger)
# Getting all the parameters for "ranger" function with their default values
lParams<-formals(ranger)
lParams<-as.list(lParams)

# Replace some default values for testing purposes
lParams$formula<-as.formula("Species~.")
lParams$data<-substitute(iris)
lParams[["..."]]<-NULL

#lParams[["sample.fraction"]]<-NULL

do.call("ranger",lParams)

But it does not work:

Error in as.logical(test) : 
  cannot coerce type 'closure' to vector of type 'logical'

It seems sample.fraction element is the cause of the error. It is a call object. If I remove that element using:

lParams[["sample.fraction"]]<-NULL

...so, my code works.

I do not know how to treat this object to prevent this error.

DischordDynne
  • 117
  • 15
Lev
  • 693
  • 1
  • 8
  • 24
  • 3
    I'm not sure why you want to extract all the formal arguments just to change default values. Defaults will be used if they are missing, so you just need to include arguments you actually want to change. From your code using just `do.call("ranger", list(data = quote(iris), formula = Species ~ .))` should be sufficient. – Ritchie Sacramento Feb 24 '23 at 02:53
  • I try to get formals parameters because I'm building a graphic Interface (`shiny`) for classification purposes. It allows the user to modify (if needed) any parameter for any function. So, in order to show all of them in the UI for the user modification, I need to get all the parameters and set the default values in the widgets (`numericInput, textInput, selectInput`,... etc). Finally I get all the inputs from my UI (modified or not), store them in a list and use it in `do.call`. Is there any better way to do it? – Lev Feb 24 '23 at 08:51

1 Answers1

4

do.call is limited by the same restrictions you are when calling a function. By taking all the formals and readding them, you are essentially calling

ranger(formula = Species ~ ., data = iris, num.trees = 500, 
    mtry = NULL, importance = "none", write.forest = TRUE, probability = FALSE, 
    min.node.size = NULL, max.depth = NULL, replace = TRUE, sample.fraction = ifelse(replace, 
        1, 0.632), case.weights = NULL, class.weights = NULL, 
    splitrule = NULL, num.random.splits = 1, alpha = 0.5, minprop = 0.1, 
    split.select.weights = NULL, always.split.variables = NULL, 
    respect.unordered.factors = NULL, scale.permutation.importance = FALSE, 
    local.importance = FALSE, regularization.factor = 1, regularization.usedepth = FALSE, 
    keep.inbag = FALSE, inbag = NULL, holdout = FALSE, quantreg = FALSE, 
    oob.error = TRUE, num.threads = NULL, save.memory = FALSE, 
    verbose = TRUE, seed = NULL, dependent.variable.name = NULL, 
    status.variable.name = NULL, classification = NULL, x = NULL, 
    y = NULL)

And you get the exact same error if you run that. Let's create a simplified example

foo <- function(x, y=x+5) {
   x*y
}
foo(5)
# [1] 50

If I tried the same trick, I would get

params <- as.list(formals(foo))
params$x <- 5
do.call("foo", params)
# Error in foo(x = 5, y = x + 5) : object 'x' not found

because I cannot call

foo(x =5, y=x+5)

Default lazy parameter values are kind of special. There's nothing you can pass to them that will behave like if you leave the value missing. That's because values you pass in are evaluated in a different environment than default parameters. The specific error you get " cannot coerce type 'closure' to vector of type 'logical'" is because replace is a function in the global enviroment, but has a different meaning within the function itself.

If you want to avoid potential problems, it's probably best not to start with all the formal arguments. Or at the very list filter out any calls or symbols. You could filter them out with

lParams <- lParams[!sapply(lParams, function(x) any(c("call","symbol") %in% class(x)))]
MrFlick
  • 195,160
  • 17
  • 277
  • 295