diff --git a/NAMESPACE b/NAMESPACE index 103a03c6f..106dfe5b0 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) @@ -92,7 +97,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) @@ -115,10 +119,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 926dc3cfd..c6f1e43a9 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..607b04234 --- /dev/null +++ b/R/model-methods.R @@ -0,0 +1,133 @@ +#' 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) + Add_model(x, spec, ..., formula = formula) +} + + +#' @rdname Add_model +#' @export +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 20cc8da23..a0edb663e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -22,20 +22,20 @@ navbar: type: light articles: - - title: Get started - navbar: ~ - contents: - - epipredict - - preprocessing-and-models - - arx-classifier - - articles/update +- title: Get started + navbar: ~ + contents: + - epipredict + - preprocessing-and-models + - arx-classifier + - update - - title: Advanced methods - contents: - - articles/sliding - - articles/smooth-qr - - articles/symptom-surveys - - panel-data +- title: Advanced methods + contents: + - articles/sliding + - articles/smooth-qr + - articles/symptom-surveys + - panel-data repo: url: @@ -78,15 +78,16 @@ reference: - smooth_quantile_reg - title: Custom panel data forecasting workflows contents: - - epi_recipe - - epi_workflow - - add_epi_recipe - - adjust_epi_recipe - - add_model - - predict.epi_workflow - - fit.epi_workflow - - augment.epi_workflow - - forecast.epi_workflow + - epi_recipe + - epi_workflow + - add_epi_recipe + - adjust_epi_recipe + - Add_model + - predict.epi_workflow + - fit.epi_workflow + - augment.epi_workflow + - forecast.epi_workflow + - title: Epi recipe preprocessing steps contents: - starts_with("step_") diff --git a/man/add_model.Rd b/man/add_model.Rd index f1209b95f..6bf6b6b02 100644 --- a/man/add_model.Rd +++ b/man/add_model.Rd @@ -1,25 +1,43 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/epi_workflow.R -\name{add_model} +% 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} +\alias{Add_model.workflow} +\alias{Remove_model.workflow} +\alias{Update_model.workflow} \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{Remove_model}{epi_workflow}(x) + +\method{Update_model}{epi_workflow}(x, spec, ..., formula = NULL) + +\method{Add_model}{workflow}(x, spec, ..., formula = NULL) -\method{add_model}{epi_workflow}(x, spec, ..., formula = NULL) +\method{Remove_model}{workflow}(x) -\method{remove_model}{epi_workflow}(x) +\method{Update_model}{workflow}(x, spec, ..., formula = NULL) -\method{update_model}{epi_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}.} @@ -45,6 +63,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 +85,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. } } diff --git a/vignettes/articles/update.Rmd b/vignettes/update.Rmd similarity index 95% rename from vignettes/articles/update.Rmd rename to vignettes/update.Rmd index fd18d4353..cb19ce192 100644 --- a/vignettes/articles/update.Rmd +++ b/vignettes/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`