From 37be24c198b6e3af375d136c8d90e83b22a4c287 Mon Sep 17 00:00:00 2001 From: "Daniel J. McDonald" Date: Tue, 30 Apr 2024 15:26:21 -0700 Subject: [PATCH 1/4] recreate add/update/remove model fns --- NAMESPACE | 12 +++-- R/epi_workflow.R | 101 ------------------------------------ R/model-methods.R | 128 ++++++++++++++++++++++++++++++++++++++++++++++ man/add_model.Rd | 46 +++++++++-------- 4 files changed, 163 insertions(+), 124 deletions(-) create mode 100644 R/model-methods.R diff --git a/NAMESPACE b/NAMESPACE index bfc4b696d..fc92bd695 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,8 +1,13 @@ # Generated by roxygen2: do not edit by hand +S3method(Add_model,epi_workflow) +S3method(Add_model,workflow) S3method(Math,dist_quantiles) S3method(Ops,dist_quantiles) -S3method(add_model,epi_workflow) +S3method(Remove_model,epi_workflow) +S3method(Remove_model,workflow) +S3method(Update_model,epi_workflow) +S3method(Update_model,workflow) S3method(adjust_epi_recipe,epi_recipe) S3method(adjust_epi_recipe,epi_workflow) S3method(adjust_frosting,epi_workflow) @@ -91,7 +96,6 @@ S3method(print,step_population_scaling) S3method(print,step_training_window) S3method(quantile,dist_quantiles) S3method(refresh_blueprint,default_epi_recipe_blueprint) -S3method(remove_model,epi_workflow) S3method(residuals,flatline) S3method(run_mold,default_epi_recipe_blueprint) S3method(slather,layer_add_forecast_date) @@ -114,10 +118,12 @@ S3method(tidy,check_enough_train_data) S3method(tidy,frosting) S3method(tidy,layer) S3method(update,layer) -S3method(update_model,epi_workflow) S3method(vec_ptype_abbr,dist_quantiles) S3method(vec_ptype_full,dist_quantiles) export("%>%") +export(Add_model) +export(Remove_model) +export(Update_model) export(add_epi_recipe) export(add_frosting) export(add_layer) diff --git a/R/epi_workflow.R b/R/epi_workflow.R index e64e0f7bc..82af51f87 100644 --- a/R/epi_workflow.R +++ b/R/epi_workflow.R @@ -59,107 +59,6 @@ is_epi_workflow <- function(x) { } -#' Add a model to an `epi_workflow` -#' -#' @seealso [workflows::add_model()] -#' - `add_model()` adds a parsnip model to the `epi_workflow`. -#' -#' - `remove_model()` removes the model specification as well as any fitted -#' model object. Any extra formulas are also removed. -#' -#' - `update_model()` first removes the model then adds the new -#' specification to the workflow. -#' -#' @details -#' Has the same behaviour as [workflows::add_model()] but also ensures -#' that the returned object is an `epi_workflow`. -#' -#' @inheritParams workflows::add_model -#' -#' @param x An `epi_workflow`. -#' -#' @param spec A parsnip model specification. -#' -#' @param ... Not used. -#' -#' @return -#' `x`, updated with a new, updated, or removed model. -#' -#' @export -#' @examples -#' jhu <- case_death_rate_subset %>% -#' dplyr::filter( -#' time_value > "2021-11-01", -#' geo_value %in% c("ak", "ca", "ny") -#' ) -#' -#' r <- epi_recipe(jhu) %>% -#' step_epi_lag(death_rate, lag = c(0, 7, 14)) %>% -#' step_epi_ahead(death_rate, ahead = 7) -#' -#' rf_model <- rand_forest(mode = "regression") -#' -#' wf <- epi_workflow(r) -#' -#' wf <- wf %>% add_model(rf_model) -#' wf -#' -#' lm_model <- parsnip::linear_reg() -#' -#' wf <- update_model(wf, lm_model) -#' wf -#' -#' wf <- remove_model(wf) -#' wf -#' @export -add_model <- function(x, spec, ..., formula = NULL) { - UseMethod("add_model") -} - -#' @rdname add_model -#' @export -remove_model <- function(x) { - UseMethod("remove_model") -} - -#' @rdname add_model -#' @export -update_model <- function(x, spec, ..., formula = NULL) { - UseMethod("update_model") -} - -#' @rdname add_model -#' @export -add_model.epi_workflow <- function(x, spec, ..., formula = NULL) { - workflows::add_model(x, spec, ..., formula = formula) -} - -#' @rdname add_model -#' @export -remove_model.epi_workflow <- function(x) { - workflows:::validate_is_workflow(x) - - if (!workflows:::has_spec(x)) { - rlang::warn("The workflow has no model to remove.") - } - - new_epi_workflow( - pre = x$pre, - fit = workflows:::new_stage_fit(), - post = x$post, - trained = FALSE - ) -} - -#' @rdname add_model -#' @export -update_model.epi_workflow <- function(x, spec, ..., formula = NULL) { - rlang::check_dots_empty() - x <- remove_model(x) - workflows::add_model(x, spec, ..., formula = formula) -} - - #' Fit an `epi_workflow` object #' #' @description diff --git a/R/model-methods.R b/R/model-methods.R new file mode 100644 index 000000000..619e8747d --- /dev/null +++ b/R/model-methods.R @@ -0,0 +1,128 @@ + +#' Add a model to an `epi_workflow` +#' +#' @seealso [workflows::add_model()] +#' - `Add_model()` adds a parsnip model to the `epi_workflow`. +#' +#' - `Remove_model()` removes the model specification as well as any fitted +#' model object. Any extra formulas are also removed. +#' +#' - `Update_model()` first removes the model then adds the new +#' specification to the workflow. +#' +#' @details +#' Has the same behaviour as [workflows::add_model()] but also ensures +#' that the returned object is an `epi_workflow`. +#' +#' This family is called `Add_*` / `Update_*` / `Remove_*` to avoid +#' masking the related functions in `{workflows}`. We also provide +#' aliases with the lower-case names. However, in the event that +#' `{workflows}` is loaded after `{epipredict}`, these may fail to function +#' properly. +#' +#' @inheritParams workflows::add_model +#' +#' @param x An `epi_workflow`. +#' +#' @param spec A parsnip model specification. +#' +#' @param ... Not used. +#' +#' @return +#' `x`, updated with a new, updated, or removed model. +#' +#' @export +#' @examples +#' jhu <- case_death_rate_subset %>% +#' dplyr::filter( +#' time_value > "2021-11-01", +#' geo_value %in% c("ak", "ca", "ny") +#' ) +#' +#' r <- epi_recipe(jhu) %>% +#' step_epi_lag(death_rate, lag = c(0, 7, 14)) %>% +#' step_epi_ahead(death_rate, ahead = 7) +#' +#' rf_model <- rand_forest(mode = "regression") +#' +#' wf <- epi_workflow(r) +#' +#' wf <- wf %>% Add_model(rf_model) +#' wf +#' +#' lm_model <- parsnip::linear_reg() +#' +#' wf <- Update_model(wf, lm_model) +#' wf +#' +#' wf <- Remove_model(wf) +#' wf +#' @export +Add_model <- function(x, spec, ..., formula = NULL) { + UseMethod("Add_model") +} + +#' @rdname Add_model +#' @export +Remove_model <- function(x) { + UseMethod("Remove_model") +} + +#' @rdname Add_model +#' @export +Update_model <- function(x, spec, ..., formula = NULL) { + UseMethod("Update_model") +} + +#' @rdname Add_model +#' @export +Add_model.epi_workflow <- function(x, spec, ..., formula = NULL) { + workflows::add_model(x, spec, ..., formula = formula) +} + +#' @rdname Add_model +#' @export +Remove_model.epi_workflow <- function(x) { + workflows:::validate_is_workflow(x) + + if (!workflows:::has_spec(x)) { + rlang::warn("The workflow has no model to remove.") + } + + new_epi_workflow( + pre = x$pre, + fit = workflows:::new_stage_fit(), + post = x$post, + trained = FALSE + ) +} + +#' @rdname Add_model +#' @export +Update_model.epi_workflow <- function(x, spec, ..., formula = NULL) { + rlang::check_dots_empty() + x <- Remove_model(x) + workflows::add_model(x, spec, ..., formula = formula) +} + + +#' @export +Add_model.workflow <- workflows::remove_model + +#' @export +Remove_model.workflow <- workflows::remove_model + +#' @export +Update_model.workflow <- workflows::update_model + + +# Aliases ----------------------------------------------------------------- + +#' @export +add_model <- Add_model + +#' @export +remove_model <- Remove_model + +#' @export +update_model <- Update_model diff --git a/man/add_model.Rd b/man/add_model.Rd index f1209b95f..dc24f186d 100644 --- a/man/add_model.Rd +++ b/man/add_model.Rd @@ -1,25 +1,25 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/epi_workflow.R -\name{add_model} -\alias{add_model} -\alias{remove_model} -\alias{update_model} -\alias{add_model.epi_workflow} -\alias{remove_model.epi_workflow} -\alias{update_model.epi_workflow} +% Please edit documentation in R/model-methods.R +\name{Add_model} +\alias{Add_model} +\alias{Remove_model} +\alias{Update_model} +\alias{Add_model.epi_workflow} +\alias{Remove_model.epi_workflow} +\alias{Update_model.epi_workflow} \title{Add a model to an \code{epi_workflow}} \usage{ -add_model(x, spec, ..., formula = NULL) +Add_model(x, spec, ..., formula = NULL) -remove_model(x) +Remove_model(x) -update_model(x, spec, ..., formula = NULL) +Update_model(x, spec, ..., formula = NULL) -\method{add_model}{epi_workflow}(x, spec, ..., formula = NULL) +\method{Add_model}{epi_workflow}(x, spec, ..., formula = NULL) -\method{remove_model}{epi_workflow}(x) +\method{Remove_model}{epi_workflow}(x) -\method{update_model}{epi_workflow}(x, spec, ..., formula = NULL) +\method{Update_model}{epi_workflow}(x, spec, ..., formula = NULL) } \arguments{ \item{x}{An \code{epi_workflow}.} @@ -45,6 +45,12 @@ Add a model to an \code{epi_workflow} \details{ Has the same behaviour as \code{\link[workflows:add_model]{workflows::add_model()}} but also ensures that the returned object is an \code{epi_workflow}. + +This family is called \verb{Add_*} / \verb{Update_*} / \verb{Remove_*} to avoid +masking the related functions in \code{{workflows}}. We also provide +aliases with the lower-case names. However, in the event that +\code{{workflows}} is loaded after \code{{epipredict}}, these may fail to function +properly. } \examples{ jhu <- case_death_rate_subset \%>\% @@ -61,24 +67,24 @@ rf_model <- rand_forest(mode = "regression") wf <- epi_workflow(r) -wf <- wf \%>\% add_model(rf_model) +wf <- wf \%>\% Add_model(rf_model) wf lm_model <- parsnip::linear_reg() -wf <- update_model(wf, lm_model) +wf <- Update_model(wf, lm_model) wf -wf <- remove_model(wf) +wf <- Remove_model(wf) wf } \seealso{ \code{\link[workflows:add_model]{workflows::add_model()}} \itemize{ -\item \code{add_model()} adds a parsnip model to the \code{epi_workflow}. -\item \code{remove_model()} removes the model specification as well as any fitted +\item \code{Add_model()} adds a parsnip model to the \code{epi_workflow}. +\item \code{Remove_model()} removes the model specification as well as any fitted model object. Any extra formulas are also removed. -\item \code{update_model()} first removes the model then adds the new +\item \code{Update_model()} first removes the model then adds the new specification to the workflow. } } From 70c35c59bfbe1eb77863f4ad0f800489cd86afa3 Mon Sep 17 00:00:00 2001 From: "Daniel J. McDonald" Date: Tue, 30 Apr 2024 15:33:59 -0700 Subject: [PATCH 2/4] fix: downstream add/update/remove --- _pkgdown.yml | 2 +- vignettes/articles/update.Rmd | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 8fe027a5c..fa6964022 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -84,7 +84,7 @@ reference: - epi_workflow - add_epi_recipe - adjust_epi_recipe - - add_model + - Add_model - predict.epi_workflow - fit.epi_workflow - augment.epi_workflow diff --git a/vignettes/articles/update.Rmd b/vignettes/articles/update.Rmd index 221e1c37e..54b909072 100644 --- a/vignettes/articles/update.Rmd +++ b/vignettes/articles/update.Rmd @@ -39,8 +39,8 @@ add/remove/update an `epi_recipe` or a step in it. For this, we have `add_epi_recipe()`, `update_epi_recipe()`, and `remove_epi_recipe()` to add/update/remove an entire `epi_recipe` in an `epi_workflow` as well as `adjust_epi_recipe()` to adjust a particular step in an `epi_recipe` or -`epi_workflow` by the step number or name. For a model, one may `add_model()`, -`update_model()`, or `remove_model()` in an `epi_workflow`. For post-processing, +`epi_workflow` by the step number or name. For a model, one may `Add_model()`, +`Update_model()`, or `Remove_model()` in an `epi_workflow`.[^1] For post-processing, where the goal is to update a frosting object or a layer in it, we have `add_frosting()`, `remove_frosting()`, and `update_frosting()` to add/update/remove an entire `frosting` object in an `epi_workflow` as well as @@ -51,9 +51,14 @@ processing step is shown by the following table: | | Add/update/remove functions | adjust functions | |----------------------------|------------------------------------------------------------|---------------------| | Pre-processing | `add_epi_recipe()`, `update_epi_recipe()`, `remove_epi_recipe()` | `adjust_epi_recipe()` | -| Model specification | `add_model()`, `update_model()` `remove_model()` | | +| Model specification | `Add_model()`, `Update_model()` `Remove_model()` | | | Post-processing | `add_frosting()`, `remove_frosting()`, `update_frosting()` | `adjust_frosting()` | +[^1]: We capitalize these names to avoid possible clashes with the `{workflows}` +versions of these functions. The lower-case versions are also available, +however, if you load `{workflows}` after `{epipredict}`, these will be masked +and may not work as expected. + Since adding/removing/updating frosting as well as adjusting a layer in a `frosting` object proceeds in the same way as performing those tasks on an `epi_recipe`, we will focus on implementing those for an `epi_recipe` in this @@ -162,16 +167,16 @@ point - Any operations performed using the old recipe are not updated automatically. So we should be careful to fit the model using the new recipe, `r2`. Similarly, if predictions were made using the old recipe, then they should be re-generated using the version `epi_workflow` that contains the updated -recipe. We can use `update_model()` to replace the model used in `wf`, and then +recipe. We can use `Update_model()` to replace the model used in `wf`, and then fit as before: ```{r} # fit linear model -wf <- update_model(wf, parsnip::linear_reg()) %>% fit(jhu) +wf <- Update_model(wf, parsnip::linear_reg()) %>% fit(jhu) wf ``` -Alternatively, we may use the `remove_model()` followed by `add_model()` +Alternatively, we may use the `Remove_model()` followed by `Add_model()` combination for the same effect. ## Add/update/remove a `frosting` object in an `epi_workflow` From 286e467ad8ed7dbc98ba90999d7db7bb6425e7ef Mon Sep 17 00:00:00 2001 From: "Daniel J. McDonald" Date: Tue, 30 Apr 2024 15:51:45 -0700 Subject: [PATCH 3/4] pass local checks, move vignette back --- R/model-methods.R | 10 ++++++++-- _pkgdown.yml | 2 +- man/add_model.Rd | 18 ++++++++++++++++++ vignettes/{articles => }/update.Rmd | 0 4 files changed, 27 insertions(+), 3 deletions(-) rename vignettes/{articles => }/update.Rmd (100%) diff --git a/R/model-methods.R b/R/model-methods.R index 619e8747d..d7c1e584c 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -102,27 +102,33 @@ Remove_model.epi_workflow <- function(x) { Update_model.epi_workflow <- function(x, spec, ..., formula = NULL) { rlang::check_dots_empty() x <- Remove_model(x) - workflows::add_model(x, spec, ..., formula = formula) + Add_model(x, spec, ..., formula = formula) } +#' @rdname Add_model #' @export -Add_model.workflow <- workflows::remove_model +Add_model.workflow <- workflows::add_model +#' @rdname Add_model #' @export Remove_model.workflow <- workflows::remove_model +#' @rdname Add_model #' @export Update_model.workflow <- workflows::update_model # Aliases ----------------------------------------------------------------- +#' @rdname Add_model #' @export add_model <- Add_model +#' @rdname Add_model #' @export remove_model <- Remove_model +#' @rdname Add_model #' @export update_model <- Update_model diff --git a/_pkgdown.yml b/_pkgdown.yml index fa6964022..92e92b040 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -28,7 +28,7 @@ articles: - epipredict - preprocessing-and-models - arx-classifier - - articles/update + - update - title: Advanced methods contents: diff --git a/man/add_model.Rd b/man/add_model.Rd index dc24f186d..6bf6b6b02 100644 --- a/man/add_model.Rd +++ b/man/add_model.Rd @@ -7,6 +7,12 @@ \alias{Add_model.epi_workflow} \alias{Remove_model.epi_workflow} \alias{Update_model.epi_workflow} +\alias{Add_model.workflow} +\alias{Remove_model.workflow} +\alias{Update_model.workflow} +\alias{add_model} +\alias{remove_model} +\alias{update_model} \title{Add a model to an \code{epi_workflow}} \usage{ Add_model(x, spec, ..., formula = NULL) @@ -20,6 +26,18 @@ Update_model(x, spec, ..., formula = NULL) \method{Remove_model}{epi_workflow}(x) \method{Update_model}{epi_workflow}(x, spec, ..., formula = NULL) + +\method{Add_model}{workflow}(x, spec, ..., formula = NULL) + +\method{Remove_model}{workflow}(x) + +\method{Update_model}{workflow}(x, spec, ..., formula = NULL) + +add_model(x, spec, ..., formula = NULL) + +remove_model(x) + +update_model(x, spec, ..., formula = NULL) } \arguments{ \item{x}{An \code{epi_workflow}.} diff --git a/vignettes/articles/update.Rmd b/vignettes/update.Rmd similarity index 100% rename from vignettes/articles/update.Rmd rename to vignettes/update.Rmd From 173c39f4d10c90581cdb33964f142a4554182577 Mon Sep 17 00:00:00 2001 From: "Daniel J. McDonald" Date: Tue, 30 Apr 2024 15:58:08 -0700 Subject: [PATCH 4/4] style --- R/model-methods.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/model-methods.R b/R/model-methods.R index d7c1e584c..607b04234 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -1,4 +1,3 @@ - #' Add a model to an `epi_workflow` #' #' @seealso [workflows::add_model()]