diff --git a/R/canned-epipred.R b/R/canned-epipred.R index 7458655e8..802d0f7e4 100644 --- a/R/canned-epipred.R +++ b/R/canned-epipred.R @@ -58,39 +58,43 @@ print.alist <- function(x, ...) { #' @export print.canned_epipred <- function(x, name, ...) { - cat("\n") - bullet <- "\u2022" - header <- glue::glue("A basic forecaster of type {name}") - header <- cli::rule(header, line = 2) - cat_line(header) - cat("\n") - - date_created <- glue::glue( - "This forecaster was fit on {format(x$metadata$forecast_created)}" + d <- cli::cli_div(theme = list(rule = list("line-type" = "double"))) + cli::cli_rule("A basic forecaster of type {name}") + cli::cli_end(d) + cli::cli_text("") + cli::cli_text( + "This forecaster was fit on {.field {format(x$metadata$forecast_created)}}." ) - cat_line(date_created) - cat("\n") - - cat_line("Training data was an `epi_df` with") - cat_line(glue::glue("\u2022 Geography: {x$metadata$training$geo_type},")) - cat_line(glue::glue("{bullet} Time type: {x$metadata$training$time_type},")) - cat_line(glue::glue("{bullet} Using data up-to-date as of: {format(x$metadata$training$as_of)}.")) + cli::cli_text("") + cli::cli_text("Training data was an {.cls epi_df} with:") + cli::cli_ul(c( + "Geography: {.field {x$metadata$training$geo_type}},", + "Time type: {.field {x$metadata$training$time_type}},", + "Using data up-to-date as of: {.field {format(x$metadata$training$as_of)}}." + )) + cli::cli_text("") - cat("\n") - header <- cli::rule("Predictions") - cat_line(header) - cat("\n") + cli::cli_rule("Predictions") + cli::cli_text("") n_geos <- dplyr::n_distinct(x$predictions$geo_value) - fds <- unique(x$predictions$forecast_date) - tds <- unique(x$predictions$target_date) - - cat_line( - glue::glue("A total of {nrow(x$predictions)} predictions are available for") + fds <- cli::cli_vec( + unique(x$predictions$forecast_date), + list("vec-trunc" = 5) + ) + tds <- cli::cli_vec( + unique(x$predictions$target_date), + list("vec-trunc" = 5) ) - cat_line(glue::glue("{bullet} {n_geos} unique geographic regions,")) - cat_line(glue::glue("{bullet} At forecast dates: {fds},")) - cat_line(glue::glue("{bullet} For target dates: {tds}.")) - cat("\n") + cli::cli_text(c( + "A total of {.val {nrow(x$predictions)}} prediction{?s}", + " {?is/are} available for" + )) + cli::cli_ul(c( + "{.val {n_geos}} unique geographic region{?s},", + "At forecast date{?s}: {.val {fds}},", + "For target date{?s}: {.val {tds}}." + )) + cli::cli_text("") } diff --git a/R/epi_recipe.R b/R/epi_recipe.R index bea7604e2..3e5607dbb 100644 --- a/R/epi_recipe.R +++ b/R/epi_recipe.R @@ -431,6 +431,15 @@ adjust_epi_recipe.epi_recipe <- function( prep.epi_recipe <- function( x, training = NULL, fresh = FALSE, verbose = FALSE, retain = TRUE, log_changes = FALSE, strings_as_factors = TRUE, ...) { + if (is.null(training)) { + cli::cli_warn(c( + "!" = "No training data was supplied to {.fn prep}.", + "!" = "Unlike a {.cls recipe}, an {.cls epi_recipe} does not ", + "!" = "store the full template data in the object.", + "!" = "Please supply the training data to the {.fn prep} function,", + "!" = "to avoid addtional warning messages." + )) + } training <- recipes:::check_training_set(training, x, fresh) training <- epi_check_training_set(training, x) training <- dplyr::relocate(training, tidyselect::all_of(epi_keys(training))) @@ -598,12 +607,12 @@ print.epi_recipe <- function(x, form_width = 30, ...) { cli::cli_h3("Operations") } - i <- 1 - for (step in x$steps) { - cat(paste0(i, ". ")) - print(step, form_width = form_width) - i <- i + 1 - } + fmt <- cli::cli_fmt({ + for (step in x$steps) { + print(step, form_width = form_width) + } + }) + cli::cli_ol(fmt) cli::cli_end() invisible(x) @@ -614,20 +623,12 @@ print_preprocessor_recipe <- function(x, ...) { recipe <- workflows::extract_preprocessor(x) steps <- recipe$steps n_steps <- length(steps) - if (n_steps == 1L) { - step <- "Step" - } else { - step <- "Steps" - } - n_steps_msg <- glue::glue("{n_steps} Recipe {step}") - cat_line(n_steps_msg) + cli::cli_text("{n_steps} Recipe step{?s}.") if (n_steps == 0L) { return(invisible(x)) } - cat_line("") - step_names <- map_chr(steps, workflows:::pull_step_name) if (n_steps <= 10L) { @@ -638,17 +639,8 @@ print_preprocessor_recipe <- function(x, ...) { extra_steps <- n_steps - 10L step_names <- step_names[1:10] - if (extra_steps == 1L) { - step <- "step" - } else { - step <- "steps" - } - - extra_dots <- "..." - extra_msg <- glue::glue("and {extra_steps} more {step}.") - cli::cli_ol(step_names) - cli::cli_bullets(c(extra_dots, extra_msg)) + cli::cli_bullets("... and {extra_steps} more step{?s}.") invisible(x) } @@ -664,9 +656,8 @@ print_preprocessor <- function(x) { return(invisible(x)) } - cat_line("") - header <- cli::rule("Preprocessor") - cat_line(header) + cli::cli_rule("Preprocessor") + cli::cli_text("") if (has_preprocessor_formula) { workflows:::print_preprocessor_formula(x) diff --git a/R/epi_workflow.R b/R/epi_workflow.R index 06ea09e9b..d5e7d13a2 100644 --- a/R/epi_workflow.R +++ b/R/epi_workflow.R @@ -323,50 +323,7 @@ print.epi_workflow <- function(x, ...) { print_header(x) print_preprocessor(x) # workflows:::print_case_weights(x) - workflows:::print_model(x) + print_model(x) print_postprocessor(x) invisible(x) } - -print_header <- function(x) { - # same as in workflows but with a postprocessor - trained <- ifelse(workflows::is_trained_workflow(x), " [trained]", "") - - header <- glue::glue("Epi Workflow{trained}") - header <- cli::rule(header, line = 2) - cat_line(header) - - preprocessor_msg <- cli::style_italic("Preprocessor:") - - if (workflows:::has_preprocessor_formula(x)) { - preprocessor <- "Formula" - } else if (workflows:::has_preprocessor_recipe(x)) { - preprocessor <- "Recipe" - } else if (workflows:::has_preprocessor_variables(x)) { - preprocessor <- "Variables" - } else { - preprocessor <- "None" - } - - preprocessor_msg <- glue::glue("{preprocessor_msg} {preprocessor}") - cat_line(preprocessor_msg) - - spec_msg <- cli::style_italic("Model:") - - if (workflows:::has_spec(x)) { - spec <- class(workflows::extract_spec_parsnip(x))[[1]] - spec <- glue::glue("{spec}()") - } else { - spec <- "None" - } - - spec_msg <- glue::glue("{spec_msg} {spec}") - cat_line(spec_msg) - - postprocessor_msg <- cli::style_italic("Postprocessor:") - postprocessor <- ifelse(has_postprocessor_frosting(x), "Frosting", "None") - postprocessor_msg <- glue::glue("{postprocessor_msg} {postprocessor}") - cat_line(postprocessor_msg) - - invisible(x) -} diff --git a/R/frosting.R b/R/frosting.R index 29d5359f1..505fd5bcc 100644 --- a/R/frosting.R +++ b/R/frosting.R @@ -415,60 +415,13 @@ print.frosting <- function(x, form_width = 30, ...) { cli::cli_h1("Frosting") if (!is.null(x$layers)) cli::cli_h3("Layers") - i <- 1 - for (layer in x$layers) { - cat(paste0(i, ". ")) - print(layer, form_width = form_width) - i <- i + 1 - } - cli::cli_end() - invisible(x) -} - -# Currently only used in the workflow printing -print_frosting <- function(x, ...) { - layers <- x$layers - n_layers <- length(layers) - layer <- ifelse(n_layers == 1L, "Layer", "Layers") - n_layers_msg <- glue::glue("{n_layers} Frosting {layer}") - cat_line(n_layers_msg) - - if (n_layers == 0L) { - return(invisible(x)) - } - - cat_line("") - - layer_names <- map_chr(layers, pull_layer_name) - - if (n_layers <= 10L) { - cli::cli_ol(layer_names) - return(invisible(x)) - } - - extra_layers <- n_layers - 10L - layer_names <- layer_names[1:10] - - layer <- ifelse(extra_layers == 1L, "layer", "layers") - - extra_dots <- "..." - extra_msg <- glue::glue("and {extra_layers} more {layer}.") - - cli::cli_ol(layer_names) - cli::cli_bullets(c(extra_dots, extra_msg)) - invisible(x) -} - -print_postprocessor <- function(x) { - if (!has_postprocessor_frosting(x)) { - return(invisible(x)) - } - - header <- cli::rule("Postprocessor") - cat_line(header) - - frost <- extract_frosting(x) - print_frosting(frost) + fmt <- cli::cli_fmt({ + for (layer in x$layers) { + print(layer, form_width = form_width) + } + }) + cli::cli_ol(fmt) + cli::cli_end() invisible(x) } diff --git a/R/workflow-printing.R b/R/workflow-printing.R new file mode 100644 index 000000000..c46d10848 --- /dev/null +++ b/R/workflow-printing.R @@ -0,0 +1,192 @@ +print_header <- function(x) { + cli::cli_text("") + trained <- ifelse(workflows::is_trained_workflow(x), " [trained]", "") + d <- cli::cli_div(theme = list(rule = list("line-type" = "double"))) + cli::cli_rule("Epi Workflow{trained}") + cli::cli_end(d) + + preprocessor_msg <- cli::style_italic("Preprocessor:") + preprocessor <- dplyr::case_when( + workflows:::has_preprocessor_formula(x) ~ "Formula", + workflows:::has_preprocessor_recipe(x) ~ "Recipe", + workflows:::has_preprocessor_variables(x) ~ "Variables", + TRUE ~ "None" + ) + cli::cli_text("{.emph Preprocessor:} {preprocessor}") + + + if (workflows:::has_spec(x)) { + spec <- class(workflows::extract_spec_parsnip(x))[[1]] + spec <- glue::glue("{spec}()") + } else { + spec <- "None" + } + cli::cli_text("{.emph Model:} {spec}") + + postprocessor <- ifelse(has_postprocessor_frosting(x), "Frosting", "None") + cli::cli_text("{.emph Postprocessor:} {postprocessor}") + cli::cli_text("") + invisible(x) +} + + +print_preprocessor <- function(x) { + has_preprocessor_formula <- workflows:::has_preprocessor_formula(x) + has_preprocessor_recipe <- workflows:::has_preprocessor_recipe(x) + has_preprocessor_variables <- workflows:::has_preprocessor_variables(x) + + no_preprocessor <- !has_preprocessor_formula && !has_preprocessor_recipe && + !has_preprocessor_variables + + if (no_preprocessor) { + return(invisible(x)) + } + + cli::cli_rule("Preprocessor") + cli::cli_text("") + + if (has_preprocessor_formula) { + print_preprocessor_formula(x) + } + if (has_preprocessor_recipe) { + print_preprocessor_recipe(x) + } + if (has_preprocessor_variables) { + print_preprocessor_variables(x) + } + cli::cli_text("") + invisible(x) +} + +# revision of workflows:::print_model() +print_model <- function(x) { + has_spec <- workflows:::has_spec(x) + if (!has_spec) { + cli::cli_text("") + return(invisible(x)) + } + has_fit <- workflows:::has_fit(x) + cli::cli_rule("Model") + + if (has_fit) { + print_fit(x) + cli::cli_text("") + return(invisible(x)) + } + workflows:::print_spec(x) + cli::cli_text("") + invisible(x) +} + + +print_postprocessor <- function(x) { + if (!has_postprocessor_frosting(x)) { + return(invisible(x)) + } + + cli::cli_rule("Postprocessor") + cli::cli_text("") + + frost <- extract_frosting(x) + print_frosting(frost) + cli::cli_text("") + invisible(x) +} + + +# subfunctions for printing ----------------------------------------------- + + + +print_preprocessor_formula <- function(x) { + formula <- workflows::extract_preprocessor(x) + formula <- rlang::expr_text(formula) + cli::cli_text(formula) + invisible(x) +} + +print_prepocessor_variables <- function(x) { + variables <- workflows::extract_preprocessor(x) + outcomes <- rlang::quo_get_expr(variables$outcomes) + predictors <- rlang::quo_get_expr(variables$predictors) + outcomes <- rlang::expr_text(outcomes) + predictors <- rlang::expr_text(predictors) + cli::cli_text("Outcomes: ", outcomes) + cli::cli_text("") + cli::cli_text("Predictors: ", predictors) + invisible(x) +} + +# Currently only used in the workflow printing +print_preprocessor_recipe <- function(x, ...) { + recipe <- workflows::extract_preprocessor(x) + steps <- recipe$steps + n_steps <- length(steps) + cli::cli_text("{n_steps} Recipe step{?s}.") + + if (n_steps == 0L) { + return(invisible(x)) + } + + step_names <- map_chr(steps, workflows:::pull_step_name) + + if (n_steps <= 10L) { + cli::cli_ol(step_names) + return(invisible(x)) + } + + extra_steps <- n_steps - 10L + step_names <- step_names[1:10] + + cli::cli_ol(step_names) + cli::cli_bullets("... and {extra_steps} more step{?s}.") + invisible(x) +} + + + + +print_fit <- function(x) { + parsnip_fit <- workflows::extract_fit_parsnip(x) + fit <- parsnip_fit$fit + output <- utils::capture.output(fit) + n_output <- length(output) + if (n_output < 50L) { + print(fit) + return(invisible(x)) + } + n_extra_output <- n_output - 50L + output <- output[1:50] + empty_string <- output == "" + output[empty_string] <- " " + + cli::cli_verbatim(output) + cli::cli_text("") + cli::cli_text("... and {n_extra_output} more line{?s}.") + invisible(x) +} + +# Currently only used in the workflow printing +print_frosting <- function(x, ...) { + layers <- x$layers + n_layers <- length(layers) + cli::cli_text("{n_layers} Frosting layer{?s}.") + + if (n_layers == 0L) { + return(invisible(x)) + } + + layer_names <- map_chr(layers, pull_layer_name) + + if (n_layers <= 10L) { + cli::cli_ol(layer_names) + return(invisible(x)) + } + + extra_layers <- n_layers - 10L + layer_names <- layer_names[1:10] + + cli::cli_ol(layer_names) + cli::cli_bullets("... and {extra_layers} more layer{?s}.") + invisible(x) +} diff --git a/vignettes/articles/sliding.Rmd b/vignettes/articles/sliding.Rmd index e889f0b74..a0b3312bc 100644 --- a/vignettes/articles/sliding.Rmd +++ b/vignettes/articles/sliding.Rmd @@ -4,7 +4,7 @@ title: "Demonstrations of sliding AR and ARX forecasters" ```{r setup, include = FALSE} knitr::opts_chunk$set( - collapse = FALSE, + collapse = TRUE, comment = "#>", warning = FALSE, message = FALSE, diff --git a/vignettes/epipredict.Rmd b/vignettes/epipredict.Rmd index 8a8e20e10..fe911ede0 100644 --- a/vignettes/epipredict.Rmd +++ b/vignettes/epipredict.Rmd @@ -10,7 +10,7 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( echo = TRUE, - collapse = FALSE, + collapse = TRUE, comment = "#>", out.width = "100%" ) diff --git a/vignettes/preprocessing-and-models.Rmd b/vignettes/preprocessing-and-models.Rmd index 60291ffd1..f1cdb3c87 100644 --- a/vignettes/preprocessing-and-models.Rmd +++ b/vignettes/preprocessing-and-models.Rmd @@ -10,7 +10,7 @@ vignette: > ```{r setup, include=FALSE} knitr::opts_chunk$set( echo = TRUE, - collapse = FALSE, + collapse = TRUE, comment = "#>", out.width = "100%" ) @@ -98,10 +98,11 @@ intercept coefficients, we can allow for an intercept shift between states. The model takes the form \begin{aligned} -\log\left( \mu*{t+7} \right) &= \beta_0 + \delta_1 s*{\text{state}_1} + -\delta_2 s_{\text{state}_2} + \cdots + \nonumber \\ &\quad\beta_1 \text{deaths}_{t} + -\beta*2 \text{deaths}*{t-7} + \beta*3 \text{cases}*{t} + -\beta*4 \text{cases}*{t-7}, +\log\left( \mu_{t+7} \right) &= \beta_0 + \delta_1 s_{\text{state}_1} + +\delta_2 s_{\text{state}_2} + \cdots + \nonumber \\ +&\quad\beta_1 \text{deaths}_{t} + +\beta_2 \text{deaths}_{t-7} + \beta_3 \text{cases}_{t} + +\beta_4 \text{cases}_{t-7}, \end{aligned} where $\mu_{t+7} = \mathbb{E}(y_{t+7})$, and $y_{t+7}$ is assumed to follow a Poisson distribution with mean $\mu_{t+7}$; $s_{\text{state}}$ are dummy @@ -195,11 +196,13 @@ rates, by incorporating offset terms in the model. To model death rates, the Poisson regression would be expressed as: \begin{aligned} -\log\left( \mu*{t+7} \right) &= \log(\text{population}) + -\beta_0 + \delta_1 s*{\text{state}_1} + -\delta_2 s_{\text{state}_2} + \cdots + \nonumber \\ &\quad\beta_1 \text{deaths}_{t} + -\beta*2 \text{deaths}*{t-7} + \beta*3 \text{cases}*{t} + -\beta*4 \text{cases}*{t-7}\end{aligned} +\log\left( \mu_{t+7} \right) &= \log(\text{population}) + +\beta_0 + \delta_1 s_{\text{state}_1} + +\delta_2 s_{\text{state}_2} + \cdots + \nonumber \\ +&\quad\beta_1 \text{deaths}_{t} + +\beta_2 \text{deaths}_{t-7} + \beta_3 \text{cases}_{t} + +\beta_4 \text{cases}_{t-7} +\end{aligned} where $\log(\text{population})$ is the log of the state population that was used to scale the count data on the left-hand side of the equation. This offset is simply a predictor with coefficient fixed at 1 rather than estimated. @@ -371,9 +374,7 @@ To look at the prediction intervals: ```{r} p %>% select(geo_value, target_date, .pred_scaled, .pred_distn_scaled) %>% - mutate(.pred_distn_scaled = nested_quantiles(.pred_distn_scaled)) %>% - unnest(.pred_distn_scaled) %>% - pivot_wider(names_from = quantile_levels, values_from = values) + pivot_quantiles_wider(.pred_distn_scaled) ``` Last but not least, let's take a look at the regression fit and check the @@ -419,22 +420,22 @@ consider the trend to be `flat`. The expression of the multinomial regression we will use is as follows: $$ -\pi_{j}(x) = \text{Pr}(Z_{\ell,t} = j|x) = \frac{e^{g_j(x)}}{1 + \sum_{k=0}^2 g_j(x) } +\pi_{j}(x) = \text{Pr}(Z_{\ell,t} = j|x) = \frac{e^{g_j(x)}}{1 + \sum_{k=1}^{2}e^{g_k(x)} } $$ where $j$ is either down, flat, or up \begin{aligned} -g*{\text{down}}(x) &= 0.\\ -g*{\text{flat}}(x)&= \text{ln}\left(\frac{Pr(Z*{\ell,t}=\text{flat}|x)}{Pr(Z*{\ell,t}=\text{down}|x)}\right) = -\beta*{10} + \beta*{11}t + \delta*{10} s*{\text{state*1}} + -\delta*{11} s*{\text{state_2}} + \cdots \nonumber \\ -&\quad + \beta*{12} Y^{\Delta}_{\ell, t} + +g_{\text{down}}(x) &= 0.\\ +g_{\text{flat}}(x) &= \log\left(\frac{Pr(Z_{\ell,t}=\text{flat}\mid x)}{Pr(Z_{\ell,t}=\text{down}\mid x)}\right) = +\beta_{10} + \beta_{11} t + \delta_{10} s_{\text{state_1}} + +\delta_{11} s_{\text{state_2}} + \cdots \nonumber \\ +&\quad + \beta_{12} Y^{\Delta}_{\ell, t} + \beta_{13} Y^{\Delta}_{\ell, t-7} \\ -g_{\text{flat}}(x) &= \text{ln}\left(\frac{Pr(Z*{\ell,t}=\text{up}|x)}{Pr(Z*{\ell,t}=\text{down}|x)}\right) = -\beta*{20} + \beta*{21}t + \delta*{20} s*{\text{state*1}} + -\delta*{21} s*{\text{state}\_2} + \cdots \nonumber \\ -&\quad + \beta*{22} Y^{\Delta}_{\ell, t} + +g_{\text{flat}}(x) &= \log\left(\frac{Pr(Z_{\ell,t}=\text{up}\mid x)}{Pr(Z_{\ell,t}=\text{down} \mid x)}\right) = +\beta_{20} + \beta_{21}t + \delta_{20} s_{\text{state_1}} + +\delta_{21} s_{\text{state}\_2} + \cdots \nonumber \\ +&\quad + \beta_{22} Y^{\Delta}_{\ell, t} + \beta_{23} Y^{\Delta}\_{\ell, t-7} \end{aligned} @@ -449,40 +450,24 @@ jhu <- case_death_rate_subset %>% time_value <= "2021-12-31", geo_value %in% c("ca", "fl", "tx", "ny", "nj") ) %>% - mutate(geo_value_factor = as.factor(geo_value)) %>% - as_epi_df() + mutate(geo_value_factor = as.factor(geo_value)) r <- epi_recipe(jhu) %>% add_role(time_value, new_role = "predictor") %>% step_dummy(geo_value_factor) %>% - step_epi_lag(case_rate, lag = c(0, 7, 14)) %>% - step_epi_ahead(case_rate, ahead = 7, role = "predictor") %>% - step_mutate( - pct_diff_ahead = case_when( - lag_7_case_rate == 0 ~ 0, - TRUE ~ (ahead_7_case_rate - lag_0_case_rate) / lag_0_case_rate - ), - pct_diff_wk1 = case_when( - lag_7_case_rate == 0 ~ 0, - TRUE ~ (lag_0_case_rate - lag_7_case_rate) / lag_7_case_rate - ), - pct_diff_wk2 = case_when( - lag_14_case_rate == 0 ~ 0, - TRUE ~ (lag_7_case_rate - lag_14_case_rate) / lag_14_case_rate - ) - ) %>% + step_growth_rate(case_rate, role = "none", prefix = "gr_") %>% + step_epi_lag(starts_with("gr_"), lag = c(0, 7, 14)) %>% + step_epi_ahead(starts_with("gr_"), ahead = 7, role = "none") %>% + # note recipes::step_cut() has a bug in it, or we could use that here step_mutate( - response = case_when( - pct_diff_ahead < -0.20 ~ "down", - pct_diff_ahead > 0.25 ~ "up", - TRUE ~ "flat" + response = cut( + ahead_7_gr_7_rel_change_case_rate, + breaks = c(-Inf, -0.2, 0.25, Inf) / 7, # division gives weekly not daily + labels = c("down", "flat", "up") ), role = "outcome" ) %>% - step_rm( - death_rate, case_rate, lag_0_case_rate, lag_7_case_rate, - lag_14_case_rate, ahead_7_case_rate, pct_diff_ahead - ) %>% + step_rm(has_role("none"), has_role("raw")) %>% step_epi_naomit() ``` @@ -512,7 +497,10 @@ return a `data.frame` that could be used for model fitting. b <- bake(prep(r, jhu), jhu) epi_workflow() %>% - add_formula(response ~ geo_value + time_value + pct_diff_wk1 + pct_diff_wk2) %>% + add_formula( + response ~ geo_value + time_value + lag_0_gr_7_rel_change_case_rate + + lag_7_gr_7_rel_change_case_rate + lag_14_gr_7_rel_change_case_rate + ) %>% add_model(parsnip::multinom_reg()) %>% fit(data = b) ``` @@ -546,7 +534,7 @@ p1 <- epi_recipe(ex) %>% step_epi_lag(death_rate, lag = c(0, 7, 14)) %>% step_epi_ahead(death_rate, ahead = 7, role = "outcome") %>% step_epi_naomit() %>% - prep() + prep(ex) b1 <- bake(p1, ex) b1 @@ -563,7 +551,7 @@ p2 <- epi_recipe(ex) %>% ahead7death_rate = lead(death_rate, 7) ) %>% step_epi_naomit() %>% - prep() + prep(ex) b2 <- bake(p2, ex) b2