From c8ca1df765efc075cc92f1a5b7e6510f3c75816b Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Mon, 14 Oct 2024 14:27:31 -0700 Subject: [PATCH] wip doc: clean up reference, reorganize notebooks --- NAMESPACE | 4 - R/archive.R | 62 ++- R/epi_df.R | 1 + R/epiprocess.R | 8 +- R/methods-epi_df.R | 7 +- R/outliers.R | 3 + R/slide.R | 8 +- R/utils.R | 8 +- README.Rmd | 75 ++-- README.md | 150 +++---- _pkgdown.yml | 70 +-- man/assert_sufficient_f_args.Rd | 22 + man/compactify.Rd | 28 -- man/decay_epi_df.Rd | 22 + man/deprecated_quo_is_present.Rd | 59 +++ man/dplyr_reconstruct.epi_df.Rd | 21 + man/epi_archive.Rd | 27 +- man/epi_df.Rd | 8 +- man/epi_slide_mean.Rd | 166 ------- man/epi_slide_opt.Rd | 161 +++++++ man/epi_slide_sum.Rd | 129 ------ man/epiprocess.Rd | 1 + man/figures/README-unnamed-chunk-6-1.png | Bin 36830 -> 37316 bytes man/full_date_seq.Rd | 15 + man/geo_column_names.Rd | 1 + man/guess_period.Rd | 1 + man/is_epi_df.Rd | 17 - man/max_version_with_row_in.Rd | 1 + man/next_after.Rd | 1 + man/print.epi_df.Rd | 11 +- man/summary.epi_df.Rd | 18 - man/time_column_names.Rd | 1 + man/validate_version_bound.Rd | 35 ++ man/version_column_names.Rd | 1 + vignettes/scrap.Rmd => scrap.Rmd | 54 +++ vignettes/aggregation.Rmd | 234 ---------- vignettes/compactify.Rmd | 9 + vignettes/correlation.Rmd | 15 +- vignettes/{archive.Rmd => epi_archive.Rmd} | 38 +- vignettes/epi_df.Rmd | 479 +++++++++++++++++++++ vignettes/epiprocess.Rmd | 133 +++--- vignettes/growth_rate.Rmd | 9 + vignettes/outliers.Rmd | 11 +- vignettes/slide.Rmd | 231 ---------- 44 files changed, 1247 insertions(+), 1108 deletions(-) create mode 100644 man/assert_sufficient_f_args.Rd delete mode 100644 man/compactify.Rd create mode 100644 man/decay_epi_df.Rd create mode 100644 man/deprecated_quo_is_present.Rd create mode 100644 man/dplyr_reconstruct.epi_df.Rd delete mode 100644 man/epi_slide_mean.Rd delete mode 100644 man/epi_slide_sum.Rd create mode 100644 man/full_date_seq.Rd delete mode 100644 man/is_epi_df.Rd delete mode 100644 man/summary.epi_df.Rd create mode 100644 man/validate_version_bound.Rd rename vignettes/scrap.Rmd => scrap.Rmd (75%) delete mode 100644 vignettes/aggregation.Rmd rename vignettes/{archive.Rmd => epi_archive.Rmd} (97%) create mode 100644 vignettes/epi_df.Rmd delete mode 100644 vignettes/slide.Rmd diff --git a/NAMESPACE b/NAMESPACE index 904b2d24..7b191d6d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -42,8 +42,6 @@ S3method(key_colnames,default) S3method(key_colnames,epi_archive) S3method(key_colnames,epi_df) S3method(mean,epi_df) -S3method(next_after,Date) -S3method(next_after,integer) S3method(print,epi_archive) S3method(print,epi_df) S3method(print,grouped_epi_archive) @@ -85,11 +83,9 @@ export(guess_period) export(is_epi_df) export(is_grouped_epi_archive) export(key_colnames) -export(max_version_with_row_in) export(mutate) export(new_epi_archive) export(new_epi_df) -export(next_after) export(relocate) export(rename) export(revision_summary) diff --git a/R/archive.R b/R/archive.R index 65895349..7aefbd5d 100644 --- a/R/archive.R +++ b/R/archive.R @@ -22,7 +22,7 @@ #' #' @section Side effects: raises an error if version bound appears invalid #' -#' @noRd +#' @keywords internal validate_version_bound <- function(version_bound, x, na_ok = FALSE, version_bound_arg = rlang::caller_arg(version_bound), x_arg = rlang::caller_arg(x)) { @@ -77,7 +77,7 @@ validate_version_bound <- function(version_bound, x, na_ok = FALSE, #' #' @importFrom checkmate check_names #' -#' @export +#' @keywords internal max_version_with_row_in <- function(x) { if (nrow(x) == 0L) { cli_abort( @@ -108,45 +108,18 @@ max_version_with_row_in <- function(x) { #' @param x the starting "value"(s) #' @return same class, typeof, and length as `x` #' -#' @export +#' @keywords internal next_after <- function(x) UseMethod("next_after") -#' @export +#' @keywords internal next_after.integer <- function(x) x + 1L -#' @export +#' @keywords internal next_after.Date <- function(x) x + 1L -#' Compactify -#' -#' This section describes the internals of how compactification works in an -#' `epi_archive()`. Compactification can potentially improve code speed or -#' memory usage, depending on your data. -#' -#' In general, the last version of each observation is carried forward (LOCF) to -#' fill in data between recorded versions, and between the last recorded -#' update and the `versions_end`. One consequence is that the `DT` doesn't -#' have to contain a full snapshot of every version (although this generally -#' works), but can instead contain only the rows that are new or changed from -#' the previous version (see `compactify`, which does this automatically). -#' Currently, deletions must be represented as revising a row to a special -#' state (e.g., making the entries `NA` or including a special column that -#' flags the data as removed and performing some kind of post-processing), and -#' the archive is unaware of what this state is. Note that `NA`s *can* be -#' introduced by `epi_archive` methods for other reasons, e.g., in -#' [`epix_fill_through_version`] and [`epix_merge`], if requested, to -#' represent potential update data that we do not yet have access to; or in -#' [`epix_merge`] to represent the "value" of an observation before the -#' version in which it was first released, or if no version of that -#' observation appears in the archive data at all. -#' -#' @name compactify -NULL - - #' `epi_archive` object #' #' The second main data structure for storing time series in `epiprocess`. It is @@ -174,6 +147,28 @@ NULL #' on `DT` directly). Note that there can only be a single row per unique #' combination of key variables. #' +#' @section Compactify: +#' This section describes the internals of how compactification works in an +#' `epi_archive()`. Compactification can potentially improve code speed or +#' memory usage, depending on your data. +#' +#' In general, the last version of each observation is carried forward (LOCF) to +#' fill in data between recorded versions, and between the last recorded +#' update and the `versions_end`. One consequence is that the `DT` doesn't +#' have to contain a full snapshot of every version (although this generally +#' works), but can instead contain only the rows that are new or changed from +#' the previous version (see `compactify`, which does this automatically). +#' Currently, deletions must be represented as revising a row to a special +#' state (e.g., making the entries `NA` or including a special column that +#' flags the data as removed and performing some kind of post-processing), and +#' the archive is unaware of what this state is. Note that `NA`s *can* be +#' introduced by `epi_archive` methods for other reasons, e.g., in +#' [`epix_fill_through_version`] and [`epix_merge`], if requested, to +#' represent potential update data that we do not yet have access to; or in +#' [`epix_merge`] to represent the "value" of an observation before the +#' version in which it was first released, or if no version of that +#' observation appears in the archive data at all. +#' #' @section Metadata: #' The following pieces of metadata are included as fields in an `epi_archive` #' object: @@ -240,7 +235,8 @@ NULL #' value of `clobberable_versions_start` does not fully trust these empty #' updates, and assumes that any version `>= max(x$version)` could be #' clobbered.) If `nrow(x) == 0`, then this argument is mandatory. -#' @param compactify_tol double. the tolerance used to detect approximate equality for compactification +#' @param compactify_tol double. the tolerance used to detect approximate +#' equality for compactification #' @return An `epi_archive` object. #' #' @importFrom data.table as.data.table key setkeyv diff --git a/R/epi_df.R b/R/epi_df.R index 832d6be5..0b66750c 100644 --- a/R/epi_df.R +++ b/R/epi_df.R @@ -320,6 +320,7 @@ as_epi_df.tbl_ts <- function(x, as_of, other_keys = character(), ...) { #' @param x An object. #' @return `TRUE` if the object inherits from `epi_df`. #' +#' @rdname epi_df #' @export is_epi_df <- function(x) { inherits(x, "epi_df") diff --git a/R/epiprocess.R b/R/epiprocess.R index 5c76f882..94326df1 100644 --- a/R/epiprocess.R +++ b/R/epiprocess.R @@ -1,8 +1,8 @@ #' epiprocess: Tools for basic signal processing in epidemiology #' -#' This package introduces a common data structure for epidemiological data sets -#' measured over space and time, and offers associated utilities to perform -#' basic signal processing tasks. +#' This package introduces common data structures for epidemiological data sets +#' measured across locations and time, and offers associated utilities to +#' perform basic signal processing tasks. #' #' @importFrom checkmate assert assert_scalar assert_data_frame anyMissing #' assert_logical assert_list assert_character assert_class @@ -13,7 +13,9 @@ #' @importFrom rlang %||% #' @importFrom lifecycle deprecated #' @name epiprocess +#' @keywords internal "_PACKAGE" + utils::globalVariables(c( ".x", ".group_key", ".ref_time_value", "resid", "fitted", ".response", "geo_value", "time_value", diff --git a/R/methods-epi_df.R b/R/methods-epi_df.R index 901b9b32..8ecd36d1 100644 --- a/R/methods-epi_df.R +++ b/R/methods-epi_df.R @@ -89,6 +89,7 @@ print.epi_df <- function(x, ...) { #' @method summary epi_df #' @importFrom rlang .data #' @importFrom stats median +#' @rdname print.epi_df #' @export summary.epi_df <- function(object, ...) { cat("An `epi_df` x, with metadata:\n") @@ -123,7 +124,7 @@ summary.epi_df <- function(object, ...) { #' @return `x` with any metadata dropped and the `"epi_df"` class, if previously #' present, dropped #' -#' @noRd +#' @keywords internal decay_epi_df <- function(x) { attributes(x)$metadata <- NULL class(x) <- class(x)[class(x) != "epi_df"] @@ -140,6 +141,8 @@ decay_epi_df <- function(x) { # We'll implement `[` to allow either 1d or 2d. We'll also implement some other # methods where we want to (try to) maintain an `epi_df`. +#' dplyr_reconstruct +#' #' @param data tibble or `epi_df` (`dplyr` feeds in former, but we may #' directly feed in latter from our other methods) #' @param template `epi_df` template to use to restore @@ -147,7 +150,7 @@ decay_epi_df <- function(x) { #' @importFrom dplyr dplyr_reconstruct #' @importFrom cli cli_vec #' @export -#' @noRd +#' @keywords internal dplyr_reconstruct.epi_df <- function(data, template) { # Start from a reconstruction for the backing S3 classes; this ensures that we # keep any grouping that has been applied: diff --git a/R/outliers.R b/R/outliers.R index c2187de0..03b9c2c8 100644 --- a/R/outliers.R +++ b/R/outliers.R @@ -38,6 +38,7 @@ #' "stl", shorthand for `detect_outlr_stl()`, which detects outliers via an #' STL decomposition. #' +#' @rdname detect_outlr #' @export #' @importFrom dplyr select #' @examples @@ -152,6 +153,7 @@ detect_outlr <- function(x = seq_along(y), y, #' @template outlier-detection-options #' @template detect-outlr-return #' +#' @rdname detect_outlr #' @export #' @examples #' # Detect outliers based on a rolling median @@ -244,6 +246,7 @@ detect_outlr_rm <- function(x = seq_along(y), y, n = 21, #' The last set of arguments, `log_transform` through `replacement_multiplier`, #' are exactly as in `detect_outlr_rm()`. #' +#' @rdname detect_outlr #' @importFrom stats median #' @importFrom tidyselect starts_with #' @export diff --git a/R/slide.R b/R/slide.R index 8a59ffc4..6ff02bc0 100644 --- a/R/slide.R +++ b/R/slide.R @@ -893,8 +893,9 @@ epi_slide_opt <- function( #' #' @template opt-slide-details #' +#' @rdname epi_slide_opt #' @export -#' @seealso [`epi_slide`] [`epi_slide_opt`] [`epi_slide_sum`] +#' @seealso [`epi_slide`] #' @examples #' # slide a 7-day trailing average formula on cases #' jhu_csse_daily_subset %>% @@ -1007,8 +1008,9 @@ epi_slide_mean <- function( #' #' @template opt-slide-details #' +#' @rdname epi_slide_opt #' @export -#' @seealso [`epi_slide`] [`epi_slide_opt`] [`epi_slide_mean`] +#' @seealso [`epi_slide`] #' @examples #' # slide a 7-day trailing sum formula on cases #' jhu_csse_daily_subset %>% @@ -1074,7 +1076,7 @@ epi_slide_sum <- function( #' function (using `validate_slide_window_arg`). #' #' @importFrom checkmate assert_function -#' @noRd +#' @keywords internal full_date_seq <- function(x, before, after, time_type) { if (!time_type %in% c("day", "week", "yearmonth", "integer")) { cli_abort( diff --git a/R/utils.R b/R/utils.R index 066b374e..734140d9 100644 --- a/R/utils.R +++ b/R/utils.R @@ -182,7 +182,7 @@ format_tibble_row <- function(x, empty = "*none*") { #' @importFrom purrr map_lgl #' @importFrom utils tail #' -#' @noRd +#' @keywords internal assert_sufficient_f_args <- function(.f, ..., .ref_time_value_label) { mandatory_f_args_labels <- c("window data", "group key", .ref_time_value_label) n_mandatory_f_args <- length(mandatory_f_args_labels) @@ -670,6 +670,7 @@ upcase_snake_case <- function(vec) { #' the full list of potential substitutions for the `time_value` column name: #' `r time_column_names()` #' @export +#' @keywords internal time_column_names <- function() { substitutions <- c( "time_value", "date", "time", "datetime", "dateTime", "date_time", "target_date", @@ -686,6 +687,7 @@ time_column_names <- function() { #' the full list of potential substitutions for the `geo_value` column name: #' `r geo_column_names()` #' @export +#' @keywords internal geo_column_names <- function() { substitutions <- c( "geo_value", "geo_values", "geo_id", "geos", "location", "jurisdiction", "fips", "zip", @@ -702,6 +704,7 @@ geo_column_names <- function() { #' the full list of potential substitutions for the `version` column name: #' `r version_column_names()` #' @export +#' @keywords internal version_column_names <- function() { substitutions <- c( "version", "issue", "release" @@ -833,7 +836,7 @@ list2var <- function(x) { #' #' @importFrom lifecycle deprecated #' -#' @noRd +#' @keywords internal deprecated_quo_is_present <- function(quo) { if (!rlang::is_quosure(quo)) { cli_abort("`quo` must be a quosure; `enquo` the arg first", @@ -991,6 +994,7 @@ gcd_num <- function(dividends, ..., rrtol = 1e-6, pqlim = 1e6, irtol = 1e-6) { #' by adding `k * result` for an integer k, and such that there is no smaller #' `result` that can achieve this. #' +#' @keywords internal #' @export guess_period <- function(time_values, time_values_arg = rlang::caller_arg(time_values), ...) { UseMethod("guess_period") diff --git a/README.Rmd b/README.Rmd index 866fc537..2ff616ab 100644 --- a/README.Rmd +++ b/README.Rmd @@ -16,45 +16,39 @@ ggplot2::theme_set(ggplot2::theme_bw()) # epiprocess -## TODO: Condense these paragraphs - -The [`{epiprocess}`](https://cmu-delphi.github.io/epiprocess/) package works -with epidemiological time series data to provide situational -awareness, processing, and transformations in preparation for modeling, and -version-faithful model backtesting. It contains: - -- `epi_df`, a class for working with epidemiological time series data which -behaves like a tibble (and can be manipulated with -[`{dplyr}`](https://dplyr.tidyverse.org/)-esque "verbs") but with some -additional structure; -- `epi_archive`, a class for working with the version history of such time series data; -- sample epidemiological data in these formats; - -This package is provided by the Delphi group at Carnegie Mellon University. The -Delphi group provides many tools also hosts the Delphi Epidata API, which provides access to a wide -range of epidemiological data sets, including COVID-19 data, flu data, and more. -This package is designed to work seamlessly with the data in the Delphi Epidata -API, which can be accessed using the `epidatr` package. - -It is part of a broader suite of packages that includes -[`{epipredict}`](https://cmu-delphi.github.io/epipredict/), -[`{epidatr}`](https://cmu-delphi.github.io/epidatr/), -[`{rtestim}`](https://dajmcdon.github.io/rtestim/), and -[`{epidatasets}`](https://cmu-delphi.github.io/epidatasets/), for accessing, -analyzing, and forecasting epidemiological time series data. We have expanded -documentation and demonstrations for some of these packages available in an -online "book" format [here](https://cmu-delphi.github.io/delphi-tooling-book/). - -## Motivation - -[`{epiprocess}`](https://cmu-delphi.github.io/epiprocess/) and -[`{epipredict}`](https://cmu-delphi.github.io/epipredict/) are designed to lower -the barrier to entry and implementation cost for epidemiological time series -analysis and forecasting. Epidemiologists and forecasting groups repeatedly and -separately have had to rush to implement this type of functionality in a much -more ad hoc manner; we are trying to save such effort in the future by providing -well-documented, tested, and general packages that can be called for many common -tasks instead. +The `{epiprocess}` package works with epidemiological time series data and +provides tools to manage, analyze, and process the data in preparation for +modeling. It is designed to work in tandem with +[`{epipredict}`](https://cmu-delphi.github.io/epipredict/), which provides +pre-built epiforecasting models and as well as tools to build custom models. +Both packages are designed to lower the barrier to entry and implementation cost +for epidemiological time series analysis and forecasting. + +`{epiprocess}` contains: + +- `epi_df()` and `epi_archive()`, two data frame classes (that work like a +`{tibble}` with `{dplyr}` verbs) for working with epidemiological time +series data; +- signal processing tools building on these data structures such as + - `epi_slide()` for sliding window operations; + - `epix_slide()` for sliding window operations on archives; + - `growth_rate()` for computing growth rates; + - `detect_outlr()` for outlier detection; + - `epi_cor()` for computing correlations; + +If you are new to this set of tools, you may be interested learning through a +book format: [Introduction to Epidemiological +Forecasting](https://cmu-delphi.github.io/delphi-tooling-book/). + +You may also be interested in: + +- [`{epidatr}`](https://cmu-delphi.github.io/epidatr/), for accessing wide range +of epidemiological data sets, including COVID-19 data, flu data, and more. +- [`{rtestim}`](https://dajmcdon.github.io/rtestim/), a package for estimating +the time-varying reproduction number of an epidemic. + +This package is provided by the [Delphi group](https://delphi.cmu.edu/) at +Carnegie Mellon University. ## Installation @@ -133,7 +127,7 @@ edf %>% mutate(cases_growth = growth_rate(x = time_value, y = cases_cumulative, method = "rel_change", h = 7)) ``` -Detect outliers in the growth rate of the confirmed cumulative cases for each +Detect outliers in daily reported cases for each geo_value ```{r} edf %>% @@ -163,3 +157,4 @@ edf %>% epi_slide_mean(deaths_daily, .window_size = 7, na.rm = TRUE) %>% epi_cor(cases_daily, deaths_daily) ``` + diff --git a/README.md b/README.md index a5bfc6dc..665733d9 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,48 @@ - # epiprocess -## TODO: Condense these paragraphs - -The [`{epiprocess}`](https://cmu-delphi.github.io/epiprocess/) package -works with epidemiological time series data to provide situational -awareness, processing, and transformations in preparation for modeling, -and version-faithful model backtesting. It contains: - - - `epi_df`, a class for working with epidemiological time series data - which behaves like a tibble (and can be manipulated with - [`{dplyr}`](https://dplyr.tidyverse.org/)-esque “verbs”) but with - some additional structure; - - `epi_archive`, a class for working with the version history of such - time series data; - - sample epidemiological data in these formats; - -This package is provided by the Delphi group at Carnegie Mellon -University. The Delphi group provides many tools also hosts the Delphi -Epidata API, which provides access to a wide range of epidemiological -data sets, including COVID-19 data, flu data, and more. This package is -designed to work seamlessly with the data in the Delphi Epidata API, -which can be accessed using the `epidatr` package. - -It is part of a broader suite of packages that includes -[`{epipredict}`](https://cmu-delphi.github.io/epipredict/), -[`{epidatr}`](https://cmu-delphi.github.io/epidatr/), -[`{rtestim}`](https://dajmcdon.github.io/rtestim/), and -[`{epidatasets}`](https://cmu-delphi.github.io/epidatasets/), for -accessing, analyzing, and forecasting epidemiological time series data. -We have expanded documentation and demonstrations for some of these -packages available in an online “book” format -[here](https://cmu-delphi.github.io/delphi-tooling-book/). - -## Motivation - -[`{epiprocess}`](https://cmu-delphi.github.io/epiprocess/) and -[`{epipredict}`](https://cmu-delphi.github.io/epipredict/) are designed -to lower the barrier to entry and implementation cost for -epidemiological time series analysis and forecasting. Epidemiologists -and forecasting groups repeatedly and separately have had to rush to -implement this type of functionality in a much more ad hoc manner; we -are trying to save such effort in the future by providing -well-documented, tested, and general packages that can be called for -many common tasks instead. +The `{epiprocess}` package works with epidemiological time series data +and provides tools to manage, analyze, and process the data in +preparation for modeling. It is designed to work in tandem with +[`{epipredict}`](https://cmu-delphi.github.io/epipredict/), which +provides pre-built epiforecasting models and as well as tools to build +custom models. Both packages are designed to lower the barrier to entry +and implementation cost for epidemiological time series analysis and +forecasting. + +`{epiprocess}` contains: + +- `epi_df()` and `epi_archive()`, two data frame classes (that work + like a `{tibble}` with `{dplyr}` verbs) for working with + epidemiological time series data; +- signal processing tools building on these data structures such as + - `epi_slide()` for sliding window operations; + - `epix_slide()` for sliding window operations on archives; + - `growth_rate()` for computing growth rates; + - `detect_outlr()` for outlier detection; + - `epi_cor()` for computing correlations; + +If you are new to this set of tools, you may be interested learning +through a book format: [Introduction to Epidemiological +Forecasting](https://cmu-delphi.github.io/delphi-tooling-book/). + +You may also be interested in: + +- [`{epidatr}`](https://cmu-delphi.github.io/epidatr/), for accessing + wide range of epidemiological data sets, including COVID-19 data, + flu data, and more. +- [`{rtestim}`](https://dajmcdon.github.io/rtestim/), a package for + estimating the time-varying reproduction number of an epidemic. + +This package is provided by the [Delphi group](https://delphi.cmu.edu/) +at Carnegie Mellon University. ## Installation To install: -``` r +```r # Stable version pak::pkg_install("cmu-delphi/epiprocess@main") @@ -66,7 +57,7 @@ The package is not yet on CRAN. Once `epiprocess` and `epidatr` are installed, you can use the following code to get started: -``` r +```r library(epiprocess) library(epidatr) library(dplyr) @@ -77,7 +68,7 @@ Get COVID-19 confirmed cumulative case data from JHU CSSE for California, Florida, New York, and Texas, from March 1, 2020 to January 31, 2022 -``` r +```r df <- pub_covidcast( source = "jhu-csse", signals = "confirmed_cumulative_num", @@ -104,11 +95,11 @@ df #> # ℹ 2,798 more rows ``` -Convert the data to an epi\_df object and sort by geo\_value and -time\_value. You can work with the epi\_df object like a tibble using +Convert the data to an epi_df object and sort by geo_value and +time_value. You can work with the epi_df object like a tibble using dplyr -``` r +```r edf <- df %>% as_epi_df() %>% arrange_canonical() %>% @@ -118,8 +109,8 @@ edf #> An `epi_df` object, 2,808 x 4 with metadata: #> * geo_type = state #> * time_type = day -#> * as_of = 2024-10-04 22:33:55.95561 -#> +#> * as_of = 2024-10-10 18:09:02.911134 +#> #> # A tibble: 2,808 × 4 #> # Groups: geo_value [4] #> geo_value time_value cases_cumulative cases_daily @@ -137,9 +128,9 @@ edf #> # ℹ 2,798 more rows ``` -Autoplot the confirmed daily cases for each geo\_value +Autoplot the confirmed daily cases for each geo_value -``` r +```r edf %>% autoplot(cases_cumulative) ``` @@ -147,46 +138,46 @@ edf %>% Compute the 7 day moving average of the confirmed daily cases for each -geo\_value +geo_value -``` r +```r edf %>% group_by(geo_value) %>% epi_slide_mean(cases_daily, .window_size = 7, na.rm = TRUE) #> An `epi_df` object, 2,808 x 5 with metadata: #> * geo_type = state #> * time_type = day -#> * as_of = 2024-10-04 22:33:55.95561 -#> +#> * as_of = 2024-10-10 18:09:02.911134 +#> #> # A tibble: 2,808 × 5 #> # Groups: geo_value [4] #> geo_value time_value cases_cumulative cases_daily slide_value_cases_daily #> * -#> 1 ca 2020-03-01 19 19 19 -#> 2 ca 2020-03-02 23 4 11.5 +#> 1 ca 2020-03-01 19 19 19 +#> 2 ca 2020-03-02 23 4 11.5 #> 3 ca 2020-03-03 29 6 9.67 -#> 4 ca 2020-03-04 40 11 10 -#> 5 ca 2020-03-05 50 10 10 -#> 6 ca 2020-03-06 68 18 11.3 -#> 7 ca 2020-03-07 94 26 13.4 -#> 8 ca 2020-03-08 113 19 13.4 -#> 9 ca 2020-03-09 136 23 16.1 -#> 10 ca 2020-03-10 158 22 18.4 +#> 4 ca 2020-03-04 40 11 10 +#> 5 ca 2020-03-05 50 10 10 +#> 6 ca 2020-03-06 68 18 11.3 +#> 7 ca 2020-03-07 94 26 13.4 +#> 8 ca 2020-03-08 113 19 13.4 +#> 9 ca 2020-03-09 136 23 16.1 +#> 10 ca 2020-03-10 158 22 18.4 #> # ℹ 2,798 more rows ``` Compute the growth rate of the confirmed cumulative cases for each -geo\_value +geo_value -``` r +```r edf %>% group_by(geo_value) %>% mutate(cases_growth = growth_rate(x = time_value, y = cases_cumulative, method = "rel_change", h = 7)) #> An `epi_df` object, 2,808 x 5 with metadata: #> * geo_type = state #> * time_type = day -#> * as_of = 2024-10-04 22:33:55.95561 -#> +#> * as_of = 2024-10-10 18:09:02.911134 +#> #> # A tibble: 2,808 × 5 #> # Groups: geo_value [4] #> geo_value time_value cases_cumulative cases_daily cases_growth @@ -204,10 +195,9 @@ edf %>% #> # ℹ 2,798 more rows ``` -Detect outliers in the growth rate of the confirmed cumulative cases for -each +Detect outliers in daily reported cases for each geo_value -``` r +```r edf %>% group_by(geo_value) %>% mutate(outlier_info = detect_outlr(x = time_value, y = cases_daily)) %>% @@ -231,8 +221,8 @@ edf %>% #> An `epi_df` object, 2,808 x 5 with metadata: #> * geo_type = state #> * time_type = day -#> * as_of = 2024-10-04 22:33:55.95561 -#> +#> * as_of = 2024-10-10 18:09:02.911134 +#> #> # A tibble: 2,808 × 5 #> geo_value time_value cases_cumulative cases_daily outlier_info$rm_geo_value #> * @@ -252,11 +242,11 @@ edf %>% #> # $combined_replacement ``` -Add a column to the epi\_df object with the daily deaths for each -geo\_value and compute the correlations between cases and deaths for -each geo\_value +Add a column to the epi_df object with the daily deaths for each +geo_value and compute the correlations between cases and deaths for +each geo_value -``` r +```r df <- pub_covidcast( source = "jhu-csse", signals = "deaths_incidence_num", diff --git a/_pkgdown.yml b/_pkgdown.yml index 1bc7f795..15b44afa 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -42,12 +42,14 @@ articles: navbar: ~ contents: - epiprocess - - slide + - epi_df + - epi_archive + - outliers - growth_rate - correlation - - aggregation - - outliers - - archive + + - title: Developer + contents: - compactify repo: @@ -61,48 +63,48 @@ reference: - title: "`epi_df` basics" desc: Details on `epi_df` format, and basic functionality. - contents: - - matches("epi_df") - - matches("column_names") - - title: "`epi_*()` functions" + - epi_df + - print.epi_df + - group_epi_df + - autoplot.epi_df + + - title: "`epi_df` manipulation" desc: Functions that act on `epi_df` objects. - contents: + - complete.epi_df - epi_slide - epi_slide_mean - - epi_slide_sum - epi_slide_opt + - epi_slide_sum + - sum_groups_epi_df - epi_cor - - title: Vector functions - desc: Functions that act directly on signal variables. - - contents: - - growth_rate - detect_outlr - - detect_outlr_rm - - detect_outlr_stl + - growth_rate + - as_tibble.epi_df + - as_tsibble.epi_df + - title: "`epi_archive` basics" desc: Details on `epi_archive`, and basic functionality. - contents: - - matches("archive") - - revision_summary - - title: "`epix_*()` functions" - desc: Functions that act on an `epi_archive` and/or `grouped_epi_archive` object. - - contents: - - starts_with("epix") + - epi_archive + - print.epi_archive + - clone - group_by.epi_archive - - title: Example data - - contents: - - archive_cases_dv_subset - - incidence_num_outlier_example - - contains("jhu_csse") - - title: Basic automatic plotting + + - title: "`epi_archive` manipulation" + desc: Functions that act on `epi_archive` objects. - contents: - - autoplot.epi_df - - title: Advanced internals + - epix_as_of + - epix_slide + - epix_merge + - revision_summary + - epix_fill_through_version + - epix_truncate_versions_after + + - title: Example data - contents: - - compactify + - has_keyword("datasets") + - title: internal - contents: - - epiprocess - - max_version_with_row_in - - next_after - - guess_period - - key_colnames + - starts_with("internal") diff --git a/man/assert_sufficient_f_args.Rd b/man/assert_sufficient_f_args.Rd new file mode 100644 index 00000000..a0c2cbb8 --- /dev/null +++ b/man/assert_sufficient_f_args.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{assert_sufficient_f_args} +\alias{assert_sufficient_f_args} +\title{Assert that a sliding computation function takes enough args} +\usage{ +assert_sufficient_f_args(.f, ..., .ref_time_value_label) +} +\arguments{ +\item{...}{Dots that will be forwarded to \code{f} from the dots of \code{epi_slide} or +\code{epix_slide}.} + +\item{.ref_time_value_label}{String; how to describe/label the \code{ref_time_value} in +error messages; e.g., "reference time value" or "version".} + +\item{f}{Function; specifies a computation to slide over an \code{epi_df} or +\code{epi_archive} in \code{epi_slide} or \code{epix_slide}.} +} +\description{ +Assert that a sliding computation function takes enough args +} +\keyword{internal} diff --git a/man/compactify.Rd b/man/compactify.Rd deleted file mode 100644 index 2f210315..00000000 --- a/man/compactify.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/archive.R -\name{compactify} -\alias{compactify} -\title{Compactify} -\description{ -This section describes the internals of how compactification works in an -\code{epi_archive()}. Compactification can potentially improve code speed or -memory usage, depending on your data. -} -\details{ -In general, the last version of each observation is carried forward (LOCF) to -fill in data between recorded versions, and between the last recorded -update and the \code{versions_end}. One consequence is that the \code{DT} doesn't -have to contain a full snapshot of every version (although this generally -works), but can instead contain only the rows that are new or changed from -the previous version (see \code{compactify}, which does this automatically). -Currently, deletions must be represented as revising a row to a special -state (e.g., making the entries \code{NA} or including a special column that -flags the data as removed and performing some kind of post-processing), and -the archive is unaware of what this state is. Note that \code{NA}s \emph{can} be -introduced by \code{epi_archive} methods for other reasons, e.g., in -\code{\link{epix_fill_through_version}} and \code{\link{epix_merge}}, if requested, to -represent potential update data that we do not yet have access to; or in -\code{\link{epix_merge}} to represent the "value" of an observation before the -version in which it was first released, or if no version of that -observation appears in the archive data at all. -} diff --git a/man/decay_epi_df.Rd b/man/decay_epi_df.Rd new file mode 100644 index 00000000..d581db08 --- /dev/null +++ b/man/decay_epi_df.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/methods-epi_df.R +\name{decay_epi_df} +\alias{decay_epi_df} +\title{Drop any \code{epi_df} metadata and class on a data frame} +\usage{ +decay_epi_df(x) +} +\arguments{ +\item{x}{an \code{epi_df} or other data frame} +} +\value{ +\code{x} with any metadata dropped and the \code{"epi_df"} class, if previously +present, dropped +} +\description{ +Useful in implementing \code{?dplyr_extending} when manipulations cause invariants +of \code{epi_df}s to be violated and we need to return some other class. Note that +this will maintain any grouping (keeping the \code{grouped_df} class and +associated attributes, if present). +} +\keyword{internal} diff --git a/man/deprecated_quo_is_present.Rd b/man/deprecated_quo_is_present.Rd new file mode 100644 index 00000000..add87529 --- /dev/null +++ b/man/deprecated_quo_is_present.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{deprecated_quo_is_present} +\alias{deprecated_quo_is_present} +\title{\code{\link[lifecycle:deprecated]{lifecycle::is_present}} for enquosed deprecated NSE arg} +\usage{ +deprecated_quo_is_present(quo) +} +\arguments{ +\item{quo}{\link[rlang:enquo]{enquosed} arg} +} +\value{ +bool; was \code{quo} "present", or did it look like a missing quosure or +have an expr that looked like \code{deprecated()} or \code{lifecycle::deprecated()}? +} +\description{ +\code{\link[lifecycle:deprecated]{lifecycle::is_present}} is designed for use with args that undergo standard +evaluation, rather than non-standard evaluation (NSE). This function is +designed to fulfill a similar purpose, but for args we have +\link[rlang:enquo]{enquosed} in preparation for NSE. +} +\examples{ + +fn <- function(x = deprecated()) { + deprecated_quo_is_present(rlang::enquo(x)) +} + +fn() # FALSE +fn(.data$something) # TRUE + +# Functions that wrap `fn` should forward the NSE arg to `fn` using +# [`{{ arg }}`][rlang::embrace-operator] (or, if they are working from an +# argument that has already been defused into a quosure, `!!quo`). (This is +# already how NSE arguments that will be enquosed should be forwarded.) + +wrapper1 <- function(x = deprecated()) fn({{ x }}) +wrapper2 <- function(x = lifecycle::deprecated()) fn({{ x }}) +wrapper3 <- function(x) fn({{ x }}) +wrapper4 <- function(x) fn(!!rlang::enquo(x)) + +wrapper1() # FALSE +wrapper2() # FALSE +wrapper3() # FALSE +wrapper4() # FALSE + +# More advanced: wrapper that receives an already-enquosed arg: + +inner_wrapper <- function(quo) fn(!!quo) +outer_wrapper1 <- function(x = deprecated()) inner_wrapper(rlang::enquo(x)) + +outer_wrapper1() # FALSE + +# Improper argument forwarding from a wrapper function will cause this +# function to produce incorrect results. +bad_wrapper1 <- function(x) fn(x) +bad_wrapper1() # TRUE, bad + +} +\keyword{internal} diff --git a/man/dplyr_reconstruct.epi_df.Rd b/man/dplyr_reconstruct.epi_df.Rd new file mode 100644 index 00000000..36557154 --- /dev/null +++ b/man/dplyr_reconstruct.epi_df.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/methods-epi_df.R +\name{dplyr_reconstruct.epi_df} +\alias{dplyr_reconstruct.epi_df} +\title{dplyr_reconstruct} +\usage{ +\method{dplyr_reconstruct}{epi_df}(data, template) +} +\arguments{ +\item{data}{tibble or \code{epi_df} (\code{dplyr} feeds in former, but we may +directly feed in latter from our other methods)} + +\item{template}{\code{epi_df} template to use to restore} +} +\value{ +\code{epi_df} or degrade into \code{tbl_df} +} +\description{ +dplyr_reconstruct +} +\keyword{internal} diff --git a/man/epi_archive.Rd b/man/epi_archive.Rd index 84ea039d..6048628e 100644 --- a/man/epi_archive.Rd +++ b/man/epi_archive.Rd @@ -84,7 +84,8 @@ value of \code{clobberable_versions_start} does not fully trust these empty updates, and assumes that any version \verb{>= max(x$version)} could be clobbered.) If \code{nrow(x) == 0}, then this argument is mandatory.} -\item{compactify_tol}{double. the tolerance used to detect approximate equality for compactification} +\item{compactify_tol}{double. the tolerance used to detect approximate +equality for compactification} \item{.versions_end}{location based versions_end, used to avoid prefix \code{version = issue} from being assigned to \code{versions_end} instead of being @@ -124,6 +125,30 @@ as well as any others (these can be specified when instantiating the on \code{DT} directly). Note that there can only be a single row per unique combination of key variables. } +\section{Compactify}{ + +This section describes the internals of how compactification works in an +\code{epi_archive()}. Compactification can potentially improve code speed or +memory usage, depending on your data. + +In general, the last version of each observation is carried forward (LOCF) to +fill in data between recorded versions, and between the last recorded +update and the \code{versions_end}. One consequence is that the \code{DT} doesn't +have to contain a full snapshot of every version (although this generally +works), but can instead contain only the rows that are new or changed from +the previous version (see \code{compactify}, which does this automatically). +Currently, deletions must be represented as revising a row to a special +state (e.g., making the entries \code{NA} or including a special column that +flags the data as removed and performing some kind of post-processing), and +the archive is unaware of what this state is. Note that \code{NA}s \emph{can} be +introduced by \code{epi_archive} methods for other reasons, e.g., in +\code{\link{epix_fill_through_version}} and \code{\link{epix_merge}}, if requested, to +represent potential update data that we do not yet have access to; or in +\code{\link{epix_merge}} to represent the "value" of an observation before the +version in which it was first released, or if no version of that +observation appears in the archive data at all. +} + \section{Metadata}{ The following pieces of metadata are included as fields in an \code{epi_archive} diff --git a/man/epi_df.Rd b/man/epi_df.Rd index 73460b4a..770bccb3 100644 --- a/man/epi_df.Rd +++ b/man/epi_df.Rd @@ -8,6 +8,7 @@ \alias{as_epi_df.tbl_ts} \alias{new_epi_df} \alias{epi_df} +\alias{is_epi_df} \title{\code{epi_df} object} \usage{ as_epi_df(x, ...) @@ -35,10 +36,11 @@ new_epi_df( other_keys = character(), ... ) + +is_epi_df(x) } \arguments{ -\item{x}{An \code{epi_df}, \code{data.frame}, \link[tibble:tibble]{tibble::tibble}, or \link[tsibble:tsibble]{tsibble::tsibble} -to be converted} +\item{x}{An object.} \item{...}{Additional arguments passed to methods.} @@ -64,6 +66,8 @@ as a character vector here (typical examples are "age" or sub-geographies).} } \value{ An \code{epi_df} object. + +\code{TRUE} if the object inherits from \code{epi_df}. } \description{ One of the two main data structures for storing time series in \code{epiprocess}. diff --git a/man/epi_slide_mean.Rd b/man/epi_slide_mean.Rd deleted file mode 100644 index 75b83b10..00000000 --- a/man/epi_slide_mean.Rd +++ /dev/null @@ -1,166 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/slide.R -\name{epi_slide_mean} -\alias{epi_slide_mean} -\title{Optimized slide function for performing rolling averages on an \code{epi_df} object} -\usage{ -epi_slide_mean( - .x, - .col_names, - ..., - .window_size = NULL, - .align = c("right", "center", "left"), - .ref_time_values = NULL, - .all_rows = FALSE -) -} -\arguments{ -\item{.x}{The \code{epi_df} object under consideration, \link[dplyr:group_by]{grouped} -or ungrouped. If ungrouped, all data in \code{.x} will be treated as part of a -single data group.} - -\item{.col_names}{<\code{\link[=dplyr_tidy_select]{tidy-select}}> An unquoted column -name(e.g., \code{cases}), multiple column names (e.g., \code{c(cases, deaths)}), -\link[tidyselect:language]{other tidy-select expression}, or a vector of -characters (e.g. \code{c("cases", "deaths")}). Variable names can be used as if -they were positions in the data frame, so expressions like \code{x:y} can be -used to select a range of variables. - -The tidy-selection renaming interface is not supported, and cannot be used -to provide output column names; if you want to customize the output column -names, use \code{\link[dplyr:rename]{dplyr::rename}} after the slide.} - -\item{...}{Additional arguments to pass to the slide computation \code{.f}, for -example, \code{algo} or \code{na.rm} in data.table functions. You don't need to -specify \code{.x}, \code{.window_size}, or \code{.align} (or \code{before}/\code{after} for slider -functions).} - -\item{.window_size}{The size of the sliding window. By default, this is 1, -meaning that only the current ref_time_value is included. The accepted values -here depend on the \code{time_value} column: -\itemize{ -\item if time_type is Date and the cadence is daily, then \code{.window_size} can be -an integer (which will be interpreted in units of days) or a difftime -with units "days" -\item if time_type is Date and the cadence is weekly, then \code{.window_size} must -be a difftime with units "weeks" -\item if time_type is an yearmonth or integer, then \code{.window_size} must be an -integer -}} - -\item{.align}{The alignment of the sliding window. If \code{right} (default), then -the window has its end at the reference time; if \code{center}, then the window is -centered at the reference time; if \code{left}, then the window has its start at -the reference time. If the alignment is \code{center} and the window size is odd, -then the window will have floor(window_size/2) points before and after the -reference time. If the window size is even, then the window will be -asymmetric and have one less value on the right side of the reference time -(assuming time increases from left to right).} - -\item{.ref_time_values}{Time values for sliding computations, meaning, each -element of this vector serves as the reference time point for one sliding -window. If missing, then this will be set to all unique time values in the -underlying data table, by default.} - -\item{.all_rows}{If \code{.all_rows = TRUE}, then all rows of \code{.x} will be kept in -the output even with \code{.ref_time_values} provided, with some type of missing -value marker for the slide computation output column(s) for \code{time_value}s -outside \code{.ref_time_values}; otherwise, there will be one row for each row in -\code{.x} that had a \code{time_value} in \code{.ref_time_values}. Default is \code{FALSE}. The -missing value marker is the result of \code{vctrs::vec_cast}ing \code{NA} to the type -of the slide computation output.} -} -\value{ -An \code{epi_df} object given by appending one or more new columns to \code{.x}, -named according to the \code{.new_col_name} argument. -} -\description{ -Slides an n-timestep mean over variables in an \code{epi_df} object. See the \href{https://cmu-delphi.github.io/epiprocess/articles/slide.html}{slide vignette} for -examples. -} -\details{ -Wrapper around \code{epi_slide_opt} with \code{.f = datatable::frollmean}. - -To "slide" means to apply a function or formula over a rolling -window. The \code{.window_size} arg determines the width of the window -(including the reference time) and the \code{.align} arg governs how the window -is aligned (see below for examples). The \code{.ref_time_values} arg controls -which time values to consider for the slide and \code{.all_rows} allows you to -keep NAs around. - -\verb{epi_slide_*()} does not require a complete window (such as on the left -boundary of the dataset) and will attempt to perform the computation -anyway. The issue of what to do with partial computations (those run on -incomplete windows) is therefore left up to the user, either through the -specified function or formula \code{f}, or through post-processing. - -Let's look at some window examples, assuming that the reference time value -is "tv". With .align = "right" and .window_size = 3, the window will be: - -time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv - 2, tv - 1, tv - -With .align = "center" and .window_size = 3, the window will be: - -time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv - 1, tv, tv + 1 - -With .align = "center" and .window_size = 4, the window will be: - -time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv - 2, tv - 1, tv, tv + 1 - -With .align = "left" and .window_size = 3, the window will be: - -time_values: ttv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv, tv + 1, tv + 2 -} -\examples{ -# slide a 7-day trailing average formula on cases -jhu_csse_daily_subset \%>\% - group_by(geo_value) \%>\% - epi_slide_mean(cases, .window_size = 7) \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() - -# slide a 7-day trailing average formula on cases. Adjust `frollmean` settings for speed -# and accuracy, and to allow partially-missing windows. -jhu_csse_daily_subset \%>\% - group_by(geo_value) \%>\% - epi_slide_mean( - cases, - .window_size = 7, - # `frollmean` options - na.rm = TRUE, algo = "exact", hasNA = TRUE - ) \%>\% - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() - -# slide a 7-day leading average -jhu_csse_daily_subset \%>\% - group_by(geo_value) \%>\% - epi_slide_mean(cases, .window_size = 7, .align = "right") \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() - -# slide a 7-day center-aligned average -jhu_csse_daily_subset \%>\% - group_by(geo_value) \%>\% - epi_slide_mean(cases, .window_size = 7, .align = "center") \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() - -# slide a 14-day center-aligned average -jhu_csse_daily_subset \%>\% - group_by(geo_value) \%>\% - epi_slide_mean(cases, .window_size = 14, .align = "center") \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_14dav = slide_value_cases) \%>\% - ungroup() -} -\seealso{ -\code{\link{epi_slide}} \code{\link{epi_slide_opt}} \code{\link{epi_slide_sum}} -} diff --git a/man/epi_slide_opt.Rd b/man/epi_slide_opt.Rd index 24b813f0..cb8453c9 100644 --- a/man/epi_slide_opt.Rd +++ b/man/epi_slide_opt.Rd @@ -2,6 +2,8 @@ % Please edit documentation in R/slide.R \name{epi_slide_opt} \alias{epi_slide_opt} +\alias{epi_slide_mean} +\alias{epi_slide_sum} \title{Optimized slide function for performing common rolling computations on an \code{epi_df} object} \usage{ @@ -15,6 +17,26 @@ epi_slide_opt( .ref_time_values = NULL, .all_rows = FALSE ) + +epi_slide_mean( + .x, + .col_names, + ..., + .window_size = NULL, + .align = c("right", "center", "left"), + .ref_time_values = NULL, + .all_rows = FALSE +) + +epi_slide_sum( + .x, + .col_names, + ..., + .window_size = NULL, + .align = c("right", "center", "left"), + .ref_time_values = NULL, + .all_rows = FALSE +) } \arguments{ \item{.x}{The \code{epi_df} object under consideration, \link[dplyr:group_by]{grouped} @@ -86,6 +108,12 @@ missing value marker is the result of \code{vctrs::vec_cast}ing \code{NA} to the of the slide computation output.} } \value{ +An \code{epi_df} object given by appending one or more new columns to \code{.x}, +named according to the \code{.new_col_name} argument. + +An \code{epi_df} object given by appending one or more new columns to \code{.x}, +named according to the \code{.new_col_name} argument. + An \code{epi_df} object given by appending one or more new columns to \code{.x}, named according to the \code{.new_col_name} argument. } @@ -94,6 +122,12 @@ Slides an n-timestep \link[data.table:froll]{data.table::froll} or \link[slider: over variables in an \code{epi_df} object. See the \href{https://cmu-delphi.github.io/epiprocess/articles/slide.html}{slide vignette} for examples. + +Slides an n-timestep mean over variables in an \code{epi_df} object. See the \href{https://cmu-delphi.github.io/epiprocess/articles/slide.html}{slide vignette} for +examples. + +Slides an n-timestep sum over variables in an \code{epi_df} object. See the \href{https://cmu-delphi.github.io/epiprocess/articles/slide.html}{slide vignette} for +examples. } \details{ To "slide" means to apply a function or formula over a rolling @@ -127,6 +161,78 @@ window: tv - 2, tv - 1, tv, tv + 1 With .align = "left" and .window_size = 3, the window will be: +time_values: ttv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv, tv + 1, tv + 2 + +Wrapper around \code{epi_slide_opt} with \code{.f = datatable::frollmean}. + +To "slide" means to apply a function or formula over a rolling +window. The \code{.window_size} arg determines the width of the window +(including the reference time) and the \code{.align} arg governs how the window +is aligned (see below for examples). The \code{.ref_time_values} arg controls +which time values to consider for the slide and \code{.all_rows} allows you to +keep NAs around. + +\verb{epi_slide_*()} does not require a complete window (such as on the left +boundary of the dataset) and will attempt to perform the computation +anyway. The issue of what to do with partial computations (those run on +incomplete windows) is therefore left up to the user, either through the +specified function or formula \code{f}, or through post-processing. + +Let's look at some window examples, assuming that the reference time value +is "tv". With .align = "right" and .window_size = 3, the window will be: + +time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv - 2, tv - 1, tv + +With .align = "center" and .window_size = 3, the window will be: + +time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv - 1, tv, tv + 1 + +With .align = "center" and .window_size = 4, the window will be: + +time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv - 2, tv - 1, tv, tv + 1 + +With .align = "left" and .window_size = 3, the window will be: + +time_values: ttv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv, tv + 1, tv + 2 + +Wrapper around \code{epi_slide_opt} with \code{.f = datatable::frollsum}. + +To "slide" means to apply a function or formula over a rolling +window. The \code{.window_size} arg determines the width of the window +(including the reference time) and the \code{.align} arg governs how the window +is aligned (see below for examples). The \code{.ref_time_values} arg controls +which time values to consider for the slide and \code{.all_rows} allows you to +keep NAs around. + +\verb{epi_slide_*()} does not require a complete window (such as on the left +boundary of the dataset) and will attempt to perform the computation +anyway. The issue of what to do with partial computations (those run on +incomplete windows) is therefore left up to the user, either through the +specified function or formula \code{f}, or through post-processing. + +Let's look at some window examples, assuming that the reference time value +is "tv". With .align = "right" and .window_size = 3, the window will be: + +time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv - 2, tv - 1, tv + +With .align = "center" and .window_size = 3, the window will be: + +time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv - 1, tv, tv + 1 + +With .align = "center" and .window_size = 4, the window will be: + +time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 +window: tv - 2, tv - 1, tv, tv + 1 + +With .align = "left" and .window_size = 3, the window will be: + time_values: ttv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 window: tv, tv + 1, tv + 2 } @@ -176,7 +282,62 @@ jhu_csse_daily_subset \%>\% # Remove a nonessential var. to ensure new col is printed dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% ungroup() +# slide a 7-day trailing average formula on cases +jhu_csse_daily_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_mean(cases, .window_size = 7) \%>\% + # Remove a nonessential var. to ensure new col is printed + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% + ungroup() + +# slide a 7-day trailing average formula on cases. Adjust `frollmean` settings for speed +# and accuracy, and to allow partially-missing windows. +jhu_csse_daily_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_mean( + cases, + .window_size = 7, + # `frollmean` options + na.rm = TRUE, algo = "exact", hasNA = TRUE + ) \%>\% + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% + ungroup() + +# slide a 7-day leading average +jhu_csse_daily_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_mean(cases, .window_size = 7, .align = "right") \%>\% + # Remove a nonessential var. to ensure new col is printed + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% + ungroup() + +# slide a 7-day center-aligned average +jhu_csse_daily_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_mean(cases, .window_size = 7, .align = "center") \%>\% + # Remove a nonessential var. to ensure new col is printed + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% + ungroup() + +# slide a 14-day center-aligned average +jhu_csse_daily_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_mean(cases, .window_size = 14, .align = "center") \%>\% + # Remove a nonessential var. to ensure new col is printed + dplyr::select(geo_value, time_value, cases, cases_14dav = slide_value_cases) \%>\% + ungroup() +# slide a 7-day trailing sum formula on cases +jhu_csse_daily_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_sum(cases, .window_size = 7) \%>\% + # Remove a nonessential var. to ensure new col is printed + dplyr::select(geo_value, time_value, cases, cases_7dsum = slide_value_cases) \%>\% + ungroup() } \seealso{ \code{\link{epi_slide}} \code{\link{epi_slide_mean}} \code{\link{epi_slide_sum}} + +\code{\link{epi_slide}} + +\code{\link{epi_slide}} } diff --git a/man/epi_slide_sum.Rd b/man/epi_slide_sum.Rd deleted file mode 100644 index 2cf05cca..00000000 --- a/man/epi_slide_sum.Rd +++ /dev/null @@ -1,129 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/slide.R -\name{epi_slide_sum} -\alias{epi_slide_sum} -\title{Optimized slide function for performing rolling sums on an \code{epi_df} object} -\usage{ -epi_slide_sum( - .x, - .col_names, - ..., - .window_size = NULL, - .align = c("right", "center", "left"), - .ref_time_values = NULL, - .all_rows = FALSE -) -} -\arguments{ -\item{.x}{The \code{epi_df} object under consideration, \link[dplyr:group_by]{grouped} -or ungrouped. If ungrouped, all data in \code{.x} will be treated as part of a -single data group.} - -\item{.col_names}{<\code{\link[=dplyr_tidy_select]{tidy-select}}> An unquoted column -name(e.g., \code{cases}), multiple column names (e.g., \code{c(cases, deaths)}), -\link[tidyselect:language]{other tidy-select expression}, or a vector of -characters (e.g. \code{c("cases", "deaths")}). Variable names can be used as if -they were positions in the data frame, so expressions like \code{x:y} can be -used to select a range of variables. - -The tidy-selection renaming interface is not supported, and cannot be used -to provide output column names; if you want to customize the output column -names, use \code{\link[dplyr:rename]{dplyr::rename}} after the slide.} - -\item{...}{Additional arguments to pass to the slide computation \code{.f}, for -example, \code{algo} or \code{na.rm} in data.table functions. You don't need to -specify \code{.x}, \code{.window_size}, or \code{.align} (or \code{before}/\code{after} for slider -functions).} - -\item{.window_size}{The size of the sliding window. By default, this is 1, -meaning that only the current ref_time_value is included. The accepted values -here depend on the \code{time_value} column: -\itemize{ -\item if time_type is Date and the cadence is daily, then \code{.window_size} can be -an integer (which will be interpreted in units of days) or a difftime -with units "days" -\item if time_type is Date and the cadence is weekly, then \code{.window_size} must -be a difftime with units "weeks" -\item if time_type is an yearmonth or integer, then \code{.window_size} must be an -integer -}} - -\item{.align}{The alignment of the sliding window. If \code{right} (default), then -the window has its end at the reference time; if \code{center}, then the window is -centered at the reference time; if \code{left}, then the window has its start at -the reference time. If the alignment is \code{center} and the window size is odd, -then the window will have floor(window_size/2) points before and after the -reference time. If the window size is even, then the window will be -asymmetric and have one less value on the right side of the reference time -(assuming time increases from left to right).} - -\item{.ref_time_values}{Time values for sliding computations, meaning, each -element of this vector serves as the reference time point for one sliding -window. If missing, then this will be set to all unique time values in the -underlying data table, by default.} - -\item{.all_rows}{If \code{.all_rows = TRUE}, then all rows of \code{.x} will be kept in -the output even with \code{.ref_time_values} provided, with some type of missing -value marker for the slide computation output column(s) for \code{time_value}s -outside \code{.ref_time_values}; otherwise, there will be one row for each row in -\code{.x} that had a \code{time_value} in \code{.ref_time_values}. Default is \code{FALSE}. The -missing value marker is the result of \code{vctrs::vec_cast}ing \code{NA} to the type -of the slide computation output.} -} -\value{ -An \code{epi_df} object given by appending one or more new columns to \code{.x}, -named according to the \code{.new_col_name} argument. -} -\description{ -Slides an n-timestep sum over variables in an \code{epi_df} object. See the \href{https://cmu-delphi.github.io/epiprocess/articles/slide.html}{slide vignette} for -examples. -} -\details{ -Wrapper around \code{epi_slide_opt} with \code{.f = datatable::frollsum}. - -To "slide" means to apply a function or formula over a rolling -window. The \code{.window_size} arg determines the width of the window -(including the reference time) and the \code{.align} arg governs how the window -is aligned (see below for examples). The \code{.ref_time_values} arg controls -which time values to consider for the slide and \code{.all_rows} allows you to -keep NAs around. - -\verb{epi_slide_*()} does not require a complete window (such as on the left -boundary of the dataset) and will attempt to perform the computation -anyway. The issue of what to do with partial computations (those run on -incomplete windows) is therefore left up to the user, either through the -specified function or formula \code{f}, or through post-processing. - -Let's look at some window examples, assuming that the reference time value -is "tv". With .align = "right" and .window_size = 3, the window will be: - -time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv - 2, tv - 1, tv - -With .align = "center" and .window_size = 3, the window will be: - -time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv - 1, tv, tv + 1 - -With .align = "center" and .window_size = 4, the window will be: - -time_values: tv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv - 2, tv - 1, tv, tv + 1 - -With .align = "left" and .window_size = 3, the window will be: - -time_values: ttv - 3, tv - 2, tv - 1, tv, tv + 1, tv + 2, tv + 3 -window: tv, tv + 1, tv + 2 -} -\examples{ -# slide a 7-day trailing sum formula on cases -jhu_csse_daily_subset \%>\% - group_by(geo_value) \%>\% - epi_slide_sum(cases, .window_size = 7) \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_7dsum = slide_value_cases) \%>\% - ungroup() -} -\seealso{ -\code{\link{epi_slide}} \code{\link{epi_slide_opt}} \code{\link{epi_slide_mean}} -} diff --git a/man/epiprocess.Rd b/man/epiprocess.Rd index f6345cbe..f275485c 100644 --- a/man/epiprocess.Rd +++ b/man/epiprocess.Rd @@ -43,3 +43,4 @@ Other contributors: } } +\keyword{internal} diff --git a/man/figures/README-unnamed-chunk-6-1.png b/man/figures/README-unnamed-chunk-6-1.png index b435c65146d50812909fca5fd4781c6ac7e1ff3d..4e301e97a7a819bf6a17af49577e22ef4b12b412 100644 GIT binary patch literal 37316 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

r8)zDg%BP-vgMmn;@bXwK@I;vSpfhE!5j<7mj~o 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�{COu#y?Zy;^KhZ zXji8{>xPE`ITjCBgod=DBL4Hh=;&yWU*`mSKg@RU^lrT`5%dgR2d$aGkU@YBh$Z7% z@y;Iz6DK7l0jAPUn64bbd^a`>d;_f=P^oyiBCbz28RKO^;cHui`e$U@0ccqpU%iNie37@vjTbOADq?+*~Z)0z7N-%+@NS46xZt?dn-+zpY*XzPEI~4&w)GX z+WsK?8%!@`_|^YRv*O5?MRiThSn6%ihk8rI6Dz{O(y~x8`xAR7s3GK<93Ka|u`czE z$PHC9Gdhv0b8~aANTj2l%6OjEVac57X*hJ3+RY^|rsOX>>h z4Onp4&DOl3pb*U-DKTjF{2PKjz~V7P`Le_cNFKN)YJ)c0KwpdBrj(>?f8eR?6cDoT z@bQICx~YKL-;p7~$kJnOPkqDd;3_VgHCvmD+W}whsqR@+r1{T1CZJhOxuc`wndd#| z>;>A|*zD@9WJd{kZl=1))4T&Ex_<&uf%I95;YpeVht}_t;Fy>gAe{Z^Qg%=ZE(cnE zOifJz`EP5`-W#1ctIf>bvCOVVr4*gmLpS-}w{j7k>%KhmgzLkIfKEu`%v?nz(GTN2p(p~~eGC-}@BJq5i z5)7v>F>RtQm>{M@%yq)W<@M!-5tY60x1~nn%IUk~E;7*iO`bVXw^Sok+;KCMd^S!a zP8`Z&m<^h)eAOueKoe|k$2I9B`H5xg zIr^%GY2tr(W`{|y#%I72=1w0bL5V%OfZOf!^Z&^t1Ux*-XgbjIlZS_pLQPruJ@H{? z(rX~V%WB+-rVAHj^s?kR#b1XxK&b^Kvx^nQv)Wa;VDSF`Yw$-tY_5|?xcsd~l(bFI z$5a;UFW~bHyhT$OBQyq#Tp->D^wX0DHh@?f(7pzD&DFX#f6lE+&dM51-2>F|cs?|f zlzbhC#9HBr%(G0}^hm31u4{a_<@8#0a?y;mvPC5JAO;)YF`WNBnCszKeQp&Ejf=Hd zr3JzDuiOiSe9i?U7I)xpoEI-rQ&Tm|4gObS=Ni-08OHI$2GSrxEm&Y1*D=y5L$V;7 zm5N*nmd&D6x>Cg!x`9jOqQPUrflz zGTrUlHiN1#H{dWN`2h6sK+X^hPrf{D_@R;czA|RAI1OD}%g8p#o8gI2|0{3ehW}ap z_?#9-dxJ(uoT%o!3*TgKzP3LS%aEO`etK46c`yl6>s}sXUu=uaO~Tky?f^Ks-xvk; zHa+_q#Gp{%<}4ZHDA*OP`|Fo~bz}V$UcZCY#}eP1~JS&j@U7ZH>W54i^!L z#EH?-#HIwuS1-O)SL~wWAdCr6c4NN9kZUxM@JCkx1eglvU3<$YkFaMSWHCA^3`~!FsutNELpF=dz;z@g4TwL7TKI06QL0xLPeOq?6 zhv^ELh{hDyZ7)Bh)CaP93kSZ?5JFWm{@(Hm13FFrM;pKtTdcm1XbkmbOkQmIU1dOh%#ejI+KP$+6@Y9PNYURQzK z1{Ciho@Vi$9B|lcHBQQl^2WCo5N_+2nMJMTWS*GD3D1HFnUQkc~iu(_tHQuo4 z_H##$#9Ffs%|7_jB}D+{_quM3Z{6%cVH8FZmcLSG`~%Qqy{s;%csre(tS%UAq}9p} zZ`kp5i?j=<&tFoh+jET~U}!^$9Ta)TYu>Q9KI-tIFSM!mR~yJm=y-9k*b}cZ0@IRY z<&{9!fejn-`YtXm0t-SrBRFJ$z?eXzEji{fPo1j0e!UGQXznd{+$j-XJP<5g(AqIV zW&RpAw&K&Vn|7UF1q)K}ojW3S@b>qQk_AdXt{jO_v91DH&@!yQ&KrR^iT9s&k#>8CBDah{8zLy!slzDmBM1}tfvHMvTUROy%s@iPCz?_nX{jt Wi`JA@sdvK~j!?+d=+;Q~)xQCriuO1F literal 36830 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ⅅD2qEK%5Pz+^?~i znfUZal$SUX*m2u1;S8+ED+deLFO|^JvdU?4|3jT$yhgHp^N~fAdgD11MHxGA z&kyQMk3)A^O}s<;>(p{Xqt%Qe5WX2dRb!Y?iHyumJM}nk?~s)zX>OUY)^DWAuuyg$ z3U3zxW|bPAZr!BHqM)~e_2GV8^Or#(c3&i?36$7COGK4c#gHy49Fdk;2Jzv{v8P|%c;w^09u4IhirrXaR7?7kG zCw}5n&*||5vKc|0&x4D`rx-L_iUp6-Fmnnb*OF72ofNwl;aGo@OPITFKPa^kYwcFv z%lrdWJL3i?^<;vs751h1c3IvhD!8`Q zOgp!nbNu1KE^5VEVF-Ed)W!=3ose%3t3HW>!yaPI-7O)BpVyAf?-iZbRI;%8s`Pu* z?|2w9O=ynq`9|n5?CE9~hcn3(UWu{`w=^0>*ljxPGD;LkFCNB1%s1R~Q|R=*wchH* zZ*mwElnYIGo`=H3t6s`dnYv$8|Q=%XW>=HwhA_8`Zj9$HO%GJ+TGvQ z;hnNIu8Qicz;jwrqa5)ri~ji3EQXJ}D(MoByB}Zv+6v=!qFIOoohc?(nCRP9_h9z1 zf${T4P8~bGjAWi7jV_9vB1V_f`uyy0d0OzGv;-t2o+Eh&v7nH1jPqF+c=%JIMl#)! zfEAtAJ7Oz*F_`?!#=_m#hqx@v!3&Er?CkcL@;!;RUZ%RzjA6~c3@!cz5o1zmtQ_4# zB;n{|p11SBs2SL>X{4mUK9g+WH!xfOhAmFR>1W}|A&u2O&33tN_p~lyQUAdR*0SN< zt3-Sh<=kNfOT|siYy5NgFkNBFcQmf`!_pY1n<-%me0ch{tYw^h;S9?}tylD``52d> z>Zn=eh;>8j3c8S+1eaURFY@xo7<(%$F1DVy$Ah_t{uJ}1#+#!9WA$bi-RrOHTr0|D z^Ms+k6P7%Lfk)iazZNEY9puF;`d$CUDFy$R*%@>OKQE9e4*g$@IhM8rsDW!GXFo> z*Qr?ih6K?UGip0XhYY%+7cp61+ zLJafH7m;TtyT40vgzbHi!0YVh?G?)bUg>DV?wJ~y!6ALk z1s6F<*%>GKh2AfB>ZuI68;$HfYttVC#r2Koa-<8-js&bfA+$>&K!)1?O{e|3n<@U` zZlq^>M>J*?c-bB3nbv`J3i*m&>c--IYf>C}{UG69+-v=)oHIep%v-p1Lu!*Jch~zK z?f0FYamp#wcRyNnu4<0vNj^2THz&kiX}M5sLyDf>bU9r ztSwbc#y9-DesVqJ)PFqAC5+03yPR3Pt0pZcQ>|{f2%j^Km8&mkelKW3>%IKV~`I<*?AHJmR-ISrf=A`{px4l+Ms zj0Wo~o13rXzC@LE zsGYE2Qt2xe=!@qXD5_5tTc^0rZOw3N*d#qYg`;bjp9$kTKpD?okBiPAGY#4O&d##rIS*3%*u+JXr zC)h_hQXS-3&;59?%oqJ6>PPNZ%Q!T*nDX479%)&EI=3Y@w1rX2*z{=Zv>QDNH4^O{ zx4|WC(Qw&TLz^jja+9R2(b#<3u<-Iv+b3LjWMBZkZ0n1$0ps_*HvQ@#Ld&84!#nzv z9;31D5Ee>bk4Co!a45}5D!-4*$kfv9IGvVaAb#`Od3{glOLx%xR>20Gwry8@%3X(> z#?NYe<_(|DzMy65!=rH09o%R27~Z&G!_>V0BO2pHMj8rU+4B5CvktiM38W+<=>I%kpe@5XjEacRUf_cCnGyZ3%bD372r#y z(XB{+fQz5=;^bQr>KxkB6&Md~jilADQ*S3dJkiOB`KbQ}E0r9y|MZrumu%b||7!5} z6y01f%b=}eOvNGshT;*au!)gnGO*s&1-7EyJYMp@#ObgwkE49t+e$(|A>4N|T4*5O z2m<5nJ#l^ilJl$U)vtC^?N`JV?&vk?w5sLxqs)iu#Ep+W578FRr%c?=p_W6W0R)O9 zb8KyIotuZUn~TbOC)IQL^q9DKDFVl-uG7UsaS@QwPq#4xdf=zP9!jiSuvOIMYCIw8 zJZiPl9pBs;^{_P1Qzcqs1I4?_&Ewx!hbZ%!6y_XcNqbG+{UORqzJ?!-q;E{DDt5Tl z^&;!@{*J4f7Brd{a8+_cdo)T&mWl6`9*^(w;VSzmw$_kzvCkpd1zw6Vnp9NozwE?NsSuO>N7ZpsJZ# zX&edgQjmOkX87m??gq7J1gp?RpAo^4@Z11p^y=Z^V&MdEf{Oq_PqV(L{aoF8e^NY6C>EgY z0{4b8P^PG=j-#P8nXo06YHfRmc&rVrEF*lr3s1Is`b@3c zrEhpWiEnpM`aVEZ`;GtE_a0VT85^#zL_5SKH`?JfeRHZdqmx?NI+oPok3Ygc$x*1I znLW>vfIg>92%&;(O9dU*2+K-L0|7f6#onpwsAX z2dFYO<;{tSiM)VT5^)R!G*3yXAmZV3F=GKj{k}_|kV$lSATK8y)v5x0C9UQtKLz=vtLBbBt?z zOOc64F~7Od^n3n^M)!c2@myNv@|3XH=TDeeXi_vRi=Y=DJ~YQWg=gl^^FqMRlzMq{gmCPrEzkQC4{RC_v+TBqyDf)U+e0*)zk?8sAkmB$*O)S{TU_J zTdL>YBJf41I#L&705n&``O~Z`E~JfxpMOaTL~wyD_;wYb?Es=8Fh=3Whs|^kUi&#B zGO`xH#0DKG)vZ72VgzK|S9}LIX_M&a=s;h6(G7k9sx8Ws7W1pjIbh-enlW&rKW;1f zJ3rn2=YK0VcU3{deissrvc@+kHDq*64_{C=F%Q&dOIlS`-A-h-ljk@SH@oRB3^=`d zzKP$fsZe&X*D87t4}&eBKvu74S?>fF0X73nDQE!YW!erg*R&86$q7RY)5WoEvF->& zGYZ*Su8aYP4K%B8z_1y$@_PYx^gE)+_D ztlon*z?;m)HvSu`_Sl7sRpqyIo+DSpy!EoDZ+rRtVwl+}cvKiJIo+LV!Y1;^DWk%M zI-V*YW#A36mngS@SzpX+gFFDpAqvl^G>l+LtRb%cnoNAjW13fu_e~(R73P(6_&s)~ z0PZA)7&lAXRT9nlUmkyauPLXSBL-yZGe^yg?NC{^^(ib9^1oUDCiX6p?osajKd(yE znTAgln7>~Nw8W;j8&3bS0(pgFz~^d&dA_l+@%i)TYMXLJgZ)j9{?H0QfR@au6s^%@ zzUXr0OlB4lS@1J51jMHDrjx$N^>yd#pJx4g(IDgdk8!2k#u@{)RSH4){9!R_|4(jt z**ujg#XeDEtwaixFz0#6A>r;H`!eK>hPe(OhaZK9PYJ@`F%!&&(^u$QdXl2WdCt&j zm1Ej;0B}(5MQw!5%g*ZRL2tNs7}4my*ALP;NN1<&cYa-5QLCOAkMqsEUr#t0W@I?H2dgruj~ZttI3FfKy^&G+1>8$W$L18dtl~pgqQwF*MtAD}a(M%D=8WnX=k0M}s}LW5R26d58-~gU2cl$#Lyu>whv+x? zTv}jjtIJp<607&OgzuR*Qy1+!^e7C{RHDMT`Jc3}I@tL#cO)lo?5>mNziyZEzW+sa zwEpMW*juE=jcGQzcF%$`a;I0rB%@2AvE&X@6WgEf5JgX z!ApRRm33%vFrCXH>Rt+%lJhA8WJE;ahpSbG=JUi+%ZTV`e~|CzP%ZdQd3UOO@s|~@ z{20he0C`9NjQkq!VV%wY`f4@WO>q9ZctD2F-=Gu%gb`M~oQL1#N#q|kvu6E1EP37& z!?0v~k}h1|Xwg=;=587=mlRps?h}63f8(*24sP?lNwAR=SL%>iq_&Y$&w@)4g`|$x z2O5$!QlkZ{ZWiK9~WG2 zy3vHYqZ=*8?GoEJ4zb^h-S`A3DOS@|RL(iPd}na)zO5}EpPA${@V#e~Q1R6p_Md1| z%vuBcMZ&jcGtwy*7C{)zd@E6w?+h^$UO=`8G@7K5KGF-Ej$Om$7&}belX6X;IXpSo z(04=O!3jbr$~3Kyp?XBl+UF{telzOjcqUDwGgn;4-SEUqp?*cRR4)a0alq*e~^4hufRLEgZ@lYJy}b-tXbTOJj^NBDPV@cRfp$i{R11N@9_@SZ{r zkG+uRL-3e@h|CPw6w>EObO*WF*ZdG&;dnG^|8%xt!$v!lNh%mYGgnSc3-Nv0>A&eT z{$)GG`MK~>sKfR3 zo(KKU&rNF4!z6CQS8KB`l*X{-e;H|X&X5XP#MM*N9qpXzj&1xGQ1>l3A% zA%2_jHhPO_j1y(|ALH=8gSG1xe#QM+FlVdrt4{2k=D-ms8RQZ;)5W4ktX<6nzMf{L zjBY#{cg&*1Y97=5U1qiQIh<*#btfez!mjf=Z?fmj5n9x3I{)WTU#j+iGqGCfF;B|L zFZOp^c^?8{MR|IlPRDI#5yvBqasH3w)1S{B40p=|rxrO1B@0~MpIQIN$+ z&uGoE@g~_a2qu8RPb}iYXn7p^o;y^E332p)8v4JzheO)(XJ=A$kB_*(+x1*08V48> zti|?7O}o7Y|NAsGmD~1smqQ|~(L(rFR`(ySTS>;&=n9~VWG8KbA3vE|JPJY}o`_E) zzdbb^MWaYWghOiih)rB;yo(LJSA_n(2;uWL%}=h@@#|wK_CGwHNtf}!mGxKlUJ?De zO{H?AvT`?*)j|0WZma*varG_jy#^ZVU9AU6{#eEFTWCwu>bCw^?uU&rUmp1eOpQU= z$U}+vhEw?|-nE0#nSskQ;%a8=rdrtO_6{5*;~5z3;F;5QHcAfhsSn-9ESi_Xl&_>; zPgU10xB(v&{-Lj8>{^WlA#Gsoo1i;#V6|(LGOqIIxH^SS#;~Km^!qfb3q#sEo@M|M z&(zM)e&er`c{4<5ionVTolu%iY>47ME%wUaJ%Y`nM*l^S6O!KK zZTwWB(8A~2|I|}C@`w&`L;$uhs`9+Pya}N>zx-PSR`j`p@h&Cwo-7+)sO|eu=%o4?8GjGEJX!V6;B9Ete?QpnP$r-At>8~hX8%o&)DxzAB2TcZXS zaf&ohdiOBO9`1Yh^t!7anjC#(RAEwyqPiYtZ-@MGR4Iv(*^ZM68kG4VZ6;6dO^89u zjh>J7KtdjF#j>95!xyDynI5vep0F4Fb_*ZOH!J%uBNY9P%8d3|hfSKA&;CrVP{+d~ zH^(-?Bz~8(L%!P`Z9~L7@*d-qJu9yUeTOMKkZc#EUuW(cHc$t;leGT4b$y7hl48pg z`8gkS9iYU1Z+=Iw4j zlbcsren~L{J3ZP&IhU7dcN>};-#*=wHMm$d_D^a5Vq&{XrRo`$dh?R<(#FFJ0(p9g z?)JGbwSKrwWs%ZZ|BLkd)*#SQRcf-x-q!a})XGYLR&kJ6$i>p1jqQ3riEVtUtbdM@ zq6fS-(GolbvK=8GH$fQfd$=XneeYQ6!h#;oE2~|@hE%JKDUe=%{ZJ=E=VjA5&>N6b zt-Zgm0Zv&T*847(ITt}Uf?Sayt|XufdPY2>#7VZEOWU~EPQu$&q^QWXjWR)!w`Yl& zjKtdA^iub}*DsPadz|L>HD+{tolF)MDj$v=@|hE%iXIR#i>D2Sl{#+ob*Kip|F*`X zlFU+THTCv`i}%CqBI%`a-^2Mpb3o2pMAh53L!?Fh4<8N`f#ycm|K`!`Jhu6*XPFD6nRi=}4MmD4D#-Uzx?q$N& z%J*j@q!^HqC;RRCc|zjQ>%+QDrg^9-r%@Z>e*T9b$;O);@<;;j)v%XiUp#&}=-As# zyiaNgQ9*1;`-SzF6f(l|^i{mBtEuQrUsJvOWHRa(KDf$RwFVEa_A^WdWU9S1Li%;0 z0|xW$&1=8ZMSQpWVL^|<9ps;|H{c+8Pp`^gqUwBy1>MV~omF}HxA5_zZ@1j}I(f8L zZyyD&bS4Jka}`s#@GuO2F(6iyP$kdGqI!CUe|D`IjrQ}owL zy>1Q%D|=zOt7wMW-Hk>tLzvI?r^by)>AfqS3#8(M;bepJ_-jA@JM8-JZ)<_ASyBhf z(lS`trm1f8AsG{ox0dxx@oLuYZ@T$(^kw?I7q`jY_Xh{&)ck`ZLXSYK4i`3aUs;N? zg^IkG1IWh%FoBlb`+lPBIIAmsQ`1?zQiA{Krsd%0e6b~!AHAiz z${%sm)kJ>k#zUURdP&%ERt|w6KTTsh;hbK9J8#v!-Vs!va4TQf-xD1cEnqLN!vkjW zs^gKmOEQG={bLk=xc@Oem~XnjwP9`j#_v#|_-Idv0fC?Zt1y!PC0o&KTV*r!4sAkl zyPQ#~uQT+SixwVqHrC_*^)igIF=u>_2@$-VBqr*YefMbf^W~zVpJ9mL-^d}7sMWbM zRVfsX7XcjZ?)ZAkE!`w013yb#!Y#BW7?W1omi?{A?KRO}D0PO69XpecUpzu>Kw>_^MIPtnsRB;7l`2z!b9 zKD(hph_uK+I*WN;a|LC{TlA;TSfYj8C*)F8&R7tx~56{ zB|#t8qrRF%Sf#jk_hCa3#a%a(#wlTOuH8%t^UDHYW&B7mPqH8r8BD&&ol_RPwnwwW zo)~A2AbLpQo47dQREsPn-+$wBcEZD!Y&wy~u^OKZPG$`i24ts=Ok=fQNjBLpqbV`F zaMEpJks%OLL^#YFtf=*&gJ#c_My~74usF#?;d}Q;&9G_~Y2Iact&yglf||xs9ZU0D zholx^pI%wI>(k%IE>iS;w0b1slWm&%JvVe!d9K!ueLHnlEzoz3r|k$3u_<7|FuPrR z)+cuET;6H?J*kS?L<&|@Y|ixDdl%rZ^oBqoYrOm_7;)ZOFDGch@9M9jg^L^Au5bL8 zk~Ftqxw&(j2(^ukP0m*PiXC3`51zRO%+MZXbOf+EqbY#H@~^dP{f~0}MqPEs2kqq` zP%B5YaZ2>Kr4m$3r3rS{Q-#*j*=|$50DUQg;+M)`S^w}uYHZn96JRe3C>DKW0|wQ84o#F z6ai|1J%LwE<J=md(g_AOVGQwoR?4me^6#L9-Ewj1VZ$^*5?&&bhvfsA2>QlS(zpZYqwR_NQf zKOt9s{yKh_SGfhOwONhHpDJxx1?y^eREiwVy4o+9g3`ZvB0(SxnDAtEg20bp?XA2q zng(_`MbB-pq0g&*de-W$73Ij;=ZKd4KY{0ID;|iayc~EXP9sAb7#d=mbNe#d$uDTb zN8AkjRx$e84*kHC+p+SSKpyro6K|rhchUY(#iiFqpiw^Jd^@o5Fq}vp=YX#P0%@BE zb}uQwOIJrF^FzbZ?@t4BTtO^u+JP~O6_-a=4E_4a{Or}8!+Z!_ixjqRLud-HOSU*@ zNEi~(#S0>tc36;HHmXy87k#m*mcPbNp-Y!$B+~(6AGRIcz3lPdav_RK2#NT3*rWGI!0|Cab1232LZ0PZgu0cP<0;QyBgl zPD2JI|G4j#mHjH;GzdW;mV{soqKdcQSv?8>FqKQ>#Gt-kUzP_g+v9HBcw=w~4K$G7 zw7=`j!e>nRPa?&#A@l)-@>Pxna>BSZ6LE^{ZrJl;;_xD3oyaNrA186L#?U+PLznmG zPtf%_<_Y?VdPrcit2fX~=+`w>_02U2d;fiV-!1#PmV|`_vSdWDBwlV^WOZ$}yS&;s zG)^$Z)U>{OSif3^+)W=PO`(LFKTPF~=`iq4m*+QuM3L>E3aWXqEjI!-;-t@|pU3K= zmkgo#D+gr@S{L0jFQyKJ@hp)ONVN)$v)I_>(9h#N5Th5Vf3g`ZcH@1XYOup8P=wYF zC{~)`PWewQhkyn~@KN@Q7!6UQ;TrBCi)^~cM^pc|HT!k6gjGQ8$V9G{vujXFnxKa5 z$!wrJW-3{rD)vS{ysCjeZc3h(93bvYW|G;4qgvG?Dk;B-YW?PtZ@PSl?R9_ICIz zM!j;&l17wFf-lTYRdKu8eU2F6Xf8XG(RXTvuHhA7@de}l{lJ=-^l;5Q0^}nr*et#I zZEjX0ayc=aNpgaAJmb04=@(!Hq$xIRe8lU)E;(!(Q6*e7XJ}#H_(V({0xT<=9Vh}flgTY!C~ROvRWYOch3a6n z6$0pesIKtL3{N)QzYz&yZA%l}((-D{;z5>k0=x9;>SW%NZ&E0sF!8zpJ{4?74W$#M z%pQI-b~#K0h@%JA(f8exAp*avwN|Ebb$Vf52C1;WFE%VQKHO5C|0!TYg^yimjG<^D z>|0`Agb{1YZK$Ufe&yiNJa;1~oiw-3j|2|+@IlMf8Dg~JcPH$ieD1i=j1ZOP4k_hj zc)~5RShxV>V>i%6=*4>EFBuFU_K0fWCE#G6?d-)*ps(Iam8QI5mb|8{xeF}7Q@MxB zhBj81A(&L3@Dg{Y2>ff)lxMbEx0Rf?ZOU94p*5`e0>qXdhQ;_8 zv$&PAF=7g`zkbq(Cbvr9Q>Z_KHH}kweUzXLk_DUi7Vv;vG3+YuZ-Yp0Ye>RiFUTJk z;@*C(#z{Y(@6L}RR|=N%UoO@1?iEBA2$-zmHn3f`Wggysi8ffp8wH}~krok8x51lH z<;=^}{Y~@VA8o4)o~pCM>>G0`mG4-vHTX(h3hejrUzJ`lf%BOeW0vY?#7`>aNACf65EE7p2nK7EIj>NHL;!TSu|GUhP_k=Xe(*lcfrM9+WG_iiHM~VTlWdHRmP3 zsIYop$3kB#E8%d^DxuK8$El#^Ie(I1hzVg>wlki6ipy*#RBpjR+RVh;KHw1#@4O6; z;@Gt{=aL#DF#&gC0Xj%6TP19U)oyiPJRmV;ezbu|Kx6M7*PVMdEuq(^Mlc7yel6an* z0Fw-*Nq{jV6uslD7t02F@7#|o*zGG2Hu4}bD*OhEWSQ$azes3v`j9G#vWde${xRkf^7`Gie5kRiN zPW&5!c~p6uZ^vs~ZdoJK1Y8exFI(y_6TZ`|{;>E7{i+orN3*tzx{V{zRtuaWjsjRm ztbf`xhxiCBY?%5Y(P<^+aqYxVutSardJ*#Uf%GE&GY$f+{*(CO50mQ7Sk6>4I!jmi z<4tC!r~tDXO#kNMrQNrk`gBoI?i$ybE$%o~tRjD$xa2pr9-w3Mh;WCdBpbk=X!T%L>t`A11;^|*77c+=tKKx5{ zVXc%PTCNJfigdLP0!;~Gz_(l)KDn;#*xqP;FGu})J%BEoj+2(~eW%aSAxbSy?crfH zlR%F-l$4;_{4~>C*qdHDHpyin^7O{|L1`d_EcW{0CH6=^=;RzKqK`ux3VG@9ebbs} zWtNE6{Na2SX5_h|wzvxKgD`p8(u(MWwT zB=FN=PcG$9DJ}#eMkv;HS;uU5eXDUvxMlO@=4>&o!6(-tqKXNM{#Cwlan{507B#$G z(9!jdONX4i)-rd(oaYs%OS%2aSAIj;1e?41WP-MZRZQRzGx84W=uJqsr_FIJo2UB)x^Z~StP?CD zp}wrj1@89MPY(bAzU2oV@y;UKbmpjT1C{yT%xrogGe0N2oR%f1(?N3a6sL@AxvC|P zAb7Q$m8}UV$`ONbHk3tbx-Getuhxf-FoAF?Shqx5*yHX!0`=X8R#nh$)HAq4PvMPL z<~P?ML*l`B;i96v&#WY}v_)MbRzod8StU+QKh|Jgb?h^i$~EKa`87kHv;vV zELFEKHedB-S3$e5ec70L;nfQ_2n5qmT0&eEe>`DR?d!VwMkY&1y?Qq%3A9IOgvs4< zJomQyLUE6fPq@JV`dS@I(j#X zKJ^YB?hy8pC2!d5Fh*qs^F6tfp^4x>xv8%_<4I3H+*8%#y2a&;1%(V{7M4hq5~u|+ z-@b!Ay2aggS;!}&d!#Sd!PlU0(+XC6_{?cj;a}S#s(LvFwQ#JW(V{9w1kOJCiPUa| zml+^b844ekYxPfg0#FVdpG9F_0j|5E=)G$BR5UT4JqT#GrZ(l;6nnU&Hh*s~npJkU zR$RY^>x;Sk!lo9!rZ)|f^|dL^uP#*A(a0`jDJ^Fi1vhBPQ`#We>Vyn2`BUx$`+DH}Rdk<`Uo9#detk?^bH$xmikz&H4KJc_g}E`sDOnfy2nJpG2on zoN&azkIy5pf+SF>q|lIF`!+WvIKN~{{D+4V|IJ!erjHgH4zQXNa4CtfYTvGEw&RXq=x3 zSs0n3mXeeBJ9U%fd}$4X@`;@n>e~Ir)Pq$CBcEZ!l&1JHNNBL2w6Ba>osK%cV&cQ? z^l^%{&DHu#IkBm6xnww4zF(z!1alafuc@nBo2l{+1;3R^i7{s88+DF5rIW6Pt+`oPFK-g<;$5g`V;M+_9jj1M-z~;GkVZ%o^lR#Wi4A$nn1Lylo08kaAoE*U z|AmUlUvlggB5980fLJ0*8tB%==%5qKp@PD6d#`xIi;OaNY8REy*202q!Fc`;@^5m+}wx5G?#QBj5{876PQfcpxgdM78(sY^z&8z&?&qy&aJOkP( zzRK)J=cyFxmrJ3fi~0kQFas48JS8_s6b7We`;Cu_fIb!Z(ZS`MH`5i~=icBmEq`J* z3p9)lRSpMbG{n@40Yrw(;6G{R#e~r;JkQf*Ji>mC9};6kLm301W}r(zL1mAlgxU`8 z5!-V!En%&Ap9jG~AV*^0@*oRt)N(JHg&-W7C5FPo#|KiDOY|BiADS{V4SuLQoJtFk zlV`H%)J&5D{-GXg(g!a$kBpPN_q84tZq53jKvjd*+ru2Ie}?P@s*HrTZPTovLTFqr zY|W34jz4P5y4tz^PMxn*m!O0FMF$uh3&OuvpnQ``p(+s7HO44(5D`a8=BWT7Uzt?# z#PguvsiMsvGGR2%-NUs9j*e}}NJxMKJwrYPm<4v;kkU}*znY-(}sH4Rw45c`gH{~;g`8lRZ>nwWSZ?0>Y>e!SXE z9SBG`?tnM|>VzhmJu%1t*_Ar5E1J9&h~YqfbZ}764Edk#?%I0qR7GH^AWOU?cjy_} z1%N*4@b6b>{x&wXq+AL&DkUBk))7#pw4P7ufO=7&>JC6wAB^wViJ*dSeVnD(+xL9a z1|&tGROS~9(w#ZZxf`-301Xpb$Z@{>@KiX;5wm5An}>%1?|B{aV!!S(*&V3ObwBbJ zGbk{Szw(RzhF|8!Q(vB37EgEb6pRC(n7}CtK0Lv(7ksoK0DltrBm;qW@Npytq8s4) z$l$mSK83OVzrTt+Ot+~Ea`(-&ChK{=4VrtTeF`uls+s{ME>OUkk|o`|Y6cdEhcb6) zy5R*9i+k3cYnd|4w4zplS`18Y?sNtEuQKya)>ILdoyJbx(S(69qn5d^Wf;E-Rw9AY zf`6KYf;I=}l0X5L*x2PgMy&py_LoMHSk|_-%&%V)fnsDren-c^Q|;XCbu?h^0+dO{ z#@V&CwN*CTBoQ24)B8~ZT~`3i3gD0cL@`vBH^$y2Jp3LHZV8jKzN{Y-RpUG-+n zx^*Ms8FhfzI8NI{JNEr2Fi?g;kv@k!H@C0Ehc_Zxn5X*=`N>`w?bXhzXc%N-yA zfUIx@B%p);vgmx+e3_r0F9Ie5$RcuZsuFE`8s-t~|7Rp+&maIyL?8?PI679$&a1iE zF!x(?zY-M{^{_47h+^-muCD&C%9l`MVhaHGgn(kn+WULZ8-o9RMD0^go5VJRdV^rr zk$iyB27JkyU%w=zq{_9bx)vA7DHD>CF2Mu>MI)S6yI2fb=KQXkS7jIWqS1hg)B@0W z_}HodBpGmE7rZ;mvQwoN4!}Qv+K14rqv+YzK^>RWYGKVoa!39NJy9G2M{^1XZ zNC%2Jqs;nEbrlsO#+5#2qf!n{$4Fh@%x0v>ylB8n!?;oy^cy7UACAZI*|@+(t=c}a zQB#|Ad_ix%&DMkRMK|fWxt*_chRf59k=OdC)^70Hc(MN=0T*iYnszwVpKkYBc)Ntt zOy4s+45~awg@?z(Xk0djSeTh5P|PeWZm+Lt>FH%@;y^z#`JJ-g%Py1*J_D2MIWR&o zG`(ObM1U6Tf3GGC-2!E&^&8zwXKd|VZfm`5>{}C&UHagH-#4cQ->V;XqB~|>V%13; zqJn}{|1WK;5+K@ode+_d0%m7dSC;~dSJf<=BUtyq3;;B0hNl&4S*jEn<=JmEHj2Q5 z`ppd9TK{5d|J!QeZ&es|dYNzj>(ROX9AwiA^OM6-24H7TcN+);87iTHPo-%uPGC&C zVI9Xw)qg_MbnO%KTCI;}GnxJmUf@0T<_2C*oiEy_@t?2F%`1AcqZXeckJF0UTm81D z5h891W+!?9%C~?jQ{JnQd{k6aSm3m~(zW)SAbhacs^h&3`o6t zdV0R8q=V@~VApxr4Bl4c{(Faq!|ZZSPRF1c6FdR}Ahm-HsJ}-*n<*zYVr){9#baef zMGOT>y28{ci}D^0pVWzj}Zy7Ow@AL#5qfNJOa<*g6enwp?&bFoUH zLWZz_$HAQFbKDVw5R{m zG*szz8c=fTmp=m5IH0zLbNB(PBBbf{;{pTX$O-g}ZWkZBiDt+6K;3i`W-JuIA2Bj8 z1Vk9XR=apYj-zDF&CQ*ixn?O9Ojz#^S22S*dIc)%&ResA+UQ`(y>I$ITeq%H3i@pXykTtW{Q2{1vpE(5^;W;$f9FojZzf=10&hXMcJ16d;9`}S0)9CL zaUnUmy!r=k-^vOK7Czqi_u$pl;gW4Tce=`*-UKYBx8Ir-v~mvcKFogme-`QI=Gaz$ z2pVE6Fls?gQI+8THm);8cm>g3~nfg)EY-m=A3l(jEjpxSBGuX`3=0DPCt4Zk7A06)N$a|Py!rcx=~+%_jm&jx2p!G8{pMohYe!y zSu-5yd?07Tke7U6fumIKGvMum-{iBivVf&nDe&Id+iQ%nujzCj{RB)qz!LFYBfFdf zhpc^_jlKPU;PFGiD(udh$jvNG+y0;0Us(LtO?Z#q8=#k6fOV7wqvs@Gu+Gh0C?+m$ z4m{rv*piV29uW^b^Mv2#L&L=k8Iz0)xnUDeKLuW<7qh$UE$|dHXD25Cr+xeOnazIN zE?>7|)v8~>1;W6U)T*0r-nnw6085Mj7ndWz`_t>TPX}&I0B)cFHtz3n{8;xE=qVkRV>fOBE6f-{ z-^(Vxt3$NX#hDmhaKV~8+Eom9ZUdErHkCZ9xBc_s@VB;7;F=1KeZYxbP(w)SCaC@< esX?^hKl4TI8G#RuE8PNWW$<+Mb6Mw<&;$S>3-l)d diff --git a/man/full_date_seq.Rd b/man/full_date_seq.Rd new file mode 100644 index 00000000..eb36b2c1 --- /dev/null +++ b/man/full_date_seq.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/slide.R +\name{full_date_seq} +\alias{full_date_seq} +\title{Make a complete date sequence between min(x$time_value) and max +(x$time_value). Produce lists of dates before min(x$time_value) and after +max(x$time_value) for padding initial and final windows to size \code{n}.} +\usage{ +full_date_seq(x, before, after, time_type) +} +\description{ +\code{before} and \code{after} args are assumed to have been validated by the calling +function (using \code{validate_slide_window_arg}). +} +\keyword{internal} diff --git a/man/geo_column_names.Rd b/man/geo_column_names.Rd index 4b3810dc..839d7ce8 100644 --- a/man/geo_column_names.Rd +++ b/man/geo_column_names.Rd @@ -10,3 +10,4 @@ geo_column_names() the full list of potential substitutions for the \code{geo_value} column name: geo_value, geo_values, geo_id, geos, location, jurisdiction, fips, zip, county, hrr, msa, state, province, nation, states, provinces, counties, geo_Value, Geo_Value, Geo_Values, Geo_Id, Geos, Location, Jurisdiction, Fips, Zip, County, Hrr, Msa, State, Province, Nation, States, Provinces, Counties, Geo_Value } +\keyword{internal} diff --git a/man/guess_period.Rd b/man/guess_period.Rd index 0be9fdf2..5f17cf4e 100644 --- a/man/guess_period.Rd +++ b/man/guess_period.Rd @@ -30,3 +30,4 @@ by adding \code{k * result} for an integer k, and such that there is no smaller \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} } +\keyword{internal} diff --git a/man/is_epi_df.Rd b/man/is_epi_df.Rd deleted file mode 100644 index 62e2f43a..00000000 --- a/man/is_epi_df.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/epi_df.R -\name{is_epi_df} -\alias{is_epi_df} -\title{Test for \code{epi_df} format} -\usage{ -is_epi_df(x) -} -\arguments{ -\item{x}{An object.} -} -\value{ -\code{TRUE} if the object inherits from \code{epi_df}. -} -\description{ -Test for \code{epi_df} format -} diff --git a/man/max_version_with_row_in.Rd b/man/max_version_with_row_in.Rd index cca554fa..47ce2abb 100644 --- a/man/max_version_with_row_in.Rd +++ b/man/max_version_with_row_in.Rd @@ -16,3 +16,4 @@ an \code{NA} version value \description{ Exported to make defaults more easily copyable. } +\keyword{internal} diff --git a/man/next_after.Rd b/man/next_after.Rd index 5170e8d9..da9e321c 100644 --- a/man/next_after.Rd +++ b/man/next_after.Rd @@ -15,3 +15,4 @@ same class, typeof, and length as \code{x} \description{ Get the next possible value greater than \code{x} of the same type } +\keyword{internal} diff --git a/man/print.epi_df.Rd b/man/print.epi_df.Rd index d1664cd7..5a232de0 100644 --- a/man/print.epi_df.Rd +++ b/man/print.epi_df.Rd @@ -2,6 +2,7 @@ % Please edit documentation in R/methods-epi_df.R \name{print.epi_df} \alias{print.epi_df} +\alias{summary.epi_df} \alias{group_by.epi_df} \alias{ungroup.epi_df} \alias{group_modify.epi_df} @@ -10,6 +11,8 @@ \usage{ \method{print}{epi_df}(x, ...) +\method{summary}{epi_df}(object, ...) + \method{group_by}{epi_df}(.data, ...) \method{ungroup}{epi_df}(x, ...) @@ -21,7 +24,10 @@ \arguments{ \item{x}{an \code{epi_df}} -\item{...}{additional arguments to forward to \code{NextMethod()}, or unused} +\item{...}{Additional arguments, for compatibility with \code{summary()}. +Currently unused.} + +\item{object}{an \code{epi_df}} \item{.data}{an \code{epi_df}} @@ -33,4 +39,7 @@ } \description{ Print and summary functions for an \code{epi_df} object. + +Prints a variety of summary statistics about the \code{epi_df} object, such as +the time range included and geographic coverage. } diff --git a/man/summary.epi_df.Rd b/man/summary.epi_df.Rd deleted file mode 100644 index 831d4d4e..00000000 --- a/man/summary.epi_df.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/methods-epi_df.R -\name{summary.epi_df} -\alias{summary.epi_df} -\title{Summarize \code{epi_df} object} -\usage{ -\method{summary}{epi_df}(object, ...) -} -\arguments{ -\item{object}{an \code{epi_df}} - -\item{...}{Additional arguments, for compatibility with \code{summary()}. -Currently unused.} -} -\description{ -Prints a variety of summary statistics about the \code{epi_df} object, such as -the time range included and geographic coverage. -} diff --git a/man/time_column_names.Rd b/man/time_column_names.Rd index 2e2db6b5..01668ebd 100644 --- a/man/time_column_names.Rd +++ b/man/time_column_names.Rd @@ -10,3 +10,4 @@ time_column_names() the full list of potential substitutions for the \code{time_value} column name: time_value, date, time, datetime, dateTime, date_time, target_date, week, epiweek, month, mon, year, yearmon, yearmonth, yearMon, yearMonth, dates, time_values, target_dates, time_Value, Time_Value, Date, Time, Datetime, DateTime, Date_Time, Target_Date, Week, Epiweek, Month, Mon, Year, Yearmon, Yearmonth, YearMon, YearMonth, Dates, Time_Values, Target_Dates, Time_Value } +\keyword{internal} diff --git a/man/validate_version_bound.Rd b/man/validate_version_bound.Rd new file mode 100644 index 00000000..b6456a2a --- /dev/null +++ b/man/validate_version_bound.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/archive.R +\name{validate_version_bound} +\alias{validate_version_bound} +\title{Validate a version bound arg} +\usage{ +validate_version_bound( + version_bound, + x, + na_ok = FALSE, + version_bound_arg = rlang::caller_arg(version_bound), + x_arg = rlang::caller_arg(x) +) +} +\arguments{ +\item{version_bound}{the version bound to validate} + +\item{x}{a data frame containing a version column with which to check +compatibility} + +\item{na_ok}{Boolean; is \code{NA} an acceptable "bound"? (If so, \code{NA} will +have a special context-dependent meaning.)} + +\item{version_bound_arg}{optional string; what to call the version bound in +error messages} +} +\description{ +Expected to be used on \code{clobberable_versions_start}, \code{versions_end}, +and similar arguments. Some additional context-specific checks may be needed. +} +\section{Side effects}{ + raises an error if version bound appears invalid +} + +\keyword{internal} diff --git a/man/version_column_names.Rd b/man/version_column_names.Rd index 75ee2315..2761dedd 100644 --- a/man/version_column_names.Rd +++ b/man/version_column_names.Rd @@ -10,3 +10,4 @@ version_column_names() the full list of potential substitutions for the \code{version} column name: version, issue, release, Version, Issue, Release } +\keyword{internal} diff --git a/vignettes/scrap.Rmd b/scrap.Rmd similarity index 75% rename from vignettes/scrap.Rmd rename to scrap.Rmd index 11deebae..cea73604 100644 --- a/vignettes/scrap.Rmd +++ b/scrap.Rmd @@ -1,5 +1,59 @@ This notebook contains sections removed from other notebooks. It currently doesn't compile. +## Getting data into `epi_df` format + +As another example, here we will import the daily new (not cumulative) SARS +cases in Canada in 2003, from the +[outbreaks](https://github.com/reconverse/outbreaks) package: + +```{r} +edf <- outbreaks::sars_canada_2003 %>% + mutate(geo_value = "ca") %>% + select(geo_value, time_value = date, starts_with("cases")) %>% + pivot_longer(starts_with("cases"), names_to = "type") %>% + mutate(type = substring(type, 7)) %>% + as_epi_df(other_keys = "type") + +head(edf) + +edf %>% + autoplot() +``` + +Get confirmed cases of Ebola in Sierra Leone from 2014 to 2015 by province and +date of onset, prepared from line list data from the same package: + +```{r, fig.width = 9, fig.height = 6} +edf <- outbreaks::ebola_sierraleone_2014 %>% + select(district, date_of_onset, status) %>% + mutate(province = case_when( + district %in% c("Kailahun", "Kenema", "Kono") ~ + "Eastern", + district %in% c( + "Bombali", "Kambia", "Koinadugu", "Port Loko", + "Tonkolili" + ) ~ + "Northern", + district %in% c("Bo", "Bonthe", "Moyamba", "Pujehun") ~ + "Sourthern", + district %in% c("Western Rural", "Western Urban") ~ + "Western" + )) %>% + group_by(geo_value = province, time_value = date_of_onset) %>% + summarise(cases = sum(status == "confirmed"), .groups = "drop") %>% + complete(geo_value, + time_value = full_seq(time_value, period = 1), + fill = list(cases = 0) + ) %>% + as_epi_df() + +head(edf) + +edf %>% + autoplot() +``` + + ## Some details on metadata In general, an `epi_df` object has the following fields in its metadata: diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd deleted file mode 100644 index 4a415a42..00000000 --- a/vignettes/aggregation.Rmd +++ /dev/null @@ -1,234 +0,0 @@ ---- -title: Aggregate signals over space and time -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Aggregate signals over space and time} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -Aggregation, both time-wise and geo-wise, are common tasks when working with -epidemiological data sets. This vignette demonstrates how to carry out these -kinds of tasks with `epi_df` objects. We'll work with county-level reported -COVID-19 cases in MA and VT. - -```{r, message = FALSE, eval= FALSE, warning= FALSE} -library(readr) -library(epidatr) -library(epiprocess) -library(dplyr) - -# Get mapping between FIPS codes and county&state names: -y <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv", # nolint: line_length_linter - col_types = c( - FIPS = col_character(), - CTYNAME = col_character(), - STNAME = col_character() - ) -) %>% - filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% - select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) - -# Fetch only counties from Massachusetts and Vermont, then append names columns as well -x <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_incidence_num", - geo_type = "county", - time_type = "day", - geo_values = paste(y$geo_value, collapse = ","), - time_values = epirange(20200601, 20211231), -) %>% - select(geo_value, time_value, cases = value) %>% - inner_join(y, by = "geo_value", relationship = "many-to-one", unmatched = c("error", "drop")) %>% - as_epi_df(as_of = as.Date("2024-03-20")) -``` - -The data contains 16,212 rows and 5 columns. - -```{r, echo=FALSE, warning=FALSE, message=FALSE} -library(readr) -library(epidatr) -library(epiprocess) -library(dplyr) - -data(jhu_csse_county_level_subset) -x <- jhu_csse_county_level_subset -``` - -## Converting to `tsibble` format - -For manipulating and wrangling time series data, the -[`tsibble`](https://tsibble.tidyverts.org/index.html) already provides a host of -useful tools. A tsibble object (formerly, of class `tbl_ts`) is basically a -tibble (data frame) but with two specially-marked columns: an **index** column -representing the time variable (defining an order from past to present), and a -**key** column identifying a unique observational unit for each time point. In -fact, the key can be made up of any number of columns, not just a single one. - -In an `epi_df` object, the index variable is `time_value`, and the key variable -is typically `geo_value` (though this need not always be the case: for example, -if we have an age group variable as another column, then this could serve as a -second key variable). The `epiprocess` package thus provides an implementation -of `as_tsibble()` for `epi_df` objects, which sets these variables according to -these defaults. - -```{r, message = FALSE} -library(tsibble) - -xt <- as_tsibble(x) -head(xt) -key(xt) -index(xt) -interval(xt) -``` - -We can also set the key variable(s) directly in a call to `as_tsibble()`. -Similar to SQL keys, if the key does not uniquely identify each time point (that -is, the key and index together do not not uniquely identify each row), then -`as_tsibble()` throws an error: - -```{r, error = TRUE} -head(as_tsibble(x, key = "county_name")) -``` - -As we can see, there are duplicate county names between Massachusetts and -Vermont, which caused the error. - -```{r, message = FALSE} -head(duplicates(x, key = "county_name")) -``` - -Keying by both county name and state name, however, does work: - -```{r, message = FALSE} -head(as_tsibble(x, key = c("county_name", "state_name"))) -``` - -## Detecting and filling time gaps - -One of the major advantages of the `tsibble` package is its ability to handle -**implicit gaps** in time series data. In other words, it can infer what time -scale we're interested in (say, daily data), and detect apparent gaps (say, when -values are reported on January 1 and 3 but not January 2). We can subsequently -use functionality to make these missing entries explicit, which will generally -help avoid bugs in further downstream data processing tasks. - -Let's first remove certain dates from our data set to create gaps: - -```{r} -state_naming <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/state_census.csv", # nolint: line_length_linter - col_types = c(NAME = col_character(), ABBR = col_character()) -) %>% - transmute(state_name = NAME, abbr = tolower(ABBR)) %>% - as_tibble() - -# First make geo value more readable for tables, plots, etc. -x <- x %>% - inner_join(state_naming, by = "state_name", relationship = "many-to-one", unmatched = c("error", "drop")) %>% - mutate(geo_value = paste(substr(county_name, 1, nchar(county_name) - 7), state_name, sep = ", ")) %>% - select(geo_value, time_value, cases) - -xt <- as_tsibble(x) %>% filter(cases >= 3) -``` - -The functions `has_gaps()`, `scan_gaps()`, `count_gaps()` in the `tsibble` -package each provide useful summaries, in slightly different formats. - -```{r} -head(has_gaps(xt)) -head(scan_gaps(xt)) -head(count_gaps(xt)) -``` - -We can also visualize the patterns of missingness: - -```{r, message = FALSE, warning = FALSE} -library(ggplot2) -theme_set(theme_bw()) - -ggplot( - count_gaps(xt), - aes( - x = reorder(geo_value, desc(geo_value)), - color = geo_value - ) -) + - geom_linerange(aes(ymin = .from, ymax = .to)) + - geom_point(aes(y = .from)) + - geom_point(aes(y = .to)) + - coord_flip() + - labs(x = "County", y = "Date") + - theme(legend.position = "none") -``` - -Using the `fill_gaps()` function from `tsibble`, we can replace all gaps by an -explicit value. The default is `NA`, but in the current case, where missingness -is not at random but rather represents a small value that was censored (only a -hypothetical with COVID-19 reports, but certainly a real phenomenon that occurs -in other signals), it is better to replace it by zero, which is what we do here. -(Other approaches, such as LOCF: last observation carried forward in time, could -be accomplished by first filling with `NA` values and then following up with a -second call to `tidyr::fill()`.) - -```{r} -fill_gaps(xt, cases = 0) %>% - head() -``` - -Note that the time series for Addison, VT only starts on August 27, 2020, even -though the original (uncensored) data set itself was drawn from a period that -went back to June 6, 2020. By setting `.full = TRUE`, we can at zero-fill over -the entire span of the observed (censored) data. - -```{r} -xt_filled <- fill_gaps(xt, cases = 0, .full = TRUE) - -head(xt_filled) -``` - -Explicit imputation for missingness (zero-filling in our case) can be important -for protecting against bugs in all sorts of downstream tasks. For example, even -something as simple as a 7-day trailing average is complicated by missingness. -The function `epi_slide()` looks for all rows within a window of 7 days anchored -on the right at the reference time point (when `.window_size = 7`). -But when some days in a given week are missing because they were censored -because they had small case counts, taking an average of the observed case -counts can be misleading and is unintentionally biased upwards. Meanwhile, -running `epi_slide()` on the zero-filled data brings these trailing averages -(appropriately) downwards, as we can see inspecting Plymouth, MA around July 1, -2021. - -```{r} -xt %>% - as_epi_df(as_of = as.Date("2024-03-20")) %>% - group_by(geo_value) %>% - epi_slide(cases_7dav = mean(cases), .window_size = 7) %>% - ungroup() %>% - filter( - geo_value == "Plymouth, MA", - abs(time_value - as.Date("2021-07-01")) <= 3 - ) %>% - print(n = 7) - -xt_filled %>% - as_epi_df(as_of = as.Date("2024-03-20")) %>% - group_by(geo_value) %>% - epi_slide(cases_7dav = mean(cases), .window_size = 7) %>% - ungroup() %>% - filter( - geo_value == "Plymouth, MA", - abs(time_value - as.Date("2021-07-01")) <= 3 - ) %>% - print(n = 7) -``` - -## Geographic aggregation - -TODO - -## Attribution -This document contains a dataset that is a modified part of the [COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University](https://github.com/CSSEGISandData/COVID-19) as [republished in the COVIDcast Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html). This data set is licensed under the terms of the [Creative Commons Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/) by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. Copyright Johns Hopkins University 2020. - -[From the COVIDcast Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html): - These signals are taken directly from the JHU CSSE [COVID-19 GitHub repository](https://github.com/CSSEGISandData/COVID-19) without changes. - diff --git a/vignettes/compactify.Rmd b/vignettes/compactify.Rmd index 72a2d266..7eb6e7a3 100644 --- a/vignettes/compactify.Rmd +++ b/vignettes/compactify.Rmd @@ -7,6 +7,15 @@ vignette: > %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +ggplot2::theme_set(ggplot2::theme_bw()) +``` + ## Removing redundant update data to save space We do not need to store version update rows that look like the last version of diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index 34e8c0f0..9b37191e 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -1,14 +1,23 @@ --- -title: Correlate signals over space and time +title: Correlate signals across locations and time output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Correlate signals over space and time} + %\VignetteIndexEntry{Correlate signals across locations and time} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +ggplot2::theme_set(ggplot2::theme_bw()) +``` + The `epiprocess` package provides some simple functionality for computing lagged -correlations between two signals, over space or time (or other variables), via +correlations between two signals, across locations or time (or other variables), via `epi_cor()`. This function is really just a convenience wrapper over some basic commands: it first performs specified time shifts, then computes correlations, grouped in a specified way. In this vignette, we'll examine correlations between diff --git a/vignettes/archive.Rmd b/vignettes/epi_archive.Rmd similarity index 97% rename from vignettes/archive.Rmd rename to vignettes/epi_archive.Rmd index f05568e0..3a74a5e9 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/epi_archive.Rmd @@ -1,12 +1,21 @@ --- -title: Work with archive objects and data revisions +title: Work with epi_archive objects and data revisions output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Work with archive objects and data revisions} + %\VignetteIndexEntry{Work with epi_archive objects and data revisions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +ggplot2::theme_set(ggplot2::theme_bw()) +``` + In addition to the `epi_df` data structure, the `epiprocess` package has a companion structure called `epi_archive`. In comparison to an `epi_df` object, which can be seen as storing a single snapshot of a data set with the most @@ -332,8 +341,8 @@ to modeling weekly sums of COVID-19 cases. ```{r cache=TRUE} edf_latest <- epix_as_of(archive_merged, archive_merged$versions_end) - fc_time_values <- seq(as.Date("2020-09-01"), as.Date("2021-11-01"), by = "1 months") + edf_latest %>% group_by(geo_value) %>% epi_slide(fc = prob_ar(.data$case_rate_7d_av), .window_size = 120, .ref_time_values = fc_time_values) %>% @@ -353,7 +362,7 @@ over the last year, at multiple horizons: 7, 14, 21, and 28 days ahead. To do so, we encapsulate the process of generating forecasts into a simple function, so that we can call it a few times. -```{r, cache=TRUE, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} +```{r cache=TRUE, message = FALSE, warning = FALSE} # Note the use of .all_rows = TRUE (keeps all original rows in the output) k_week_ahead <- function(epi_df, ahead = 7) { epi_df %>% @@ -375,22 +384,16 @@ z <- bind_rows( k_week_ahead(edf_latest, ahead = 21), k_week_ahead(edf_latest, ahead = 28) ) +``` +```{r fig.width = 9, fig.height = 6} # Now plot them, on top of actual COVID-19 case counts ggplot(z) + geom_line(aes(x = time_value, y = case_rate_7d_av), color = "gray50") + - geom_ribbon(aes( - x = target_date, ymin = fc$lower, ymax = fc$upper, - group = time_value - ), fill = 6, alpha = 0.4) + + geom_ribbon(aes(x = target_date, ymin = fc$lower, ymax = fc$upper, group = time_value), fill = 6, alpha = 0.4) + geom_line(aes(x = target_date, y = fc$point, group = time_value)) + - geom_point(aes(x = target_date, y = fc$point, group = time_value), - size = 0.5 - ) + - geom_vline( - data = tibble(x = fc_time_values), aes(xintercept = edf), - linetype = 2, alpha = 0.5 - ) + + geom_point(aes(x = target_date, y = fc$point, group = time_value), size = 0.5) + + geom_vline(data = tibble(x = fc_time_values), aes(xintercept = x), linetype = 2, alpha = 0.5) + facet_wrap(vars(geo_value), scales = "free_y") + scale_x_date(minor_breaks = "month", date_labels = "%b %y") + labs(x = "Date", y = "Reported COVID-19 cases") @@ -528,7 +531,7 @@ points in time and forecast horizons. The former comes from using `epix_slide()` with the `epi_archive` object `x`, and the latter from applying `epi_slide()` to the latest snapshot of the data `x_latest`. -```{r, cache=TRUE, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} +```{r, cache=TRUE, message = FALSE, warning = FALSE} # Simple function to produce forecasts k weeks ahead forecast_k_week_ahead <- function(x, ahead = 7, as_of = TRUE) { if (as_of) { @@ -563,7 +566,10 @@ fc <- bind_rows( forecast_k_week_ahead(archive_merged, ahead = 21, as_of = FALSE), forecast_k_week_ahead(archive_merged, ahead = 28, as_of = FALSE) ) +``` + +```{r fig.width = 9, fig.height = 6} # Plot them, on top of latest COVID-19 case rates ggplot(fc, aes(x = target_date, group = time_value, fill = as_of)) + geom_ribbon(aes(ymin = fc$lower, ymax = fc$upper), alpha = 0.4) + diff --git a/vignettes/epi_df.Rmd b/vignettes/epi_df.Rmd new file mode 100644 index 00000000..3c442bdc --- /dev/null +++ b/vignettes/epi_df.Rmd @@ -0,0 +1,479 @@ +--- +title: Working with epi_df objects and time series data +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Working with epi_df objects and time series data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +ggplot2::theme_set(ggplot2::theme_bw()) +``` + +Aggregation, both time-wise and geo-wise, are common tasks when working with +epidemiological data sets. This vignette demonstrates how to carry out these +kinds of tasks with `epi_df` objects. We'll work with county-level reported +COVID-19 cases in MA and VT. + +```{r, message = FALSE, eval= FALSE, warning= FALSE} +library(readr) +library(epidatr) +library(epiprocess) +library(dplyr) + +# Get mapping between FIPS codes and county&state names: +y <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv", # nolint: line_length_linter + col_types = c( + FIPS = col_character(), + CTYNAME = col_character(), + STNAME = col_character() + ) +) %>% + filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% + select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) + +# Fetch only counties from Massachusetts and Vermont, then append names columns as well +x <- pub_covidcast( + source = "jhu-csse", + signals = "confirmed_incidence_num", + geo_type = "county", + time_type = "day", + geo_values = paste(y$geo_value, collapse = ","), + time_values = epirange(20200601, 20211231), +) %>% + select(geo_value, time_value, cases = value) %>% + inner_join(y, by = "geo_value", relationship = "many-to-one", unmatched = c("error", "drop")) %>% + as_epi_df(as_of = as.Date("2024-03-20")) +``` + +The data contains 16,212 rows and 5 columns. + +```{r, echo=FALSE, warning=FALSE, message=FALSE} +library(readr) +library(epidatr) +library(epiprocess) +library(dplyr) + +data(jhu_csse_county_level_subset) +x <- jhu_csse_county_level_subset +``` + +## Converting to `tsibble` format + +For manipulating and wrangling time series data, the +[`tsibble`](https://tsibble.tidyverts.org/index.html) already provides a host of +useful tools. A tsibble object (formerly, of class `tbl_ts`) is basically a +tibble (data frame) but with two specially-marked columns: an **index** column +representing the time variable (defining an order from past to present), and a +**key** column identifying a unique observational unit for each time point. In +fact, the key can be made up of any number of columns, not just a single one. + +In an `epi_df` object, the index variable is `time_value`, and the key variable +is typically `geo_value` (though this need not always be the case: for example, +if we have an age group variable as another column, then this could serve as a +second key variable). The `epiprocess` package thus provides an implementation +of `as_tsibble()` for `epi_df` objects, which sets these variables according to +these defaults. + +```{r, message = FALSE} +library(tsibble) + +xt <- as_tsibble(x) +head(xt) +key(xt) +index(xt) +interval(xt) +``` + +We can also set the key variable(s) directly in a call to `as_tsibble()`. +Similar to SQL keys, if the key does not uniquely identify each time point (that +is, the key and index together do not not uniquely identify each row), then +`as_tsibble()` throws an error: + +```{r, error = TRUE} +head(as_tsibble(x, key = "county_name")) +``` + +As we can see, there are duplicate county names between Massachusetts and +Vermont, which caused the error. + +```{r, message = FALSE} +head(duplicates(x, key = "county_name")) +``` + +Keying by both county name and state name, however, does work: + +```{r, message = FALSE} +head(as_tsibble(x, key = c("county_name", "state_name"))) +``` + +## Detecting and filling time gaps + +One of the major advantages of the `tsibble` package is its ability to handle +**implicit gaps** in time series data. In other words, it can infer what time +scale we're interested in (say, daily data), and detect apparent gaps (say, when +values are reported on January 1 and 3 but not January 2). We can subsequently +use functionality to make these missing entries explicit, which will generally +help avoid bugs in further downstream data processing tasks. + +Let's first remove certain dates from our data set to create gaps: + +```{r} +state_naming <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/state_census.csv", # nolint: line_length_linter + col_types = c(NAME = col_character(), ABBR = col_character()) +) %>% + transmute(state_name = NAME, abbr = tolower(ABBR)) %>% + as_tibble() + +# First make geo value more readable for tables, plots, etc. +x <- x %>% + inner_join(state_naming, by = "state_name", relationship = "many-to-one", unmatched = c("error", "drop")) %>% + mutate(geo_value = paste(substr(county_name, 1, nchar(county_name) - 7), state_name, sep = ", ")) %>% + select(geo_value, time_value, cases) + +xt <- as_tsibble(x) %>% filter(cases >= 3) +``` + +The functions `has_gaps()`, `scan_gaps()`, `count_gaps()` in the `tsibble` +package each provide useful summaries, in slightly different formats. + +```{r} +head(has_gaps(xt)) +head(scan_gaps(xt)) +head(count_gaps(xt)) +``` + +We can also visualize the patterns of missingness: + +```{r, message = FALSE, warning = FALSE} +library(ggplot2) +theme_set(theme_bw()) + +ggplot( + count_gaps(xt), + aes( + x = reorder(geo_value, desc(geo_value)), + color = geo_value + ) +) + + geom_linerange(aes(ymin = .from, ymax = .to)) + + geom_point(aes(y = .from)) + + geom_point(aes(y = .to)) + + coord_flip() + + labs(x = "County", y = "Date") + + theme(legend.position = "none") +``` + +Using the `fill_gaps()` function from `tsibble`, we can replace all gaps by an +explicit value. The default is `NA`, but in the current case, where missingness +is not at random but rather represents a small value that was censored (only a +hypothetical with COVID-19 reports, but certainly a real phenomenon that occurs +in other signals), it is better to replace it by zero, which is what we do here. +(Other approaches, such as LOCF: last observation carried forward in time, could +be accomplished by first filling with `NA` values and then following up with a +second call to `tidyr::fill()`.) + +```{r} +fill_gaps(xt, cases = 0) %>% + head() +``` + +Note that the time series for Addison, VT only starts on August 27, 2020, even +though the original (uncensored) data set itself was drawn from a period that +went back to June 6, 2020. By setting `.full = TRUE`, we can at zero-fill over +the entire span of the observed (censored) data. + +```{r} +xt_filled <- fill_gaps(xt, cases = 0, .full = TRUE) + +head(xt_filled) +``` + +Explicit imputation for missingness (zero-filling in our case) can be important +for protecting against bugs in all sorts of downstream tasks. For example, even +something as simple as a 7-day trailing average is complicated by missingness. +The function `epi_slide()` looks for all rows within a window of 7 days anchored +on the right at the reference time point (when `.window_size = 7`). +But when some days in a given week are missing because they were censored +because they had small case counts, taking an average of the observed case +counts can be misleading and is unintentionally biased upwards. Meanwhile, +running `epi_slide()` on the zero-filled data brings these trailing averages +(appropriately) downwards, as we can see inspecting Plymouth, MA around July 1, +2021. + +```{r} +xt %>% + as_epi_df(as_of = as.Date("2024-03-20")) %>% + group_by(geo_value) %>% + epi_slide(cases_7dav = mean(cases), .window_size = 7) %>% + ungroup() %>% + filter( + geo_value == "Plymouth, MA", + abs(time_value - as.Date("2021-07-01")) <= 3 + ) %>% + print(n = 7) + +xt_filled %>% + as_epi_df(as_of = as.Date("2024-03-20")) %>% + group_by(geo_value) %>% + epi_slide(cases_7dav = mean(cases), .window_size = 7) %>% + ungroup() %>% + filter( + geo_value == "Plymouth, MA", + abs(time_value - as.Date("2021-07-01")) <= 3 + ) %>% + print(n = 7) +``` + + +A central tool in the `epiprocess` package is `epi_slide()`, which is based on +the powerful functionality provided in the +[`slider`](https://cran.r-project.org/web/packages/slider) package. In +`epiprocess`, to "slide" means to apply a computation---represented as a +function or formula---over a sliding/rolling data window. The function always +applies the slide inside each group and the grouping is assumed to be across all +group keys of the `epi_df` (this is the grouping used by default if you do not +group the `epi_df` with a `group_by()`). + +By default, the `.window_size` units depend on the `time_type` of the `epi_df`, +which is determined from the types in the `time_value` column of the `epi_df`. +See the "Details" in `epi_slide()` for more. + +As in getting started guide, we'll fetch daily reported COVID-19 cases from CA, +FL, NY, and TX (note: here we're using new, not cumulative cases) using the +[`epidatr`](https://github.com/cmu-delphi/epidatr) package, and then convert +this to `epi_df` format. + +```{r, message = FALSE, warning=FALSE} +library(epidatr) +library(epiprocess) +library(dplyr) +``` + +The data is fetched with the following query: + +```{r, message = FALSE, eval=F} +edf <- pub_covidcast( + source = "jhu-csse", + signals = "confirmed_incidence_num", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx,ga,pa", + time_values = epirange(20200301, 20211231), +) %>% + select(geo_value, time_value, cases = value) %>% + arrange(geo_value, time_value) %>% + as_epi_df() +``` + +The data has 2,684 rows and 3 columns. + +```{r, echo=FALSE} +data(jhu_csse_daily_subset) +edf <- jhu_csse_daily_subset %>% + select(geo_value, time_value, cases) %>% + arrange(geo_value, time_value) %>% + as_epi_df() +``` + +## Optimized rolling mean and sums + +For the two most common sliding operations, we offer two optimized versions: +`epi_slide_mean()` and `epi_slide_sum()`. This example gets the 7-day trailing +average of the daily cases. Note that the name of the column(s) that we want to +average is specified as the first argument of `epi_slide_mean()`. + +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide_mean("cases", .window_size = 7, na.rm = TRUE) %>% + ungroup() %>% + head(10) +``` + +Note that we passed `na.rm = TRUE` to `data.table::frollmean()` via `...` to +`epi_slide_mean`. + +The following computes the 7-day trailing sum of daily cases (and passed `na.rm` +to `data.table::frollsum()` similarly): + +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide_sum("cases", .window_size = 7, na.rm = TRUE) %>% + ungroup() %>% + head(10) +``` + +## General sliding with a formula + +The previous computations can also be performed using `epi_slide()`, which can +be used for more general sliding computations (but is much slower for the +specific cases of mean and sum). + +The same 7-day trailing average of daily cases can be computed by passing in a +formula for the first argument of `epi_slide()`: + +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide(~ mean(.x$cases, na.rm = TRUE), .window_size = 7) %>% + ungroup() %>% + head(10) +``` + +If your formula returns a data.frame, then the columns of the data.frame +will be unpacked into the resulting `epi_df`. For example, the following +computes the 7-day trailing average of daily cases and the 7-day trailing sum of +daily cases: + +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide( + ~ data.frame(cases_mean = mean(.x$cases, na.rm = TRUE), cases_sum = sum(.x$cases, na.rm = TRUE)), + .window_size = 7 + ) %>% + ungroup() %>% + head(10) +``` + +Note that this formula has access to all non-grouping columns present in the +original `epi_df` object and must refer to them with the prefix `.x$...`. As we +can see, the function `epi_slide()` returns an `epi_df` object with a new column +appended that contains the results (from sliding), named `slide_value` as the +default. + +Some other information is available in additional variables: + +* `.group_key` is a one-row tibble containing the values of the grouping + variables for the associated group +* `.ref_time_value` is the reference time value the time window was based on + +```{r} +# Returning geo_value in the formula +edf %>% + group_by(geo_value) %>% + epi_slide(~ .x$geo_value[[1]], .window_size = 7) %>% + ungroup() %>% + head(10) + +# Returning time_value in the formula +edf %>% + group_by(geo_value) %>% + epi_slide(~ .x$time_value[[1]], .window_size = 7) %>% + ungroup() %>% + head(10) +``` + +While the computations above do not look very useful, these can be used as +building blocks for computations that do something different depending on the +geo_value or ref_time_value. + +## Slide the tidy way + +Perhaps the most convenient way to setup a computation in `epi_slide()` is to +pass in an expression for tidy evaluation. In this case, we can simply define +the name of the new column directly as part of the expression, setting it equal +to a computation in which we can access any columns of `.x` by name, just as we +would in a call to `dplyr::mutate()`, or any of the `dplyr` verbs. For example: + +```{r} +slide_output <- edf %>% + group_by(geo_value) %>% + epi_slide(cases_7dav = mean(cases, na.rm = TRUE), .window_size = 7) %>% + ungroup() %>% + head(10) +``` + +In addition to referring to individual columns by name, you can refer to +`epi_df` time window as `.x` (`.group_key` and `.ref_time_value` are still +available). Also, the tidyverse "pronouns" `.data` and `.env` can also be used +if you need distinguish between the data and environment. + +As a simple sanity check, we visualize the 7-day trailing averages computed on +top of the original counts: + +```{r, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} +library(ggplot2) +theme_set(theme_bw()) + +ggplot(slide_output, aes(x = time_value)) + + geom_col(aes(y = cases, fill = geo_value), alpha = 0.5, show.legend = FALSE) + + geom_line(aes(y = cases_7dav, col = geo_value), show.legend = FALSE) + + facet_wrap(~geo_value, scales = "free_y") + + scale_x_date(minor_breaks = "month", date_labels = "%b %y") + + labs(x = "Date", y = "Reported COVID-19 cases") +``` + +As we can see from the top right panel, it looks like Texas moved to weekly +reporting of COVID-19 cases in summer of 2021. + +## Slide with a function + +We can also pass a function to the second argument in `epi_slide()`. In this +case, the passed function `.f` must have the form `function(x, g, t, ...)`, +where + +- "x" is an epi_df with the same column names as the archive's `DT`, minus + the `version` column +- "g" is a one-row tibble containing the values of the grouping variables +for the associated group +- "t" is the ref_time_value for the current window +- "..." are additional arguments + +Recreating the last example of a 7-day trailing average: + +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide(function(x, g, t) mean(x$cases, na.rm = TRUE), .window_size = 7) %>% + ungroup() %>% + head(10) +``` + +## Running a forecaster on a sliding window of data + +The natural next step is to use the sliding window to forecast future values. +However to do this correctly, we should make sure that our data is historically +accurate. The data structure we use for that is the `epi_archive` and the +analogous slide function is `epix_slide()`. To read further along this train of +thought, see the ["Working with archives"]() vignette. + + +## Geographic aggregation + +We do not yet provide tools for geographic aggregation in `epiprocess`. However, +we have some Python geocoding utilities available. Reach out to us if this is +functionality you would like to see us add to `epiprocess`. + +## Attribution + +The `percent_cli` data is a modified part of the [COVIDcast Epidata API Doctor +Visits +data](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html). +This dataset is licensed under the terms of the [Creative Commons Attribution +4.0 International license](https://creativecommons.org/licenses/by/4.0/). +Copyright Delphi Research Group at Carnegie Mellon University 2020. + +This document contains a dataset that is a modified part of the [COVID-19 Data +Repository by the Center for Systems Science and Engineering (CSSE) at Johns +Hopkins University](https://github.com/CSSEGISandData/COVID-19) as [republished +in the COVIDcast Epidata +API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html). +This data set is licensed under the terms of the [Creative Commons Attribution +4.0 International license](https://creativecommons.org/licenses/by/4.0/) by the +Johns Hopkins University on behalf of its Center for Systems Science in +Engineering. Copyright Johns Hopkins University 2020. + +[From the COVIDcast Epidata +API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html): +These signals are taken directly from the JHU CSSE [COVID-19 GitHub +repository](https://github.com/CSSEGISandData/COVID-19) without changes. + diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 269f80d7..6787a0be 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -25,21 +25,21 @@ ggplot2::theme_set(ggplot2::theme_bw()) This vignette provides a brief introduction to the `epiprocess` package. We will do the following: -- Get data into `epi_df` format +- Get data into `epi_df()` format - Plot the data - Perform basic signal processing tasks (rolling average, cumulative sum, etc.) - Detect outliers in the data and apply corrections - Calculate the growth rate of the data -- Get data into `epi_archive` format +- Get data into `epi_archive()` format - Analyze the revision history of the data -- Demonstrate the use of `epi_archive` for backtesting +- Demonstrate the use of `epi_archive()` for backtesting ## Getting data into `epi_df` format -We'll start by getting data into `epi_df` format, which is just a tibble with a -bit of special structure. See `as_epi_df()` for more details. As an example, we -will get COVID-19 confirmed cumulative case data from JHU CSSE for California, -Florida, New York, and Texas, from March 1, 2020 to January 31, 2022. +We'll start by getting data into `epi_df()` format, which is just a tibble with +a bit of special structure. As an example, we will get COVID-19 confirmed +cumulative case data from JHU CSSE for California, Florida, New York, and Texas, +from March 1, 2020 to January 31, 2022. ```{r, results=FALSE, warning=FALSE, message=FALSE} library(epiprocess) @@ -62,81 +62,94 @@ df <- pub_covidcast( df ``` -The tibble returned by `epidatr::pub_covidcast()` has the columns required -for an `epi_df` object, `geo_value` and `time_value`, so we can convert it -directly to an `epi_df` object using `as_epi_df()`. +The tibble returned by `epidatr::pub_covidcast()` has the columns required for +an `epi_df` object, `geo_value` and `time_value`, so we can convert it directly +to an `epi_df` object using `as_epi_df()`. ```{r, message = FALSE} -edf <- as_epi_df(df) +edf <- as_epi_df(df) %>% + arrange_canonical() %>% + group_by(geo_value) %>% + mutate(cases_daily = cases_cumulative - lag(cases_cumulative, default = 0)) edf ``` -In brief, we can think of an `epi_df` object as snapshot of an epidemiological data set -as it was at a particular point in time (recorded in the `as_of` attribute). We can easily plot the -data using the `autoplot()` method (which is a convenience wrapper to `ggplot2`). +In brief, we can think of an `epi_df` object as snapshot of an epidemiological +data set as it was at a particular point in time (recorded in the `as_of` +attribute). We can easily plot the data using the `autoplot()` method (which is +a convenience wrapper to `ggplot2`). ```{r, message = FALSE, warning = FALSE} edf %>% autoplot(cases_cumulative) ``` -

+We can compute the 7 day moving average of the confirmed daily cases for each +geo_value by using the `epi_slide_mean()` function. For a more in-depth guide to +sliding, see `vignette("slide")`. -As another example, here we will import the daily new (not cumulative) SARS -cases in Canada in 2003, from the -[outbreaks](https://github.com/reconverse/outbreaks) package: +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide_mean(cases_daily, .window_size = 7, na.rm = TRUE) +``` + +We can compute the growth rate of the confirmed cumulative cases for each +geo_value. For a more in-depth guide to growth rates, see `vignette("growth_rate")`. ```{r} -edf <- outbreaks::sars_canada_2003 %>% - mutate(geo_value = "ca") %>% - select(geo_value, time_value = date, starts_with("cases")) %>% - pivot_longer(starts_with("cases"), names_to = "type") %>% - mutate(type = substring(type, 7)) %>% - as_epi_df(other_keys = "type") +edf %>% + group_by(geo_value) %>% + mutate(cases_growth = growth_rate(x = time_value, y = cases_cumulative, method = "rel_change", h = 7)) +``` -head(edf) +Detect outliers in daily reported cases for each geo_value. For a more in-depth +guide to outlier detection, see `vignette("outliers")`. +```{r} edf %>% - autoplot() + group_by(geo_value) %>% + mutate(outlier_info = detect_outlr(x = time_value, y = cases_daily)) %>% + ungroup() ``` -Get confirmed cases of Ebola in Sierra Leone from 2014 to 2015 by province and -date of onset, prepared from line list data from the same package: - -```{r, fig.width = 9, fig.height = 6} -edf <- outbreaks::ebola_sierraleone_2014 %>% - select(district, date_of_onset, status) %>% - mutate(province = case_when( - district %in% c("Kailahun", "Kenema", "Kono") ~ - "Eastern", - district %in% c( - "Bombali", "Kambia", "Koinadugu", "Port Loko", - "Tonkolili" - ) ~ - "Northern", - district %in% c("Bo", "Bonthe", "Moyamba", "Pujehun") ~ - "Sourthern", - district %in% c("Western Rural", "Western Urban") ~ - "Western" - )) %>% - group_by(geo_value = province, time_value = date_of_onset) %>% - summarise(cases = sum(status == "confirmed"), .groups = "drop") %>% - complete(geo_value, - time_value = full_seq(time_value, period = 1), - fill = list(cases = 0) - ) %>% - as_epi_df() - -head(edf) +Add a column to the epi_df object with the daily deaths for each geo_value and +compute the correlations between cases and deaths for each geo_value. For a more +in-depth guide to correlations, see `vignette("correlations")`. +```{r cache=TRUE} +df <- pub_covidcast( + source = "jhu-csse", + signals = "deaths_incidence_num", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200301, 20220131), +) %>% + select(geo_value, time_value, deaths_daily = value) %>% + as_epi_df() %>% + arrange_canonical() +edf <- inner_join(edf, df, by = c("geo_value", "time_value")) edf %>% - autoplot() + group_by(geo_value) %>% + epi_slide_mean(deaths_daily, .window_size = 7, na.rm = TRUE) %>% + epi_cor(cases_daily, deaths_daily) ``` -
-## Attribution -This document contains a dataset that is a modified part of the [COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University](https://github.com/CSSEGISandData/COVID-19) as [republished in the COVIDcast Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html). This data set is licensed under the terms of the [Creative Commons Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/) by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. Copyright Johns Hopkins University 2020. +## Data attribution + +This document contains a dataset that is a modified part of the [COVID-19 Data +Repository by the Center for Systems Science and Engineering (CSSE) at Johns +Hopkins University](https://github.com/CSSEGISandData/COVID-19) as [republished +in the COVIDcast Epidata +API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html). +This data set is licensed under the terms of the [Creative Commons Attribution +4.0 International license](https://creativecommons.org/licenses/by/4.0/) by the +Johns Hopkins University on behalf of its Center for Systems Science in +Engineering. Copyright Johns Hopkins University 2020. -[From the COVIDcast Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html): - These signals are taken directly from the JHU CSSE [COVID-19 GitHub repository](https://github.com/CSSEGISandData/COVID-19) without changes. +[From the COVIDcast Epidata +API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html): +These signals are taken directly from the JHU CSSE [COVID-19 GitHub +repository](https://github.com/CSSEGISandData/COVID-19) without changes. diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index acbb53ee..8f45d35d 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -7,6 +7,15 @@ vignette: > %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +ggplot2::theme_set(ggplot2::theme_bw()) +``` + A basic way of assessing growth in a signal is to look at its relative change over two neighboring time windows. The `epiprocess` package provides a function `growth_rate()` to compute such relative changes, as well as more sophisticated diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index 1a2cfa41..bde6db0a 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -7,9 +7,18 @@ vignette: > %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +ggplot2::theme_set(ggplot2::theme_bw()) +``` + This vignette describes functionality for detecting and correcting outliers in signals in the `detect_outlr()` and `correct_outlr()` functions provided in the -`epiprocess` package. These functions is designed to be modular and extendable, +`epiprocess` package. These functions are designed to be modular and extendable, so that you can define your own outlier detection and correction routines and apply them to `epi_df` objects. We'll demonstrate this using state-level daily reported COVID-19 case counts from FL and NJ. diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd deleted file mode 100644 index 0d8436f7..00000000 --- a/vignettes/slide.Rmd +++ /dev/null @@ -1,231 +0,0 @@ ---- -title: Slide a computation over signal values -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Slide a computation over signal values} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -A central tool in the `epiprocess` package is `epi_slide()`, which is based on -the powerful functionality provided in the -[`slider`](https://cran.r-project.org/web/packages/slider) package. In -`epiprocess`, to "slide" means to apply a computation---represented as a -function or formula---over a sliding/rolling data window. The function always -applies the slide inside each group and the grouping is assumed to be across all -group keys of the `epi_df` (this is the grouping used by default if you do not -group the `epi_df` with a `group_by()`). - -By default, the `.window_size` units depend on the `time_type` of the `epi_df`, -which is determined from the types in the `time_value` column of the `epi_df`. -See the "Details" in `epi_slide()` for more. - -As in getting started guide, we'll fetch daily reported COVID-19 cases from CA, -FL, NY, and TX (note: here we're using new, not cumulative cases) using the -[`epidatr`](https://github.com/cmu-delphi/epidatr) package, and then convert -this to `epi_df` format. - -```{r, message = FALSE, warning=FALSE} -library(epidatr) -library(epiprocess) -library(dplyr) -``` - -The data is fetched with the following query: - -```{r, message = FALSE, eval=F} -edf <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_incidence_num", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx,ga,pa", - time_values = epirange(20200301, 20211231), -) %>% - select(geo_value, time_value, cases = value) %>% - arrange(geo_value, time_value) %>% - as_epi_df() -``` - -The data has 2,684 rows and 3 columns. - -```{r, echo=FALSE} -data(jhu_csse_daily_subset) -edf <- jhu_csse_daily_subset %>% - select(geo_value, time_value, cases) %>% - arrange(geo_value, time_value) %>% - as_epi_df() -``` - -## Optimized rolling mean and sums - -For the two most common sliding operations, we offer two optimized versions: -`epi_slide_mean()` and `epi_slide_sum()`. This example gets the 7-day trailing -average of the daily cases. Note that the name of the column(s) that we want to -average is specified as the first argument of `epi_slide_mean()`. - -```{r} -edf %>% - group_by(geo_value) %>% - epi_slide_mean("cases", .window_size = 7, na.rm = TRUE) %>% - ungroup() %>% - head(10) -``` - -Note that we passed `na.rm = TRUE` to `data.table::frollmean()` via `...` to -`epi_slide_mean`. - -The following computes the 7-day trailing sum of daily cases (and passed `na.rm` -to `data.table::frollsum()` similarly): - -```{r} -edf %>% - group_by(geo_value) %>% - epi_slide_sum("cases", .window_size = 7, na.rm = TRUE) %>% - ungroup() %>% - head(10) -``` - -## General sliding with a formula - -The previous computations can also be performed using `epi_slide()`, which can -be used for more general sliding computations (but is much slower for the -specific cases of mean and sum). - -The same 7-day trailing average of daily cases can be computed by passing in a -formula for the first argument of `epi_slide()`: - -```{r} -edf %>% - group_by(geo_value) %>% - epi_slide(~ mean(.x$cases, na.rm = TRUE), .window_size = 7) %>% - ungroup() %>% - head(10) -``` - -If your formula returns a data.frame, then the columns of the data.frame -will be unpacked into the resulting `epi_df`. For example, the following -computes the 7-day trailing average of daily cases and the 7-day trailing sum of -daily cases: - -```{r} -edf %>% - group_by(geo_value) %>% - epi_slide( - ~ data.frame(cases_mean = mean(.x$cases, na.rm = TRUE), cases_sum = sum(.x$cases, na.rm = TRUE)), - .window_size = 7 - ) %>% - ungroup() %>% - head(10) -``` - -Note that this formula has access to all non-grouping columns present in the -original `epi_df` object and must refer to them with the prefix `.x$...`. As we -can see, the function `epi_slide()` returns an `epi_df` object with a new column -appended that contains the results (from sliding), named `slide_value` as the -default. - -Some other information is available in additional variables: - -* `.group_key` is a one-row tibble containing the values of the grouping - variables for the associated group -* `.ref_time_value` is the reference time value the time window was based on - -```{r} -# Returning geo_value in the formula -edf %>% - group_by(geo_value) %>% - epi_slide(~ .x$geo_value[[1]], .window_size = 7) %>% - ungroup() %>% - head(10) - -# Returning time_value in the formula -edf %>% - group_by(geo_value) %>% - epi_slide(~ .x$time_value[[1]], .window_size = 7) %>% - ungroup() %>% - head(10) -``` - -While the computations above do not look very useful, these can be used as -building blocks for computations that do something different depending on the -geo_value or ref_time_value. - -## Slide the tidy way - -Perhaps the most convenient way to setup a computation in `epi_slide()` is to -pass in an expression for tidy evaluation. In this case, we can simply define -the name of the new column directly as part of the expression, setting it equal -to a computation in which we can access any columns of `.x` by name, just as we -would in a call to `dplyr::mutate()`, or any of the `dplyr` verbs. For example: - -```{r} -slide_output <- edf %>% - group_by(geo_value) %>% - epi_slide(cases_7dav = mean(cases, na.rm = TRUE), .window_size = 7) %>% - ungroup() %>% - head(10) -``` - -In addition to referring to individual columns by name, you can refer to -`epi_df` time window as `.x` (`.group_key` and `.ref_time_value` are still -available). Also, the tidyverse "pronouns" `.data` and `.env` can also be used -if you need distinguish between the data and environment. - -As a simple sanity check, we visualize the 7-day trailing averages computed on -top of the original counts: - -```{r, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} -library(ggplot2) -theme_set(theme_bw()) - -ggplot(slide_output, aes(x = time_value)) + - geom_col(aes(y = cases, fill = geo_value), alpha = 0.5, show.legend = FALSE) + - geom_line(aes(y = cases_7dav, col = geo_value), show.legend = FALSE) + - facet_wrap(~geo_value, scales = "free_y") + - scale_x_date(minor_breaks = "month", date_labels = "%b %y") + - labs(x = "Date", y = "Reported COVID-19 cases") -``` - -As we can see from the top right panel, it looks like Texas moved to weekly -reporting of COVID-19 cases in summer of 2021. - -## Slide with a function - -We can also pass a function to the second argument in `epi_slide()`. In this -case, the passed function `.f` must have the form `function(x, g, t, ...)`, -where - -- "x" is an epi_df with the same column names as the archive's `DT`, minus - the `version` column -- "g" is a one-row tibble containing the values of the grouping variables -for the associated group -- "t" is the ref_time_value for the current window -- "..." are additional arguments - -Recreating the last example of a 7-day trailing average: - -```{r} -edf %>% - group_by(geo_value) %>% - epi_slide(function(x, g, t) mean(x$cases, na.rm = TRUE), .window_size = 7) %>% - ungroup() %>% - head(10) -``` - -## Running a forecaster on a sliding window of data - -The natural next step is to use the sliding window to forecast future values. -However to do this correctly, we should make sure that our data is historically -accurate. The data structure we use for that is the `epi_archive` and the -analogous slide function is `epix_slide()`. To read further along this train of -thought, see the ["Working with archives"]() vignette. - -## Attribution - -The `percent_cli` data is a modified part of the [COVIDcast Epidata API Doctor Visits data](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html). This dataset is licensed under the terms of the [Creative Commons Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/). Copyright Delphi Research Group at Carnegie Mellon University 2020. - -This document contains a dataset that is a modified part of the [COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University](https://github.com/CSSEGISandData/COVID-19) as [republished in the COVIDcast Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html). This data set is licensed under the terms of the [Creative Commons Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/) by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. Copyright Johns Hopkins University 2020. - -[From the COVIDcast Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html): -These signals are taken directly from the JHU CSSE [COVID-19 GitHub repository](https://github.com/CSSEGISandData/COVID-19) without changes.