Thursday, October 13, 2016

Error: "'what' must be a function or character string" when tuning svm models.

TL;DR: Pass the svm() function used to fit your svm model to tune(), not the model itself. Also, make sure you didn't overshadow the svm() function by assigning your model or any other object to the svm variable. If you do this, tune() will not recognize svm as the original svm() function, which it expects.


Here is a first post for any future person who googles this problem.

You have generated an svm model model using the svm() function from the e1071 library, like so:

data(iris)
model <- svm(Species ~ ., data = iris)

Now you want to tune your model. You decide to do it by passing it to the tune function, like so:

tuning_result <- tune(model, Species ~ ., data = iris, ranges = list(epsilon = seq(0,1,0.1), cost = 2^(2:9)))

You run the command, and R spits out the following error:

Error in do.call(method, c(list(train.x, data = data, subset = train.ind[[sample]]), : 'what' must be a function or character string

The manual for the tune function (accessed via ?tune) reveals that the first argument passed to tune should be the function you want to tune, not the model itself.

You re-run your code, like so:

tuning_result <- tune(svm, Species ~ ., data = iris,ranges = list(epsilon = seq(0,1,0.1), cost = 2^(2:9)))

And this time around it should work.


If tune(svm, Species ~ ., data = iris,ranges = list(epsilon = seq(0,1,0.1), cost = 2^(2:9))) still throws the same error, then check in your code or workspace history, that you didn't accidentally assign a different object to the variable svm.

Here is an example. If you fit a model using the svm() function, and assign it to the variable svm, the function tune() will get confused, as it sees your model assigned to svm, instead of the svm() function it expects:

We fit a model using the svm() function and assign it to the variable svm, and the try to tune svm the same way as in the previous working example.

svm <- svm(Species ~ ., data = iris)
tuning_result <- tune(svm, Species ~ ., data = iris,ranges = list(epsilon = seq(0,1,0.1), cost = 2^(2:9)))

Again, we'll get the same error as before, even though we passed svm to the tune() function:

Error in do.call(method, c(list(train.x, data = data, subset = train.ind[[sample]]), : 'what' must be a function or character string

If you overshadow the svm() function this way, tune() will not recognize svm as the svm() function.

To fix this, assign the contents of svm to a different variable, and run rm(svm), or be more explicit about which svm you're referring to, by specifying that you want to use the svm() function from the e1071 package, like so:

# This should work: tuning_result <- tune(e1071::svm, Species ~ ., data = iris,ranges = list(epsilon = seq(0,1,0.1), cost = 2^(2:9)))


2017-01-13 Edit: Fixed an error in the text (thanks to Morris for pointing it out in the comment section), and edited the text for clarity and less resignation.

2018-07-18 Edit: I rewrote the post to make it more reproducible (since y'all are still googling this), and added an example of things going wrong if you overshadow the svm() function.

12 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Erm sorry, I don't get it. The first and the second snippet are exactly the same. I reckon the proper one would be:

    tuneResult <- tune(svm, y ~ x, data = hellTable, ranges = list(epsilon = seq(0,1,0.1), cost = 2^(2:9)))

    ReplyDelete
    Replies
    1. You're right — my bad. Thanks for pointing it out!

      Delete
  3. Thank you sooooo much

    ReplyDelete
  4. Thanks, I've spent 20 minutes figuring this out on my own.
    Don't shadow function names by local variables!

    ReplyDelete
  5. Error in do.call(method, c(list(train.x, data = data, subset = train.ind[[sample]]), :
    'what' must be a function or character string

    Getting this error for my code below:
    svm_ <- svm(Next.Close ~ Open + High + Low + Close, train_)
    predResults1_SVM <- predict(svm_, test_[,-1])
    finetuned <- tune(svm, Next.Close ~ Open + High + Low + Close, data=test_[,-1],ranges=list(elsilon=seq(0,1,0.1), cost=1:100))

    Notice, I am using svm for my first argument not my model

    ReplyDelete
    Replies
    1. You sure you didnt assign a different object (like a data frame or vector) to the svm variable? Doing so overshadows the svm variable, but the svm() function will (probably) still work as it should.

      Here's a reproducible example of what I'm talking about, using the built-in mean() function:

      > test_data <- c(2,1,2,2,3,3)
      >
      > # This works as it should:
      > mean(test_data)
      [1] 2.166667
      >
      > # Assign string to variable mean
      > mean <- "I am a string"
      >
      > # Printing the variable mean yields a string:
      > mean
      [1] "I am a string"
      >
      > # However, this still works:
      > mean(test_data)
      [1] 2.166667

      R is hell.

      Delete
  6. Spent the last 60 mins. trying to resolve this.

    ReplyDelete
  7. Thank you so much!!

    ReplyDelete
  8. Thank you so much! This saved me a lot of time!

    ReplyDelete
    Replies
    1. I'm glad this short post of mine helps people along the way still after four years!

      Delete
  9. Thanks. That is also a great response for 2020! It almost solved my problem. It rather helped me realize the problem was caused by one variable (docvar) which comes with my quanteda dfmat preprocessing. I removed the docvar from the quanteda dfmat and converted it to a data frame and then to a matrix. Now, svm with hyperparameter tuning and 10-fold cv seems working. I wanted to share my experience for others who may be working on text data and are in a similar problem.

    The docvar was in column number 1, I removed it with the [,-1].

    dfmat_train_matrix <- dfmat_trainnew[,-1] %>% as.data.frame() %>% as.matrix
    head(dfmat_train_matrix)

    ## Tuned cross validation svm model
    set.seed(123)
    library(e1071)

    svm_tuned <- tune(svm,
    train.x=dfmat_train_matrix, y=test$Include,
    kernel = "linear",
    type = "C-classification",
    ranges =list(cost=c(0.001,0.01,0.1, 1,5,10,100)),
    tune.control(sampling = "cross",cross=10))

    You also need to remove the docvar from the test dfmat in the same was to make the predict function work for you.

    ReplyDelete