tidymodels / poissonreg

parsnip wrappers for Poisson regression
https://poissonreg.tidymodels.org
Other
22 stars 4 forks source link

Setting `relax = TRUE` for glmnet models fails #82

Open hfrick opened 5 months ago

hfrick commented 5 months ago

Slightly adapted from this SO post

library(poissonreg)
#> Loading required package: parsnip

set.seed(123)
df <- tibble::tibble(
  y = rpois(1000, lambda = 3),
  x_1 = 2 * y + rnorm(1000),
  x_2 = 0.1 * y + rnorm(1000)
)

# setting `relax` by itself works
mod <- poisson_reg(penalty = 0.5) %>%
  set_engine("glmnet", relax = TRUE) |> 
  fit(y ~ ., data = df) 

# setting `family` by itself works
mod <- poisson_reg(penalty = 0.5) %>%
  set_engine("glmnet", family = "poisson") |> 
  fit(y ~ ., data = df)

# setting both together fails
mod <- poisson_reg(penalty = 0.5) %>%
  set_engine("glmnet", family = "poisson", relax = TRUE) |> 
  fit(y ~ ., data = df)
#> Error in glmnet.path(x, y, weights, lambda, nlambda, lambda.min.ratio, : Invalid family argument; must be either character, function or family object

# glmnet directly works
mod <- glmnet::glmnet(x = as.matrix(df[,2:3]), y = df$y, family = "poisson")
mod <- glmnet::glmnet(x = as.matrix(df[,2:3]), y = df$y, family = "poisson", relax = TRUE)

Created on 2024-02-03 with reprex v2.1.0

hfrick commented 5 months ago

This affects all glmnet models that we wrap in tidymodels (not just here in poissonreg) where we set relax = TRUE. Here is an example with linear_reg() and main arguments and engines arguments, respectively.

library(parsnip)

mod <- linear_reg(penalty = 0.5, mixture = 1) %>%
  set_engine("glmnet", relax = TRUE) |> 
  fit(mpg ~ ., data = mtcars)
#> Error in `glmnet::glmnet()`:
#> ! Base operators are not defined for quosures. Do you need to unquote
#>   the quosure?
#> 
#> # Bad: myquosure > rhs
#> 
#> # Good: !!myquosure > rhs

mod <- linear_reg(penalty = 0.5) %>%
  set_engine("glmnet", relax = TRUE, maxit = 1000) |> 
  fit(mpg ~ ., data = mtcars)
#> Warning: from glmnet C++ code (error code -63); Convergence for 63th lambda
#> value not reached after maxit=1000 iterations; solutions for larger lambdas
#> returned
#> Error in elnet(xd, is.sparse, y, weights, offset, type.gaussian, alpha, : 'language' object cannot be coerced to type 'integer'

Created on 2024-02-16 with reprex v2.1.0

This is because glmnet::glmnet() calls glmnet::relax.glmnet() with the "regular" fit object (glmnet code snippet).

glmnet::relax.glmnet() uses that fit object to refit without any regularization . That happens via stats::update.default() (glmnet code snippet) which uses eval() to evaluate the call -- but that call contains the arguments as quosures. That is what ultimately breaks eval().