5

The MASS::stepAIC function takes an lm result as a parameter and does stepwise regression to find the "best" model. The following code is brain dead simple and works:

library(MASS)
data("mtcars")

lm1 = lm(mpg ~ ., mtcars)
step1 = stepAIC(lm1, direction = "both", trace = FALSE)

I'm trying to put this inside a function. Eventually I want to do more, but I can't even get these two lines of code to work when wrapped in a function:

fit_model = function(formula, data) {
  full_model = lm(formula = formula, data = data)
  step_model = stepAIC(full_model, direction = "both", trace = FALSE)
  return(step_model)
}

step2 = fit_model(mpg ~ ., mtcars)
Error in eval(predvars, data, env) : 
  invalid 'envir' argument of type 'closure' 

I'm running:

R version 3.6.2 (2019-12-12)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Linux Mint 19.1
Lee Hachadoorian
  • 364
  • 2
  • 15

3 Answers3

5

Here is your culprit (within the fit_model function). Notice the environment where the formula was created.

Browse[1]> str(formula)
Class 'formula'  language mpg ~ .
  ..- attr(*, ".Environment")=<environment: R_GlobalEnv> 

What you could do is perhaps force in a new environment

fit_model = function(formula, data) {
  environment(formula) <- new.env()
  full_model = lm(formula = formula, data = data)
  step_model = stepAIC(full_model, direction = "both", trace = FALSE)
  return(step_model)
}

> step2

Call:
lm(formula = mpg ~ wt + qsec + am, data = data)

Coefficients:
(Intercept)           wt         qsec           am  
      9.618       -3.917        1.226        2.936 
Roman Luštrik
  • 69,533
  • 24
  • 154
  • 197
  • I have tried three alternative answers using `environment()`, `enquote()`, and `do.call()`. All of them seem to work. Why is your proposed solution preferable? Would your solution also handle more complex functions, particularly if I added `...` to the parameters to allow passthrough to `lm`, `stepAIC`, etc.? – Lee Hachadoorian Apr 29 '20 at 21:17
  • If I had to guess, you could technically create a self-encapsulated environment into which you would write your data and the formula would tap into the data there. You could technically save this environment into a .RData object, which would make it more portable and perhaps even reproducible. I'm just glad you were able to solve your problem. – Roman Luštrik Apr 30 '20 at 10:07
2

As far as I can tell, this is a perfect case for using enquote:

fit_model <- function(formula, data) {
  formula <- enquote(formula)
  full_model <- lm(formula = formula, data = data)
  stepAIC(full_model, direction = "both", trace = FALSE)
}

fit_model(mpg ~ ., mtcars)
#
#Call:
#lm(formula = mpg ~ wt + qsec + am, data = data)
# 
#Coefficients:
#(Intercept)           wt         qsec           am  
#      9.618       -3.917        1.226        2.936  

Edit:

This is equivalent to:

fit_model2 <- function(formula, data) {
  full_model <- lm(formula = formula, data = data)
  MASS::stepAIC(full_model, direction = "both", trace = FALSE)
}

fit_model2(quote(mpg ~ .), mtcars)
Anders Ellern Bilgrau
  • 9,928
  • 1
  • 30
  • 37
  • I have tried three alternative answers using `environment()`, `enquote()`, and `do.call()`. All of them seem to work. Why is your proposed solution preferable? Would your solution also handle more complex functions, particularly if I added `...` to the parameters to allow passthrough to `lm`, `stepAIC`, etc.? – Lee Hachadoorian Apr 29 '20 at 21:17
2

A solution based on do.call and described at this link:

fit_model = function(formula, data) {
  full_model <- do.call("lm", list(formula=formula, data=data))
  step_model <- stepAIC(full_model, direction = "both", trace = FALSE)
  return(step_model)
}
step2 <- fit_model(mpg ~ ., mtcars)
Marco Sandri
  • 23,289
  • 7
  • 54
  • 58
  • I have tried three alternative answers using `environment()`, `enquote()`, and `do.call()`. All of them seem to work. Why is your proposed solution preferable? Would your solution also handle more complex functions, particularly if I added `...` to the parameters to allow passthrough to `lm`, `stepAIC`, etc.? – Lee Hachadoorian Apr 29 '20 at 21:17