Search code examples
rlapplyenvironment

expand.model.frame within lapply() & map(): environment confusion with fixest::feols and lm


I am trying to use expand.model.frame() within lapply() after estimating models with both lm and fixest::feols().

As it happens, I continuously run into errors for fixest::feols, likely because I am confused about environments.

Here's a prototypical example:

library(fixest) 
library(lfe)
library(estimatr)

fun <- function(object, clustid){
  
  fml <- object$call$formula
  if(!inherits(clustid, "formula")){
    clustid <- reformulate(clustid)
  }
  
  expand.model.frame(
    model = object, 
    extras = clustid, 
    #envir = environment(terms(object)),
    #envir = .GlobalEnv,
    na.expand = FALSE
  )
  
}

data(mtcars)

tmp_lm <- lapply(list(mtcars, mtcars), function(x){lm(mpg ~ qsec ,data = x)})
lapply(tmp_lm, function(x) fun(x, clustid = "carb"))

tmp_feols <- lapply(list(mtcars, mtcars), function(x){feols(mpg ~ qsec ,data = x)})
lapply(tmp_feols, function(x) fun(x, clustid = ~carb))
# Error in eval(model$call$data, envir) : object 'x' not found

tmp_felm <- lapply(list(mtcars, mtcars), function(x){felm(mpg ~ qsec ,data = x)})
lapply(tmp_felm, function(x) fun(x, clustid = ~carb))

tmp_estimatr <- lapply(list(mtcars, mtcars), function(x){lm_robust(mpg ~ qsec ,data = x)})
lapply(tmp_estimatr, function(x) fun(x, clustid = ~carb))

Does anyone have a suggestion how I would have to tweak fun() so that I would no longer run into the error below?


Solution

  • I can see from your commented code that you have been experimenting with the envir argument. You are on the right track here. The "fixest" class contains an object called call_env to ensure you can access the correct evaluating environment. That means you need to alter your code to get the correct calling environment according to the type of object passed:

    fun <- function(object, clustid){
    
      if(!inherits(clustid, "formula")){
        clustid <- reformulate(clustid)
      }
      
      e <- if(class(object) == "fixest") 
        object$call_env else environment(formula(object))
      
      expand.model.frame(
        model = object, 
        extras = clustid, 
        envir = e,
        na.expand = FALSE
      )
      
    }
    

    This allows:

    data(mtcars)
    
    tmp_feols <- lapply(list(mtcars, mtcars), function(x){feols(mpg ~ qsec ,data = x)})
    lapply(tmp_feols, function(x) fun(x, clustid = ~carb))
    #> [[1]]
    #>                      mpg  qsec carb
    #> Mazda RX4           21.0 16.46    4
    #> Mazda RX4 Wag       21.0 17.02    4
    #> Datsun 710          22.8 18.61    1
    #> Hornet 4 Drive      21.4 19.44    1
    #> Hornet Sportabout   18.7 17.02    2
    #> Valiant             18.1 20.22    1
    #> Duster 360          14.3 15.84    4
    #> Merc 240D           24.4 20.00    2
    #> Merc 230            22.8 22.90    2
    #> Merc 280            19.2 18.30    4
    #> Merc 280C           17.8 18.90    4
    #> Merc 450SE          16.4 17.40    3
    #> Merc 450SL          17.3 17.60    3
    #> Merc 450SLC         15.2 18.00    3
    #> Cadillac Fleetwood  10.4 17.98    4
    #> Lincoln Continental 10.4 17.82    4
    #> Chrysler Imperial   14.7 17.42    4
    #> Fiat 128            32.4 19.47    1
    #> Honda Civic         30.4 18.52    2
    #> Toyota Corolla      33.9 19.90    1
    #> Toyota Corona       21.5 20.01    1
    #> Dodge Challenger    15.5 16.87    2
    #> AMC Javelin         15.2 17.30    2
    #> Camaro Z28          13.3 15.41    4
    #> Pontiac Firebird    19.2 17.05    2
    #> Fiat X1-9           27.3 18.90    1
    #> Porsche 914-2       26.0 16.70    2
    #> Lotus Europa        30.4 16.90    2
    #> Ford Pantera L      15.8 14.50    4
    #> Ferrari Dino        19.7 15.50    6
    #> Maserati Bora       15.0 14.60    8
    #> Volvo 142E          21.4 18.60    2
    #> 
    #> [[2]]
    #>                      mpg  qsec carb
    #> Mazda RX4           21.0 16.46    4
    #> Mazda RX4 Wag       21.0 17.02    4
    #> Datsun 710          22.8 18.61    1
    #> Hornet 4 Drive      21.4 19.44    1
    #> Hornet Sportabout   18.7 17.02    2
    #> Valiant             18.1 20.22    1
    #> Duster 360          14.3 15.84    4
    #> Merc 240D           24.4 20.00    2
    #> Merc 230            22.8 22.90    2
    #> Merc 280            19.2 18.30    4
    #> Merc 280C           17.8 18.90    4
    #> Merc 450SE          16.4 17.40    3
    #> Merc 450SL          17.3 17.60    3
    #> Merc 450SLC         15.2 18.00    3
    #> Cadillac Fleetwood  10.4 17.98    4
    #> Lincoln Continental 10.4 17.82    4
    #> Chrysler Imperial   14.7 17.42    4
    #> Fiat 128            32.4 19.47    1
    #> Honda Civic         30.4 18.52    2
    #> Toyota Corolla      33.9 19.90    1
    #> Toyota Corona       21.5 20.01    1
    #> Dodge Challenger    15.5 16.87    2
    #> AMC Javelin         15.2 17.30    2
    #> Camaro Z28          13.3 15.41    4
    #> Pontiac Firebird    19.2 17.05    2
    #> Fiat X1-9           27.3 18.90    1
    #> Porsche 914-2       26.0 16.70    2
    #> Lotus Europa        30.4 16.90    2
    #> Ford Pantera L      15.8 14.50    4
    #> Ferrari Dino        19.7 15.50    6
    #> Maserati Bora       15.0 14.60    8
    #> Volvo 142E          21.4 18.60    2
    

    But will still work on the lm version.

    Of course, a more sophisticated way to do this would be to create a new generic function that dispatches according to class, rather than handling classes inside one big function.

    Created on 2022-05-28 by the reprex package (v2.0.1)