diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index e1c58b9..c552a83 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -4,7 +4,7 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] + branches: [main, master, develop] name: R-CMD-check diff --git a/.github/workflows/lint-changed-files.yaml b/.github/workflows/lint-changed-files.yaml index cf5c34b..c6a9228 100644 --- a/.github/workflows/lint-changed-files.yaml +++ b/.github/workflows/lint-changed-files.yaml @@ -3,7 +3,6 @@ on: workflow_dispatch: pull_request: - branches: [main, master] paths: - '**.R' - '**.Rmd' diff --git a/.lintr b/.lintr index e26a154..3866cf5 100644 --- a/.lintr +++ b/.lintr @@ -6,6 +6,9 @@ linters: all_linters( object_length_linter = NULL # due to length of method names ) exclusions: list( + "R/fn_plotting_impact.R" = list( + object_overwrite_linter = Inf + ), "tests/testthat.R" = list( unused_import_linter = Inf, undesirable_function_linter = Inf diff --git a/DESCRIPTION b/DESCRIPTION index d96f7e7..ed0d8c3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: vimcheck Title: Diagnostics for Vaccine Impact Modelling Consortium Burden and Impact Estimates -Version: 0.0.4 +Version: 0.0.5 Authors@R: c( person("Pratik", "Gupte", , "p.gupte24@imperial.ac.uk", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5294-7819")), diff --git a/NAMESPACE b/NAMESPACE index cb7c3f0..571eae6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,6 +14,7 @@ export(MIN_TS_MONTH) export(MIN_TS_YEAR) export(N_TS_MIN_CHARS) export(N_TS_YEAR_CHARS) +export(PINE) export(basic_burden_sanity) export(burden_outcome_names) export(check_demography_alignment) @@ -31,12 +32,14 @@ export(gen_national_iqr) export(generate_diffs) export(plot_age_patterns) export(plot_compare_demography) +export(plot_coverage_fvps) export(plot_coverage_set) export(plot_cumul) export(plot_diff) export(plot_fvp) export(plot_global_burden) export(plot_global_burden_decades) +export(plot_impact) export(plot_modelling_group_variation) export(plot_sig_diff) export(plot_vaccine_gavi) diff --git a/NEWS.md b/NEWS.md index fcaa798..0ee9eac 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# vimcheck 0.0.5 + +- Added functions to plot impact and FVPs and coverage. + # vimcheck 0.0.4 - Added impact diagnostics functions in `R/fn_impact_diagnostics.R`. diff --git a/R/constants.R b/R/constants.R index e4c3532..c30bdaf 100644 --- a/R/constants.R +++ b/R/constants.R @@ -215,3 +215,11 @@ DEF_TOUCHSTONE_OLD_OLD <- "202110" #' #' @export COLOUR_VIMC <- "#008080" + +#' @name constants +#' +#' @examples +#' PINE +#' +#' @export +PINE <- c("PAK", "IND", "NGA", "ETH") diff --git a/R/example_data.R b/R/example_data.R index c6a1d7b..dfa0fae 100644 --- a/R/example_data.R +++ b/R/example_data.R @@ -115,6 +115,28 @@ #' @source Prepared by the VIMC secretariat. "eg_fvps" +#' A second FVP data example +#' +#' Exampled data for fully-vaccinated persons, created manually from arbitrary +#' data. +#' +#' @format ## `eg_fvps_2` +#' A data frame with 4 rows and 7 columns: +#' \describe{ +#' \item{country}{Country name as ISO 3 character code.} +#' \item{year}{Year.} +#' \item{activity_type}{Vaccination activity identifier.} +#' \item{scenario_type}{Scenario type name.} +#' \item{vaccine}{Vaccine identifier.} +#' \item{coverage_adjusted}{Ratio of adjusted FVPs to adjusted target.} +#' \item{fvps}{Count of fully vaccinated persons.} +#' } +#' +#' @keywords data +#' +#' @source Example data prepared by the VIMC secretariat. +"eg_fvps_2" + #' Example of impact data #' #' Example of vaccine impact data taken from data used to test \pkg{vimpact}. @@ -139,3 +161,21 @@ #' #' @source Prepared by the VIMC secretariat. "eg_impact" + +#' Second example of impact data +#' +#' @format ## `eg_impact_2` +#' A data frame with 4 rows and 6 columns: +#' \describe{ +#' \item{country}{Example country identifier.} +#' \item{year}{Year.} +#' \item{birth_cohort}{Numeric for the birth year.} +#' \item{burden_outcome}{Outcome identifier.} +#' \item{impact}{Numeric for impact.} +#' \item{short_name}{Example identifier.} +#' } +#' +#' @keywords data +#' +#' @source Prepared manually by the VIMC secretariat. +"eg_impact_2" diff --git a/R/fn_plotting_impact.R b/R/fn_plotting_impact.R new file mode 100644 index 0000000..345617b --- /dev/null +++ b/R/fn_plotting_impact.R @@ -0,0 +1,267 @@ +#' Plot central impact estimates by cohort and year +#' +#' Produces faceted plots of central impact estimates for priority countries, +#' stratified either by birth cohort or by year of vaccination. +#' Impact metrics include cases, deaths, DALYs, and YLLs. +#' +#' @param data A tibble containing impact estimates. +#' +#' @param country The country names as a character vector. Defaults to PINE +#' countries. +#' +#' @param burden_type Burden metric used to evaluate impact; may be one of: +#' `"cases", "deaths", "dalys", "s"`. +#' +#' @param view A string for the way impact is assigned, either by birth +#' cohort ("cohort") or by year of vaccination ("year"). +#' +#' @param title Title of the plot to be rendered. Defaults to `NULL`. +#' +#' @return ggplot object showing central impact estimates +#' +#' @examples +#' impact_data <- eg_impact_2 +#' +#' plot_impact( +#' data = impact_data, +#' "A", +#' burden_type = "cases", +#' title = "Cases averted", +#' view = "year" +#' ) +#' +#' @export +plot_impact <- function( + data, + country = PINE, + burden_type = c("cases", "deaths", "dalys", "yll"), + view = c("cohort", "year"), + title = NULL +) { + required_cols <- c("country", "burden_outcome", "impact", "short_name") + + checkmate::assert_data_frame( + data, + min.rows = 1L, + min.cols = length(required_cols) + ) + checkmate::assert_names(colnames(data), must.include = required_cols) + + checkmate::assert_character(country, any.missing = FALSE) + + burden_type <- rlang::arg_match(burden_type) + view <- rlang::arg_match(view) + + checkmate::assert_string(title, null.ok = TRUE) + + # check if country is in data + if (!all(country %in% data[["country"]])) { + missing_country <- setdiff(country, data[["country"]]) # nolint used in err + cli::cli_abort( + "Impact data `data` expected to have country {.str {missing_country}} \ + but it is missing." + ) + } + + impact <- dplyr::filter( + data, + .data$country %in% country, + .data$burden_outcome == burden_type, + .data$impact != 0 # can this be safely written as impact > 0? + ) + + if (nrow(impact) > 0) { + if (view == "cohort") { + checkmate::assert_names(names(data), must.include = "birth_cohort") + x_var <- "birth_cohort" + x_lab <- "Birth cohort" + } else { + checkmate::assert_names(names(data), must.include = view) + x_var <- view + x_lab <- "Year" + } + + ggplot( + impact, + aes( + x = .data[[x_var]], + y = .data$impact, + ymin = .data$impact, + ymax = .data$impact, + fill = .data$short_name + ) + ) + + ggplot2::geom_ribbon(alpha = 0.3) + + ggplot2::geom_line(aes(colour = .data$short_name), linewidth = 0.5) + + ggplot2::geom_point(aes(colour = .data$short_name), size = 0.5) + + # TODO: theme definition may not be right for this plot + theme_vimc() + + facet_wrap(ggplot2::vars("country"), scales = "free_y") + + labs( + x = x_lab, + y = glue::glue("{burden_type} averted"), + title = title + ) + + theme( + legend.position = "bottom", + legend.key.size = ggplot2::unit(0.5, "cm"), + legend.key.width = ggplot2::unit(0.3, "cm") + ) + } else { + cli::cli_abort( + "No estimates remaining in the data after filtering for \\ + countries: {.str {country}} and impact != 0 for `burden_type`: \\ + {.str {burden_type}}." + ) + } +} + +#' Plot coverage and fully vaccinated persons (FVPs) +#' +#' Generates plots of routine vaccine coverage and fully vaccinated +#' persons (FVPs) over time for selected countries. +#' +#' @param fvps A data.frame (or class extending it) showing the number of +#' FVPs (fully vaccinated persons) by country, year and scenario/activity type. +#' +#' @param country A character vector of country identifiers, with all +#' identifiers expected to be found in `fvps`. Defaults to PINE countries. +#' +#' @return A named list with two ggplot objects: +#' \describe{ +#' \item{coverage}{A plot of routine vaccine coverage over time.} +#' \item{fvps}{A plot of fully vaccinated persons over time.} +#' } +#' +#' If there is no data on routine vaccination in the dataset, the `coverage` +#' element of the return will be an empty `` object, and a warning is +#' thrown. +#' +#' @examples +#' fvps <- eg_fvps_2 +#' +#' plots <- plot_coverage_fvps(fvps, "AGO") +#' plots$coverage +#' plots$fvps +#' +#' @export +plot_coverage_fvps <- function(fvps, country = PINE) { + required_cols <- c( + "country", + "activity_type", + "scenario_type", + "vaccine", + "coverage_adjusted", + "year", + "fvps" + ) + + checkmate::assert_data_frame( + fvps, + min.rows = 1L, + min.cols = length(required_cols) + ) + checkmate::assert_names(colnames(fvps), must.include = required_cols) + + country <- checkmate::assert_character(country, any.missing = FALSE) + if (!all(country %in% fvps[["country"]])) { + missing_country <- setdiff(country, fvps[["country"]]) # nolint used in err + cli::cli_abort( + "Impact data `fvps` expected to have country {.str {missing_country}} \ + but it is missing." + ) + } + + # handle FVPs plot + fvps <- dplyr::filter(fvps, .data$country %in% country) + cov <- dplyr::filter(fvps, .data$activity_type == "routine") + + fvps <- dplyr::mutate( + fvps, + vaccine_delivery = paste( + .data$scenario_type, + .data$activity_type, + sep = "_" + ) + ) + cols_to_select <- c("country", "vaccine_delivery", "year", "fvps") + + fvps <- dplyr::select(fvps, dplyr::all_of(cols_to_select)) + + fvps <- dplyr::group_by( + fvps, + .data$country, + .data$vaccine_delivery, + .data$year + ) + + fvps <- dplyr::summarise( + fvps, + fvps = round(sum(.data$fvps) / 1e6, 2), + .groups = "drop" + ) + + # handle coverage plot + cov <- dplyr::mutate( + cov, + vaccine_delivery = paste(.data$scenario_type, .data$vaccine, sep = "_"), + coverage_adjusted = round(.data$coverage_adjusted * 100, 2) + ) + + cols_to_select <- c( + "country", + "vaccine_delivery", + "year", + "coverage_adjusted" + ) + cov <- dplyr::select(cov, dplyr::all_of(cols_to_select)) + cov <- dplyr::rename(cov, coverage = "coverage_adjusted") + + if (nrow(cov) > 0) { + p <- .plot_cov_fvp( + cov, + "coverage", + "Coverage (%)", + "Routine vaccine coverage" + ) + } else { + p <- ggplot() + cli::cli_warn( + "There is no routine coverage in the database after filtering for \ + country: {.str {country}}" + ) + } + + # assumed FVP data always available + q <- .plot_cov_fvp(fvps, "fvps", "FVPs (in millions)", "FVPs") + + list(coverage = p, fvps = q) +} + +#' @keywords internal +.plot_cov_fvp <- function(data, col, ylab, title) { + ggplot( + data, + aes( + x = .data$year, + y = .data[[col]], + fill = .data$vaccine_delivery + ) + ) + + geom_point(aes(colour = .data$vaccine_delivery), size = 0.5) + + theme_vimc() + # TODO: same note above on theme + facet_wrap( + ggplot2::vars("country"), + scales = "free_y" + ) + + labs( + x = "Year", + y = ylab, + title = title + ) + + theme( + legend.position = "bottom", + legend.key.size = ggplot2::unit(0.5, "cm"), + legend.key.width = ggplot2::unit(0.3, "cm") + ) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 40a29ee..fc17cac 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -21,7 +21,7 @@ reference: - plot_burden_diagnostics - - title: Impact estimate diagnostics + - title: Impact estimates and diagnostics - subtitle: Check impact estimates contents: @@ -32,6 +32,8 @@ reference: - subtitle: Plot impact estimates contents: - plot_impact_diagnostics + - plot_impact + - plot_coverage_fvps - title: Plotting helper functions contents: diff --git a/data-raw/eg_fvps.R b/data-raw/eg_fvps.R index 2bcc468..4541123 100644 --- a/data-raw/eg_fvps.R +++ b/data-raw/eg_fvps.R @@ -14,3 +14,16 @@ eg_fvps$target_adjusted <- eg_fvps$target # assumed same as target eg_fvps$coverage_adjusted <- eg_fvps$fvps_adjusted / eg_fvps$target_adjusted usethis::use_data(eg_fvps, overwrite = TRUE) + +## A second example of FVP data +eg_fvps_2 <- tibble::tibble( + country = c("AGO", "AGO", "BEN", "BEN"), + year = c(2020, 2021, 2020, 2021), + activity_type = c("routine", "campaign", "routine", "campaign"), + scenario_type = c("default", "default", "default", "default"), + vaccine = c("measles", "measles", "measles", "measles"), + coverage_adjusted = c(0.8, 0.85, 0.4, 0.7), + fvps = c(1000000, 1200000, 800000, 900000) +) + +usethis::use_data(eg_fvps_2, overwrite = TRUE) diff --git a/data-raw/eg_impact.R b/data-raw/eg_impact.R index 29e5c27..6eea856 100644 --- a/data-raw/eg_impact.R +++ b/data-raw/eg_impact.R @@ -26,3 +26,15 @@ eg_impact <- mutate( ) usethis::use_data(eg_impact, overwrite = TRUE) + +# second impact dataset +eg_impact_2 <- tibble::tibble( + country = c("A", "A", "B", "B"), + year = c(2020, 2021, 2020, 2021), + birth_cohort = c(2000, 2001, 2000, 2001), + burden_outcome = c("deaths", "cases", "deaths", "cases"), + impact = c(15, 5, 14, 8), + short_name = c("short1", "short2", "short3", "short4") +) + +usethis::use_data(eg_impact_2, overwrite = TRUE) diff --git a/data/eg_fvps_2.rda b/data/eg_fvps_2.rda new file mode 100644 index 0000000..4c81e66 Binary files /dev/null and b/data/eg_fvps_2.rda differ diff --git a/data/eg_impact_2.rda b/data/eg_impact_2.rda new file mode 100644 index 0000000..3e1af86 Binary files /dev/null and b/data/eg_impact_2.rda differ diff --git a/man/constants.Rd b/man/constants.Rd index 1d93a1f..7f7e66b 100644 --- a/man/constants.Rd +++ b/man/constants.Rd @@ -22,6 +22,7 @@ \alias{DEF_TOUCHSTONE_NEW} \alias{DEF_TOUCHSTONE_OLD_OLD} \alias{COLOUR_VIMC} +\alias{PINE} \title{Package constants} \format{ An object of class \code{character} of length 5. @@ -61,6 +62,8 @@ An object of class \code{character} of length 1. An object of class \code{character} of length 1. An object of class \code{character} of length 1. + +An object of class \code{character} of length 4. } \usage{ file_dict_colnames @@ -100,6 +103,8 @@ DEF_TOUCHSTONE_NEW DEF_TOUCHSTONE_OLD_OLD COLOUR_VIMC + +PINE } \description{ Constant values used in \emph{vimcheck}. See the \strong{Examples} section for the @@ -144,6 +149,8 @@ DEF_TOUCHSTONE_OLD_OLD COLOUR_VIMC +PINE + } \keyword{constants} \keyword{datasets} diff --git a/man/eg_fvps_2.Rd b/man/eg_fvps_2.Rd new file mode 100644 index 0000000..d49fa06 --- /dev/null +++ b/man/eg_fvps_2.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/example_data.R +\docType{data} +\name{eg_fvps_2} +\alias{eg_fvps_2} +\title{A second FVP data example} +\format{ +\subsection{\code{eg_fvps_2}}{ + +A data frame with 4 rows and 7 columns: +\describe{ +\item{country}{Country name as ISO 3 character code.} +\item{year}{Year.} +\item{activity_type}{Vaccination activity identifier.} +\item{scenario_type}{Scenario type name.} +\item{vaccine}{Vaccine identifier.} +\item{coverage_adjusted}{Ratio of adjusted FVPs to adjusted target.} +\item{fvps}{Count of fully vaccinated persons.} +} +} +} +\source{ +Example data prepared by the VIMC secretariat. +} +\usage{ +eg_fvps_2 +} +\description{ +Exampled data for fully-vaccinated persons, created manually from arbitrary +data. +} +\keyword{data} diff --git a/man/eg_impact_2.Rd b/man/eg_impact_2.Rd new file mode 100644 index 0000000..1f8ef23 --- /dev/null +++ b/man/eg_impact_2.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/example_data.R +\docType{data} +\name{eg_impact_2} +\alias{eg_impact_2} +\title{Second example of impact data} +\format{ +\subsection{\code{eg_impact_2}}{ + +A data frame with 4 rows and 6 columns: +\describe{ +\item{country}{Example country identifier.} +\item{year}{Year.} +\item{birth_cohort}{Numeric for the birth year.} +\item{burden_outcome}{Outcome identifier.} +\item{impact}{Numeric for impact.} +\item{short_name}{Example identifier.} +} +} +} +\source{ +Prepared manually by the VIMC secretariat. +} +\usage{ +eg_impact_2 +} +\description{ +Second example of impact data +} +\keyword{data} diff --git a/man/plot_coverage_fvps.Rd b/man/plot_coverage_fvps.Rd new file mode 100644 index 0000000..17e1ea3 --- /dev/null +++ b/man/plot_coverage_fvps.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fn_plotting_impact.R +\name{plot_coverage_fvps} +\alias{plot_coverage_fvps} +\title{Plot coverage and fully vaccinated persons (FVPs)} +\usage{ +plot_coverage_fvps(fvps, country = PINE) +} +\arguments{ +\item{fvps}{A data.frame (or class extending it) showing the number of +FVPs (fully vaccinated persons) by country, year and scenario/activity type.} + +\item{country}{A character vector of country identifiers, with all +identifiers expected to be found in \code{fvps}. Defaults to PINE countries.} +} +\value{ +A named list with two ggplot objects: +\describe{ +\item{coverage}{A plot of routine vaccine coverage over time.} +\item{fvps}{A plot of fully vaccinated persons over time.} +} + +If there is no data on routine vaccination in the dataset, the \code{coverage} +element of the return will be an empty \verb{} object, and a warning is +thrown. +} +\description{ +Generates plots of routine vaccine coverage and fully vaccinated +persons (FVPs) over time for selected countries. +} +\examples{ +fvps <- eg_fvps_2 + +plots <- plot_coverage_fvps(fvps, "AGO") +plots$coverage +plots$fvps + +} diff --git a/man/plot_impact.Rd b/man/plot_impact.Rd new file mode 100644 index 0000000..4ccd8a0 --- /dev/null +++ b/man/plot_impact.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fn_plotting_impact.R +\name{plot_impact} +\alias{plot_impact} +\title{Plot central impact estimates by cohort and year} +\usage{ +plot_impact( + data, + country = PINE, + burden_type = c("cases", "deaths", "dalys", "yll"), + view = c("cohort", "year"), + title = NULL +) +} +\arguments{ +\item{data}{A tibble containing impact estimates.} + +\item{country}{The country names as a character vector. Defaults to PINE +countries.} + +\item{burden_type}{Burden metric used to evaluate impact; may be one of: +\verb{"cases", "deaths", "dalys", "s"}.} + +\item{view}{A string for the way impact is assigned, either by birth +cohort ("cohort") or by year of vaccination ("year").} + +\item{title}{Title of the plot to be rendered. Defaults to \code{NULL}.} +} +\value{ +ggplot object showing central impact estimates +} +\description{ +Produces faceted plots of central impact estimates for priority countries, +stratified either by birth cohort or by year of vaccination. +Impact metrics include cases, deaths, DALYs, and YLLs. +} +\examples{ +impact_data <- eg_impact_2 + +plot_impact( + data = impact_data, + "A", + burden_type = "cases", + title = "Cases averted", + view = "year" +) + +} diff --git a/tests/testthat/test-plot_impact.R b/tests/testthat/test-plot_impact.R new file mode 100644 index 0000000..dab2b59 --- /dev/null +++ b/tests/testthat/test-plot_impact.R @@ -0,0 +1,104 @@ +test_that("Plotting impact works", { + p <- plot_impact( + eg_impact_2, + "A", + "cases", + "year", + "Dummy title" + ) + expect_class(p, "ggplot") + + p <- plot_impact( + eg_impact_2, + "A", + "deaths", + "year", + "Dummy title" + ) + expect_class(p, "ggplot") + + p <- plot_impact( + eg_impact_2, + "A", + "cases", + "cohort", + "Dummy title" + ) + expect_class(p, "ggplot") + + # errors + expect_error( + plot_impact("dummy_data"), + "Must be of type 'data.frame'" + ) + expect_error( + plot_impact("dummy_data"), + "Must be of type 'data.frame'" + ) + + expect_error( + plot_impact(eg_impact_2[-1]), + "Names must include" + ) + + expect_error( + plot_impact("dummy_data"), + "Must be of type 'data.frame'" + ) + + expect_error( + plot_impact(eg_impact_2), + "expected to have country" + ) + + expect_error( + plot_impact(eg_impact_2, "A", "dummy_impact"), + "`burden_type` must be one of" + ) + + expect_error( + plot_impact(eg_impact_2, "A", view = "dummy"), + "`view` must be one of" + ) + + expect_error( + plot_impact(eg_impact_2, "A", "dummy_impact"), + "`burden_type` must be one of" + ) + + ei2 <- eg_impact_2 + ei2$impact <- 0 + + expect_error( + plot_impact(ei2, "A"), + "No estimates remaining in the data" + ) +}) + +test_that("Plotting coverage and FVPs works", { + p <- plot_coverage_fvps(eg_fvps_2, "AGO") + expect_list(p, "ggplot") + + # errors + expect_error( + plot_coverage_fvps("dummy_data"), + "Must be of type 'data.frame'" + ) + expect_error( + plot_coverage_fvps(eg_fvps_2[-1]), + "Must have at least 7 cols" + ) + + expect_error( + plot_coverage_fvps(eg_fvps_2, "ETH"), + "expected to have country" + ) + + expect_warning( + plot_coverage_fvps( + eg_fvps_2[eg_fvps_2$activity_type == "campaign", ], + "AGO" + ), + "There is no routine coverage" + ) +}) diff --git a/vignettes/pressure_testing.Rmd b/vignettes/pressure_testing.Rmd index a39330f..d7acfda 100644 --- a/vignettes/pressure_testing.Rmd +++ b/vignettes/pressure_testing.Rmd @@ -78,7 +78,8 @@ The excluded diseases are stored as the package constant `EXCLUDED_DISEASES` (`r # make a copy and add dummy disease values df_copy <- df df_copy$disease <- rep( - EXCLUDED_DISEASES, each = nrow(df_copy) / length(EXCLUDED_DISEASES) + EXCLUDED_DISEASES, + each = nrow(df_copy) / length(EXCLUDED_DISEASES) ) # pass dummy touchstone to filter out all rows @@ -331,4 +332,4 @@ Use `plot_cumul()` on data prepared using `prep_plot_cumul()` and `gen_combined_ gen_combined_df(prev_df, curr_df) |> prep_plot_cumul("deaths_averted", "Measles") |> plot_cumul() -``` \ No newline at end of file +```