From 2c5d29a45e8da03fd2fee4066addc7a9b3206218 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:03:13 -0400 Subject: [PATCH 01/59] import epidatasets --- DESCRIPTION | 2 ++ man/epiprocess.Rd | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 18aec43f..7eb05ac6 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,6 +30,7 @@ Imports: cli, data.table, dplyr (>= 1.0.8), + epidatasets, genlasso, glue, ggplot2, @@ -58,6 +59,7 @@ Suggests: VignetteBuilder: knitr Remotes: + cmu-delphi/epidatasets, cmu-delphi/epidatr, reconverse/outbreaks, glmgen/genlasso diff --git a/man/epiprocess.Rd b/man/epiprocess.Rd index a3e98366..f6345cbe 100644 --- a/man/epiprocess.Rd +++ b/man/epiprocess.Rd @@ -2,8 +2,8 @@ % Please edit documentation in R/epiprocess.R \docType{package} \name{epiprocess} -\alias{epiprocess} \alias{epiprocess-package} +\alias{epiprocess} \title{epiprocess: Tools for basic signal processing in epidemiology} \description{ This package introduces a common data structure for epidemiological data sets From 3f4a07d6b4a0967bbcd0997af2d02c50e6c57c5b Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:14:08 -0400 Subject: [PATCH 02/59] remove locally-generated data --- NAMESPACE | 1 - R/data.R | 165 ----------------------- data-raw/archive_cases_dv_subset.R | 42 ------ data-raw/incidence_num_outlier_example.R | 18 --- data-raw/jhu_csse_county_level_subset.R | 24 ---- data-raw/jhu_csse_daily_subset.R | 61 --------- data/incidence_num_outlier_example.rda | Bin 3213 -> 0 bytes data/jhu_csse_county_level_subset.rda | Bin 20371 -> 0 bytes data/jhu_csse_daily_subset.rda | Bin 81174 -> 0 bytes man/archive_cases_dv_subset.Rd | 56 -------- man/incidence_num_outlier_example.Rd | 48 ------- man/jhu_csse_county_level_subset.Rd | 52 ------- man/jhu_csse_daily_subset.Rd | 57 -------- 13 files changed, 524 deletions(-) delete mode 100644 data-raw/archive_cases_dv_subset.R delete mode 100644 data-raw/incidence_num_outlier_example.R delete mode 100644 data-raw/jhu_csse_county_level_subset.R delete mode 100644 data-raw/jhu_csse_daily_subset.R delete mode 100644 data/incidence_num_outlier_example.rda delete mode 100644 data/jhu_csse_county_level_subset.rda delete mode 100644 data/jhu_csse_daily_subset.rda delete mode 100644 man/archive_cases_dv_subset.Rd delete mode 100644 man/incidence_num_outlier_example.Rd delete mode 100644 man/jhu_csse_county_level_subset.Rd delete mode 100644 man/jhu_csse_daily_subset.Rd diff --git a/NAMESPACE b/NAMESPACE index fc6aaf74..1c3ac6c8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,7 +45,6 @@ S3method(ungroup,epi_df) S3method(ungroup,grouped_epi_archive) S3method(unnest,epi_df) export("%>%") -export(archive_cases_dv_subset) export(arrange) export(arrange_canonical) export(as_epi_archive) diff --git a/R/data.R b/R/data.R index ec677547..7776690d 100644 --- a/R/data.R +++ b/R/data.R @@ -1,94 +1,3 @@ -#' Subset of JHU daily state cases and deaths -#' -#' This data source of confirmed COVID-19 cases and deaths -#' is based on reports made available by the Center for -#' Systems Science and Engineering at Johns Hopkins University. -#' This example data ranges from Mar 1, 2020 to Dec 31, 2021, and is limited to -#' California, Florida, Texas, New York, Georgia, and Pennsylvania. -#' -#' @format A tibble with 4026 rows and 6 variables: -#' \describe{ -#' \item{geo_value}{the geographic value associated with each row -#' of measurements.} -#' \item{time_value}{the time value associated with each row of measurements.} -#' \item{case_rate_7d_av}{7-day average signal of number of new -#' confirmed COVID-19 cases per 100,000 population, daily} -#' \item{death_rate_7d_av}{7-day average signal of number of new confirmed -#' deaths due to COVID-19 per 100,000 population, daily} -#' \item{cases}{Number of new confirmed COVID-19 cases, daily} -#' \item{cases_7d_av}{7-day average signal of number of new confirmed -#' COVID-19 cases, daily} -#' } -#' @source This object contains a modified part of the -#' \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository -#' by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins -#' University} as -#' \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished -#' in the COVIDcast Epidata API}. This data set is licensed under the terms of -#' the \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons -#' Attribution 4.0 International license} by the Johns Hopkins University on -#' behalf of its Center for Systems Science in Engineering. Copyright Johns -#' Hopkins University 2020. -#' -#' Modifications: -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From -#' the COVIDcast Epidata API}: The case signal is taken directly from the JHU -#' CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub -#' repository}. The rate signals were computed by Delphi using Census -#' population data. The 7-day average signals were computed by Delphi by -#' calculating moving averages of the preceding 7 days, so the signal for June -#' 7 is the average of the underlying data for June 1 through 7, inclusive. -#' * Furthermore, the data has been limited to a very small number of rows, the -#' signal names slightly altered, and formatted into a tibble. -"jhu_csse_daily_subset" - - -#' Subset of daily doctor visits and cases in archive format -#' -#' This data source is based on information about outpatient visits, -#' provided to us by health system partners, and also contains confirmed -#' COVID-19 cases based on reports made available by the Center for -#' Systems Science and Engineering at Johns Hopkins University. -#' This example data ranges from June 1, 2020 to Dec 1, 2021, and -#' is also limited to California, Florida, Texas, and New York. -#' -#' @format An `epi_archive` data format. The data table DT has 129,638 rows and 5 columns: -#' \describe{ -#' \item{geo_value}{the geographic value associated with each row of measurements.} -#' \item{time_value}{the time value associated with each row of measurements.} -#' \item{version}{the time value specifying the version for each row of measurements. } -#' \item{percent_cli}{percentage of doctor’s visits with CLI (COVID-like -#' illness) computed from medical insurance claims} -#' \item{case_rate_7d_av}{7-day average signal of number of new confirmed -#' deaths due to COVID-19 per 100,000 population, daily} -#' } -#' @source -#' This object contains a modified part of the -#' \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by -#' the Center for Systems Science and Engineering (CSSE) at Johns Hopkins -#' University} as -#' \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished -#' in the COVIDcast Epidata API}. This data set is licensed under the terms of -#' the \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons -#' Attribution 4.0 International license} by Johns Hopkins University on behalf -#' of its Center for Systems Science in Engineering. Copyright Johns Hopkins -#' University 2020. -#' -#' Modifications: -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From -#' the COVIDcast Doctor Visits API}: The signal `percent_cli` is taken -#' directly from the API without changes. -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From -#' the COVIDcast Epidata API}: `case_rate_7d_av` signal was computed by Delphi -#' from the original JHU-CSSE data by calculating moving averages of the -#' preceding 7 days, so the signal for June 7 is the average of the underlying -#' data for June 1 through 7, inclusive. -#' * Furthermore, the data is a subset of the full dataset, the signal names -#' slightly altered, and formatted into a tibble. -#' -#' @export -"archive_cases_dv_subset" - #' Detect whether `pkgload` is unregistering a package (with some unlikely false positives) #' #' More precisely, detects the presence of a call to an `unregister` or @@ -215,77 +124,3 @@ delayed_assign_with_unregister_awareness( "archive_cases_dv_subset", as_epi_archive(archive_cases_dv_subset_dt, compactify = FALSE) ) - -#' Subset of JHU daily cases from California and Florida -#' -#' This data source of confirmed COVID-19 cases -#' is based on reports made available by the Center for -#' Systems Science and Engineering at Johns Hopkins University. -#' This example data is a snapshot as of Oct 28, 2021 and captures the cases -#' from June 1, 2020 to May 31, 2021 -#' and is limited to California and Florida. -#' -#' @format A tibble with 730 rows and 3 variables: -#' \describe{ -#' \item{geo_value}{the geographic value associated with each row of measurements.} -#' \item{time_value}{the time value associated with each row of measurements.} -#' \item{cases}{Number of new confirmed COVID-19 cases, daily} -#' } -#' @source This object contains a modified part of the -#' \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by -#' the Center for Systems Science and Engineering (CSSE) at Johns Hopkins -#' University} as -#' \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished -#' in the COVIDcast Epidata API}. This data set is licensed under the terms of -#' the \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons -#' Attribution 4.0 International license} by the Johns Hopkins University on -#' behalf of its Center for Systems Science in Engineering. Copyright Johns -#' Hopkins University 2020. -#' -#' Modifications: -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From -#' the COVIDcast Epidata API}: These signals are taken directly from the JHU -#' CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub -#' repository} without changes. -#' * Furthermore, the data has been limited to a very small number of rows, the -#' signal names slightly altered, and formatted into a tibble. -"incidence_num_outlier_example" - -#' Subset of JHU daily cases from counties in Massachusetts and Vermont -#' -#' This data source of confirmed COVID-19 cases and deaths -#' is based on reports made available by the Center for -#' Systems Science and Engineering at Johns Hopkins University. -#' This example data ranges from Mar 1, 2020 to Dec 31, 2021, -#' and is limited to Massachusetts and Vermont. -#' -#' @format A tibble with 16,212 rows and 5 variables: -#' \describe{ -#' \item{geo_value}{the geographic value associated with each row of measurements.} -#' \item{time_value}{the time value associated with each row of measurements.} -#' \item{cases}{Number of new confirmed COVID-19 cases, daily} -#' \item{county_name}{the name of the county} -#' \item{state_name}{the full name of the state} -#' } -#' @source This object contains a modified part of the -#' \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by -#' the Center for Systems Science and Engineering (CSSE) at Johns Hopkins -#' University} as -#' \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished -#' in the COVIDcast Epidata API}. This data set is licensed under the terms of -#' the -#' \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} -#' by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. -#' Copyright Johns Hopkins University 2020. -#' -#' Modifications: -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From -#' the COVIDcast Epidata API}: These signals are taken directly from the JHU -#' CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub -#' repository} without changes. The 7-day average signals are computed by -#' Delphi by calculating moving averages of the preceding 7 days, so the -#' signal for June 7 is the average of the underlying data for June 1 through -#' 7, inclusive. -#' * Furthermore, the data has been limited to a very small number of rows, the -#' signal names slightly altered, and formatted into a tibble. -"jhu_csse_county_level_subset" diff --git a/data-raw/archive_cases_dv_subset.R b/data-raw/archive_cases_dv_subset.R deleted file mode 100644 index 5ba7ac4b..00000000 --- a/data-raw/archive_cases_dv_subset.R +++ /dev/null @@ -1,42 +0,0 @@ -library(epidatr) -library(epiprocess) -library(data.table) -library(dplyr) - -dv_subset <- pub_covidcast( - source = "doctor-visits", - signals = "smoothed_adj_cli", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx", - time_values = epirange(20200601, 20211201), - issues = epirange(20200601, 20211201) -) %>% - select(geo_value, time_value, version = issue, percent_cli = value) %>% - # We're using compactify=FALSE here and below to avoid some testthat test - # failures on tests that were based on a non-compactified version. - as_epi_archive(compactify = FALSE) - -case_rate_subset <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_7dav_incidence_prop", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx", - time_values = epirange(20200601, 20211201), - issues = epirange(20200601, 20211201) -) %>% - select(geo_value, time_value, version = issue, case_rate_7d_av = value) %>% - as_epi_archive(compactify = FALSE) - -archive_cases_dv_subset <- epix_merge(dv_subset, case_rate_subset, - sync = "locf", - compactify = FALSE -) - -# If we directly store an epi_archive R6 object as data, it will store its class -# implementation there as well. To prevent mismatches between these stored -# implementations and the latest class definition, don't store them as R6 -# objects; store the DT and construct the R6 object on request. -archive_cases_dv_subset_dt <- archive_cases_dv_subset$DT -usethis::use_data(archive_cases_dv_subset_dt, overwrite = TRUE, internal = TRUE) diff --git a/data-raw/incidence_num_outlier_example.R b/data-raw/incidence_num_outlier_example.R deleted file mode 100644 index a5cb4d89..00000000 --- a/data-raw/incidence_num_outlier_example.R +++ /dev/null @@ -1,18 +0,0 @@ -library(epidatr) -library(epiprocess) -library(dplyr) -library(tidyr) - -incidence_num_outlier_example <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_incidence_num", - geo_type = "state", - time_type = "day", - geo_values = "fl,nj", - time_values = epirange(20200601, 20210531), - as_of = 20211028 -) %>% - select(geo_value, time_value, cases = value) %>% - as_epi_df() - -usethis::use_data(incidence_num_outlier_example, overwrite = TRUE) diff --git a/data-raw/jhu_csse_county_level_subset.R b/data-raw/jhu_csse_county_level_subset.R deleted file mode 100644 index faed75e8..00000000 --- a/data-raw/jhu_csse_county_level_subset.R +++ /dev/null @@ -1,24 +0,0 @@ -library(epidatr) -library(covidcast) -library(epiprocess) -library(dplyr) - -# Use covidcast::county_census to get the county and state names -y <- covidcast::county_census %>% - 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 -jhu_csse_county_level_subset <- 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) %>% - full_join(y, by = "geo_value") %>% - as_epi_df() - -usethis::use_data(jhu_csse_county_level_subset, overwrite = TRUE) diff --git a/data-raw/jhu_csse_daily_subset.R b/data-raw/jhu_csse_daily_subset.R deleted file mode 100644 index affeb193..00000000 --- a/data-raw/jhu_csse_daily_subset.R +++ /dev/null @@ -1,61 +0,0 @@ -library(epidatr) -library(epiprocess) -library(dplyr) - -confirmed_incidence_num <- 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) - -confirmed_7dav_incidence_num <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_7dav_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_7d_av = value) %>% - arrange(geo_value, time_value) - -confirmed_7dav_incidence_prop <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_7dav_incidence_prop", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx,ga,pa", - time_values = epirange(20200301, 20211231), -) %>% - select(geo_value, time_value, case_rate_7d_av = value) %>% - arrange(geo_value, time_value) - -deaths_7dav_incidence_prop <- pub_covidcast( - source = "jhu-csse", - signals = "deaths_7dav_incidence_prop", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx,ga,pa", - time_values = epirange(20200301, 20211231), -) %>% - select(geo_value, time_value, death_rate_7d_av = value) %>% - arrange(geo_value, time_value) - -jhu_csse_daily_subset <- confirmed_incidence_num %>% - full_join(confirmed_7dav_incidence_num, - by = c("geo_value", "time_value") - ) %>% - full_join(confirmed_7dav_incidence_prop, - by = c("geo_value", "time_value") - ) %>% - full_join(deaths_7dav_incidence_prop, - by = c("geo_value", "time_value") - ) %>% - as_epi_df() - -usethis::use_data(jhu_csse_daily_subset, overwrite = TRUE) diff --git a/data/incidence_num_outlier_example.rda b/data/incidence_num_outlier_example.rda deleted file mode 100644 index e898b5eaa6b359ce9ab2bc6aa435a4a071eab54e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3213 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 b0Y9{K1Sk&auyI=J=h0v@?K2sN-;aL)=ZuL` diff --git a/data/jhu_csse_county_level_subset.rda b/data/jhu_csse_county_level_subset.rda deleted file mode 100644 index aca0983debba2ca6617234f82193e2a9dc63294c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20371 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

9Zq_+dd1od4~t8 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 Date: Fri, 30 Aug 2024 15:10:18 -0400 Subject: [PATCH 03/59] reexport data from epidatasets --- NAMESPACE | 5 ++ R/reexports.R | 93 ++++++++++++++++++++++++++++ _pkgdown.yml | 8 ++- man/archive_cases_dv_subset_dt.Rd | 66 ++++++++++++++++++++ man/cases_deaths_subset.Rd | 77 +++++++++++++++++++++++ man/covid_incidence_county_subset.Rd | 72 +++++++++++++++++++++ man/covid_incidence_outliers.Rd | 64 +++++++++++++++++++ man/jhu_confirmed_cumulative_num.Rd | 65 +++++++++++++++++++ 8 files changed, 447 insertions(+), 3 deletions(-) create mode 100644 man/archive_cases_dv_subset_dt.Rd create mode 100644 man/cases_deaths_subset.Rd create mode 100644 man/covid_incidence_county_subset.Rd create mode 100644 man/covid_incidence_outliers.Rd create mode 100644 man/jhu_confirmed_cumulative_num.Rd diff --git a/NAMESPACE b/NAMESPACE index 1c3ac6c8..a7d65d51 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,14 +45,18 @@ S3method(ungroup,epi_df) S3method(ungroup,grouped_epi_archive) S3method(unnest,epi_df) export("%>%") +export(archive_cases_dv_subset_dt) export(arrange) export(arrange_canonical) export(as_epi_archive) export(as_epi_df) export(as_tsibble) export(autoplot) +export(cases_deaths_subset) export(clone) export(complete) +export(covid_incidence_county_subset) +export(covid_incidence_outliers) export(detect_outlr) export(detect_outlr_rm) export(detect_outlr_stl) @@ -75,6 +79,7 @@ export(growth_rate) export(guess_period) export(is_epi_df) export(is_grouped_epi_archive) +export(jhu_confirmed_cumulative_num) export(key_colnames) export(max_version_with_row_in) export(mutate) diff --git a/R/reexports.R b/R/reexports.R index 00ac83c2..f0a3938e 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -75,3 +75,96 @@ tidyr::full_seq #' @importFrom ggplot2 autoplot #' @export ggplot2::autoplot + + +# epidatasets ------------------------------------------------------------------- + +#' @inherit epidatasets::cases_deaths_subset description source references title +#' @inheritSection epidatasets::cases_deaths_subset Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::cases_deaths_subset +#' +#' # works +#' library(epiprocess) +#' cases_deaths_subset +#' +#' # fails +#' data(cases_deaths_subset, package = "epiprocess") +#' @export +cases_deaths_subset <- epidatasets::cases_deaths_subset + +#' @inherit epidatasets::archive_cases_dv_subset_dt description source references title +#' @inheritSection epidatasets::archive_cases_dv_subset_dt Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::archive_cases_dv_subset_dt +#' +#' # works +#' library(epiprocess) +#' archive_cases_dv_subset_dt +#' +#' # fails +#' data(archive_cases_dv_subset_dt, package = "epiprocess") +#' @export +archive_cases_dv_subset_dt <- epidatasets::archive_cases_dv_subset_dt + +#' @inherit epidatasets::covid_incidence_county_subset description source references title +#' @inheritSection epidatasets::covid_incidence_county_subset Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_incidence_county_subset +#' +#' # works +#' library(epiprocess) +#' covid_incidence_county_subset +#' +#' # fails +#' data(covid_incidence_county_subset, package = "epiprocess") +#' @export +covid_incidence_county_subset <- epidatasets::covid_incidence_county_subset + +#' @inherit epidatasets::covid_incidence_outliers description source references title +#' @inheritSection epidatasets::covid_incidence_outliers Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_incidence_outliers +#' +#' # works +#' library(epiprocess) +#' covid_incidence_outliers +#' +#' # fails +#' data(covid_incidence_outliers, package = "epiprocess") +#' @export +covid_incidence_outliers <- epidatasets::covid_incidence_outliers + +#' @inherit epidatasets::jhu_confirmed_cumulative_num description source references title +#' @inheritSection epidatasets::jhu_confirmed_cumulative_num Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::jhu_confirmed_cumulative_num +#' +#' # works +#' library(epiprocess) +#' jhu_confirmed_cumulative_num +#' +#' # fails +#' data(jhu_confirmed_cumulative_num, package = "epiprocess") +#' @export +jhu_confirmed_cumulative_num <- epidatasets::jhu_confirmed_cumulative_num diff --git a/_pkgdown.yml b/_pkgdown.yml index 62f006fe..442273db 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -91,9 +91,11 @@ reference: - group_by.epi_archive - title: Example data - contents: - - archive_cases_dv_subset - - incidence_num_outlier_example - - contains("jhu_csse") + - cases_deaths_subset + - archive_cases_dv_subset_dt + - covid_incidence_county_subset + - covid_incidence_outliers + - jhu_confirmed_cumulative_num - title: Basic automatic plotting - contents: - autoplot.epi_df diff --git a/man/archive_cases_dv_subset_dt.Rd b/man/archive_cases_dv_subset_dt.Rd new file mode 100644 index 00000000..01430384 --- /dev/null +++ b/man/archive_cases_dv_subset_dt.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{archive_cases_dv_subset_dt} +\alias{archive_cases_dv_subset_dt} +\title{Subset of daily COVID-19 doctor visits and cases from 6 states in archive format} +\format{ +An object of class \code{data.table} (inherits from \code{data.frame}) with 129638 rows and 5 columns. +} +\source{ +This object contains a modified part of the \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by Johns Hopkins University on behalf of its Center for Systems Science in Engineering. +Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From the COVIDcast Doctor Visits API}: The signal \code{percent_cli} is taken directly from the API without changes. +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: \code{case_rate_7d_av} signal was computed by Delphi from the original JHU-CSSE data by calculating moving averages of the preceding 7 days, so the signal for June 7 is the average of the underlying data for June 1 through 7, inclusive. +\item Furthermore, the data has been limited to a very small number of rows, the +signal names slightly altered, and formatted into a tibble. +} +} +\usage{ +archive_cases_dv_subset_dt +} +\description{ +This data source is based on information about outpatient visits, +provided to us by health system partners, and also contains confirmed +COVID-19 cases based on reports made available by the Center for +Systems Science and Engineering at Johns Hopkins University. +This example data ranges from June 1, 2020 to Dec 1, 2021, and +is also limited to California, Florida, Texas, and New York. + +It is used in the {epiprocess} \code{compactify}, \code{epix_archive}, and +advanced-use vignettes. +} +\section{Data dictionary}{ + + +An \code{epi_archive$DT} data format with columns: +\describe{ +\item{geo_value}{the geographic value associated with each row of measurements.} +\item{time_value}{the time value associated with each row of measurements.} +\item{version}{the time value specifying the version for each row of measurements. } +\item{percent_cli}{percentage of doctor’s visits with CLI (COVID-like illness) computed from medical insurance claims} +\item{case_rate_7d_av}{7-day average signal of number of new confirmed deaths due to COVID-19 per 100,000 population, daily} +} + +} + +\examples{ + # Since this is a re-exported dataset, it cannot be loaded using + # the `data()` function. `data()` looks for a file of the same name + # in the `data/` directory, which doesn't exist in this package. + # works + epiprocess::archive_cases_dv_subset_dt + + # works + library(epiprocess) + archive_cases_dv_subset_dt + + # fails + data(archive_cases_dv_subset_dt, package = "epiprocess") +} +\keyword{datasets} diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd new file mode 100644 index 00000000..7eb58582 --- /dev/null +++ b/man/cases_deaths_subset.Rd @@ -0,0 +1,77 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{cases_deaths_subset} +\alias{cases_deaths_subset} +\title{Subset of JHU daily state COVID-19 cases and deaths from 4 states} +\format{ +An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 4026 rows and 6 columns. +} +\source{ +This object contains a modified part of the +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} +as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. +This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Johns Hopkins University on behalf of its Center for Systems Science +in Engineering. Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: +The case signal is taken directly from the JHU CSSE +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository}. +The rate signals were computed by Delphi using Census population data. +The 7-day average signals were computed by Delphi by calculating moving +averages of the preceding 7 days, so the signal for June 7 is the +average of the underlying data for June 1 through 7, inclusive. +\item Furthermore, the data has been limited to a very small number of rows, the +signal names slightly altered, and formatted into a tibble. +} +} +\usage{ +cases_deaths_subset +} +\description{ +This data source of confirmed COVID-19 cases and deaths +is based on reports made available by the Center for +Systems Science and Engineering at Johns Hopkins University. +This example data ranges from Mar 1, 2020 to Dec 31, 2021, and is limited to +California, Florida, Texas, New York, Georgia, and Pennsylvania. + +It is used in the {epiprocess} growth rate and \code{epi_slide} vignettes. +} +\section{Data dictionary}{ + + +A tibble with columns: +\describe{ +\item{geo_value}{the geographic value associated with each row +of measurements.} +\item{time_value}{the time value associated with each row of measurements.} +\item{case_rate_7d_av}{7-day average signal of number of new +confirmed COVID-19 cases per 100,000 population, daily} +\item{death_rate_7d_av}{7-day average signal of number of new confirmed +deaths due to COVID-19 per 100,000 population, daily} +\item{cases}{Number of new confirmed COVID-19 cases, daily} +\item{cases_7d_av}{7-day average signal of number of new confirmed +COVID-19 cases, daily} +} + +} + +\examples{ + # Since this is a re-exported dataset, it cannot be loaded using + # the `data()` function. `data()` looks for a file of the same name + # in the `data/` directory, which doesn't exist in this package. + # works + epiprocess::cases_deaths_subset + + # works + library(epiprocess) + cases_deaths_subset + + # fails + data(cases_deaths_subset, package = "epiprocess") +} +\keyword{datasets} diff --git a/man/covid_incidence_county_subset.Rd b/man/covid_incidence_county_subset.Rd new file mode 100644 index 00000000..0b70e3c3 --- /dev/null +++ b/man/covid_incidence_county_subset.Rd @@ -0,0 +1,72 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{covid_incidence_county_subset} +\alias{covid_incidence_county_subset} +\title{Subset of JHU daily COVID-19 cases from counties in Massachusetts and Vermont} +\format{ +An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 16212 rows and 5 columns. +} +\source{ +This object contains a modified part of the +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as +\href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. +This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Johns Hopkins University on behalf of its Center for Systems +Science in Engineering. Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: +These signals are taken directly from the JHU CSSE +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} +without changes. The 7-day average signals are computed by Delphi by +as moving averages of the preceding 7 days, so the signal for +June 7 is the average of the underlying data for June 1 through 7, +inclusive. +\item Furthermore, the data has been limited to a very small number of rows, +formatted into a tibble, and the signal names slightly altered. +} +} +\usage{ +covid_incidence_county_subset +} +\description{ +This data source of confirmed COVID-19 cases and deaths +is based on reports made available by the Center for +Systems Science and Engineering at Johns Hopkins University. +This example data ranges from Mar 1, 2020 to Dec 31, 2021, +and is limited to Massachusetts and Vermont. + +It is used in the {epiprocess} aggregation vignette. +} +\section{Data dictionary}{ + + +A tibble with columns: +\describe{ +\item{geo_value}{the geographic value associated with each row of measurements.} +\item{time_value}{the time value associated with each row of measurements.} +\item{cases}{Number of new confirmed COVID-19 cases, daily} +\item{county_name}{the name of the county} +\item{state_name}{the full name of the state} +} + +} + +\examples{ + # Since this is a re-exported dataset, it cannot be loaded using + # the `data()` function. `data()` looks for a file of the same name + # in the `data/` directory, which doesn't exist in this package. + # works + epiprocess::covid_incidence_county_subset + + # works + library(epiprocess) + covid_incidence_county_subset + + # fails + data(covid_incidence_county_subset, package = "epiprocess") +} +\keyword{datasets} diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd new file mode 100644 index 00000000..cf07b64b --- /dev/null +++ b/man/covid_incidence_outliers.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{covid_incidence_outliers} +\alias{covid_incidence_outliers} +\title{Subset of JHU daily COVID-19 cases from California and Florida} +\format{ +An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 730 rows and 3 columns. +} +\source{ +This object contains a modified part of the +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} +as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. +This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Johns Hopkins University on behalf of its Center for Systems +Science in Engineering. Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: +These signals are taken directly from the JHU CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} without changes. +\item Furthermore, the data has been limited to a very small number of rows, +formatted into a tibble, and the signal names slightly altered. +} +} +\usage{ +covid_incidence_outliers +} +\description{ +This data source of confirmed COVID-19 cases is based on reports made +available by the Center for Systems Science and Engineering at Johns +Hopkins University. This example data is downloaded from the CMU Delphi +COVIDcast Epidata API. It is a snapshot as of Oct 28, 2021 and captures the +cases from June 1, 2020 to May 31, 2021 and is limited to California and +Florida. This data set is used in the {epiprocess} vignette on outliers. +} +\section{Data dictionary}{ + + +A tibble with columns: +\describe{ +\item{geo_value}{the geographic value associated with each row of measurements.} +\item{time_value}{the time value associated with each row of measurements.} +\item{cases}{Number of new confirmed COVID-19 cases, daily} +} + +} + +\examples{ + # Since this is a re-exported dataset, it cannot be loaded using + # the `data()` function. `data()` looks for a file of the same name + # in the `data/` directory, which doesn't exist in this package. + # works + epiprocess::covid_incidence_outliers + + # works + library(epiprocess) + covid_incidence_outliers + + # fails + data(covid_incidence_outliers, package = "epiprocess") +} +\keyword{datasets} diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd new file mode 100644 index 00000000..37f80805 --- /dev/null +++ b/man/jhu_confirmed_cumulative_num.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{jhu_confirmed_cumulative_num} +\alias{jhu_confirmed_cumulative_num} +\title{Subset of COVID-19 Cumulative Cases from 4 states} +\format{ +An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 2808 rows and 15 columns. +} +\source{ +This object contains a modified part of the \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. +Copyright Johns Hopkins University 2020. +} +\usage{ +jhu_confirmed_cumulative_num +} +\description{ +Data set for 4 states containing COVID-19 Cumulative Cases as reported by +JHU-CSSE and downloaded from the CMU Delphi COVIDcast Epidata API. This +data set covers the period from March 2020 to January 2022, and is limited +to California, Florida, New York, and Texas. + +It is used in the {epiprocess} "Getting Started" vignette. +} +\section{Data dictionary}{ + + +A tibble with 2,808 rows and 15 variables: +\describe{ +\item{geo_value}{the geographic value associated with each row of measurements.} +\item{signal}{name of metric, derived from upstream data.} +\item{source}{name of upstream data source.} +\item{geo_type}{spatial resolution of the signal.} +\item{time_type}{temporal resolution of the signal.} +\item{time_value}{the time value associated with each row of measurements.} +\item{direction}{trend classifier (+1 -> increasing, 0 -> steady or not determined, -1 -> decreasing).} +\item{issue}{time unit (e.g., date) when the signal data were published.} +\item{lag}{time delta (e.g. days) between when the underlying events happened and when the data were published.} +\item{missing_value}{an integer code that is zero when the value field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} +\item{missing_stderr}{an integer code that is zero when the stderr field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} +\item{missing_sample_size}{an integer code that is zero when the sample_size field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} +\item{value}{Cumulative number of confirmed COVID-19 cases, derived from the underlying data source.} +\item{stderr}{approximate standard error of the statistic with respect to its sampling distribution, null when not applicable.} +\item{sample_size}{number of “data points” used in computing the statistic, null when not applicable.} +} + +} + +\examples{ + # Since this is a re-exported dataset, it cannot be loaded using + # the `data()` function. `data()` looks for a file of the same name + # in the `data/` directory, which doesn't exist in this package. + # works + epiprocess::jhu_confirmed_cumulative_num + + # works + library(epiprocess) + jhu_confirmed_cumulative_num + + # fails + data(jhu_confirmed_cumulative_num, package = "epiprocess") +} +\keyword{datasets} From 6a7555363dd890474f3d14b8f85af4f20a9b74f2 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:31:21 -0400 Subject: [PATCH 04/59] jhu_csse_daily_subset -> cases_deaths_subset --- R/autoplot.R | 10 ++++----- R/correlation.R | 8 +++---- R/growth_rate.R | 4 ++-- R/slide.R | 30 +++++++++++++-------------- man/autoplot.epi_df.Rd | 10 ++++----- man/epi_cor.Rd | 8 +++---- man/epi_slide.Rd | 10 ++++----- man/epi_slide_mean.Rd | 10 ++++----- man/epi_slide_opt.Rd | 8 +++---- man/epi_slide_sum.Rd | 2 +- man/growth_rate.Rd | 4 ++-- tests/testthat/test-archive.R | 2 +- tests/testthat/test-as_tibble-decay.R | 4 ++-- tests/testthat/test-correlation.R | 4 ++-- vignettes/growth_rate.Rmd | 4 ++-- vignettes/slide.Rmd | 4 ++-- 16 files changed, 61 insertions(+), 61 deletions(-) diff --git a/R/autoplot.R b/R/autoplot.R index 7443628b..073758a3 100644 --- a/R/autoplot.R +++ b/R/autoplot.R @@ -26,19 +26,19 @@ #' @export #' #' @examples -#' autoplot(jhu_csse_daily_subset, cases, death_rate_7d_av) -#' autoplot(jhu_csse_daily_subset, case_rate_7d_av, .facet_by = "geo_value") -#' autoplot(jhu_csse_daily_subset, case_rate_7d_av, +#' autoplot(cases_deaths_subset, cases, death_rate_7d_av) +#' autoplot(cases_deaths_subset, case_rate_7d_av, .facet_by = "geo_value") +#' autoplot(cases_deaths_subset, case_rate_7d_av, #' .color_by = "none", #' .facet_by = "geo_value" #' ) -#' autoplot(jhu_csse_daily_subset, case_rate_7d_av, +#' autoplot(cases_deaths_subset, case_rate_7d_av, #' .color_by = "none", #' .base_color = "red", .facet_by = "geo_value" #' ) #' #' # .base_color specification won't have any effect due .color_by default -#' autoplot(jhu_csse_daily_subset, case_rate_7d_av, +#' autoplot(cases_deaths_subset, case_rate_7d_av, #' .base_color = "red", .facet_by = "geo_value" #' ) autoplot.epi_df <- function( diff --git a/R/correlation.R b/R/correlation.R index 5e9694c4..ff153364 100644 --- a/R/correlation.R +++ b/R/correlation.R @@ -44,7 +44,7 @@ #' #' # linear association of case and death rates on any given day #' epi_cor( -#' x = jhu_csse_daily_subset, +#' x = cases_deaths_subset, #' var1 = case_rate_7d_av, #' var2 = death_rate_7d_av, #' cor_by = "time_value" @@ -52,7 +52,7 @@ #' #' # correlation of death rates and lagged case rates #' epi_cor( -#' x = jhu_csse_daily_subset, +#' x = cases_deaths_subset, #' var1 = case_rate_7d_av, #' var2 = death_rate_7d_av, #' cor_by = time_value, @@ -61,7 +61,7 @@ #' #' # correlation grouped by location #' epi_cor( -#' x = jhu_csse_daily_subset, +#' x = cases_deaths_subset, #' var1 = case_rate_7d_av, #' var2 = death_rate_7d_av, #' cor_by = geo_value @@ -69,7 +69,7 @@ #' #' # correlation grouped by location and incorporates lagged cases rates #' epi_cor( -#' x = jhu_csse_daily_subset, +#' x = cases_deaths_subset, #' var1 = case_rate_7d_av, #' var2 = death_rate_7d_av, #' cor_by = geo_value, diff --git a/R/growth_rate.R b/R/growth_rate.R index 4537375d..3997dd91 100644 --- a/R/growth_rate.R +++ b/R/growth_rate.R @@ -102,12 +102,12 @@ #' @export #' @examples #' # COVID cases growth rate by state using default method relative change -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' mutate(cases_gr = growth_rate(x = time_value, y = cases)) #' #' # Log scale, degree 4 polynomial and 6-fold cross validation -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' mutate(gr_poly = growth_rate(x = time_value, y = cases, log_scale = TRUE, ord = 4, k = 6)) growth_rate <- function(x = seq_along(y), y, x0 = x, diff --git a/R/slide.R b/R/slide.R index d62b3bd8..b328b280 100644 --- a/R/slide.R +++ b/R/slide.R @@ -46,7 +46,7 @@ #' # slide a 7-day trailing average formula on cases #' # Simple sliding means and sums are much faster to do using #' # the `epi_slide_mean` and `epi_slide_sum` functions instead. -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide(cases_7dav = mean(cases), before = 6) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -54,7 +54,7 @@ #' ungroup() #' #' # slide a 7-day leading average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide(cases_7dav = mean(cases), after = 6) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -62,7 +62,7 @@ #' ungroup() #' #' # slide a 7-day centre-aligned average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide(cases_7dav = mean(cases), before = 3, after = 3) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -70,7 +70,7 @@ #' ungroup() #' #' # slide a 14-day centre-aligned average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide(cases_14dav = mean(cases), before = 6, after = 7) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -78,7 +78,7 @@ #' ungroup() #' #' # nested new columns -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide( #' a = data.frame( @@ -339,7 +339,7 @@ epi_slide <- function(x, f, ..., before = NULL, after = NULL, ref_time_values = #' @seealso [`epi_slide`] [`epi_slide_mean`] [`epi_slide_sum`] #' @examples #' # slide a 7-day trailing average formula on cases. This can also be done with `epi_slide_mean` -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_opt( #' cases, @@ -351,7 +351,7 @@ epi_slide <- function(x, f, ..., before = NULL, after = NULL, ref_time_values = #' #' # 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 %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_opt( #' cases, @@ -363,7 +363,7 @@ epi_slide <- function(x, f, ..., before = NULL, after = NULL, ref_time_values = #' ungroup() #' #' # slide a 7-day leading average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_opt( #' cases, @@ -374,7 +374,7 @@ epi_slide <- function(x, f, ..., before = NULL, after = NULL, ref_time_values = #' ungroup() #' #' # slide a 7-day centre-aligned sum. This can also be done with `epi_slide_sum` -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_opt( #' cases, @@ -638,7 +638,7 @@ epi_slide_opt <- function(x, col_names, f, ..., before = NULL, after = NULL, ref #' @seealso [`epi_slide`] [`epi_slide_opt`] [`epi_slide_sum`] #' @examples #' # slide a 7-day trailing average formula on cases -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_mean(cases, before = 6) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -647,7 +647,7 @@ epi_slide_opt <- function(x, col_names, f, ..., before = NULL, after = NULL, ref #' #' # 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 %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_mean( #' cases, @@ -659,7 +659,7 @@ epi_slide_opt <- function(x, col_names, f, ..., before = NULL, after = NULL, ref #' ungroup() #' #' # slide a 7-day leading average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_mean(cases, after = 6) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -667,7 +667,7 @@ epi_slide_opt <- function(x, col_names, f, ..., before = NULL, after = NULL, ref #' ungroup() #' #' # slide a 7-day centre-aligned average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_mean(cases, before = 3, after = 3) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -675,7 +675,7 @@ epi_slide_opt <- function(x, col_names, f, ..., before = NULL, after = NULL, ref #' ungroup() #' #' # slide a 14-day centre-aligned average -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_mean(cases, before = 6, after = 7) %>% #' # Remove a nonessential var. to ensure new col is printed @@ -720,7 +720,7 @@ epi_slide_mean <- function(x, col_names, ..., before = NULL, after = NULL, ref_t #' @seealso [`epi_slide`] [`epi_slide_opt`] [`epi_slide_mean`] #' @examples #' # slide a 7-day trailing sum formula on cases -#' jhu_csse_daily_subset %>% +#' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_sum(cases, before = 6) %>% #' # Remove a nonessential var. to ensure new col is printed diff --git a/man/autoplot.epi_df.Rd b/man/autoplot.epi_df.Rd index c97ea02f..d53335c1 100644 --- a/man/autoplot.epi_df.Rd +++ b/man/autoplot.epi_df.Rd @@ -50,19 +50,19 @@ A ggplot object Automatically plot an epi_df } \examples{ -autoplot(jhu_csse_daily_subset, cases, death_rate_7d_av) -autoplot(jhu_csse_daily_subset, case_rate_7d_av, .facet_by = "geo_value") -autoplot(jhu_csse_daily_subset, case_rate_7d_av, +autoplot(cases_deaths_subset, cases, death_rate_7d_av) +autoplot(cases_deaths_subset, case_rate_7d_av, .facet_by = "geo_value") +autoplot(cases_deaths_subset, case_rate_7d_av, .color_by = "none", .facet_by = "geo_value" ) -autoplot(jhu_csse_daily_subset, case_rate_7d_av, +autoplot(cases_deaths_subset, case_rate_7d_av, .color_by = "none", .base_color = "red", .facet_by = "geo_value" ) # .base_color specification won't have any effect due .color_by default -autoplot(jhu_csse_daily_subset, case_rate_7d_av, +autoplot(cases_deaths_subset, case_rate_7d_av, .base_color = "red", .facet_by = "geo_value" ) } diff --git a/man/epi_cor.Rd b/man/epi_cor.Rd index fb56073f..5e6698c8 100644 --- a/man/epi_cor.Rd +++ b/man/epi_cor.Rd @@ -61,7 +61,7 @@ for examples. # linear association of case and death rates on any given day epi_cor( - x = jhu_csse_daily_subset, + x = cases_deaths_subset, var1 = case_rate_7d_av, var2 = death_rate_7d_av, cor_by = "time_value" @@ -69,7 +69,7 @@ epi_cor( # correlation of death rates and lagged case rates epi_cor( - x = jhu_csse_daily_subset, + x = cases_deaths_subset, var1 = case_rate_7d_av, var2 = death_rate_7d_av, cor_by = time_value, @@ -78,7 +78,7 @@ epi_cor( # correlation grouped by location epi_cor( - x = jhu_csse_daily_subset, + x = cases_deaths_subset, var1 = case_rate_7d_av, var2 = death_rate_7d_av, cor_by = geo_value @@ -86,7 +86,7 @@ epi_cor( # correlation grouped by location and incorporates lagged cases rates epi_cor( - x = jhu_csse_daily_subset, + x = cases_deaths_subset, var1 = case_rate_7d_av, var2 = death_rate_7d_av, cor_by = geo_value, diff --git a/man/epi_slide.Rd b/man/epi_slide.Rd index 5f4db7b4..86654cd7 100644 --- a/man/epi_slide.Rd +++ b/man/epi_slide.Rd @@ -151,7 +151,7 @@ through the \code{new_col_name} argument. # slide a 7-day trailing average formula on cases # Simple sliding means and sums are much faster to do using # the `epi_slide_mean` and `epi_slide_sum` functions instead. -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide(cases_7dav = mean(cases), before = 6) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -159,7 +159,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 7-day leading average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide(cases_7dav = mean(cases), after = 6) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -167,7 +167,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 7-day centre-aligned average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide(cases_7dav = mean(cases), before = 3, after = 3) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -175,7 +175,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 14-day centre-aligned average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide(cases_14dav = mean(cases), before = 6, after = 7) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -183,7 +183,7 @@ jhu_csse_daily_subset \%>\% ungroup() # nested new columns -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide( a = data.frame( diff --git a/man/epi_slide_mean.Rd b/man/epi_slide_mean.Rd index 0e2cde93..bbd2e117 100644 --- a/man/epi_slide_mean.Rd +++ b/man/epi_slide_mean.Rd @@ -114,7 +114,7 @@ zero-width windows are considered, manually pass both the \code{before} and } \examples{ # slide a 7-day trailing average formula on cases -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_mean(cases, before = 6) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -123,7 +123,7 @@ jhu_csse_daily_subset \%>\% # 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 \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_mean( cases, @@ -135,7 +135,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 7-day leading average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_mean(cases, after = 6) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -143,7 +143,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 7-day centre-aligned average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_mean(cases, before = 3, after = 3) \%>\% # Remove a nonessential var. to ensure new col is printed @@ -151,7 +151,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 14-day centre-aligned average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_mean(cases, before = 6, after = 7) \%>\% # Remove a nonessential var. to ensure new col is printed diff --git a/man/epi_slide_opt.Rd b/man/epi_slide_opt.Rd index 125c7b14..d9403cca 100644 --- a/man/epi_slide_opt.Rd +++ b/man/epi_slide_opt.Rd @@ -135,7 +135,7 @@ zero-width windows are considered, manually pass both the \code{before} and } \examples{ # slide a 7-day trailing average formula on cases. This can also be done with `epi_slide_mean` -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_opt( cases, @@ -147,7 +147,7 @@ jhu_csse_daily_subset \%>\% # 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 \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_opt( cases, @@ -159,7 +159,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 7-day leading average -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_opt( cases, @@ -170,7 +170,7 @@ jhu_csse_daily_subset \%>\% ungroup() # slide a 7-day centre-aligned sum. This can also be done with `epi_slide_sum` -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_opt( cases, diff --git a/man/epi_slide_sum.Rd b/man/epi_slide_sum.Rd index c14d7419..10be79a0 100644 --- a/man/epi_slide_sum.Rd +++ b/man/epi_slide_sum.Rd @@ -114,7 +114,7 @@ zero-width windows are considered, manually pass both the \code{before} and } \examples{ # slide a 7-day trailing sum formula on cases -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_sum(cases, before = 6) \%>\% # Remove a nonessential var. to ensure new col is printed diff --git a/man/growth_rate.Rd b/man/growth_rate.Rd index 7a3f1151..c4e82a09 100644 --- a/man/growth_rate.Rd +++ b/man/growth_rate.Rd @@ -136,12 +136,12 @@ user. \examples{ # COVID cases growth rate by state using default method relative change -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% mutate(cases_gr = growth_rate(x = time_value, y = cases)) # Log scale, degree 4 polynomial and 6-fold cross validation -jhu_csse_daily_subset \%>\% +cases_deaths_subset \%>\% group_by(geo_value) \%>\% mutate(gr_poly = growth_rate(x = time_value, y = cases, log_scale = TRUE, ord = 4, k = 6)) } diff --git a/tests/testthat/test-archive.R b/tests/testthat/test-archive.R index 7f20ddeb..2cf28934 100644 --- a/tests/testthat/test-archive.R +++ b/tests/testthat/test-archive.R @@ -153,7 +153,7 @@ test_that("epi_archives are correctly instantiated with a variety of data types" expect_equal(ea8$additional_metadata, list(value = df$value)) # epi_df - edf1 <- jhu_csse_daily_subset %>% + edf1 <- cases_deaths_subset %>% select(geo_value, time_value, cases) %>% mutate(version = max(time_value), code = "USA") diff --git a/tests/testthat/test-as_tibble-decay.R b/tests/testthat/test-as_tibble-decay.R index 488ace63..a5557816 100644 --- a/tests/testthat/test-as_tibble-decay.R +++ b/tests/testthat/test-as_tibble-decay.R @@ -1,5 +1,5 @@ test_that("as_tibble checks an attr to avoid decay to tibble", { - edf <- jhu_csse_daily_subset + edf <- cases_deaths_subset expect_identical(class(as_tibble(edf)), c("tbl_df", "tbl", "data.frame")) attr(edf, "decay_to_tibble") <- TRUE expect_identical(class(as_tibble(edf)), c("tbl_df", "tbl", "data.frame")) @@ -10,7 +10,7 @@ test_that("as_tibble checks an attr to avoid decay to tibble", { test_that("as_tibble ungroups if needed", { # tsibble is doing some method piracy, and overwriting as_tibble.grouped_df as of 1.1.5 skip_if(packageVersion("tsibble") > "1.1.4") - edf <- jhu_csse_daily_subset %>% group_by(geo_value) + edf <- cases_deaths_subset %>% group_by(geo_value) # removes the grouped_df class expect_identical(class(as_tibble(edf)), c("tbl_df", "tbl", "data.frame")) attr(edf, "decay_to_tibble") <- TRUE diff --git a/tests/testthat/test-correlation.R b/tests/testthat/test-correlation.R index 886d94c4..240f2897 100644 --- a/tests/testthat/test-correlation.R +++ b/tests/testthat/test-correlation.R @@ -11,13 +11,13 @@ test_that("epi_cor requires two var arguments, var1 and var2", { test_that("epi_cor functions as intended", { expect_equal( epi_cor( - x = jhu_csse_daily_subset, + x = cases_deaths_subset, var1 = case_rate_7d_av, var2 = death_rate_7d_av, cor_by = geo_value, dt1 = -2 )[1], - tibble(geo_value = unique(jhu_csse_daily_subset$geo_value)) + tibble(geo_value = unique(cases_deaths_subset$geo_value)) ) edf <- as_epi_df(data.frame( diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index abef646f..682b77ee 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -40,8 +40,8 @@ The data has 1,158 rows and 3 columns. ```{r, echo=FALSE} -data(jhu_csse_daily_subset) -x <- jhu_csse_daily_subset %>% +data(cases_deaths_subset) +x <- cases_deaths_subset %>% select(geo_value, time_value, cases = cases_7d_av) %>% filter(geo_value %in% c("pa", "ga") & time_value >= "2020-06-01") %>% arrange(geo_value, time_value) %>% diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd index 92590fb1..841942cc 100644 --- a/vignettes/slide.Rmd +++ b/vignettes/slide.Rmd @@ -52,8 +52,8 @@ x <- pub_covidcast( The data has 2,684 rows and 3 columns. ```{r, echo=FALSE} -data(jhu_csse_daily_subset) -x <- jhu_csse_daily_subset %>% +data(cases_deaths_subset) +x <- cases_deaths_subset %>% select(geo_value, time_value, cases) %>% arrange(geo_value, time_value) %>% as_epi_df() From d7195bd2f2eba6ffb8d63f6fd86622dce3a42500 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:32:17 -0400 Subject: [PATCH 05/59] jhu_csse_county_level_subset -> covid_incidence_county_subset --- R/epi_df.R | 2 +- man/epi_df.Rd | 2 +- tests/testthat/test-epi_df.R | 2 +- vignettes/aggregation.Rmd | 4 ++-- vignettes/epiprocess.Rmd | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/epi_df.R b/R/epi_df.R index 56424bf0..8d6177eb 100644 --- a/R/epi_df.R +++ b/R/epi_df.R @@ -135,7 +135,7 @@ #' #' # Adding additional keys to an `epi_df` object #' -#' ex3_input <- jhu_csse_county_level_subset %>% +#' ex3_input <- covid_incidence_county_subset %>% #' dplyr::filter(time_value > "2021-12-01", state_name == "Massachusetts") %>% #' dplyr::slice_tail(n = 6) #' diff --git a/man/epi_df.Rd b/man/epi_df.Rd index dcda0872..e5608bd1 100644 --- a/man/epi_df.Rd +++ b/man/epi_df.Rd @@ -208,7 +208,7 @@ attr(ex2, "metadata") # Adding additional keys to an `epi_df` object -ex3_input <- jhu_csse_county_level_subset \%>\% +ex3_input <- covid_incidence_county_subset \%>\% dplyr::filter(time_value > "2021-12-01", state_name == "Massachusetts") \%>\% dplyr::slice_tail(n = 6) diff --git a/tests/testthat/test-epi_df.R b/tests/testthat/test-epi_df.R index a49855aa..6d26b88e 100644 --- a/tests/testthat/test-epi_df.R +++ b/tests/testthat/test-epi_df.R @@ -25,7 +25,7 @@ test_that("new_epi_df works as intended", { test_that("as_epi_df errors when additional_metadata is not a list", { # This is the 3rd example from as_epi_df - ex_input <- jhu_csse_county_level_subset %>% + ex_input <- covid_incidence_county_subset %>% dplyr::filter(time_value > "2021-12-01", state_name == "Massachusetts") %>% dplyr::slice_tail(n = 6) %>% tsibble::as_tsibble() %>% diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index ec5f36af..90aadacd 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -45,8 +45,8 @@ library(covidcast) library(epiprocess) library(dplyr) -data(jhu_csse_county_level_subset) -x <- jhu_csse_county_level_subset +data(covid_incidence_county_subset) +x <- covid_incidence_county_subset ``` ## Converting to `tsibble` format diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 24a98505..04fc3a47 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -248,7 +248,7 @@ In the above examples, all the keys are added to objects that are not `epi_df` o We use a toy data set included in `epiprocess` prepared using the `covidcast` library and are filtering to a single state for simplicity. ```{r} -ex3 <- jhu_csse_county_level_subset %>% +ex3 <- covid_incidence_county_subset %>% filter(time_value > "2021-12-01", state_name == "Massachusetts") %>% slice_tail(n = 6) From d805c04d244d5616eae94f33c2b39035041d6f1a Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:52:06 -0400 Subject: [PATCH 06/59] incidence_num_outlier_example -> covid_incidence_outliers --- R/outliers.R | 6 +++--- man/covid_incidence_outliers.Rd | 8 +++++--- man/detect_outlr.Rd | 2 +- man/detect_outlr_rm.Rd | 2 +- man/detect_outlr_stl.Rd | 2 +- vignettes/outliers.Rmd | 23 ++--------------------- 6 files changed, 13 insertions(+), 30 deletions(-) diff --git a/R/outliers.R b/R/outliers.R index 3d0ff5e5..15201efe 100644 --- a/R/outliers.R +++ b/R/outliers.R @@ -71,7 +71,7 @@ #' ) #' ) #' -#' x <- incidence_num_outlier_example %>% +#' x <- covid_incidence_outliers %>% #' dplyr::select(geo_value, time_value, cases) %>% #' as_epi_df() %>% #' group_by(geo_value) %>% @@ -155,7 +155,7 @@ detect_outlr <- function(x = seq_along(y), y, #' @export #' @examples #' # Detect outliers based on a rolling median -#' incidence_num_outlier_example %>% +#' covid_incidence_outliers %>% #' dplyr::select(geo_value, time_value, cases) %>% #' as_epi_df() %>% #' group_by(geo_value) %>% @@ -250,7 +250,7 @@ detect_outlr_rm <- function(x = seq_along(y), y, n = 21, #' @export #' @examples #' # Detects outliers based on a seasonal-trend decomposition using LOESS -#' incidence_num_outlier_example %>% +#' covid_incidence_outliers %>% #' dplyr::select(geo_value, time_value, cases) %>% #' as_epi_df() %>% #' group_by(geo_value) %>% diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd index cf07b64b..e1baa9da 100644 --- a/man/covid_incidence_outliers.Rd +++ b/man/covid_incidence_outliers.Rd @@ -3,7 +3,7 @@ \docType{data} \name{covid_incidence_outliers} \alias{covid_incidence_outliers} -\title{Subset of JHU daily COVID-19 cases from California and Florida} +\title{Subset of JHU daily COVID-19 cases from New Jersey and Florida} \format{ An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 730 rows and 3 columns. } @@ -32,8 +32,10 @@ This data source of confirmed COVID-19 cases is based on reports made available by the Center for Systems Science and Engineering at Johns Hopkins University. This example data is downloaded from the CMU Delphi COVIDcast Epidata API. It is a snapshot as of Oct 28, 2021 and captures the -cases from June 1, 2020 to May 31, 2021 and is limited to California and -Florida. This data set is used in the {epiprocess} vignette on outliers. +cases from June 1, 2020 to May 31, 2021 and is limited to New Jersey and +Florida. + +This data set is used in the {epiprocess} vignette on outliers. } \section{Data dictionary}{ diff --git a/man/detect_outlr.Rd b/man/detect_outlr.Rd index 3ac08585..744b9345 100644 --- a/man/detect_outlr.Rd +++ b/man/detect_outlr.Rd @@ -94,7 +94,7 @@ detection_methods <- dplyr::bind_rows( ) ) -x <- incidence_num_outlier_example \%>\% +x <- covid_incidence_outliers \%>\% dplyr::select(geo_value, time_value, cases) \%>\% as_epi_df() \%>\% group_by(geo_value) \%>\% diff --git a/man/detect_outlr_rm.Rd b/man/detect_outlr_rm.Rd index 333c4a7b..a02588ae 100644 --- a/man/detect_outlr_rm.Rd +++ b/man/detect_outlr_rm.Rd @@ -59,7 +59,7 @@ terms of multiples of the rolling interquartile range (IQR). } \examples{ # Detect outliers based on a rolling median -incidence_num_outlier_example \%>\% +covid_incidence_outliers \%>\% dplyr::select(geo_value, time_value, cases) \%>\% as_epi_df() \%>\% group_by(geo_value) \%>\% diff --git a/man/detect_outlr_stl.Rd b/man/detect_outlr_stl.Rd index 695c2de7..b2adf9e2 100644 --- a/man/detect_outlr_stl.Rd +++ b/man/detect_outlr_stl.Rd @@ -90,7 +90,7 @@ are exactly as in \code{detect_outlr_rm()}. } \examples{ # Detects outliers based on a seasonal-trend decomposition using LOESS -incidence_num_outlier_example \%>\% +covid_incidence_outliers \%>\% dplyr::select(geo_value, time_value, cases) \%>\% as_epi_df() \%>\% group_by(geo_value) \%>\% diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index ea3c30ac..fa1e3393 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -14,25 +14,6 @@ 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. -```{r, message = FALSE, eval= FALSE} -library(epidatr) -library(epiprocess) -library(dplyr) -library(tidyr) - -x <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_incidence_num", - geo_type = "state", - time_type = "day", - geo_values = "fl,nj", - time_values = epirange(20200601, 20210531), - as_of = 20211028 -) %>% - select(geo_value, time_value, cases = value) %>% - as_epi_df() -``` - The dataset has 730 rows and 3 columns. ```{r, echo=FALSE, warning=FALSE, message=FALSE} @@ -41,8 +22,8 @@ library(epiprocess) library(dplyr) library(tidyr) -data(incidence_num_outlier_example) -x <- incidence_num_outlier_example +data(covid_incidence_outliers) +x <- covid_incidence_outliers ``` ```{r, fig.width = 8, fig.height = 7, warning=FALSE,message=FALSE} From aedce88b7a036d5da56133bb4c3f3ef9bc86b787 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:54:21 -0400 Subject: [PATCH 07/59] cases in intro vignette -> jhu_confirmed_cumulative_num --- vignettes/epiprocess.Rmd | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 04fc3a47..59a37699 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -110,14 +110,8 @@ library(dplyr) library(tidyr) library(withr) -cases <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_cumulative_num", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx", - time_values = epirange(20200301, 20220131), -) +data(jhu_confirmed_cumulative_num) +cases <- jhu_confirmed_cumulative_num colnames(cases) ``` From 93ee50dab670f2dc046fb863e72ed0123957d474 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:15:29 -0400 Subject: [PATCH 08/59] switch all vignettes to visibly loading prepared data; provide API call as example --- vignettes/advanced.Rmd | 24 +++++++++++++----------- vignettes/aggregation.Rmd | 20 +++++++++----------- vignettes/archive.Rmd | 28 +++++++++------------------- vignettes/correlation.Rmd | 13 +++++++++++-- vignettes/growth_rate.Rmd | 23 ++++++++++++----------- vignettes/slide.Rmd | 20 +++++++++++--------- 6 files changed, 65 insertions(+), 63 deletions(-) diff --git a/vignettes/advanced.Rmd b/vignettes/advanced.Rmd index 1ea13c5f..76212a7c 100644 --- a/vignettes/advanced.Rmd +++ b/vignettes/advanced.Rmd @@ -285,7 +285,19 @@ vignette](https://cmu-delphi.github.io/epiprocess/articles/slide.html) in order to demonstrate the preceding points regarding forecast evaluation in a more realistic setting. First, we fetch the versioned data and build the archive. -```{r, message = FALSE, warning = FALSE, eval =FALSE} +The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: +```{r, message = FALSE} +library(data.table) +library(ggplot2) +theme_set(theme_bw()) + +x <- archive_cases_dv_subset$DT %>% + filter(geo_value %in% c("ca", "fl")) %>% + as_epi_archive(compactify = FALSE) +``` + +The data can also be fetched from the Delphi API with the following query: +```{r, eval = FALSE} library(epidatr) library(data.table) library(ggplot2) @@ -332,16 +344,6 @@ x <- epix_merge( ) ``` -```{r, message = FALSE, echo =FALSE} -library(data.table) -library(ggplot2) -theme_set(theme_bw()) - -x <- archive_cases_dv_subset$DT %>% - filter(geo_value %in% c("ca", "fl")) %>% - as_epi_archive(compactify = FALSE) -``` - Next, we extend the ARX function to handle multiple geo values, since in the present case, we will not be grouping by geo value and each slide computation will be run on multiple geo values at once. Note that, because `epix_slide()` diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 90aadacd..b185676e 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -12,12 +12,20 @@ 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} +The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r, warning=FALSE, message=FALSE} library(epidatr) library(covidcast) library(epiprocess) library(dplyr) +data(covid_incidence_county_subset) +x <- covid_incidence_county_subset +``` + +The data can also be fetched from the Delphi API with the following query: +```{r, message = FALSE, eval= FALSE, warning= FALSE} # Use covidcast::county_census to get the county and state names y <- covidcast::county_census %>% filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% @@ -39,16 +47,6 @@ x <- pub_covidcast( The data contains 16,212 rows and 5 columns. -```{r, echo=FALSE, warning=FALSE, message=FALSE} -library(epidatr) -library(covidcast) -library(epiprocess) -library(dplyr) - -data(covid_incidence_county_subset) -x <- covid_incidence_county_subset -``` - ## Converting to `tsibble` format For manipulating and wrangling time series data, the diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index 35f80a2c..009ba156 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -25,7 +25,9 @@ signal is subject to very heavy and regular revision; you can read more about it on its [API documentation page](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html). -```{r, message = FALSE, warning = FALSE, eval=FALSE} +The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r, message=FALSE, warning=FALSE} library(epidatr) library(epiprocess) library(data.table) @@ -33,6 +35,12 @@ library(dplyr) library(purrr) library(ggplot2) +# This fetches the raw data backing the archive_cases_dv_subset object. +dv <- archive_cases_dv_subset$DT +``` + +The data can also be fetched from the Delphi API with the following query: +```{r, message = FALSE, warning = FALSE, eval=FALSE} dv <- pub_covidcast( source = "doctor-visits", signals = "smoothed_adj_cli", @@ -44,15 +52,6 @@ dv <- pub_covidcast( ) ``` -```{r, echo=FALSE, message=FALSE, warning=FALSE} -library(epidatr) -library(epiprocess) -library(data.table) -library(dplyr) -library(purrr) -library(ggplot2) -``` - ## Getting data into `epi_archive` format An `epi_archive()` object can be constructed from a data frame, data table, or @@ -81,15 +80,6 @@ class(x) print(x) ``` -```{r, echo=FALSE, message=FALSE, warning=FALSE} -x <- archive_cases_dv_subset$DT %>% - select(geo_value, time_value, version, percent_cli) %>% - as_epi_archive(compactify = TRUE) - -class(x) -print(x) -``` - An `epi_archive` is consists of a primary field `DT`, which is a data table (from the `data.table` package) that has the columns `geo_value`, `time_value`, `version` (and possibly additional ones), and other metadata fields, such as diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index 34e8c0f0..fbb3c907 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -21,8 +21,17 @@ library(epiprocess) library(dplyr) ``` -The data is fetched with the following query: -```{r, message = FALSE} +The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: +```{r} +data(cases_deaths_subset) +x <- cases_deaths_subset %>% + select(geo_value, time_value, case_rate = cases, death_rate = deaths) %>% + arrange(geo_value, time_value) %>% + as_epi_df() +``` + +The data can also be fetched from the Delphi API with the following query: +```{r, eval = FALSE} x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_7dav_incidence_prop", diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index 682b77ee..845a5f91 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -21,7 +21,18 @@ library(dplyr) library(tidyr) ``` -The data is fetched with the following query: +The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r} +data(cases_deaths_subset) +x <- cases_deaths_subset %>% + select(geo_value, time_value, cases = cases_7d_av) %>% + filter(geo_value %in% c("pa", "ga") & time_value >= "2020-06-01") %>% + arrange(geo_value, time_value) %>% + as_epi_df() +``` + +The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval=F} x <- pub_covidcast( source = "jhu-csse", @@ -38,16 +49,6 @@ x <- pub_covidcast( The data has 1,158 rows and 3 columns. - -```{r, echo=FALSE} -data(cases_deaths_subset) -x <- cases_deaths_subset %>% - select(geo_value, time_value, cases = cases_7d_av) %>% - filter(geo_value %in% c("pa", "ga") & time_value >= "2020-06-01") %>% - arrange(geo_value, time_value) %>% - as_epi_df() -``` - ## Growth rate basics The growth rate of a function $f$ defined over a continuously-valued parameter diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd index 841942cc..77e7aa31 100644 --- a/vignettes/slide.Rmd +++ b/vignettes/slide.Rmd @@ -34,7 +34,17 @@ library(epiprocess) library(dplyr) ``` -The data is fetched with the following query: +The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r} +data(cases_deaths_subset) +x <- cases_deaths_subset %>% + select(geo_value, time_value, cases) %>% + arrange(geo_value, time_value) %>% + as_epi_df() +``` + +The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval=F} x <- pub_covidcast( source = "jhu-csse", @@ -51,14 +61,6 @@ x <- pub_covidcast( The data has 2,684 rows and 3 columns. -```{r, echo=FALSE} -data(cases_deaths_subset) -x <- cases_deaths_subset %>% - select(geo_value, time_value, cases) %>% - arrange(geo_value, time_value) %>% - as_epi_df() -``` - ## Optimized rolling mean We first demonstrate how to apply a 7-day trailing average to the daily cases From 9066ef51e9f7411e1c512d0c07a066b14c04b83f Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:31:51 -0400 Subject: [PATCH 09/59] can't use data() to fetch reexports in vignettes --- vignettes/aggregation.Rmd | 9 ++++----- vignettes/archive.Rmd | 4 ++-- vignettes/correlation.Rmd | 3 +-- vignettes/epiprocess.Rmd | 3 +-- vignettes/growth_rate.Rmd | 5 ++--- vignettes/outliers.Rmd | 3 +-- vignettes/slide.Rmd | 7 +++---- 7 files changed, 14 insertions(+), 20 deletions(-) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index b185676e..266cf9f3 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -14,18 +14,17 @@ COVID-19 cases in MA and VT. The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: -```{r, warning=FALSE, message=FALSE} +```{r, warning = FALSE, message = FALSE} library(epidatr) library(covidcast) library(epiprocess) library(dplyr) -data(covid_incidence_county_subset) -x <- covid_incidence_county_subset +x <- epiprocess::covid_incidence_county_subset ``` The data can also be fetched from the Delphi API with the following query: -```{r, message = FALSE, eval= FALSE, warning= FALSE} +```{r, message = FALSE, eval = FALSE, warning = FALSE} # Use covidcast::county_census to get the county and state names y <- covidcast::county_census %>% filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% @@ -42,7 +41,7 @@ x <- pub_covidcast( ) %>% select(geo_value, time_value, cases = value) %>% full_join(y, by = "geo_value") %>% - as_epi_df(as_of = as.Date("2024-03-20")) + as_epi_df() ``` The data contains 16,212 rows and 5 columns. diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index 009ba156..715dbd8b 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -27,7 +27,7 @@ page](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-v The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: -```{r, message=FALSE, warning=FALSE} +```{r, message = FALSE, warning = FALSE} library(epidatr) library(epiprocess) library(data.table) @@ -40,7 +40,7 @@ dv <- archive_cases_dv_subset$DT ``` The data can also be fetched from the Delphi API with the following query: -```{r, message = FALSE, warning = FALSE, eval=FALSE} +```{r, message = FALSE, warning = FALSE, eval = FALSE} dv <- pub_covidcast( source = "doctor-visits", signals = "smoothed_adj_cli", diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index fbb3c907..8259df38 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -23,9 +23,8 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -data(cases_deaths_subset) x <- cases_deaths_subset %>% - select(geo_value, time_value, case_rate = cases, death_rate = deaths) %>% + select(geo_value, time_value, case_rate = case_rate_7d_av, death_rate = death_rate_7d_av) %>% arrange(geo_value, time_value) %>% as_epi_df() ``` diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 59a37699..986b7c07 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -110,8 +110,7 @@ library(dplyr) library(tidyr) library(withr) -data(jhu_confirmed_cumulative_num) -cases <- jhu_confirmed_cumulative_num +cases <- epiprocess::jhu_confirmed_cumulative_num colnames(cases) ``` diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index 845a5f91..029356eb 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -24,8 +24,7 @@ library(tidyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -data(cases_deaths_subset) -x <- cases_deaths_subset %>% +x <- epiprocess::cases_deaths_subset %>% select(geo_value, time_value, cases = cases_7d_av) %>% filter(geo_value %in% c("pa", "ga") & time_value >= "2020-06-01") %>% arrange(geo_value, time_value) %>% @@ -33,7 +32,7 @@ x <- cases_deaths_subset %>% ``` The data can also be fetched from the Delphi API with the following query: -```{r, message = FALSE, eval=F} +```{r, message = FALSE, eval = FALSE} x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_7dav_incidence_num", diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index fa1e3393..76bda096 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -22,8 +22,7 @@ library(epiprocess) library(dplyr) library(tidyr) -data(covid_incidence_outliers) -x <- covid_incidence_outliers +x <- epiprocess::covid_incidence_outliers ``` ```{r, fig.width = 8, fig.height = 7, warning=FALSE,message=FALSE} diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd index 77e7aa31..175a3e7d 100644 --- a/vignettes/slide.Rmd +++ b/vignettes/slide.Rmd @@ -28,7 +28,7 @@ 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} +```{r, message = FALSE, warning = FALSE} library(epidatr) library(epiprocess) library(dplyr) @@ -37,15 +37,14 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -data(cases_deaths_subset) -x <- cases_deaths_subset %>% +x <- epiprocess::cases_deaths_subset %>% select(geo_value, time_value, cases) %>% arrange(geo_value, time_value) %>% as_epi_df() ``` The data can also be fetched from the Delphi API with the following query: -```{r, message = FALSE, eval=F} +```{r, message = FALSE, eval = FALSE} x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_incidence_num", From a7d2f77d60dd0f5457d3724c7f25e8e04dc2a25e Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:38:31 -0400 Subject: [PATCH 10/59] document delayassign-ed archive_cases_dv_subset --- NAMESPACE | 1 + R/data.R | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index a7d65d51..a431313e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,6 +45,7 @@ S3method(ungroup,epi_df) S3method(ungroup,grouped_epi_archive) S3method(unnest,epi_df) export("%>%") +export(archive_cases_dv_subset) export(archive_cases_dv_subset_dt) export(arrange) export(arrange_canonical) diff --git a/R/data.R b/R/data.R index 7776690d..a5fa7ee4 100644 --- a/R/data.R +++ b/R/data.R @@ -120,6 +120,53 @@ delayed_assign_with_unregister_awareness <- function(x, value, # binding may have been created with the same name as the package promise, and # this binding will stick around even when the package is reloaded, and will # need to be `rm`-d to easily access the refreshed package promise. + +#' @name archive_cases_dv_subset +#' +#' @title Subset of daily doctor visits and cases in archive format +#' +#' @description This data source is based on information about outpatient visits, +#' provided to us by health system partners, and also contains confirmed +#' COVID-19 cases based on reports made available by the Center for +#' Systems Science and Engineering at Johns Hopkins University. +#' This example data ranges from June 1, 2020 to Dec 1, 2021, and +#' is also limited to California, Florida, Texas, and New York. +#' +#' @format An `epi_archive` data format. The data table DT has 129,638 rows and 5 columns: +#' \describe{ +#' \item{geo_value}{the geographic value associated with each row of measurements.} +#' \item{time_value}{the time value associated with each row of measurements.} +#' \item{version}{the time value specifying the version for each row of measurements. } +#' \item{percent_cli}{percentage of doctor’s visits with CLI (COVID-like +#' illness) computed from medical insurance claims} +#' \item{case_rate_7d_av}{7-day average signal of number of new confirmed +#' deaths due to COVID-19 per 100,000 population, daily} +#' } +#' @source +#' This object contains a modified part of the +#' \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by +#' the Center for Systems Science and Engineering (CSSE) at Johns Hopkins +#' University} as +#' \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished +#' in the COVIDcast Epidata API}. This data set is licensed under the terms of +#' the \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons +#' Attribution 4.0 International license} by Johns Hopkins University on behalf +#' of its Center for Systems Science in Engineering. Copyright Johns Hopkins +#' University 2020. +#' +#' Modifications: +#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From +#' the COVIDcast Doctor Visits API}: The signal `percent_cli` is taken +#' directly from the API without changes. +#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From +#' the COVIDcast Epidata API}: `case_rate_7d_av` signal was computed by Delphi +#' from the original JHU-CSSE data by calculating moving averages of the +#' preceding 7 days, so the signal for June 7 is the average of the underlying +#' data for June 1 through 7, inclusive. +#' * Furthermore, the data is a subset of the full dataset, the signal names +#' slightly altered, and formatted into a tibble. +#' +#' @export delayed_assign_with_unregister_awareness( "archive_cases_dv_subset", as_epi_archive(archive_cases_dv_subset_dt, compactify = FALSE) From 0ba4aaa38611cf848561a088738330aa09caa7d0 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:57:30 -0400 Subject: [PATCH 11/59] rcmdcheck cleanup --- DESCRIPTION | 1 - NAMESPACE | 1 + R/epiprocess.R | 1 + vignettes/archive.Rmd | 12 ++++-------- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7eb05ac6..69709930 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -34,7 +34,6 @@ Imports: genlasso, glue, ggplot2, - glue, lifecycle (>= 1.0.1), lubridate, magrittr, diff --git a/NAMESPACE b/NAMESPACE index a431313e..a1051f36 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -96,6 +96,7 @@ export(ungroup) export(unnest) export(validate_epi_archive) export(version_column_names) +import(epidatasets) importFrom(checkmate,anyInfinite) importFrom(checkmate,anyMissing) importFrom(checkmate,assert) diff --git a/R/epiprocess.R b/R/epiprocess.R index ba072a2d..2c61e5c8 100644 --- a/R/epiprocess.R +++ b/R/epiprocess.R @@ -11,6 +11,7 @@ #' test_int #' @importFrom cli cli_abort cli_warn #' @importFrom rlang %||% +#' @import epidatasets #' @name epiprocess "_PACKAGE" utils::globalVariables(c( diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index 715dbd8b..62767494 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -49,7 +49,8 @@ dv <- pub_covidcast( geo_values = "ca,fl,ny,tx", time_values = epirange(20200601, 20211201), issues = epirange(20200601, 20211201) -) +) %>% + rename(version = issue, percent_cli = value) ``` ## Getting data into `epi_archive` format @@ -71,9 +72,9 @@ format, with `issue` playing the role of `version`. We can now use redundant version updates in `as_epi_archive` using compactify, please refer to the [compactify vignette](articles/compactify.html). -```{r, eval=FALSE} +```{r} x <- dv %>% - select(geo_value, time_value, version = issue, percent_cli = value) %>% + select(geo_value, time_value, version, percent_cli) %>% as_epi_archive(compactify = TRUE) class(x) @@ -85,11 +86,6 @@ An `epi_archive` is consists of a primary field `DT`, which is a data table `version` (and possibly additional ones), and other metadata fields, such as `geo_type`. -```{r} -class(x$DT) -head(x$DT) -``` - The variables `geo_value`, `time_value`, `version` serve as **key variables** for the data table, as well as any other specified in the metadata (described below). There can only be a single row per unique combination of key variables, From e2a5da19174fd240ab13fe37e6e9f5039ee5f28b Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:59:17 -0400 Subject: [PATCH 12/59] don't export _dt underlying archive example data --- NAMESPACE | 1 - R/data.R | 70 ++++++------------- R/reexports.R | 18 ----- _pkgdown.yml | 2 +- ...ubset_dt.Rd => archive_cases_dv_subset.Rd} | 22 +++--- 5 files changed, 32 insertions(+), 81 deletions(-) rename man/{archive_cases_dv_subset_dt.Rd => archive_cases_dv_subset.Rd} (84%) diff --git a/NAMESPACE b/NAMESPACE index a1051f36..c4ca4682 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -46,7 +46,6 @@ S3method(ungroup,grouped_epi_archive) S3method(unnest,epi_df) export("%>%") export(archive_cases_dv_subset) -export(archive_cases_dv_subset_dt) export(arrange) export(arrange_canonical) export(as_epi_archive) diff --git a/R/data.R b/R/data.R index a5fa7ee4..d29dcaab 100644 --- a/R/data.R +++ b/R/data.R @@ -101,6 +101,27 @@ delayed_assign_with_unregister_awareness <- function(x, value, }) } +#' @name archive_cases_dv_subset +#' @title Subset of daily doctor visits and cases from 6 states in archive format +#' +#' @inherit epidatasets::archive_cases_dv_subset_dt description source references +#' @format An `epi_archive` data format. The data table DT has 129,638 rows and 5 columns. +#' @inheritSection epidatasets::archive_cases_dv_subset_dt Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::archive_cases_dv_subset +#' +#' # works +#' library(epiprocess) +#' archive_cases_dv_subset +#' +#' # fails +#' data(archive_cases_dv_subset, package = "epiprocess") +#' +#' @export # Like normal data objects, set `archive_cases_dv_subset` up as a promise, so it # doesn't take unnecessary space before it's evaluated. This also avoids a need # for @include tags. However, this pattern will use unnecessary space after this @@ -120,54 +141,7 @@ delayed_assign_with_unregister_awareness <- function(x, value, # binding may have been created with the same name as the package promise, and # this binding will stick around even when the package is reloaded, and will # need to be `rm`-d to easily access the refreshed package promise. - -#' @name archive_cases_dv_subset -#' -#' @title Subset of daily doctor visits and cases in archive format -#' -#' @description This data source is based on information about outpatient visits, -#' provided to us by health system partners, and also contains confirmed -#' COVID-19 cases based on reports made available by the Center for -#' Systems Science and Engineering at Johns Hopkins University. -#' This example data ranges from June 1, 2020 to Dec 1, 2021, and -#' is also limited to California, Florida, Texas, and New York. -#' -#' @format An `epi_archive` data format. The data table DT has 129,638 rows and 5 columns: -#' \describe{ -#' \item{geo_value}{the geographic value associated with each row of measurements.} -#' \item{time_value}{the time value associated with each row of measurements.} -#' \item{version}{the time value specifying the version for each row of measurements. } -#' \item{percent_cli}{percentage of doctor’s visits with CLI (COVID-like -#' illness) computed from medical insurance claims} -#' \item{case_rate_7d_av}{7-day average signal of number of new confirmed -#' deaths due to COVID-19 per 100,000 population, daily} -#' } -#' @source -#' This object contains a modified part of the -#' \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by -#' the Center for Systems Science and Engineering (CSSE) at Johns Hopkins -#' University} as -#' \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished -#' in the COVIDcast Epidata API}. This data set is licensed under the terms of -#' the \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons -#' Attribution 4.0 International license} by Johns Hopkins University on behalf -#' of its Center for Systems Science in Engineering. Copyright Johns Hopkins -#' University 2020. -#' -#' Modifications: -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From -#' the COVIDcast Doctor Visits API}: The signal `percent_cli` is taken -#' directly from the API without changes. -#' * \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From -#' the COVIDcast Epidata API}: `case_rate_7d_av` signal was computed by Delphi -#' from the original JHU-CSSE data by calculating moving averages of the -#' preceding 7 days, so the signal for June 7 is the average of the underlying -#' data for June 1 through 7, inclusive. -#' * Furthermore, the data is a subset of the full dataset, the signal names -#' slightly altered, and formatted into a tibble. -#' -#' @export delayed_assign_with_unregister_awareness( "archive_cases_dv_subset", - as_epi_archive(archive_cases_dv_subset_dt, compactify = FALSE) + as_epi_archive(epidatasets::archive_cases_dv_subset_dt, compactify = FALSE) ) diff --git a/R/reexports.R b/R/reexports.R index f0a3938e..26f0ac0c 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -97,24 +97,6 @@ ggplot2::autoplot #' @export cases_deaths_subset <- epidatasets::cases_deaths_subset -#' @inherit epidatasets::archive_cases_dv_subset_dt description source references title -#' @inheritSection epidatasets::archive_cases_dv_subset_dt Data dictionary -#' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::archive_cases_dv_subset_dt -#' -#' # works -#' library(epiprocess) -#' archive_cases_dv_subset_dt -#' -#' # fails -#' data(archive_cases_dv_subset_dt, package = "epiprocess") -#' @export -archive_cases_dv_subset_dt <- epidatasets::archive_cases_dv_subset_dt - #' @inherit epidatasets::covid_incidence_county_subset description source references title #' @inheritSection epidatasets::covid_incidence_county_subset Data dictionary #' @examples diff --git a/_pkgdown.yml b/_pkgdown.yml index 442273db..72c237aa 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -92,7 +92,7 @@ reference: - title: Example data - contents: - cases_deaths_subset - - archive_cases_dv_subset_dt + - archive_cases_dv_subset - covid_incidence_county_subset - covid_incidence_outliers - jhu_confirmed_cumulative_num diff --git a/man/archive_cases_dv_subset_dt.Rd b/man/archive_cases_dv_subset.Rd similarity index 84% rename from man/archive_cases_dv_subset_dt.Rd rename to man/archive_cases_dv_subset.Rd index 01430384..02b8539e 100644 --- a/man/archive_cases_dv_subset_dt.Rd +++ b/man/archive_cases_dv_subset.Rd @@ -1,11 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reexports.R -\docType{data} -\name{archive_cases_dv_subset_dt} -\alias{archive_cases_dv_subset_dt} -\title{Subset of daily COVID-19 doctor visits and cases from 6 states in archive format} +% Please edit documentation in R/data.R +\name{archive_cases_dv_subset} +\alias{archive_cases_dv_subset} +\title{Subset of daily doctor visits and cases from 6 states in archive format} \format{ -An object of class \code{data.table} (inherits from \code{data.frame}) with 129638 rows and 5 columns. +An \code{epi_archive} data format. The data table DT has 129,638 rows and 5 columns. } \source{ This object contains a modified part of the \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. This data set is licensed under the terms of the @@ -21,9 +20,6 @@ Modifications: signal names slightly altered, and formatted into a tibble. } } -\usage{ -archive_cases_dv_subset_dt -} \description{ This data source is based on information about outpatient visits, provided to us by health system partners, and also contains confirmed @@ -54,13 +50,13 @@ An \code{epi_archive$DT} data format with columns: # the `data()` function. `data()` looks for a file of the same name # in the `data/` directory, which doesn't exist in this package. # works - epiprocess::archive_cases_dv_subset_dt + epiprocess::archive_cases_dv_subset # works library(epiprocess) - archive_cases_dv_subset_dt + archive_cases_dv_subset # fails - data(archive_cases_dv_subset_dt, package = "epiprocess") + data(archive_cases_dv_subset, package = "epiprocess") + } -\keyword{datasets} From f1350101663ca4f5fde6cb164492941f7061463f Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:40:24 -0400 Subject: [PATCH 13/59] reexport archive and covid_case_death_rates --- DESCRIPTION | 1 - NAMESPACE | 1 + R/data.R | 147 --------------------------------- R/reexports.R | 37 +++++++++ man/archive_cases_dv_subset.Rd | 23 ++++-- man/covid_case_death_rates.Rd | 71 ++++++++++++++++ 6 files changed, 123 insertions(+), 157 deletions(-) delete mode 100644 R/data.R create mode 100644 man/covid_case_death_rates.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 69709930..b52d30a1 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -74,7 +74,6 @@ Collate: 'archive.R' 'autoplot.R' 'correlation.R' - 'data.R' 'epi_df.R' 'epiprocess.R' 'group_by_epi_df_methods.R' diff --git a/NAMESPACE b/NAMESPACE index c4ca4682..f820b5dc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -55,6 +55,7 @@ export(autoplot) export(cases_deaths_subset) export(clone) export(complete) +export(covid_case_death_rates) export(covid_incidence_county_subset) export(covid_incidence_outliers) export(detect_outlr) diff --git a/R/data.R b/R/data.R deleted file mode 100644 index d29dcaab..00000000 --- a/R/data.R +++ /dev/null @@ -1,147 +0,0 @@ -#' Detect whether `pkgload` is unregistering a package (with some unlikely false positives) -#' -#' More precisely, detects the presence of a call to an `unregister` or -#' `unregister_namespace` function from any package in the indicated part of the -#' function call stack. -#' -#' @param parent_n optional, single non-`NA` non-negative integer; how many -#' "parent"/"ancestor" calls should we skip inspecting? Default of `0L` will -#' check everything up to, but not including the call to this function. If -#' building wrappers or utilities around this function it may be useful to use -#' this default to ignore those wrappers, especially if they might trigger -#' false positives now or in some future version of this function with a looser -#' function name test. -#' -#' @return Boolean -#' -#' @noRd -some_package_is_being_unregistered <- function(parent_n = 0L) { - calls <- sys.calls() - # `calls` will include the call to this function; strip out this call plus - # `parent_n` additional requested calls to make it like we're reasoning about - # the desired call. This could prevent potential false positives from - # triggering if, in a later version, we decide to loosen the `call_name` - # checks below to something that would be `TRUE` for the name of this function - # or one of the undesired call ancestors. - calls_to_inspect <- utils::head(calls, n = -(parent_n + 1L)) - # Note that `utils::head(sys.calls(), n=-1L)` isn't equivalent, due to lazy - # argument evaluation. Note that copy-pasting the body of this function - # without this `utils::head` operation isn't always equivalent to calling it; - # e.g., within the `value` argument of a package-level `delayedAssign`, - # `sys.calls()` will return `NULL` is some or all cases, including when its - # evaluation has been triggered via `unregister`. - simple_call_names <- purrr::map_chr(calls_to_inspect, function(call) { - maybe_simple_call_name <- rlang::call_name(call) - maybe_simple_call_name %||% NA_character_ - }) - # `pkgload::unregister` is an (the?) exported function that forces - # package-level promises, while `pkgload:::unregister_namespace` is the - # internal function that does this package-level promise. Check for both just - # in case there's another exported function that calls `unregister_namespace` - # or other `pkgload` versions don't use the `unregister_namespace` internal. - # (Note that `NA_character_ %in% ` is `FALSE` rather - # than `NA`, giving the desired semantics and avoiding potential `NA`s in the - # argument to `any`.) - any(simple_call_names %in% c("unregister", "unregister_namespace")) -} - -#' [`base::delayedAssign`] with [`pkgload::unregister`] awareness, injection support -#' -#' Provides better feedback on errors during promise evaluation while a package -#' is being unregistered, to help package developers escape from a situation -#' where a buggy promise prevents package reloading. Also provide `rlang` -#' injection support (like [`rlang::env_bind_lazy`]). The call stack will look -#' different than when using `delayedAssign` directly. -#' -#' @noRd -delayed_assign_with_unregister_awareness <- function(x, value, - eval_env = rlang::caller_env(), - assign_env = rlang::caller_env()) { - value_quosure <- rlang::as_quosure(rlang::enexpr(value), eval_env) - this_env <- environment() - delayedAssign(x, eval.env = this_env, assign.env = assign_env, value = { - if (some_package_is_being_unregistered()) { - withCallingHandlers( - # `rlang::eval_tidy(value_quosure)` is shorter and would sort of work, - # but doesn't give the same `ls`, `rm`, and top-level `<-` behavior as - # we'd have with `delayedAssign`; it doesn't seem to actually evaluate - # quosure's expr in the quosure's env. Using `rlang::eval_bare` instead - # seems to do the trick. (We also could have just used a `value_expr` - # and `eval_env` together rather than introducing `value_quosure` at - # all.) - rlang::eval_bare(rlang::quo_get_expr(value_quosure), rlang::quo_get_env(value_quosure)), - error = function(err) { - cli_abort( - paste( - "An error was raised while attempting to evaluate a promise", - "(prepared with `delayed_assign_with_unregister_awareness`)", - "while an `unregister` or `unregister_namespace` call", - "was being evaluated.", - "This can happen, for example, when `devtools::load_all`", - "reloads a package that contains a buggy promise,", - "because reloading can cause old package-level promises to", - "be forced via `pkgload::unregister` and", - "`pkgload:::unregister_namespace`, due to", - "https://github.com/r-lib/pkgload/pull/157.", - "If this is the current situation, you might be able to", - "be successfully reload the package again after", - "`unloadNamespace`-ing it (but this situation will", - "keep re-occurring every other `devtools::load`", - "and every `devtools:document` until the bug or situation", - "generating the promise's error has been resolved)." - ), - class = "epiprocess__promise_evaluation_error_during_unregister", - parent = err - ) - } - ) - } else { - rlang::eval_bare(rlang::quo_get_expr(value_quosure), rlang::quo_get_env(value_quosure)) - } - }) -} - -#' @name archive_cases_dv_subset -#' @title Subset of daily doctor visits and cases from 6 states in archive format -#' -#' @inherit epidatasets::archive_cases_dv_subset_dt description source references -#' @format An `epi_archive` data format. The data table DT has 129,638 rows and 5 columns. -#' @inheritSection epidatasets::archive_cases_dv_subset_dt Data dictionary -#' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::archive_cases_dv_subset -#' -#' # works -#' library(epiprocess) -#' archive_cases_dv_subset -#' -#' # fails -#' data(archive_cases_dv_subset, package = "epiprocess") -#' -#' @export -# Like normal data objects, set `archive_cases_dv_subset` up as a promise, so it -# doesn't take unnecessary space before it's evaluated. This also avoids a need -# for @include tags. However, this pattern will use unnecessary space after this -# promise is evaluated, because `as_epi_archive` copies `archive_cases_dv_subset_dt` -# and `archive_cases_dv_subset_dt` will stick around along with `archive_cases_dv_subset` -# after they have been evaluated. We may want to add an option to avoid cloning -# in `as_epi_archive` and make use of it here. But we may also want to change -# this into an active binding that copies every time, unless we can hide the -# `DT` field from the user (make it non-`public` in general) or make it -# read-only (in this specific case), so that the user cannot modify the `DT` -# here and potentially mess up examples that they refer to later on. -# -# During development, note that reloading the package and re-evaluating this -# promise should prepare the archive from the DT using any changes that have -# been made to `as_epi_archive`; however, if earlier, any field of -# `archive_cases_dv_subset` was modified using `<-`, a global environment -# binding may have been created with the same name as the package promise, and -# this binding will stick around even when the package is reloaded, and will -# need to be `rm`-d to easily access the refreshed package promise. -delayed_assign_with_unregister_awareness( - "archive_cases_dv_subset", - as_epi_archive(epidatasets::archive_cases_dv_subset_dt, compactify = FALSE) -) diff --git a/R/reexports.R b/R/reexports.R index 26f0ac0c..733947fa 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -150,3 +150,40 @@ covid_incidence_outliers <- epidatasets::covid_incidence_outliers #' data(jhu_confirmed_cumulative_num, package = "epiprocess") #' @export jhu_confirmed_cumulative_num <- epidatasets::jhu_confirmed_cumulative_num + +#' @inherit epidatasets::covid_case_death_rates description source references title +#' @inheritSection epidatasets::covid_case_death_rates Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_case_death_rates +#' +#' # works +#' library(epiprocess) +#' covid_case_death_rates +#' +#' # fails +#' data(covid_case_death_rates, package = "epiprocess") +#' @export +covid_case_death_rates <- epidatasets::covid_case_death_rates + +#' @inherit epidatasets::archive_cases_dv_subset description source references title +#' @inheritSection epidatasets::archive_cases_dv_subset Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::archive_cases_dv_subset +#' +#' # works +#' library(epiprocess) +#' archive_cases_dv_subset +#' +#' # fails +#' data(archive_cases_dv_subset, package = "epiprocess") +#' +#' @export +archive_cases_dv_subset <- epidatasets::archive_cases_dv_subset diff --git a/man/archive_cases_dv_subset.Rd b/man/archive_cases_dv_subset.Rd index 02b8539e..e3464578 100644 --- a/man/archive_cases_dv_subset.Rd +++ b/man/archive_cases_dv_subset.Rd @@ -1,10 +1,11 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data.R +% Please edit documentation in R/reexports.R +\docType{data} \name{archive_cases_dv_subset} \alias{archive_cases_dv_subset} -\title{Subset of daily doctor visits and cases from 6 states in archive format} +\title{Subset of daily COVID-19 doctor visits and cases from 6 states in archive format} \format{ -An \code{epi_archive} data format. The data table DT has 129,638 rows and 5 columns. +An object of class \code{epi_archive} of length 6. } \source{ This object contains a modified part of the \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. This data set is licensed under the terms of the @@ -20,13 +21,16 @@ Modifications: signal names slightly altered, and formatted into a tibble. } } +\usage{ +archive_cases_dv_subset +} \description{ -This data source is based on information about outpatient visits, -provided to us by health system partners, and also contains confirmed -COVID-19 cases based on reports made available by the Center for -Systems Science and Engineering at Johns Hopkins University. -This example data ranges from June 1, 2020 to Dec 1, 2021, and -is also limited to California, Florida, Texas, and New York. +This data source is based on information about outpatient visits, provided +to us by health system partners, and also contains confirmed COVID-19 +cases based on reports made available by the Center for Systems Science +and Engineering at Johns Hopkins University. This example data ranges from +June 1, 2020 to December 1, 2021, issued on dates from June 1, 2020 to December 1, +2021. It is limited to California, Florida, Texas, and New York. It is used in the {epiprocess} \code{compactify}, \code{epix_archive}, and advanced-use vignettes. @@ -60,3 +64,4 @@ An \code{epi_archive$DT} data format with columns: data(archive_cases_dv_subset, package = "epiprocess") } +\keyword{datasets} diff --git a/man/covid_case_death_rates.Rd b/man/covid_case_death_rates.Rd new file mode 100644 index 00000000..806b0e29 --- /dev/null +++ b/man/covid_case_death_rates.Rd @@ -0,0 +1,71 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{covid_case_death_rates} +\alias{covid_case_death_rates} +\title{JHU daily COVID-19 cases and deaths rates from all states} +\format{ +An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 37576 rows and 4 columns. +} +\source{ +This object contains a modified part of the +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} +as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. +This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Johns Hopkins University on behalf of its Center for Systems Science +in Engineering. Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: +These signals are taken directly from the JHU CSSE +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} +without changes. The 7-day average signals are computed by Delphi by +calculating moving averages of the preceding 7 days, so the signal for +June 7 is the average of the underlying data for June 1 through 7, +inclusive. +} +} +\usage{ +covid_case_death_rates +} +\description{ +This data source of confirmed COVID-19 cases and deaths is based on reports +made available by the Center for Systems Science and Engineering at Johns +Hopkins University, as downloaded from the CMU Delphi COVIDcast Epidata +API. This example data is a snapshot as of March 20, 2024, and +ranges from December 31, 2020 to December 31, 2021. It +includes all states. It is used in the {epiprocess} correlation vignette. +} +\section{Data dictionary}{ + + +A tibble with columns: +\describe{ +\item{geo_value}{the geographic value associated with each row +of measurements.} +\item{time_value}{the time value associated with each row of measurements.} +\item{case_rate}{7-day average signal of number of new +confirmed COVID-19 cases per 100,000 population, daily} +\item{death_rate}{7-day average signal of number of new confirmed +deaths due to COVID-19 per 100,000 population, daily} +} + +} + +\examples{ + # Since this is a re-exported dataset, it cannot be loaded using + # the `data()` function. `data()` looks for a file of the same name + # in the `data/` directory, which doesn't exist in this package. + # works + epiprocess::covid_case_death_rates + + # works + library(epiprocess) + covid_case_death_rates + + # fails + data(covid_case_death_rates, package = "epiprocess") +} +\keyword{datasets} From 5e8ba56bfa06c5be026db630ea58a9e84d35a6d9 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:42:12 -0400 Subject: [PATCH 14/59] note as_of in data docs; use as_of in vignette data fetch code --- man/cases_deaths_subset.Rd | 12 ++++++------ man/covid_incidence_county_subset.Rd | 5 +++-- man/covid_incidence_outliers.Rd | 4 ++-- man/jhu_confirmed_cumulative_num.Rd | 7 ++++--- vignettes/aggregation.Rmd | 7 +++++-- vignettes/correlation.Rmd | 21 ++++++++++++--------- vignettes/epiprocess.Rmd | 2 +- vignettes/growth_rate.Rmd | 9 +++++---- vignettes/outliers.Rmd | 2 +- vignettes/slide.Rmd | 9 +++++---- 10 files changed, 44 insertions(+), 34 deletions(-) diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd index 7eb58582..531da748 100644 --- a/man/cases_deaths_subset.Rd +++ b/man/cases_deaths_subset.Rd @@ -3,7 +3,7 @@ \docType{data} \name{cases_deaths_subset} \alias{cases_deaths_subset} -\title{Subset of JHU daily state COVID-19 cases and deaths from 4 states} +\title{Subset of JHU daily state COVID-19 cases and deaths from 6 states} \format{ An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 4026 rows and 6 columns. } @@ -33,11 +33,11 @@ signal names slightly altered, and formatted into a tibble. cases_deaths_subset } \description{ -This data source of confirmed COVID-19 cases and deaths -is based on reports made available by the Center for -Systems Science and Engineering at Johns Hopkins University. -This example data ranges from Mar 1, 2020 to Dec 31, 2021, and is limited to -California, Florida, Texas, New York, Georgia, and Pennsylvania. +This data source of confirmed COVID-19 cases and deaths is based on reports +made available by the Center for Systems Science and Engineering at Johns +Hopkins University. This example data is a snapshot as of March 20, 2024, and +ranges from March 1, 2020 to December 31, 2021. It is limited to California, +Florida, Texas, New York, Georgia, and Pennsylvania. It is used in the {epiprocess} growth rate and \code{epi_slide} vignettes. } diff --git a/man/covid_incidence_county_subset.Rd b/man/covid_incidence_county_subset.Rd index 0b70e3c3..15721e7f 100644 --- a/man/covid_incidence_county_subset.Rd +++ b/man/covid_incidence_county_subset.Rd @@ -36,8 +36,9 @@ covid_incidence_county_subset This data source of confirmed COVID-19 cases and deaths is based on reports made available by the Center for Systems Science and Engineering at Johns Hopkins University. -This example data ranges from Mar 1, 2020 to Dec 31, 2021, -and is limited to Massachusetts and Vermont. +This example data is a snapshot as of March 20, 2024, and +ranges from March 1, 2020 to December 31, 2021. +It is limited to Massachusetts and Vermont. It is used in the {epiprocess} aggregation vignette. } diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd index e1baa9da..81c97526 100644 --- a/man/covid_incidence_outliers.Rd +++ b/man/covid_incidence_outliers.Rd @@ -31,8 +31,8 @@ covid_incidence_outliers This data source of confirmed COVID-19 cases is based on reports made available by the Center for Systems Science and Engineering at Johns Hopkins University. This example data is downloaded from the CMU Delphi -COVIDcast Epidata API. It is a snapshot as of Oct 28, 2021 and captures the -cases from June 1, 2020 to May 31, 2021 and is limited to New Jersey and +COVIDcast Epidata API. It is a snapshot as of October 28, 2021, and captures the +cases from June 1, 2020 to May 31, 2021. It is limited to New Jersey and Florida. This data set is used in the {epiprocess} vignette on outliers. diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd index 37f80805..af243a01 100644 --- a/man/jhu_confirmed_cumulative_num.Rd +++ b/man/jhu_confirmed_cumulative_num.Rd @@ -3,7 +3,7 @@ \docType{data} \name{jhu_confirmed_cumulative_num} \alias{jhu_confirmed_cumulative_num} -\title{Subset of COVID-19 Cumulative Cases from 4 states} +\title{Subset of COVID-19 cumulative case counts from 4 states} \format{ An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 2808 rows and 15 columns. } @@ -18,8 +18,9 @@ jhu_confirmed_cumulative_num } \description{ Data set for 4 states containing COVID-19 Cumulative Cases as reported by -JHU-CSSE and downloaded from the CMU Delphi COVIDcast Epidata API. This -data set covers the period from March 2020 to January 2022, and is limited +JHU-CSSE and downloaded from the CMU Delphi COVIDcast Epidata API. +This example data is a snapshot as of March 20, 2024, and +ranges from March 1, 2020 to January 31, 2022. It is limited to California, Florida, New York, and Texas. It is used in the {epiprocess} "Getting Started" vignette. diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 266cf9f3..f7e0d670 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -20,11 +20,13 @@ library(covidcast) library(epiprocess) library(dplyr) -x <- epiprocess::covid_incidence_county_subset +x <- covid_incidence_county_subset ``` The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE, warning = FALSE} +d <- as.Date("2024-03-20") + # Use covidcast::county_census to get the county and state names y <- covidcast::county_census %>% filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% @@ -38,10 +40,11 @@ x <- pub_covidcast( time_type = "day", geo_values = paste(y$geo_value, collapse = ","), time_values = epirange(20200601, 20211231), + as_of = d ) %>% select(geo_value, time_value, cases = value) %>% full_join(y, by = "geo_value") %>% - as_epi_df() + as_epi_df(as_of = d) ``` The data contains 16,212 rows and 5 columns. diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index 8259df38..e24a2193 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -23,37 +23,40 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -x <- cases_deaths_subset %>% - select(geo_value, time_value, case_rate = case_rate_7d_av, death_rate = death_rate_7d_av) %>% - arrange(geo_value, time_value) %>% - as_epi_df() +x <- covid_case_death_rates %>% + select(geo_value, time_value, case_rate, death_rate) %>% + arrange(geo_value, time_value) ``` The data can also be fetched from the Delphi API with the following query: ```{r, eval = FALSE} +d <- as.Date("2024-03-20") + x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_7dav_incidence_prop", - geo_type = "state", time_type = "day", - geo_values = "*", + geo_type = "state", time_values = epirange(20200301, 20211231), + geo_values = "*", + as_of = d ) %>% select(geo_value, time_value, case_rate = value) y <- pub_covidcast( source = "jhu-csse", signals = "deaths_7dav_incidence_prop", - geo_type = "state", time_type = "day", - geo_values = "*", + geo_type = "state", time_values = epirange(20200301, 20211231), + geo_values = "*", + as_of = d ) %>% select(geo_value, time_value, death_rate = value) x <- x %>% full_join(y, by = c("geo_value", "time_value")) %>% - as_epi_df() + as_epi_df(as_of = d) ``` ## Correlations grouped by time diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 986b7c07..2978ff0b 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -110,7 +110,7 @@ library(dplyr) library(tidyr) library(withr) -cases <- epiprocess::jhu_confirmed_cumulative_num +cases <- jhu_confirmed_cumulative_num colnames(cases) ``` diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index 029356eb..406ae36f 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -24,15 +24,15 @@ library(tidyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -x <- epiprocess::cases_deaths_subset %>% +x <- cases_deaths_subset %>% select(geo_value, time_value, cases = cases_7d_av) %>% filter(geo_value %in% c("pa", "ga") & time_value >= "2020-06-01") %>% - arrange(geo_value, time_value) %>% - as_epi_df() + arrange(geo_value, time_value) ``` The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE} +d <- as.Date("2024-03-20") x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_7dav_incidence_num", @@ -40,10 +40,11 @@ x <- pub_covidcast( time_type = "day", geo_values = "ga,pa", time_values = epirange(20200601, 20211231), + as_of = d ) %>% select(geo_value, time_value, cases = value) %>% arrange(geo_value, time_value) %>% - as_epi_df() + as_epi_df(as_of = d) ``` The data has 1,158 rows and 3 columns. diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index 76bda096..72706d50 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -22,7 +22,7 @@ library(epiprocess) library(dplyr) library(tidyr) -x <- epiprocess::covid_incidence_outliers +x <- covid_incidence_outliers ``` ```{r, fig.width = 8, fig.height = 7, warning=FALSE,message=FALSE} diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd index 175a3e7d..1f218adb 100644 --- a/vignettes/slide.Rmd +++ b/vignettes/slide.Rmd @@ -37,14 +37,14 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -x <- epiprocess::cases_deaths_subset %>% +x <- cases_deaths_subset %>% select(geo_value, time_value, cases) %>% - arrange(geo_value, time_value) %>% - as_epi_df() + arrange(geo_value, time_value) ``` The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE} +d <- as.Date("2024-03-20") x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_incidence_num", @@ -52,10 +52,11 @@ x <- pub_covidcast( time_type = "day", geo_values = "ca,fl,ny,tx,ga,pa", time_values = epirange(20200301, 20211231), + as_of = d ) %>% select(geo_value, time_value, cases = value) %>% arrange(geo_value, time_value) %>% - as_epi_df() + as_epi_df(as_of = d) ``` The data has 2,684 rows and 3 columns. From 171e984e085e731e45a59e0c1baa5643f0b09d1d Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:33:38 -0400 Subject: [PATCH 15/59] remove data.R tests --- tests/testthat/test-data.R | 78 -------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 tests/testthat/test-data.R diff --git a/tests/testthat/test-data.R b/tests/testthat/test-data.R deleted file mode 100644 index 88ecc8c7..00000000 --- a/tests/testthat/test-data.R +++ /dev/null @@ -1,78 +0,0 @@ -test_that("`archive_cases_dv_subset` is formed successfully", { - expect_class(archive_cases_dv_subset, "epi_archive") -}) - -test_that("`delayed_assign_with_unregister_awareness` works as expected on good promises", { - # Since we're testing environment stuff, use some "my_" prefixes to try to - # prevent naming coincidences from changing behavior. - my_eval_env <- rlang::new_environment(list(x = 40L, n_evals = 0L), parent = rlang::base_env()) - my_assign_env <- rlang::new_environment() - delayed_assign_with_unregister_awareness( - "good1", - { - n_evals <- n_evals + 1L - x + 2L - }, - my_eval_env, - my_assign_env - ) - force(my_assign_env[["good1"]]) - force(my_assign_env[["good1"]]) - force(my_assign_env[["good1"]]) - expect_identical(my_assign_env[["good1"]], 42L) - expect_identical(my_eval_env[["n_evals"]], 1L) -}) - -test_that("original `delayedAssign` works as expected on good promises", { - my_eval_env <- rlang::new_environment(list(x = 40L, n_evals = 0L), parent = rlang::base_env()) - my_assign_env <- rlang::new_environment() - delayedAssign( - "good1", - { - n_evals <- n_evals + 1L - x + 2L - }, - my_eval_env, - my_assign_env - ) - force(my_assign_env[["good1"]]) - force(my_assign_env[["good1"]]) - force(my_assign_env[["good1"]]) - expect_identical(my_assign_env[["good1"]], 42L) - expect_identical(my_eval_env[["n_evals"]], 1L) -}) - -test_that("`delayed_assign_with_unregister_awareness` doesn't wrap a buggy promise if not unregistering", { - delayed_assign_with_unregister_awareness("x", cli_abort("msg", class = "original_error_class")) - expect_error(force(x), class = "original_error_class") -}) - -test_that("`delayed_assign_with_unregister_awareness` doesn't wrap a buggy promise if not unregistering", { - delayed_assign_with_unregister_awareness("x", cli_abort("msg", class = "original_error_class")) - # Take advantage of a false positive / hedge against package renaming: make - # our own `unregister` function to trigger the special error message. - unregister <- function(y) y - expect_error(unregister(force(x)), class = "epiprocess__promise_evaluation_error_during_unregister") -}) - -test_that("`delayed_assign_with_unregister_awareness` injection support works", { - my_exprs <- rlang::exprs(a = b + c, d = e) - delayed_assign_with_unregister_awareness( - "good2", list(!!!my_exprs), - eval_env = rlang::new_environment(list(b = 2L, c = 3L, e = 4L), rlang::base_env()) - ) - force(good2) - expect_identical(good2, list(a = 5L, d = 4L)) -}) - -test_that("`some_package_is_being_unregistered` doesn't fail in response to non-simple calls", { - # Prerequisite for current implementation to work (testing here to help debug - # in case some R version doesn't obey): - expect_false(NA_character_ %in% letters) - f <- function() function() some_package_is_being_unregistered() - my_expr <- rlang::expr(f()()) - # Prerequisite for this to test to actually be testing on non-simple calls: - expect_false(rlang::is_call_simple(my_expr)) - # Actual test (`FALSE` is correct; `NA` or error is not): - expect_false(rlang::eval_bare(my_expr)) -}) From 012795e7a0e21f9d8dd1b2f07877e7f4e3838bfa Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:48:34 -0400 Subject: [PATCH 16/59] use delayed assignment for datasets --- R/reexports.R | 12 ++++++------ man/archive_cases_dv_subset.Rd | 10 +++++----- man/cases_deaths_subset.Rd | 4 ++-- man/covid_case_death_rates.Rd | 2 +- man/covid_incidence_county_subset.Rd | 4 ++-- man/covid_incidence_outliers.Rd | 4 ++-- man/jhu_confirmed_cumulative_num.Rd | 11 +++++------ 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/R/reexports.R b/R/reexports.R index 733947fa..81a1b574 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -95,7 +95,7 @@ ggplot2::autoplot #' # fails #' data(cases_deaths_subset, package = "epiprocess") #' @export -cases_deaths_subset <- epidatasets::cases_deaths_subset +delayedAssign("cases_deaths_subset", epidatasets::cases_deaths_subset) #' @inherit epidatasets::covid_incidence_county_subset description source references title #' @inheritSection epidatasets::covid_incidence_county_subset Data dictionary @@ -113,7 +113,7 @@ cases_deaths_subset <- epidatasets::cases_deaths_subset #' # fails #' data(covid_incidence_county_subset, package = "epiprocess") #' @export -covid_incidence_county_subset <- epidatasets::covid_incidence_county_subset +delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_county_subset) #' @inherit epidatasets::covid_incidence_outliers description source references title #' @inheritSection epidatasets::covid_incidence_outliers Data dictionary @@ -131,7 +131,7 @@ covid_incidence_county_subset <- epidatasets::covid_incidence_county_subset #' # fails #' data(covid_incidence_outliers, package = "epiprocess") #' @export -covid_incidence_outliers <- epidatasets::covid_incidence_outliers +delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) #' @inherit epidatasets::jhu_confirmed_cumulative_num description source references title #' @inheritSection epidatasets::jhu_confirmed_cumulative_num Data dictionary @@ -149,7 +149,7 @@ covid_incidence_outliers <- epidatasets::covid_incidence_outliers #' # fails #' data(jhu_confirmed_cumulative_num, package = "epiprocess") #' @export -jhu_confirmed_cumulative_num <- epidatasets::jhu_confirmed_cumulative_num +delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulative_num) #' @inherit epidatasets::covid_case_death_rates description source references title #' @inheritSection epidatasets::covid_case_death_rates Data dictionary @@ -167,7 +167,7 @@ jhu_confirmed_cumulative_num <- epidatasets::jhu_confirmed_cumulative_num #' # fails #' data(covid_case_death_rates, package = "epiprocess") #' @export -covid_case_death_rates <- epidatasets::covid_case_death_rates +delayedAssign("covid_case_death_rates", epidatasets::covid_case_death_rates) #' @inherit epidatasets::archive_cases_dv_subset description source references title #' @inheritSection epidatasets::archive_cases_dv_subset Data dictionary @@ -186,4 +186,4 @@ covid_case_death_rates <- epidatasets::covid_case_death_rates #' data(archive_cases_dv_subset, package = "epiprocess") #' #' @export -archive_cases_dv_subset <- epidatasets::archive_cases_dv_subset +delayedAssign("archive_cases_dv_subset", epidatasets::archive_cases_dv_subset) diff --git a/man/archive_cases_dv_subset.Rd b/man/archive_cases_dv_subset.Rd index e3464578..7eff52df 100644 --- a/man/archive_cases_dv_subset.Rd +++ b/man/archive_cases_dv_subset.Rd @@ -18,7 +18,7 @@ Modifications: \item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From the COVIDcast Doctor Visits API}: The signal \code{percent_cli} is taken directly from the API without changes. \item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: \code{case_rate_7d_av} signal was computed by Delphi from the original JHU-CSSE data by calculating moving averages of the preceding 7 days, so the signal for June 7 is the average of the underlying data for June 1 through 7, inclusive. \item Furthermore, the data has been limited to a very small number of rows, the -signal names slightly altered, and formatted into a tibble. +signal names slightly altered, and formatted into an \code{epi_archive}. } } \usage{ @@ -32,19 +32,19 @@ and Engineering at Johns Hopkins University. This example data ranges from June 1, 2020 to December 1, 2021, issued on dates from June 1, 2020 to December 1, 2021. It is limited to California, Florida, Texas, and New York. -It is used in the {epiprocess} \code{compactify}, \code{epix_archive}, and -advanced-use vignettes. +It is used in the {epiprocess} \code{compactify}, \code{epi_archive}, and +advanced-use (\code{advanced}) vignettes. } \section{Data dictionary}{ -An \code{epi_archive$DT} data format with columns: +The data in the \code{epi_archive$DT} attribute has columns: \describe{ \item{geo_value}{the geographic value associated with each row of measurements.} \item{time_value}{the time value associated with each row of measurements.} \item{version}{the time value specifying the version for each row of measurements. } \item{percent_cli}{percentage of doctor’s visits with CLI (COVID-like illness) computed from medical insurance claims} -\item{case_rate_7d_av}{7-day average signal of number of new confirmed deaths due to COVID-19 per 100,000 population, daily} +\item{case_rate_7d_av}{7-day average signal of number of new confirmed cases due to COVID-19 per 100,000 population, daily} } } diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd index 531da748..397ef93c 100644 --- a/man/cases_deaths_subset.Rd +++ b/man/cases_deaths_subset.Rd @@ -26,7 +26,7 @@ The 7-day average signals were computed by Delphi by calculating moving averages of the preceding 7 days, so the signal for June 7 is the average of the underlying data for June 1 through 7, inclusive. \item Furthermore, the data has been limited to a very small number of rows, the -signal names slightly altered, and formatted into a tibble. +signal names slightly altered, and formatted into an \code{epi_df}. } } \usage{ @@ -44,7 +44,7 @@ It is used in the {epiprocess} growth rate and \code{epi_slide} vignettes. \section{Data dictionary}{ -A tibble with columns: +The data has columns: \describe{ \item{geo_value}{the geographic value associated with each row of measurements.} diff --git a/man/covid_case_death_rates.Rd b/man/covid_case_death_rates.Rd index 806b0e29..f51cc858 100644 --- a/man/covid_case_death_rates.Rd +++ b/man/covid_case_death_rates.Rd @@ -41,7 +41,7 @@ includes all states. It is used in the {epiprocess} correlation vignette. \section{Data dictionary}{ -A tibble with columns: +The data has columns: \describe{ \item{geo_value}{the geographic value associated with each row of measurements.} diff --git a/man/covid_incidence_county_subset.Rd b/man/covid_incidence_county_subset.Rd index 15721e7f..ba2c9bc4 100644 --- a/man/covid_incidence_county_subset.Rd +++ b/man/covid_incidence_county_subset.Rd @@ -26,7 +26,7 @@ as moving averages of the preceding 7 days, so the signal for June 7 is the average of the underlying data for June 1 through 7, inclusive. \item Furthermore, the data has been limited to a very small number of rows, -formatted into a tibble, and the signal names slightly altered. +formatted into an \code{epi_df}, and the signal names slightly altered. } } \usage{ @@ -45,7 +45,7 @@ It is used in the {epiprocess} aggregation vignette. \section{Data dictionary}{ -A tibble with columns: +The data has columns: \describe{ \item{geo_value}{the geographic value associated with each row of measurements.} \item{time_value}{the time value associated with each row of measurements.} diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd index 81c97526..0d2ea889 100644 --- a/man/covid_incidence_outliers.Rd +++ b/man/covid_incidence_outliers.Rd @@ -21,7 +21,7 @@ Modifications: \item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: These signals are taken directly from the JHU CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} without changes. \item Furthermore, the data has been limited to a very small number of rows, -formatted into a tibble, and the signal names slightly altered. +formatted into an \code{epi_df}, and the signal names slightly altered. } } \usage{ @@ -40,7 +40,7 @@ This data set is used in the {epiprocess} vignette on outliers. \section{Data dictionary}{ -A tibble with columns: +The data has columns: \describe{ \item{geo_value}{the geographic value associated with each row of measurements.} \item{time_value}{the time value associated with each row of measurements.} diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd index af243a01..fad62fa8 100644 --- a/man/jhu_confirmed_cumulative_num.Rd +++ b/man/jhu_confirmed_cumulative_num.Rd @@ -5,7 +5,7 @@ \alias{jhu_confirmed_cumulative_num} \title{Subset of COVID-19 cumulative case counts from 4 states} \format{ -An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 2808 rows and 15 columns. +An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 2808 rows and 14 columns. } \source{ This object contains a modified part of the \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. This data set is licensed under the terms of the @@ -28,7 +28,7 @@ It is used in the {epiprocess} "Getting Started" vignette. \section{Data dictionary}{ -A tibble with 2,808 rows and 15 variables: +The data has columns: \describe{ \item{geo_value}{the geographic value associated with each row of measurements.} \item{signal}{name of metric, derived from upstream data.} @@ -36,15 +36,14 @@ A tibble with 2,808 rows and 15 variables: \item{geo_type}{spatial resolution of the signal.} \item{time_type}{temporal resolution of the signal.} \item{time_value}{the time value associated with each row of measurements.} -\item{direction}{trend classifier (+1 -> increasing, 0 -> steady or not determined, -1 -> decreasing).} \item{issue}{time unit (e.g., date) when the signal data were published.} \item{lag}{time delta (e.g. days) between when the underlying events happened and when the data were published.} \item{missing_value}{an integer code that is zero when the value field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} \item{missing_stderr}{an integer code that is zero when the stderr field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} \item{missing_sample_size}{an integer code that is zero when the sample_size field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} -\item{value}{Cumulative number of confirmed COVID-19 cases, derived from the underlying data source.} -\item{stderr}{approximate standard error of the statistic with respect to its sampling distribution, null when not applicable.} -\item{sample_size}{number of “data points” used in computing the statistic, null when not applicable.} +\item{value}{cumulative number of confirmed COVID-19 cases, derived from the underlying data source.} +\item{stderr}{approximate standard error of the statistic with respect to its sampling distribution, NA when not applicable.} +\item{sample_size}{number of “data points” used in computing the statistic, NA when not applicable.} } } From ad225b2dcee135c856dab5692894cedebc3c4823 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:52:58 -0400 Subject: [PATCH 17/59] linting --- vignettes/archive.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index 62767494..c414095c 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -49,7 +49,7 @@ dv <- pub_covidcast( geo_values = "ca,fl,ny,tx", time_values = epirange(20200601, 20211201), issues = epirange(20200601, 20211201) -) %>% +) %>% rename(version = issue, percent_cli = value) ``` From d88d44d4b9af91ca8a44a8cf0f6c2623f01d1cda Mon Sep 17 00:00:00 2001 From: nmdefries Date: Mon, 16 Sep 2024 20:58:10 +0000 Subject: [PATCH 18/59] style: styler (GHA) --- R/reexports.R | 120 +++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/R/reexports.R b/R/reexports.R index 81a1b574..0ba0e1b9 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -82,108 +82,108 @@ ggplot2::autoplot #' @inherit epidatasets::cases_deaths_subset description source references title #' @inheritSection epidatasets::cases_deaths_subset Data dictionary #' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::cases_deaths_subset +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::cases_deaths_subset #' -#' # works -#' library(epiprocess) -#' cases_deaths_subset +#' # works +#' library(epiprocess) +#' cases_deaths_subset #' -#' # fails -#' data(cases_deaths_subset, package = "epiprocess") +#' # fails +#' data(cases_deaths_subset, package = "epiprocess") #' @export delayedAssign("cases_deaths_subset", epidatasets::cases_deaths_subset) #' @inherit epidatasets::covid_incidence_county_subset description source references title #' @inheritSection epidatasets::covid_incidence_county_subset Data dictionary #' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::covid_incidence_county_subset +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_incidence_county_subset #' -#' # works -#' library(epiprocess) -#' covid_incidence_county_subset +#' # works +#' library(epiprocess) +#' covid_incidence_county_subset #' -#' # fails -#' data(covid_incidence_county_subset, package = "epiprocess") +#' # fails +#' data(covid_incidence_county_subset, package = "epiprocess") #' @export delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_county_subset) #' @inherit epidatasets::covid_incidence_outliers description source references title #' @inheritSection epidatasets::covid_incidence_outliers Data dictionary #' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::covid_incidence_outliers +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_incidence_outliers #' -#' # works -#' library(epiprocess) -#' covid_incidence_outliers +#' # works +#' library(epiprocess) +#' covid_incidence_outliers #' -#' # fails -#' data(covid_incidence_outliers, package = "epiprocess") +#' # fails +#' data(covid_incidence_outliers, package = "epiprocess") #' @export delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) #' @inherit epidatasets::jhu_confirmed_cumulative_num description source references title #' @inheritSection epidatasets::jhu_confirmed_cumulative_num Data dictionary #' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::jhu_confirmed_cumulative_num +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::jhu_confirmed_cumulative_num #' -#' # works -#' library(epiprocess) -#' jhu_confirmed_cumulative_num +#' # works +#' library(epiprocess) +#' jhu_confirmed_cumulative_num #' -#' # fails -#' data(jhu_confirmed_cumulative_num, package = "epiprocess") +#' # fails +#' data(jhu_confirmed_cumulative_num, package = "epiprocess") #' @export delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulative_num) #' @inherit epidatasets::covid_case_death_rates description source references title #' @inheritSection epidatasets::covid_case_death_rates Data dictionary #' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::covid_case_death_rates +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_case_death_rates #' -#' # works -#' library(epiprocess) -#' covid_case_death_rates +#' # works +#' library(epiprocess) +#' covid_case_death_rates #' -#' # fails -#' data(covid_case_death_rates, package = "epiprocess") +#' # fails +#' data(covid_case_death_rates, package = "epiprocess") #' @export delayedAssign("covid_case_death_rates", epidatasets::covid_case_death_rates) #' @inherit epidatasets::archive_cases_dv_subset description source references title #' @inheritSection epidatasets::archive_cases_dv_subset Data dictionary #' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::archive_cases_dv_subset +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::archive_cases_dv_subset #' -#' # works -#' library(epiprocess) -#' archive_cases_dv_subset +#' # works +#' library(epiprocess) +#' archive_cases_dv_subset #' -#' # fails -#' data(archive_cases_dv_subset, package = "epiprocess") +#' # fails +#' data(archive_cases_dv_subset, package = "epiprocess") #' #' @export delayedAssign("archive_cases_dv_subset", epidatasets::archive_cases_dv_subset) From 779ec2aa42eae35e1f6ca3e6de426c7c15c73b84 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:41:00 -0400 Subject: [PATCH 19/59] use cases_deaths_subset instead of covid_case_death_rates --- NAMESPACE | 1 - R/reexports.R | 18 ------- man/archive_cases_dv_subset.Rd | 20 ++++---- man/cases_deaths_subset.Rd | 20 ++++---- man/covid_case_death_rates.Rd | 71 ---------------------------- man/covid_incidence_county_subset.Rd | 20 ++++---- man/covid_incidence_outliers.Rd | 20 ++++---- man/jhu_confirmed_cumulative_num.Rd | 20 ++++---- vignettes/correlation.Rmd | 4 +- 9 files changed, 52 insertions(+), 142 deletions(-) delete mode 100644 man/covid_case_death_rates.Rd diff --git a/NAMESPACE b/NAMESPACE index f820b5dc..c4ca4682 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -55,7 +55,6 @@ export(autoplot) export(cases_deaths_subset) export(clone) export(complete) -export(covid_case_death_rates) export(covid_incidence_county_subset) export(covid_incidence_outliers) export(detect_outlr) diff --git a/R/reexports.R b/R/reexports.R index 0ba0e1b9..a86fdd1d 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -151,24 +151,6 @@ delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) #' @export delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulative_num) -#' @inherit epidatasets::covid_case_death_rates description source references title -#' @inheritSection epidatasets::covid_case_death_rates Data dictionary -#' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::covid_case_death_rates -#' -#' # works -#' library(epiprocess) -#' covid_case_death_rates -#' -#' # fails -#' data(covid_case_death_rates, package = "epiprocess") -#' @export -delayedAssign("covid_case_death_rates", epidatasets::covid_case_death_rates) - #' @inherit epidatasets::archive_cases_dv_subset description source references title #' @inheritSection epidatasets::archive_cases_dv_subset Data dictionary #' @examples diff --git a/man/archive_cases_dv_subset.Rd b/man/archive_cases_dv_subset.Rd index 7eff52df..f07f8149 100644 --- a/man/archive_cases_dv_subset.Rd +++ b/man/archive_cases_dv_subset.Rd @@ -50,18 +50,18 @@ The data in the \code{epi_archive$DT} attribute has columns: } \examples{ - # Since this is a re-exported dataset, it cannot be loaded using - # the `data()` function. `data()` looks for a file of the same name - # in the `data/` directory, which doesn't exist in this package. - # works - epiprocess::archive_cases_dv_subset +# Since this is a re-exported dataset, it cannot be loaded using +# the `data()` function. `data()` looks for a file of the same name +# in the `data/` directory, which doesn't exist in this package. +# works +epiprocess::archive_cases_dv_subset - # works - library(epiprocess) - archive_cases_dv_subset +# works +library(epiprocess) +archive_cases_dv_subset - # fails - data(archive_cases_dv_subset, package = "epiprocess") +# fails +data(archive_cases_dv_subset, package = "epiprocess") } \keyword{datasets} diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd index 397ef93c..fdd38c61 100644 --- a/man/cases_deaths_subset.Rd +++ b/man/cases_deaths_subset.Rd @@ -61,17 +61,17 @@ COVID-19 cases, daily} } \examples{ - # Since this is a re-exported dataset, it cannot be loaded using - # the `data()` function. `data()` looks for a file of the same name - # in the `data/` directory, which doesn't exist in this package. - # works - epiprocess::cases_deaths_subset +# Since this is a re-exported dataset, it cannot be loaded using +# the `data()` function. `data()` looks for a file of the same name +# in the `data/` directory, which doesn't exist in this package. +# works +epiprocess::cases_deaths_subset - # works - library(epiprocess) - cases_deaths_subset +# works +library(epiprocess) +cases_deaths_subset - # fails - data(cases_deaths_subset, package = "epiprocess") +# fails +data(cases_deaths_subset, package = "epiprocess") } \keyword{datasets} diff --git a/man/covid_case_death_rates.Rd b/man/covid_case_death_rates.Rd deleted file mode 100644 index f51cc858..00000000 --- a/man/covid_case_death_rates.Rd +++ /dev/null @@ -1,71 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reexports.R -\docType{data} -\name{covid_case_death_rates} -\alias{covid_case_death_rates} -\title{JHU daily COVID-19 cases and deaths rates from all states} -\format{ -An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 37576 rows and 4 columns. -} -\source{ -This object contains a modified part of the -\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} -as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. -This data set is licensed under the terms of the -\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} -by the Johns Hopkins University on behalf of its Center for Systems Science -in Engineering. Copyright Johns Hopkins University 2020. - -Modifications: -\itemize{ -\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: -These signals are taken directly from the JHU CSSE -\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} -without changes. The 7-day average signals are computed by Delphi by -calculating moving averages of the preceding 7 days, so the signal for -June 7 is the average of the underlying data for June 1 through 7, -inclusive. -} -} -\usage{ -covid_case_death_rates -} -\description{ -This data source of confirmed COVID-19 cases and deaths is based on reports -made available by the Center for Systems Science and Engineering at Johns -Hopkins University, as downloaded from the CMU Delphi COVIDcast Epidata -API. This example data is a snapshot as of March 20, 2024, and -ranges from December 31, 2020 to December 31, 2021. It -includes all states. It is used in the {epiprocess} correlation vignette. -} -\section{Data dictionary}{ - - -The data has columns: -\describe{ -\item{geo_value}{the geographic value associated with each row -of measurements.} -\item{time_value}{the time value associated with each row of measurements.} -\item{case_rate}{7-day average signal of number of new -confirmed COVID-19 cases per 100,000 population, daily} -\item{death_rate}{7-day average signal of number of new confirmed -deaths due to COVID-19 per 100,000 population, daily} -} - -} - -\examples{ - # Since this is a re-exported dataset, it cannot be loaded using - # the `data()` function. `data()` looks for a file of the same name - # in the `data/` directory, which doesn't exist in this package. - # works - epiprocess::covid_case_death_rates - - # works - library(epiprocess) - covid_case_death_rates - - # fails - data(covid_case_death_rates, package = "epiprocess") -} -\keyword{datasets} diff --git a/man/covid_incidence_county_subset.Rd b/man/covid_incidence_county_subset.Rd index ba2c9bc4..dbd382b8 100644 --- a/man/covid_incidence_county_subset.Rd +++ b/man/covid_incidence_county_subset.Rd @@ -57,17 +57,17 @@ The data has columns: } \examples{ - # Since this is a re-exported dataset, it cannot be loaded using - # the `data()` function. `data()` looks for a file of the same name - # in the `data/` directory, which doesn't exist in this package. - # works - epiprocess::covid_incidence_county_subset +# Since this is a re-exported dataset, it cannot be loaded using +# the `data()` function. `data()` looks for a file of the same name +# in the `data/` directory, which doesn't exist in this package. +# works +epiprocess::covid_incidence_county_subset - # works - library(epiprocess) - covid_incidence_county_subset +# works +library(epiprocess) +covid_incidence_county_subset - # fails - data(covid_incidence_county_subset, package = "epiprocess") +# fails +data(covid_incidence_county_subset, package = "epiprocess") } \keyword{datasets} diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd index 0d2ea889..19110970 100644 --- a/man/covid_incidence_outliers.Rd +++ b/man/covid_incidence_outliers.Rd @@ -50,17 +50,17 @@ The data has columns: } \examples{ - # Since this is a re-exported dataset, it cannot be loaded using - # the `data()` function. `data()` looks for a file of the same name - # in the `data/` directory, which doesn't exist in this package. - # works - epiprocess::covid_incidence_outliers +# Since this is a re-exported dataset, it cannot be loaded using +# the `data()` function. `data()` looks for a file of the same name +# in the `data/` directory, which doesn't exist in this package. +# works +epiprocess::covid_incidence_outliers - # works - library(epiprocess) - covid_incidence_outliers +# works +library(epiprocess) +covid_incidence_outliers - # fails - data(covid_incidence_outliers, package = "epiprocess") +# fails +data(covid_incidence_outliers, package = "epiprocess") } \keyword{datasets} diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd index fad62fa8..b1636115 100644 --- a/man/jhu_confirmed_cumulative_num.Rd +++ b/man/jhu_confirmed_cumulative_num.Rd @@ -49,17 +49,17 @@ The data has columns: } \examples{ - # Since this is a re-exported dataset, it cannot be loaded using - # the `data()` function. `data()` looks for a file of the same name - # in the `data/` directory, which doesn't exist in this package. - # works - epiprocess::jhu_confirmed_cumulative_num +# Since this is a re-exported dataset, it cannot be loaded using +# the `data()` function. `data()` looks for a file of the same name +# in the `data/` directory, which doesn't exist in this package. +# works +epiprocess::jhu_confirmed_cumulative_num - # works - library(epiprocess) - jhu_confirmed_cumulative_num +# works +library(epiprocess) +jhu_confirmed_cumulative_num - # fails - data(jhu_confirmed_cumulative_num, package = "epiprocess") +# fails +data(jhu_confirmed_cumulative_num, package = "epiprocess") } \keyword{datasets} diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index e24a2193..6556f9d7 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -23,8 +23,8 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -x <- covid_case_death_rates %>% - select(geo_value, time_value, case_rate, death_rate) %>% +x <- cases_deaths_subset %>% + select(geo_value, time_value, case_rate = case_rate_7d_av, death_rate = death_rate_7d_av) %>% arrange(geo_value, time_value) ``` From c8015602b0b425b0a4a67ef5df02a34d8212a16a Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Mon, 30 Sep 2024 16:56:35 -0700 Subject: [PATCH 20/59] Fix validate_version_bound typo --- DESCRIPTION | 2 +- R/archive.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 790b36a5..784496b2 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: epiprocess Title: Tools for basic signal processing in epidemiology -Version: 0.9.0 +Version: 0.10.0 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), diff --git a/R/archive.R b/R/archive.R index e877d397..d8102165 100644 --- a/R/archive.R +++ b/R/archive.R @@ -25,7 +25,7 @@ #' @noRd validate_version_bound <- function(version_bound, x, na_ok = FALSE, version_bound_arg = rlang::caller_arg(version_bound), - x_arg = rlang::caller_arg(version_bound)) { + x_arg = rlang::caller_arg(x)) { if (is.null(version_bound)) { cli_abort( "{version_bound_arg} cannot be NULL", From e5adcbd05d78940d56522f100cf2bea4252cb900 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Mon, 30 Sep 2024 17:43:31 -0700 Subject: [PATCH 21/59] feat(epi[x]_slide): hint on forgotten syntax specifying comp --- DESCRIPTION | 2 +- NEWS.md | 7 ++++++ R/grouped_epi_archive.R | 5 +++-- R/slide.R | 4 +++- R/utils.R | 41 +++++++++++++++++++++++++--------- man/as_slide_computation.Rd | 16 +++++++++++-- tests/testthat/_snaps/utils.md | 27 ++++++++++++++++++++++ tests/testthat/test-utils.R | 29 ++++++++++++++++++++++++ 8 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 tests/testthat/_snaps/utils.md diff --git a/DESCRIPTION b/DESCRIPTION index 790b36a5..086db905 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: epiprocess Title: Tools for basic signal processing in epidemiology -Version: 0.9.0 +Version: 0.10.1 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), diff --git a/NEWS.md b/NEWS.md index ee04b7f3..4561bce2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,13 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicate PR's. +# epiprocess 0.10 + +## Improvements +- `epi_slide` and `epix_slide` now provide some hints if you forget a `~` when + using a formula to specify the slide computation, and other bits of forgotten + syntax. + # epiprocess 0.9 ## Breaking changes diff --git a/R/grouped_epi_archive.R b/R/grouped_epi_archive.R index b592cd91..7bdadaa5 100644 --- a/R/grouped_epi_archive.R +++ b/R/grouped_epi_archive.R @@ -312,14 +312,15 @@ epix_slide.grouped_epi_archive <- function( cli_abort("If `f` is missing then a computation must be specified via `...`.") } - .slide_comp <- as_diagonal_slide_computation(quosures) + .f_arg <- ".f" # dummy val, shouldn't be used since we're not using `.f` + .slide_comp <- as_diagonal_slide_computation(quosures, .f_arg = .f_arg, .call = caller_env()) # Magic value that passes zero args as dots in calls below. Equivalent to # `... <- missing_arg()`, but use `assign` to avoid warning about # improper use of dots. assign("...", missing_arg()) } else { used_data_masking <- FALSE - .slide_comp <- as_diagonal_slide_computation(.f, ...) + .slide_comp <- as_diagonal_slide_computation(.f, ..., .f_arg = caller_arg(.f), .call = caller_env()) } # Computation for one group, one time value diff --git a/R/slide.R b/R/slide.R index 5a7fbd6a..fa72d1cb 100644 --- a/R/slide.R +++ b/R/slide.R @@ -147,14 +147,16 @@ epi_slide <- function( } .f <- quosures + .f_arg <- ".f" # dummy val, shouldn't be used since we're not using `.f` # Magic value that passes zero args as dots in calls below. Equivalent to # `... <- missing_arg()`, but `assign` avoids warning about improper use of # dots. assign("...", missing_arg()) } else { used_data_masking <- FALSE + .f_arg <- caller_arg(.f) } - .slide_comp <- as_time_slide_computation(.f, ...) + .slide_comp <- as_time_slide_computation(.f, ..., .f_arg = .f_arg, .call = caller_env()) .align <- rlang::arg_match(.align) time_type <- attr(.x, "metadata")$time_type diff --git a/R/utils.R b/R/utils.R index bb30264a..e48a9c99 100644 --- a/R/utils.R +++ b/R/utils.R @@ -358,9 +358,24 @@ assert_sufficient_f_args <- function(.f, ..., .ref_time_value_label) { #' @importFrom rlang is_function new_function f_env is_environment missing_arg #' f_rhs is_formula caller_arg caller_env #' @keywords internal -as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_time_value_label) { - arg <- caller_arg(.f) - call <- caller_env() +as_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = caller_env(), .ref_time_value_long_varnames, .ref_time_value_label) { + f_arg <- .f_arg # for cli interpolation, avoid dot prefix + withCallingHandlers( + { + force(.f) + }, + error = function(e) { + cli_abort( + c("Failed to convert {.code {f_arg}} to a slide computation.", + "*" = "If you were trying to use the formula interface, maybe you forgot a tilde at the beginning.", + "*" = "If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`.", + "*" = "If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma." + ), + parent = e, + class = "epiprocess__as_slide_computation__error_forcing_.f" + ) + } + ) if (rlang::is_quosures(.f)) { quosures <- rlang::quos_auto_name(.f) # resolves := among other things @@ -463,10 +478,10 @@ as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_ti } if (length(.f) > 2) { - cli_abort("{.code {arg}} must be a one-sided formula", + cli_abort("{.code {f_arg}} must be a one-sided formula", class = "epiprocess__as_slide_computation__formula_is_twosided", epiprocess__f = .f, - call = call + .call = .call ) } if (rlang::dots_n(...) > 0L) { @@ -486,7 +501,7 @@ as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_ti class = "epiprocess__as_slide_computation__formula_has_no_env", epiprocess__f = .f, epiprocess__f_env = env, - arg = arg, call = call + .f_arg = .f_arg, .call = .call ) } @@ -513,26 +528,32 @@ as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_ti class = "epiprocess__as_slide_computation__cant_convert_catchall", epiprocess__f = .f, epiprocess__f_class = class(.f), - arg = arg, - call = call + .f_arg = .f_arg, + .call = .call ) } #' @rdname as_slide_computation +#' @importFrom rlang caller_arg caller_env #' @keywords internal -as_time_slide_computation <- function(.f, ...) { +as_time_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = caller_env()) { as_slide_computation( .f, ..., + .f_arg = .f_arg, + .call = .call, .ref_time_value_long_varnames = ".ref_time_value", .ref_time_value_label = "reference time value" ) } #' @rdname as_slide_computation +#' @importFrom rlang caller_arg caller_env #' @keywords internal -as_diagonal_slide_computation <- function(.f, ...) { +as_diagonal_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = caller_env()) { as_slide_computation( .f, ..., + .f_arg = .f_arg, + .call = .call, .ref_time_value_long_varnames = c(".version", ".ref_time_value"), .ref_time_value_label = "version" ) diff --git a/man/as_slide_computation.Rd b/man/as_slide_computation.Rd index 3db5a940..72526b02 100644 --- a/man/as_slide_computation.Rd +++ b/man/as_slide_computation.Rd @@ -58,13 +58,25 @@ for evaluating quosures as_slide_computation( .f, ..., + .f_arg = caller_arg(.f), + .call = caller_env(), .ref_time_value_long_varnames, .ref_time_value_label ) -as_time_slide_computation(.f, ...) +as_time_slide_computation( + .f, + ..., + .f_arg = caller_arg(.f), + .call = caller_env() +) -as_diagonal_slide_computation(.f, ...) +as_diagonal_slide_computation( + .f, + ..., + .f_arg = caller_arg(.f), + .call = caller_env() +) } \arguments{ \item{...}{Additional arguments to pass to the function or formula diff --git a/tests/testthat/_snaps/utils.md b/tests/testthat/_snaps/utils.md new file mode 100644 index 00000000..8a8b0470 --- /dev/null +++ b/tests/testthat/_snaps/utils.md @@ -0,0 +1,27 @@ +# as_slide_computation raises errors as expected + + Code + toy_edf %>% group_by(geo_value) %>% epi_slide(.window_size = 6, tibble( + slide_value = mean(.x$value))) + Condition + Error in `as_slide_computation()`: + ! Failed to convert `tibble(slide_value = mean(.x$value))` to a slide computation. + * If you were trying to use the formula interface, maybe you forgot a tilde at the beginning. + * If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`. + * If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma. + Caused by error: + ! object '.x' not found + +--- + + Code + toy_archive %>% epix_slide(tibble(slide_value = mean(.x$value))) + Condition + Error in `as_slide_computation()`: + ! Failed to convert `.f` to a slide computation. + * If you were trying to use the formula interface, maybe you forgot a tilde at the beginning. + * If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`. + * If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma. + Caused by error: + ! object '.x' not found + diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index a159f98e..22cd7533 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -230,6 +230,35 @@ test_that("as_slide_computation raises errors as expected", { expect_error(as_time_slide_computation(5), class = "epiprocess__as_slide_computation__cant_convert_catchall" ) + + # If `.f` doesn't look like tidyeval and we fail to force it, then we hint to + # the user some potential problems: + toy_edf <- tibble(geo_value = 1, time_value = c(1, 2), value = 1:2) %>% + as_epi_df(as_of = 1) + toy_archive <- tibble(version = c(1, 2, 2), geo_value = 1, time_value = c(1, 1, 2), value = 1:3) %>% + as_epi_archive() + expect_error( + toy_edf %>% + group_by(geo_value) %>% + epi_slide(.window_size = 6, tibble(slide_value = mean(.x$value))), + class = "epiprocess__as_slide_computation__error_forcing_.f" + ) + expect_snapshot( + error = TRUE, + toy_edf %>% + group_by(geo_value) %>% + epi_slide(.window_size = 6, tibble(slide_value = mean(.x$value))) + ) + expect_error( + toy_archive %>% + epix_slide(tibble(slide_value = mean(.x$value))), + class = "epiprocess__as_slide_computation__error_forcing_.f" + ) + expect_snapshot( + error = TRUE, + toy_archive %>% + epix_slide(tibble(slide_value = mean(.x$value))) + ) }) test_that("as_slide_computation works", { From d9cf933e03bb186255f8b49f404b59ccfab2cf28 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Tue, 1 Oct 2024 15:23:21 -0700 Subject: [PATCH 22/59] fix(validate_slide_window_arg): match doc'd Date x difftime reqs --- R/utils.R | 2 +- tests/testthat/test-utils.R | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/utils.R b/R/utils.R index bb30264a..88c7eaa0 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1001,7 +1001,7 @@ validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRU msg <- "" if (!identical(arg, Inf)) { if (time_type == "day") { - if (!test_int(arg, lower = 0L) && !(inherits(arg, "difftime") && units(arg) == "days")) { + if (!test_int(arg, lower = 0L) || inherits(arg, "difftime") && units(arg) != "days") { msg <- glue::glue_collapse(c("difftime with units in days", "non-negative integer", "Inf"), " or ") } } else if (time_type == "week") { diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index a159f98e..5767751d 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -306,7 +306,8 @@ test_that("validate_slide_window_arg works", { } expect_no_error(validate_slide_window_arg(as.difftime(1, units = "days"), "day")) expect_no_error(validate_slide_window_arg(1, "day")) - expect_no_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "day")) + expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "day")) + expect_error(validate_slide_window_arg(as.difftime(1, units = "secs"), "day")) expect_no_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "week")) expect_error(validate_slide_window_arg(1, "week")) From 4f37e4aa245b7fa1f5fc182a4292ef43b88a0c12 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Tue, 1 Oct 2024 16:06:47 -0700 Subject: [PATCH 23/59] Complete docs and match code&messages wrt .window_size validation * Mention what's acceptable for yearmonth time_type * Mention Inf in validation errors as acceptable iff it's actually acceptable. * Reject any other strange "int" classes that pass test_int. (It rejects Date and POSIXt, but perhaps there are others.) * Refactor to use helper function test_sensible_int instead of test_int (as latter accepts difftimes and makes logic look confusing). --- R/slide.R | 6 +-- R/utils.R | 83 +++++++++++++++++++++----------- man-roxygen/basic-slide-params.R | 3 +- man/epi_slide.Rd | 3 +- man/epi_slide_mean.Rd | 5 +- man/epi_slide_opt.Rd | 5 +- man/epi_slide_sum.Rd | 5 +- man/test_sensible_int.Rd | 28 +++++++++++ tests/testthat/test-utils.R | 24 ++++++--- 9 files changed, 118 insertions(+), 44 deletions(-) create mode 100644 man/test_sensible_int.Rd diff --git a/R/slide.R b/R/slide.R index 5a7fbd6a..b971b9d5 100644 --- a/R/slide.R +++ b/R/slide.R @@ -576,7 +576,7 @@ get_before_after_from_window <- function(window_size, align, time_type) { #' ungroup() epi_slide_opt <- function( .x, .col_names, .f, ..., - .window_size = 1, .align = c("right", "center", "left"), + .window_size = NULL, .align = c("right", "center", "left"), .ref_time_values = NULL, .all_rows = FALSE) { assert_class(.x, "epi_df") @@ -902,7 +902,7 @@ epi_slide_opt <- function( #' ungroup() epi_slide_mean <- function( .x, .col_names, ..., - .window_size = 1, .align = c("right", "center", "left"), + .window_size = NULL, .align = c("right", "center", "left"), .ref_time_values = NULL, .all_rows = FALSE) { # Deprecated argument handling provided_args <- rlang::call_args_names(rlang::call_match()) @@ -979,7 +979,7 @@ epi_slide_mean <- function( #' ungroup() epi_slide_sum <- function( .x, .col_names, ..., - .window_size = 1, .align = c("right", "center", "left"), + .window_size = NULL, .align = c("right", "center", "left"), .ref_time_values = NULL, .all_rows = FALSE) { # Deprecated argument handling provided_args <- rlang::call_args_names(rlang::call_match()) diff --git a/R/utils.R b/R/utils.R index 88c7eaa0..6b44081f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -982,14 +982,31 @@ guess_period.POSIXt <- function(time_values, time_values_arg = rlang::caller_arg as.numeric(NextMethod(), units = "secs") } -validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRUE, arg_name = rlang::caller_arg(arg)) { - if (!checkmate::test_scalar(arg) || arg < lower) { - cli_abort( - "Slide function expected `{arg_name}` to be a non-null, scalar integer >= {lower}.", - class = "epiprocess__validate_slide_window_arg" +#' Is `x` an "int" with a sensible class? TRUE/FALSE +#' +#' Like [`checkmate::test_int`] but disallowing some non-sensible classes that +#' `test_int` accepts, such as `difftime`s. We rely on [`is.numeric`] to +#' determine class appropriateness; note that `is.numeric` is NOT simply +#' checking for the class to be "numeric" (or else we'd fail on integer class). +#' +#' @param x object +#' @return Boolean +#' +#' @importFrom checkmate test_int +#' @keywords internal +test_sensible_int <- function(x, na.ok = FALSE, lower = -Inf, upper = Inf, + tol = sqrt(.Machine$double.eps), null.ok = FALSE) { + if (null.ok && is.null(x)) { + TRUE + } else { + is.numeric(x) && test_int(x, + na.ok = FALSE, lower = -Inf, upper = Inf, + tol = sqrt(.Machine$double.eps) ) } +} +validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRUE, arg_name = rlang::caller_arg(arg)) { if (time_type == "custom") { cli_abort( "Unsure how to interpret slide units with a custom time type. Consider converting your time @@ -999,31 +1016,43 @@ validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRU } msg <- "" - if (!identical(arg, Inf)) { - if (time_type == "day") { - if (!test_int(arg, lower = 0L) || inherits(arg, "difftime") && units(arg) != "days") { - msg <- glue::glue_collapse(c("difftime with units in days", "non-negative integer", "Inf"), " or ") - } - } else if (time_type == "week") { - if (!(inherits(arg, "difftime") && units(arg) == "weeks")) { - msg <- glue::glue_collapse(c("difftime with units in weeks", "Inf"), " or ") - } - } else if (time_type == "yearmonth") { - if (!test_int(arg, lower = 0L) || inherits(arg, "difftime")) { - msg <- glue::glue_collapse(c("non-negative integer", "Inf"), " or ") - } - } else if (time_type == "integer") { - if (!test_int(arg, lower = 0L) || inherits(arg, "difftime")) { - msg <- glue::glue_collapse(c("non-negative integer", "Inf"), " or ") - } - } else { - msg <- glue::glue_collapse(c("difftime", "non-negative integer", "Inf"), " or ") - } + inf_if_okay <- if (allow_inf) { + "Inf" } else { - if (!allow_inf) { - msg <- glue::glue_collapse(c("a difftime", "a non-negative integer"), " or ") + character(0L) + } + + if (time_type == "day") { + if (!(test_sensible_int(arg, lower = 0L) || + inherits(arg, "difftime") && length(arg) == 1L && units(arg) == "days" || + allow_inf && identical(arg, Inf) + )) { + msg <- glue::glue_collapse(c("length-1 difftime with units in days", "non-negative integer", inf_if_okay), " or ") } + } else if (time_type == "week") { + if (!(inherits(arg, "difftime") && length(arg) == 1L && units(arg) == "weeks" || + allow_inf && identical(arg, Inf) + )) { + msg <- glue::glue_collapse(c("length-1 difftime with units in weeks", inf_if_okay), " or ") + } + } else if (time_type == "yearmonth") { + if (!(test_sensible_int(arg, lower = 0L) || + allow_inf && identical(arg, Inf) + )) { + msg <- glue::glue_collapse(c("non-negative integer", inf_if_okay), " or ") + } + } else if (time_type == "integer") { + if (!(test_sensible_int(arg, lower = 0L) || + allow_inf && identical(arg, Inf) + )) { + msg <- glue::glue_collapse(c("non-negative integer", inf_if_okay), " or ") + } + } else { + cli_abort('`epiprocess` internal error: unrecognized time_type: "{time_type}"', + class = "epiprocess__unrecognized_time_type" + ) } + if (msg != "") { cli_abort( "Slide function expected `{arg_name}` to be a {msg}.", diff --git a/man-roxygen/basic-slide-params.R b/man-roxygen/basic-slide-params.R index 8a63a817..dfa2512f 100644 --- a/man-roxygen/basic-slide-params.R +++ b/man-roxygen/basic-slide-params.R @@ -10,7 +10,8 @@ #' with units "days" #' - if time_type is Date and the cadence is weekly, then `.window_size` must #' be a difftime with units "weeks" -#' - if time_type is an integer, then `.window_size` must be an integer +#' - if time_type is an yearmonth or integer, then `.window_size` must be an +#' integer #' #' @param .align The alignment of the sliding window. If `right` (default), then #' the window has its end at the reference time; if `center`, then the window is diff --git a/man/epi_slide.Rd b/man/epi_slide.Rd index 74929eb1..8029e2a4 100644 --- a/man/epi_slide.Rd +++ b/man/epi_slide.Rd @@ -57,7 +57,8 @@ 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 integer, then \code{.window_size} must be an integer +\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 diff --git a/man/epi_slide_mean.Rd b/man/epi_slide_mean.Rd index 09faefb6..75b83b10 100644 --- a/man/epi_slide_mean.Rd +++ b/man/epi_slide_mean.Rd @@ -8,7 +8,7 @@ epi_slide_mean( .x, .col_names, ..., - .window_size = 1, + .window_size = NULL, .align = c("right", "center", "left"), .ref_time_values = NULL, .all_rows = FALSE @@ -44,7 +44,8 @@ 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 integer, then \code{.window_size} must be an integer +\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 diff --git a/man/epi_slide_opt.Rd b/man/epi_slide_opt.Rd index dcaab3f8..24b813f0 100644 --- a/man/epi_slide_opt.Rd +++ b/man/epi_slide_opt.Rd @@ -10,7 +10,7 @@ epi_slide_opt( .col_names, .f, ..., - .window_size = 1, + .window_size = NULL, .align = c("right", "center", "left"), .ref_time_values = NULL, .all_rows = FALSE @@ -59,7 +59,8 @@ 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 integer, then \code{.window_size} must be an integer +\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 diff --git a/man/epi_slide_sum.Rd b/man/epi_slide_sum.Rd index 0c83c432..2cf05cca 100644 --- a/man/epi_slide_sum.Rd +++ b/man/epi_slide_sum.Rd @@ -8,7 +8,7 @@ epi_slide_sum( .x, .col_names, ..., - .window_size = 1, + .window_size = NULL, .align = c("right", "center", "left"), .ref_time_values = NULL, .all_rows = FALSE @@ -44,7 +44,8 @@ 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 integer, then \code{.window_size} must be an integer +\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 diff --git a/man/test_sensible_int.Rd b/man/test_sensible_int.Rd new file mode 100644 index 00000000..eda7aacb --- /dev/null +++ b/man/test_sensible_int.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{test_sensible_int} +\alias{test_sensible_int} +\title{Is \code{x} an "int" with a sensible class? TRUE/FALSE} +\usage{ +test_sensible_int( + x, + na.ok = FALSE, + lower = -Inf, + upper = Inf, + tol = sqrt(.Machine$double.eps), + null.ok = FALSE +) +} +\arguments{ +\item{x}{object} +} +\value{ +Boolean +} +\description{ +Like \code{\link[checkmate:checkInt]{checkmate::test_int}} but disallowing some non-sensible classes that +\code{test_int} accepts, such as \code{difftime}s. We rely on \code{\link{is.numeric}} to +determine class appropriateness; note that \code{is.numeric} is NOT simply +checking for the class to be "numeric" (or else we'd fail on integer class). +} +\keyword{internal} diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 5767751d..8b942f28 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -306,16 +306,28 @@ test_that("validate_slide_window_arg works", { } expect_no_error(validate_slide_window_arg(as.difftime(1, units = "days"), "day")) expect_no_error(validate_slide_window_arg(1, "day")) - expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "day")) - expect_error(validate_slide_window_arg(as.difftime(1, units = "secs"), "day")) + expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "day"), + class = "epiprocess__validate_slide_window_arg" + ) + expect_error(validate_slide_window_arg(as.difftime(1, units = "secs"), "day"), + class = "epiprocess__validate_slide_window_arg" + ) expect_no_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "week")) - expect_error(validate_slide_window_arg(1, "week")) + expect_error(validate_slide_window_arg(1, "week"), + class = "epiprocess__validate_slide_window_arg" + ) expect_no_error(validate_slide_window_arg(1, "integer")) - expect_error(validate_slide_window_arg(as.difftime(1, units = "days"), "integer")) - expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "integer")) + expect_error(validate_slide_window_arg(as.difftime(1, units = "days"), "integer"), + class = "epiprocess__validate_slide_window_arg" + ) + expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "integer"), + class = "epiprocess__validate_slide_window_arg" + ) expect_no_error(validate_slide_window_arg(1, "yearmonth")) - expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "yearmonth")) + expect_error(validate_slide_window_arg(as.difftime(1, units = "weeks"), "yearmonth"), + class = "epiprocess__validate_slide_window_arg" + ) }) From 5518c67914ed755f67958f0a402104ce74cfd985 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:21:17 -0400 Subject: [PATCH 24/59] remove epidatr calls in vignettes; dontrun failing example code --- R/reexports.R | 20 +++++++++++++++----- vignettes/aggregation.Rmd | 5 +++-- vignettes/archive.Rmd | 3 ++- vignettes/correlation.Rmd | 3 ++- vignettes/epiprocess.Rmd | 1 - vignettes/growth_rate.Rmd | 4 +++- vignettes/outliers.Rmd | 1 - vignettes/slide.Rmd | 4 +++- 8 files changed, 28 insertions(+), 13 deletions(-) diff --git a/R/reexports.R b/R/reexports.R index a86fdd1d..bb8c519b 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -93,7 +93,9 @@ ggplot2::autoplot #' cases_deaths_subset #' #' # fails -#' data(cases_deaths_subset, package = "epiprocess") +#' \dontrun{ +#' data(cases_deaths_subset, package = "epiprocess") +#' } #' @export delayedAssign("cases_deaths_subset", epidatasets::cases_deaths_subset) @@ -111,7 +113,9 @@ delayedAssign("cases_deaths_subset", epidatasets::cases_deaths_subset) #' covid_incidence_county_subset #' #' # fails -#' data(covid_incidence_county_subset, package = "epiprocess") +#' \dontrun{ +#' data(covid_incidence_county_subset, package = "epiprocess") +#' } #' @export delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_county_subset) @@ -129,7 +133,9 @@ delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_coun #' covid_incidence_outliers #' #' # fails -#' data(covid_incidence_outliers, package = "epiprocess") +#' \dontrun{ +#' data(covid_incidence_outliers, package = "epiprocess") +#' } #' @export delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) @@ -147,7 +153,9 @@ delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) #' jhu_confirmed_cumulative_num #' #' # fails -#' data(jhu_confirmed_cumulative_num, package = "epiprocess") +#' \dontrun{ +#' data(jhu_confirmed_cumulative_num, package = "epiprocess") +#' } #' @export delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulative_num) @@ -165,7 +173,9 @@ delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulat #' archive_cases_dv_subset #' #' # fails -#' data(archive_cases_dv_subset, package = "epiprocess") +#' \dontrun{ +#' data(archive_cases_dv_subset, package = "epiprocess") +#' } #' #' @export delayedAssign("archive_cases_dv_subset", epidatasets::archive_cases_dv_subset) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index f7e0d670..784d3e59 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -15,8 +15,6 @@ COVID-19 cases in MA and VT. The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r, warning = FALSE, message = FALSE} -library(epidatr) -library(covidcast) library(epiprocess) library(dplyr) @@ -25,6 +23,9 @@ x <- covid_incidence_county_subset The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE, warning = FALSE} +library(epidatr) +library(covidcast) + d <- as.Date("2024-03-20") # Use covidcast::county_census to get the county and state names diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index c414095c..419d1e9a 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -28,7 +28,6 @@ page](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-v The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r, message = FALSE, warning = FALSE} -library(epidatr) library(epiprocess) library(data.table) library(dplyr) @@ -41,6 +40,8 @@ dv <- archive_cases_dv_subset$DT The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, warning = FALSE, eval = FALSE} +library(epidatr) + dv <- pub_covidcast( source = "doctor-visits", signals = "smoothed_adj_cli", diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index 6556f9d7..931564a6 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -16,7 +16,6 @@ state-level COVID-19 case and death rates, smoothed using 7-day trailing averages. ```{r, message = FALSE, warning = FALSE} -library(epidatr) library(epiprocess) library(dplyr) ``` @@ -30,6 +29,8 @@ x <- cases_deaths_subset %>% The data can also be fetched from the Delphi API with the following query: ```{r, eval = FALSE} +library(epidatr) + d <- as.Date("2024-03-20") x <- pub_covidcast( diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 2978ff0b..14ed0e55 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -104,7 +104,6 @@ to fetch this data from the [COVIDcast API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast.html). ```{r, message = FALSE} -library(epidatr) library(epiprocess) library(dplyr) library(tidyr) diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index 406ae36f..214776f1 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -15,7 +15,6 @@ current vignette, applied to state-level daily reported COVID-19 cases from GA and PA, smoothed using a 7-day trailing average. ```{r, message = FALSE, warning = FALSE} -library(epidatr) library(epiprocess) library(dplyr) library(tidyr) @@ -32,7 +31,10 @@ x <- cases_deaths_subset %>% The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE} +library(epidatr) + d <- as.Date("2024-03-20") + x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_7dav_incidence_num", diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index 72706d50..6e3b6972 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -17,7 +17,6 @@ reported COVID-19 case counts from FL and NJ. The dataset has 730 rows and 3 columns. ```{r, echo=FALSE, warning=FALSE, message=FALSE} -library(epidatr) library(epiprocess) library(dplyr) library(tidyr) diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd index 1f218adb..e913120b 100644 --- a/vignettes/slide.Rmd +++ b/vignettes/slide.Rmd @@ -29,7 +29,6 @@ FL, NY, and TX (note: here we're using new, not cumulative cases) using the and then convert this to `epi_df` format. ```{r, message = FALSE, warning = FALSE} -library(epidatr) library(epiprocess) library(dplyr) ``` @@ -44,7 +43,10 @@ x <- cases_deaths_subset %>% The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE} +library(epidatr) + d <- as.Date("2024-03-20") + x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_incidence_num", From 34ad569a2fb43648926ec7f8adbb8da2f2003e73 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Tue, 1 Oct 2024 16:35:16 -0700 Subject: [PATCH 25/59] Pacify lintr --- R/utils.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/utils.R b/R/utils.R index 6b44081f..8a03c626 100644 --- a/R/utils.R +++ b/R/utils.R @@ -994,8 +994,8 @@ guess_period.POSIXt <- function(time_values, time_values_arg = rlang::caller_arg #' #' @importFrom checkmate test_int #' @keywords internal -test_sensible_int <- function(x, na.ok = FALSE, lower = -Inf, upper = Inf, - tol = sqrt(.Machine$double.eps), null.ok = FALSE) { +test_sensible_int <- function(x, na.ok = FALSE, lower = -Inf, upper = Inf, # nolint: object_name_linter + tol = sqrt(.Machine$double.eps), null.ok = FALSE) { # nolint: object_name_linter if (null.ok && is.null(x)) { TRUE } else { @@ -1022,6 +1022,7 @@ validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRU character(0L) } + # nolint start: indentation_linter. if (time_type == "day") { if (!(test_sensible_int(arg, lower = 0L) || inherits(arg, "difftime") && length(arg) == 1L && units(arg) == "days" || @@ -1052,6 +1053,7 @@ validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRU class = "epiprocess__unrecognized_time_type" ) } + # nolint end if (msg != "") { cli_abort( From 44d354d4851c7962475eadd95d94657d68b3b823 Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Tue, 1 Oct 2024 20:54:53 -0700 Subject: [PATCH 26/59] fix: epi_slide_opt window_size validation, fix test, fix bug --- R/slide.R | 54 ++++++++------------------------- R/utils.R | 11 +++---- tests/testthat/test-epi_slide.R | 12 ++------ 3 files changed, 18 insertions(+), 59 deletions(-) diff --git a/R/slide.R b/R/slide.R index b971b9d5..b862693a 100644 --- a/R/slide.R +++ b/R/slide.R @@ -678,46 +678,16 @@ epi_slide_opt <- function( ref_time_values <- sort(.ref_time_values) # Handle window arguments - align <- rlang::arg_match(.align) + .align <- rlang::arg_match(.align) time_type <- attr(.x, "metadata")$time_type - validate_slide_window_arg(.window_size, time_type) - if (identical(.window_size, Inf)) { - if (align == "right") { - before <- Inf - if (time_type %in% c("day", "week")) { - after <- as.difftime(0, units = glue::glue("{time_type}s")) - } else { - after <- 0 - } - } else { - cli_abort( - "`epi_slide`: center and left alignment are not supported with an infinite window size." - ) - } - } else { - if (align == "right") { - before <- .window_size - 1 - if (time_type %in% c("day", "week")) { - after <- as.difftime(0, units = glue::glue("{time_type}s")) - } else { - after <- 0 - } - } else if (align == "center") { - # For .window_size = 5, before = 2, after = 2. For .window_size = 4, before = 2, after = 1. - before <- floor(.window_size / 2) - after <- .window_size - before - 1 - } else if (align == "left") { - if (time_type %in% c("day", "week")) { - before <- as.difftime(0, units = glue::glue("{time_type}s")) - } else { - before <- 0 - } - after <- .window_size - 1 - } + if (is.null(.window_size)) { + cli_abort("epi_slide: `.window_size` must be specified.") } + validate_slide_window_arg(.window_size, time_type) + window_args <- get_before_after_from_window(.window_size, .align, time_type) # Make a complete date sequence between min(.x$time_value) and max(.x$time_value). - date_seq_list <- full_date_seq(.x, before, after, time_type) + date_seq_list <- full_date_seq(.x, window_args$before, window_args$after, time_type) all_dates <- date_seq_list$all_dates pad_early_dates <- date_seq_list$pad_early_dates pad_late_dates <- date_seq_list$pad_late_dates @@ -786,16 +756,16 @@ epi_slide_opt <- function( # `before` and `after` params. Right-aligned `frollmean` results' # `ref_time_value`s will be `after` timesteps ahead of where they should # be; shift results to the left by `after` timesteps. - if (before != Inf) { - window_size <- before + after + 1L + if (window_args$before != Inf) { + window_size <- window_args$before + window_args$after + 1L roll_output <- .f(x = .data_group[, col_names_chr], n = window_size, ...) } else { window_size <- list(seq_along(.data_group$time_value)) roll_output <- .f(x = .data_group[, col_names_chr], n = window_size, adaptive = TRUE, ...) } - if (after >= 1) { + if (window_args$after >= 1) { .data_group[, result_col_names] <- purrr::map(roll_output, function(.x) { - c(.x[(after + 1L):length(.x)], rep(NA, after)) + c(.x[(window_args$after + 1L):length(.x)], rep(NA, window_args$after)) }) } else { .data_group[, result_col_names] <- roll_output @@ -805,8 +775,8 @@ epi_slide_opt <- function( for (i in seq_along(col_names_chr)) { .data_group[, result_col_names[i]] <- .f( x = .data_group[[col_names_chr[i]]], - before = as.numeric(before), - after = as.numeric(after), + before = as.numeric(window_args$before), + after = as.numeric(window_args$after), ... ) } diff --git a/R/utils.R b/R/utils.R index 8a03c626..29e60054 100644 --- a/R/utils.R +++ b/R/utils.R @@ -999,10 +999,7 @@ test_sensible_int <- function(x, na.ok = FALSE, lower = -Inf, upper = Inf, # nol if (null.ok && is.null(x)) { TRUE } else { - is.numeric(x) && test_int(x, - na.ok = FALSE, lower = -Inf, upper = Inf, - tol = sqrt(.Machine$double.eps) - ) + is.numeric(x) && test_int(x, na.ok = na.ok, lower = lower, upper = upper, tol = tol) } } @@ -1024,7 +1021,7 @@ validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRU # nolint start: indentation_linter. if (time_type == "day") { - if (!(test_sensible_int(arg, lower = 0L) || + if (!(test_sensible_int(arg, lower = lower) || inherits(arg, "difftime") && length(arg) == 1L && units(arg) == "days" || allow_inf && identical(arg, Inf) )) { @@ -1037,13 +1034,13 @@ validate_slide_window_arg <- function(arg, time_type, lower = 1, allow_inf = TRU msg <- glue::glue_collapse(c("length-1 difftime with units in weeks", inf_if_okay), " or ") } } else if (time_type == "yearmonth") { - if (!(test_sensible_int(arg, lower = 0L) || + if (!(test_sensible_int(arg, lower = lower) || allow_inf && identical(arg, Inf) )) { msg <- glue::glue_collapse(c("non-negative integer", inf_if_okay), " or ") } } else if (time_type == "integer") { - if (!(test_sensible_int(arg, lower = 0L) || + if (!(test_sensible_int(arg, lower = lower) || allow_inf && identical(arg, Inf) )) { msg <- glue::glue_collapse(c("non-negative integer", inf_if_okay), " or ") diff --git a/tests/testthat/test-epi_slide.R b/tests/testthat/test-epi_slide.R index d644e9a7..f658bcf4 100644 --- a/tests/testthat/test-epi_slide.R +++ b/tests/testthat/test-epi_slide.R @@ -711,17 +711,9 @@ test_that("epi_slide_opt helper `full_date_seq` returns expected date values", { test_that("`epi_slide_opt` errors when passed non-`data.table`, non-`slider` functions", { reexport_frollmean <- data.table::frollmean - expect_no_error( - epi_slide_opt( - test_data, - .col_names = value, .f = reexport_frollmean - ) - ) + expect_no_error(epi_slide_opt(test_data, .col_names = value, .f = reexport_frollmean, .window_size = 7)) expect_error( - epi_slide_opt( - test_data, - .col_names = value, .f = mean - ), + epi_slide_opt(test_data, .col_names = value, .f = mean), class = "epiprocess__epi_slide_opt__unsupported_slide_function" ) }) From ef8dc27424678ee599b8f64b25ecadd5d604f3d9 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:21:36 -0400 Subject: [PATCH 27/59] redocument; attribute jhu and delphi for data --- DESCRIPTION | 5 ++++- man/archive_cases_dv_subset.Rd | 21 +++++++++++++++++++-- man/cases_deaths_subset.Rd | 6 ++++-- man/covid_incidence_county_subset.Rd | 6 ++++-- man/covid_incidence_outliers.Rd | 4 +++- man/epiprocess.Rd | 3 +++ man/jhu_confirmed_cumulative_num.Rd | 12 +++++++++++- 7 files changed, 48 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b52d30a1..706fb2a8 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,7 +17,10 @@ Authors@R: c( person("Ryan", "Tibshirani", role = "aut"), person("Lionel", "Henry", role = "ctb", comment = "Author of included rlang fragments"), person("Hadley", "Wickham", role = "ctb", comment = "Author of included rlang fragments"), - person("Posit", role = "cph", comment = "Copyright holder of included rlang fragments") + person("Posit", role = "cph", comment = "Copyright holder of included rlang fragments"), + person("Johns Hopkins University Center for Systems Science and Engineering", role = "dtc", comment = "Owner of COVID-19 cases and deaths data from the COVID-19 Data Repository"), + person("Johns Hopkins University", role = "cph", comment = "Copyright holder of COVID-19 cases and deaths data from the COVID-19 Data Repository"), + person("Carnegie Mellon University Delphi Group", role = "dtc", comment = "Owner of claims-based CLI data from the Delphi Epidata API") ) Description: This package introduces a common data structure for epidemiological data reported by location and time, provides another data structure to diff --git a/man/archive_cases_dv_subset.Rd b/man/archive_cases_dv_subset.Rd index f07f8149..8a2a3dda 100644 --- a/man/archive_cases_dv_subset.Rd +++ b/man/archive_cases_dv_subset.Rd @@ -15,11 +15,26 @@ Copyright Johns Hopkins University 2020. Modifications: \itemize{ -\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From the COVIDcast Doctor Visits API}: The signal \code{percent_cli} is taken directly from the API without changes. \item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: \code{case_rate_7d_av} signal was computed by Delphi from the original JHU-CSSE data by calculating moving averages of the preceding 7 days, so the signal for June 7 is the average of the underlying data for June 1 through 7, inclusive. \item Furthermore, the data has been limited to a very small number of rows, the signal names slightly altered, and formatted into an \code{epi_archive}. } + +This object contains a modified part of the +\href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{Delphi \code{doctor-visits} indicator}. +This data source is computed by the Delphi +Group from information about outpatient visits, provided to Delphi by +health system partners, and published in the COVIDcast Epidata API. This +data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Delphi group. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html}{From the COVIDcast Doctor Visits signal}: The signal \code{smoothed_adj_cli} is taken directly from the API without changes. +\item Furthermore, the data has been limited to a very small number of rows, the +signal names slightly altered, and formatted into an \code{epi_archive}. +} } \usage{ archive_cases_dv_subset @@ -61,7 +76,9 @@ library(epiprocess) archive_cases_dv_subset # fails -data(archive_cases_dv_subset, package = "epiprocess") +\dontrun{ + data(archive_cases_dv_subset, package = "epiprocess") +} } \keyword{datasets} diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd index fdd38c61..d1e9c391 100644 --- a/man/cases_deaths_subset.Rd +++ b/man/cases_deaths_subset.Rd @@ -39,7 +39,7 @@ Hopkins University. This example data is a snapshot as of March 20, 2024, and ranges from March 1, 2020 to December 31, 2021. It is limited to California, Florida, Texas, New York, Georgia, and Pennsylvania. -It is used in the {epiprocess} growth rate and \code{epi_slide} vignettes. +It is used in the {epiprocess} growth rate, correlation, \code{epi_slide} vignettes. } \section{Data dictionary}{ @@ -72,6 +72,8 @@ library(epiprocess) cases_deaths_subset # fails -data(cases_deaths_subset, package = "epiprocess") +\dontrun{ + data(cases_deaths_subset, package = "epiprocess") +} } \keyword{datasets} diff --git a/man/covid_incidence_county_subset.Rd b/man/covid_incidence_county_subset.Rd index dbd382b8..299edd84 100644 --- a/man/covid_incidence_county_subset.Rd +++ b/man/covid_incidence_county_subset.Rd @@ -38,7 +38,7 @@ is based on reports made available by the Center for Systems Science and Engineering at Johns Hopkins University. This example data is a snapshot as of March 20, 2024, and ranges from March 1, 2020 to December 31, 2021. -It is limited to Massachusetts and Vermont. +It is limited to counties from Massachusetts and Vermont. It is used in the {epiprocess} aggregation vignette. } @@ -68,6 +68,8 @@ library(epiprocess) covid_incidence_county_subset # fails -data(covid_incidence_county_subset, package = "epiprocess") +\dontrun{ + data(covid_incidence_county_subset, package = "epiprocess") +} } \keyword{datasets} diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd index 19110970..1b3f62ef 100644 --- a/man/covid_incidence_outliers.Rd +++ b/man/covid_incidence_outliers.Rd @@ -61,6 +61,8 @@ library(epiprocess) covid_incidence_outliers # fails -data(covid_incidence_outliers, package = "epiprocess") +\dontrun{ + data(covid_incidence_outliers, package = "epiprocess") +} } \keyword{datasets} diff --git a/man/epiprocess.Rd b/man/epiprocess.Rd index f6345cbe..bf5f5279 100644 --- a/man/epiprocess.Rd +++ b/man/epiprocess.Rd @@ -40,6 +40,9 @@ Other contributors: \item Lionel Henry (Author of included rlang fragments) [contributor] \item Hadley Wickham (Author of included rlang fragments) [contributor] \item Posit (Copyright holder of included rlang fragments) [copyright holder] + \item Johns Hopkins University Center for Systems Science and Engineering (Owner of COVID-19 cases and deaths data from the COVID-19 Data Repository) [data contributor] + \item Johns Hopkins University (Copyright holder of COVID-19 cases and deaths data from the COVID-19 Data Repository) [copyright holder] + \item Carnegie Mellon University Delphi Group (Owner of claims-based CLI data from the Delphi Epidata API) [data contributor] } } diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd index b1636115..96af926f 100644 --- a/man/jhu_confirmed_cumulative_num.Rd +++ b/man/jhu_confirmed_cumulative_num.Rd @@ -12,6 +12,14 @@ This object contains a modified part of the \href{https://github.com/CSSEGISandD \href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: +These signals are taken directly from the JHU CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} without changes. +\item Furthermore, the data has been limited to a very small number of rows, +formatted into an \code{epi_df}, and the signal names slightly altered. +} } \usage{ jhu_confirmed_cumulative_num @@ -60,6 +68,8 @@ library(epiprocess) jhu_confirmed_cumulative_num # fails -data(jhu_confirmed_cumulative_num, package = "epiprocess") +\dontrun{ + data(jhu_confirmed_cumulative_num, package = "epiprocess") +} } \keyword{datasets} From e04f5a76097ab5451538d192bc42804d975dd54e Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:54:04 -0400 Subject: [PATCH 28/59] load covidcast in aggregation vignette --- vignettes/aggregation.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 784d3e59..74c33609 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -17,6 +17,7 @@ The data is included in this package (via the [`epidatasets` package](https://cm ```{r, warning = FALSE, message = FALSE} library(epiprocess) library(dplyr) +library(covidcast) x <- covid_incidence_county_subset ``` @@ -24,7 +25,6 @@ x <- covid_incidence_county_subset The data can also be fetched from the Delphi API with the following query: ```{r, message = FALSE, eval = FALSE, warning = FALSE} library(epidatr) -library(covidcast) d <- as.Date("2024-03-20") From 28f5327120c1767403d6dddf636c5c884075ad23 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Wed, 2 Oct 2024 11:21:01 -0700 Subject: [PATCH 29/59] "epi_slide:" -> "epi_slide_opt:" in a error for latter --- R/slide.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/slide.R b/R/slide.R index b862693a..5794b0ee 100644 --- a/R/slide.R +++ b/R/slide.R @@ -681,7 +681,7 @@ epi_slide_opt <- function( .align <- rlang::arg_match(.align) time_type <- attr(.x, "metadata")$time_type if (is.null(.window_size)) { - cli_abort("epi_slide: `.window_size` must be specified.") + cli_abort("epi_slide_opt: `.window_size` must be specified.") } validate_slide_window_arg(.window_size, time_type) window_args <- get_before_after_from_window(.window_size, .align, time_type) From c8e4d697b1d8fa43268e19f9f507f08e8c0e971c Mon Sep 17 00:00:00 2001 From: brookslogan Date: Wed, 2 Oct 2024 11:59:27 -0700 Subject: [PATCH 30/59] Fix version number in DESCRIPTION Co-authored-by: Dmitry Shemetov --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 784496b2..f880d825 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: epiprocess Title: Tools for basic signal processing in epidemiology -Version: 0.10.0 +Version: 0.9.1 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), From 6361878f021ea529b8874ed177a15fb4f671639c Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Wed, 2 Oct 2024 12:09:37 -0700 Subject: [PATCH 31/59] Update DESCRIPTION, NEWS.md --- DESCRIPTION | 2 +- NEWS.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 790b36a5..8b80431c 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: epiprocess Title: Tools for basic signal processing in epidemiology -Version: 0.9.0 +Version: 0.9.2 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), diff --git a/NEWS.md b/NEWS.md index ee04b7f3..18c146ce 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,18 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicate PR's. +# epiprocess 0.10 + +## Breaking changes + +- Removed `.window_size = 1` default from `epi_slide_{mean,sum,opt}`; this + argument is now mandatory, and should nearly always be greater than 1 except + for testing purposes. + +## Improvements + +- Improved validation of `.window_size` arguments. + # epiprocess 0.9 ## Breaking changes From c5b5b26e91dcf8695e379e66a424a3070d56a63f Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Wed, 2 Oct 2024 12:45:54 -0700 Subject: [PATCH 32/59] fix(epi[x]_slide, utils): fix `.call` args and actually use --- R/grouped_epi_archive.R | 4 ++-- R/slide.R | 2 +- R/utils.R | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/grouped_epi_archive.R b/R/grouped_epi_archive.R index 7bdadaa5..bec8c9c2 100644 --- a/R/grouped_epi_archive.R +++ b/R/grouped_epi_archive.R @@ -313,14 +313,14 @@ epix_slide.grouped_epi_archive <- function( } .f_arg <- ".f" # dummy val, shouldn't be used since we're not using `.f` - .slide_comp <- as_diagonal_slide_computation(quosures, .f_arg = .f_arg, .call = caller_env()) + .slide_comp <- as_diagonal_slide_computation(quosures, .f_arg = .f_arg) # Magic value that passes zero args as dots in calls below. Equivalent to # `... <- missing_arg()`, but use `assign` to avoid warning about # improper use of dots. assign("...", missing_arg()) } else { used_data_masking <- FALSE - .slide_comp <- as_diagonal_slide_computation(.f, ..., .f_arg = caller_arg(.f), .call = caller_env()) + .slide_comp <- as_diagonal_slide_computation(.f, ..., .f_arg = caller_arg(.f)) } # Computation for one group, one time value diff --git a/R/slide.R b/R/slide.R index 063cab9d..5df474b2 100644 --- a/R/slide.R +++ b/R/slide.R @@ -156,7 +156,7 @@ epi_slide <- function( used_data_masking <- FALSE .f_arg <- caller_arg(.f) } - .slide_comp <- as_time_slide_computation(.f, ..., .f_arg = .f_arg, .call = caller_env()) + .slide_comp <- as_time_slide_computation(.f, ..., .f_arg = .f_arg) .align <- rlang::arg_match(.align) time_type <- attr(.x, "metadata")$time_type diff --git a/R/utils.R b/R/utils.R index 5083b7e3..67fcf88f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -372,6 +372,7 @@ as_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = calle "*" = "If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma." ), parent = e, + call = .call, class = "epiprocess__as_slide_computation__error_forcing_.f" ) } From 42d8d952ce44546cb69ec81db524b9b7de885890 Mon Sep 17 00:00:00 2001 From: nmdefries Date: Wed, 2 Oct 2024 20:36:19 +0000 Subject: [PATCH 33/59] style: styler (GHA) --- R/reexports.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R/reexports.R b/R/reexports.R index bb8c519b..800b5ea3 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -94,7 +94,7 @@ ggplot2::autoplot #' #' # fails #' \dontrun{ -#' data(cases_deaths_subset, package = "epiprocess") +#' data(cases_deaths_subset, package = "epiprocess") #' } #' @export delayedAssign("cases_deaths_subset", epidatasets::cases_deaths_subset) @@ -114,7 +114,7 @@ delayedAssign("cases_deaths_subset", epidatasets::cases_deaths_subset) #' #' # fails #' \dontrun{ -#' data(covid_incidence_county_subset, package = "epiprocess") +#' data(covid_incidence_county_subset, package = "epiprocess") #' } #' @export delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_county_subset) @@ -134,7 +134,7 @@ delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_coun #' #' # fails #' \dontrun{ -#' data(covid_incidence_outliers, package = "epiprocess") +#' data(covid_incidence_outliers, package = "epiprocess") #' } #' @export delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) @@ -154,7 +154,7 @@ delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) #' #' # fails #' \dontrun{ -#' data(jhu_confirmed_cumulative_num, package = "epiprocess") +#' data(jhu_confirmed_cumulative_num, package = "epiprocess") #' } #' @export delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulative_num) @@ -174,7 +174,7 @@ delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulat #' #' # fails #' \dontrun{ -#' data(archive_cases_dv_subset, package = "epiprocess") +#' data(archive_cases_dv_subset, package = "epiprocess") #' } #' #' @export From a2c3cf0e22694559979897dde89feb4cd0e8f095 Mon Sep 17 00:00:00 2001 From: nmdefries Date: Wed, 2 Oct 2024 20:36:20 +0000 Subject: [PATCH 34/59] docs: document (GHA) --- man/cases_deaths_subset.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd index d1e9c391..85ddb81c 100644 --- a/man/cases_deaths_subset.Rd +++ b/man/cases_deaths_subset.Rd @@ -39,7 +39,7 @@ Hopkins University. This example data is a snapshot as of March 20, 2024, and ranges from March 1, 2020 to December 31, 2021. It is limited to California, Florida, Texas, New York, Georgia, and Pennsylvania. -It is used in the {epiprocess} growth rate, correlation, \code{epi_slide} vignettes. +It is used in the {epiprocess} growth rate and \code{epi_slide} vignettes. } \section{Data dictionary}{ From f4ce52e7f5bdf0fa858a4168280e1079a3478903 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Wed, 2 Oct 2024 13:44:01 -0700 Subject: [PATCH 35/59] Lint, tweak error messages, refactor class+snap tests, guide on .col_names --- R/utils.R | 33 ++++++++++++++++++++++++++++----- tests/testthat/_snaps/utils.md | 27 ++++++++++++++++++++------- tests/testthat/test-utils.R | 26 ++++++++++++++------------ 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/R/utils.R b/R/utils.R index 67fcf88f..066b374e 100644 --- a/R/utils.R +++ b/R/utils.R @@ -358,8 +358,24 @@ assert_sufficient_f_args <- function(.f, ..., .ref_time_value_label) { #' @importFrom rlang is_function new_function f_env is_environment missing_arg #' f_rhs is_formula caller_arg caller_env #' @keywords internal -as_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = caller_env(), .ref_time_value_long_varnames, .ref_time_value_label) { - f_arg <- .f_arg # for cli interpolation, avoid dot prefix +as_slide_computation <- function(.f, ..., + .f_arg = caller_arg(.f), .call = caller_env(), + .ref_time_value_long_varnames, .ref_time_value_label) { + if (".col_names" %in% rlang::call_args_names(rlang::call_match())) { + cli_abort( + c("{.code epi_slide} and {.code epix_slide} do not support `.col_names`; + consider:", + "*" = "using {.code epi_slide_mean}, {.code epi_slide_sum}, or + {.code epi_slide_opt}, if applicable", + "*" = "using {.code .f = ~ .x %>% + dplyr::reframe(across(your_col_names, list(your_func_name = your_func)))}" + ), + call = .call, + class = "epiprocess__as_slide_computation__given_.col_names" + ) + } + + f_arg <- .f_arg # for cli interpolation, avoid dot prefix; # nolint: object_usage_linter withCallingHandlers( { force(.f) @@ -367,9 +383,16 @@ as_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = calle error = function(e) { cli_abort( c("Failed to convert {.code {f_arg}} to a slide computation.", - "*" = "If you were trying to use the formula interface, maybe you forgot a tilde at the beginning.", - "*" = "If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`.", - "*" = "If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma." + "*" = "If you were trying to use the formula interface, + maybe you forgot a tilde at the beginning.", + "*" = "If you were trying to use the tidyeval interface, + maybe you forgot to specify the name, + e.g.: `my_output_col_name =`. Note that `.col_names` + is not supported.", + "*" = "If you were trying to use advanced features of the + tidyeval interface such as `!! name_variable :=`, + maybe you forgot the required leading comma.", + "*" = "Something else could have gone wrong; see below." ), parent = e, call = .call, diff --git a/tests/testthat/_snaps/utils.md b/tests/testthat/_snaps/utils.md index 8a8b0470..a9ee3acb 100644 --- a/tests/testthat/_snaps/utils.md +++ b/tests/testthat/_snaps/utils.md @@ -1,14 +1,26 @@ # as_slide_computation raises errors as expected Code - toy_edf %>% group_by(geo_value) %>% epi_slide(.window_size = 6, tibble( + toy_edf %>% group_by(geo_value) %>% epi_slide(.window_size = 7, mean, + .col_names = "value") + Condition + Error in `epi_slide()`: + ! `epi_slide` and `epix_slide` do not support `.col_names`; consider: + * using `epi_slide_mean`, `epi_slide_sum`, or `epi_slide_opt`, if applicable + * using `.f = ~ .x %>% dplyr::reframe(across(your_col_names, list(your_func_name = your_func)))` + +--- + + Code + toy_edf %>% group_by(geo_value) %>% epi_slide(.window_size = 7, tibble( slide_value = mean(.x$value))) Condition - Error in `as_slide_computation()`: + Error in `epi_slide()`: ! Failed to convert `tibble(slide_value = mean(.x$value))` to a slide computation. * If you were trying to use the formula interface, maybe you forgot a tilde at the beginning. - * If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`. - * If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma. + * If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`. Note that `.col_names` is not supported. + * If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, maybe you forgot the required leading comma. + * Something else could have gone wrong; see below. Caused by error: ! object '.x' not found @@ -17,11 +29,12 @@ Code toy_archive %>% epix_slide(tibble(slide_value = mean(.x$value))) Condition - Error in `as_slide_computation()`: + Error in `epix_slide()`: ! Failed to convert `.f` to a slide computation. * If you were trying to use the formula interface, maybe you forgot a tilde at the beginning. - * If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`. - * If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, you might have forgotten the required leading comma. + * If you were trying to use the tidyeval interface, maybe you forgot to specify the name, e.g.: `my_output_col_name =`. Note that `.col_names` is not supported. + * If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, maybe you forgot the required leading comma. + * Something else could have gone wrong; see below. Caused by error: ! object '.x' not found diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index e11f5075..c5e6c5aa 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -231,34 +231,36 @@ test_that("as_slide_computation raises errors as expected", { class = "epiprocess__as_slide_computation__cant_convert_catchall" ) + # helper to make initial snapshots less error-prone: + expect_error_snapshot <- function(x, class) { + x_quo <- rlang::enquo(x) + rlang::inject(expect_error(!!x_quo, class = class)) # quick sanity check on class + rlang::inject(expect_snapshot(!!x_quo, error = TRUE)) # don't need cnd_class = TRUE since checked above + } + # If `.f` doesn't look like tidyeval and we fail to force it, then we hint to # the user some potential problems: toy_edf <- tibble(geo_value = 1, time_value = c(1, 2), value = 1:2) %>% as_epi_df(as_of = 1) toy_archive <- tibble(version = c(1, 2, 2), geo_value = 1, time_value = c(1, 1, 2), value = 1:3) %>% as_epi_archive() - expect_error( + expect_error_snapshot( toy_edf %>% group_by(geo_value) %>% - epi_slide(.window_size = 6, tibble(slide_value = mean(.x$value))), - class = "epiprocess__as_slide_computation__error_forcing_.f" + epi_slide(.window_size = 7, mean, .col_names = "value"), + class = "epiprocess__as_slide_computation__given_.col_names" ) - expect_snapshot( - error = TRUE, + expect_error_snapshot( toy_edf %>% group_by(geo_value) %>% - epi_slide(.window_size = 6, tibble(slide_value = mean(.x$value))) + epi_slide(.window_size = 7, tibble(slide_value = mean(.x$value))), + class = "epiprocess__as_slide_computation__error_forcing_.f" ) - expect_error( + expect_error_snapshot( toy_archive %>% epix_slide(tibble(slide_value = mean(.x$value))), class = "epiprocess__as_slide_computation__error_forcing_.f" ) - expect_snapshot( - error = TRUE, - toy_archive %>% - epix_slide(tibble(slide_value = mean(.x$value))) - ) }) test_that("as_slide_computation works", { From 0e6798f02d76c8d0614971347f6d826b308c8807 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:54:37 -0400 Subject: [PATCH 36/59] import glue --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 8529528b..42977543 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -45,6 +45,7 @@ Imports: epidatasets, genlasso, ggplot2, + glue, lifecycle (>= 1.0.1), lubridate, magrittr, From 47bf883c5c4164971284bf64e98e9a9dd4d0ac30 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:00:36 -0400 Subject: [PATCH 37/59] remove trailing paren --- vignettes/archive.Rmd | 1 - 1 file changed, 1 deletion(-) diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index 6b8955d2..a33a1d9f 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -53,7 +53,6 @@ dv <- pub_covidcast( issues = epirange(20200601, 20211201) ) %>% rename(version = issue, percent_cli = value) -) ``` ## Getting data into `epi_archive` format From 50af59bd1445539f1b1bbe9c13327d5677005511 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:19:49 -0400 Subject: [PATCH 38/59] news and version --- DESCRIPTION | 2 +- NEWS.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 42977543..5e7c47a1 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: epiprocess Title: Tools for basic signal processing in epidemiology -Version: 0.9.0 +Version: 0.9.1 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), diff --git a/NEWS.md b/NEWS.md index ee04b7f3..16a941e0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,21 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicate PR's. +# epiprocess 0.10 + +## Breaking changes + +- Moved example datasets from being hosted in the package to being reexported + from the `epidatasets` package. The datasets can no longer be loaded with + `data()` but can be accessed with `epiprocess::` or just the name of the + dataset, after loading the package (#520). + +## Improvements + + +## Bug fixes + + # epiprocess 0.9 ## Breaking changes From cbab3526843a7ea5f7a31b8fe56363c64f3de514 Mon Sep 17 00:00:00 2001 From: nmdefries Date: Wed, 2 Oct 2024 21:24:06 +0000 Subject: [PATCH 39/59] docs: document (GHA) --- man/archive_cases_dv_subset.Rd | 2 +- man/cases_deaths_subset.Rd | 2 +- man/covid_incidence_county_subset.Rd | 2 +- man/covid_incidence_outliers.Rd | 2 +- man/jhu_confirmed_cumulative_num.Rd | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/man/archive_cases_dv_subset.Rd b/man/archive_cases_dv_subset.Rd index 8a2a3dda..207bb025 100644 --- a/man/archive_cases_dv_subset.Rd +++ b/man/archive_cases_dv_subset.Rd @@ -77,7 +77,7 @@ archive_cases_dv_subset # fails \dontrun{ - data(archive_cases_dv_subset, package = "epiprocess") +data(archive_cases_dv_subset, package = "epiprocess") } } diff --git a/man/cases_deaths_subset.Rd b/man/cases_deaths_subset.Rd index 85ddb81c..45e8dd4c 100644 --- a/man/cases_deaths_subset.Rd +++ b/man/cases_deaths_subset.Rd @@ -73,7 +73,7 @@ cases_deaths_subset # fails \dontrun{ - data(cases_deaths_subset, package = "epiprocess") +data(cases_deaths_subset, package = "epiprocess") } } \keyword{datasets} diff --git a/man/covid_incidence_county_subset.Rd b/man/covid_incidence_county_subset.Rd index 299edd84..edc881d9 100644 --- a/man/covid_incidence_county_subset.Rd +++ b/man/covid_incidence_county_subset.Rd @@ -69,7 +69,7 @@ covid_incidence_county_subset # fails \dontrun{ - data(covid_incidence_county_subset, package = "epiprocess") +data(covid_incidence_county_subset, package = "epiprocess") } } \keyword{datasets} diff --git a/man/covid_incidence_outliers.Rd b/man/covid_incidence_outliers.Rd index 1b3f62ef..52b49fd3 100644 --- a/man/covid_incidence_outliers.Rd +++ b/man/covid_incidence_outliers.Rd @@ -62,7 +62,7 @@ covid_incidence_outliers # fails \dontrun{ - data(covid_incidence_outliers, package = "epiprocess") +data(covid_incidence_outliers, package = "epiprocess") } } \keyword{datasets} diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd index 96af926f..b288d974 100644 --- a/man/jhu_confirmed_cumulative_num.Rd +++ b/man/jhu_confirmed_cumulative_num.Rd @@ -69,7 +69,7 @@ jhu_confirmed_cumulative_num # fails \dontrun{ - data(jhu_confirmed_cumulative_num, package = "epiprocess") +data(jhu_confirmed_cumulative_num, package = "epiprocess") } } \keyword{datasets} From 1cd4cb059c01a92d3a2b11c35640d4eb6ac43e1c Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 2 Oct 2024 19:15:37 -0700 Subject: [PATCH 40/59] refactor: remove Suggests dependence on covidcast * just download the files, don't import covidcast * covidcast depends on sf which is tough to install --- DESCRIPTION | 1 - data-raw/jhu_csse_county_level_subset.R | 4 +--- vignettes/aggregation.Rmd | 4 +--- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8b80431c..f149e29e 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,7 +53,6 @@ Imports: vctrs, waldo Suggests: - covidcast, devtools, epidatr, knitr, diff --git a/data-raw/jhu_csse_county_level_subset.R b/data-raw/jhu_csse_county_level_subset.R index faed75e8..d64e3f4a 100644 --- a/data-raw/jhu_csse_county_level_subset.R +++ b/data-raw/jhu_csse_county_level_subset.R @@ -1,10 +1,8 @@ library(epidatr) -library(covidcast) library(epiprocess) library(dplyr) -# Use covidcast::county_census to get the county and state names -y <- covidcast::county_census %>% +y <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 9d205f53..a627a441 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -14,12 +14,11 @@ COVID-19 cases in MA and VT. ```{r, message = FALSE, eval= FALSE, warning= FALSE} library(epidatr) -library(covidcast) library(epiprocess) library(dplyr) # Use covidcast::county_census to get the county and state names -y <- covidcast::county_census %>% +y <- y <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) @@ -41,7 +40,6 @@ The data contains 16,212 rows and 5 columns. ```{r, echo=FALSE, warning=FALSE, message=FALSE} library(epidatr) -library(covidcast) library(epiprocess) library(dplyr) From 111f4ce006cc0cdd31975c4c30e8b74e50081ac9 Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 2 Oct 2024 19:52:43 -0700 Subject: [PATCH 41/59] fix: linter and vignette fix --- data-raw/jhu_csse_county_level_subset.R | 2 +- vignettes/aggregation.Rmd | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/data-raw/jhu_csse_county_level_subset.R b/data-raw/jhu_csse_county_level_subset.R index d64e3f4a..4d9f3075 100644 --- a/data-raw/jhu_csse_county_level_subset.R +++ b/data-raw/jhu_csse_county_level_subset.R @@ -2,7 +2,7 @@ library(epidatr) library(epiprocess) library(dplyr) -y <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% +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 filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index a627a441..f21eca49 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -18,7 +18,7 @@ library(epiprocess) library(dplyr) # Use covidcast::county_census to get the county and state names -y <- y <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% +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 filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) @@ -108,15 +108,18 @@ help avoid bugs in further downstream data processing tasks. Let's first remove certain dates from our data set to create gaps: ```{r} +state_census <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/state_census.csv") %>% # nolint: line_length_linter + select(STATE, NAME, POPESTIMATE2019, ABBR) %>% + rename(abbr = ABBR, name = NAME, pop = POPESTIMATE2019, fips = STATE) %>% + mutate(abbr = tolower(abbr)) %>% + as_tibble() + # First make geo value more readable for tables, plots, etc. x <- x %>% - mutate( - geo_value = paste( - substr(county_name, 1, nchar(county_name) - 7), - name_to_abbr(state_name), - sep = ", " - ) + inner_join( + state_census %>% select(state_name = name, abbr) ) %>% + 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) From 44dc1b932b1fff9230fcb230462d7ad8f169bb50 Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 2 Oct 2024 20:00:18 -0700 Subject: [PATCH 42/59] fix: add readr --- DESCRIPTION | 1 + data-raw/jhu_csse_county_level_subset.R | 2 +- vignettes/aggregation.Rmd | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f149e29e..3da259cf 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,6 +57,7 @@ Suggests: epidatr, knitr, outbreaks, + readr, rmarkdown, testthat (>= 3.1.5), withr diff --git a/data-raw/jhu_csse_county_level_subset.R b/data-raw/jhu_csse_county_level_subset.R index 4d9f3075..e28ba66b 100644 --- a/data-raw/jhu_csse_county_level_subset.R +++ b/data-raw/jhu_csse_county_level_subset.R @@ -2,7 +2,7 @@ library(epidatr) library(epiprocess) library(dplyr) -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 +y <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% # nolint: line_length_linter filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index f21eca49..07f3bba6 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -18,7 +18,7 @@ library(epiprocess) library(dplyr) # Use covidcast::county_census to get the county and 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 +y <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% # nolint: line_length_linter filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) @@ -108,7 +108,7 @@ help avoid bugs in further downstream data processing tasks. Let's first remove certain dates from our data set to create gaps: ```{r} -state_census <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/state_census.csv") %>% # nolint: line_length_linter +state_census <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/state_census.csv") %>% # nolint: line_length_linter select(STATE, NAME, POPESTIMATE2019, ABBR) %>% rename(abbr = ABBR, name = NAME, pop = POPESTIMATE2019, fips = STATE) %>% mutate(abbr = tolower(abbr)) %>% From 888bb572b447be3b08fb7f9f4d3576844948d0d3 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Thu, 3 Oct 2024 13:38:12 -0700 Subject: [PATCH 43/59] Re-export editing pass + don't re-export cumulative death data Cumulative death data is in tibble format and isn't really the type of data we expect in many functions. Probably not good to make it too accessible. --- NAMESPACE | 1 - R/reexports.R | 20 -------- man/jhu_confirmed_cumulative_num.Rd | 75 ----------------------------- vignettes/aggregation.Rmd | 2 +- vignettes/archive.Rmd | 8 +-- vignettes/correlation.Rmd | 13 ++--- vignettes/epiprocess.Rmd | 9 ++-- vignettes/growth_rate.Rmd | 2 +- vignettes/outliers.Rmd | 2 +- vignettes/slide.Rmd | 2 +- 10 files changed, 20 insertions(+), 114 deletions(-) delete mode 100644 man/jhu_confirmed_cumulative_num.Rd diff --git a/NAMESPACE b/NAMESPACE index 935a1239..f422b627 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -87,7 +87,6 @@ export(growth_rate) export(guess_period) export(is_epi_df) export(is_grouped_epi_archive) -export(jhu_confirmed_cumulative_num) export(key_colnames) export(max_version_with_row_in) export(mutate) diff --git a/R/reexports.R b/R/reexports.R index 800b5ea3..7358fcf4 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -139,26 +139,6 @@ delayedAssign("covid_incidence_county_subset", epidatasets::covid_incidence_coun #' @export delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) -#' @inherit epidatasets::jhu_confirmed_cumulative_num description source references title -#' @inheritSection epidatasets::jhu_confirmed_cumulative_num Data dictionary -#' @examples -#' # Since this is a re-exported dataset, it cannot be loaded using -#' # the `data()` function. `data()` looks for a file of the same name -#' # in the `data/` directory, which doesn't exist in this package. -#' # works -#' epiprocess::jhu_confirmed_cumulative_num -#' -#' # works -#' library(epiprocess) -#' jhu_confirmed_cumulative_num -#' -#' # fails -#' \dontrun{ -#' data(jhu_confirmed_cumulative_num, package = "epiprocess") -#' } -#' @export -delayedAssign("jhu_confirmed_cumulative_num", epidatasets::jhu_confirmed_cumulative_num) - #' @inherit epidatasets::archive_cases_dv_subset description source references title #' @inheritSection epidatasets::archive_cases_dv_subset Data dictionary #' @examples diff --git a/man/jhu_confirmed_cumulative_num.Rd b/man/jhu_confirmed_cumulative_num.Rd deleted file mode 100644 index b288d974..00000000 --- a/man/jhu_confirmed_cumulative_num.Rd +++ /dev/null @@ -1,75 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reexports.R -\docType{data} -\name{jhu_confirmed_cumulative_num} -\alias{jhu_confirmed_cumulative_num} -\title{Subset of COVID-19 cumulative case counts from 4 states} -\format{ -An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 2808 rows and 14 columns. -} -\source{ -This object contains a modified part of the \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. This data set is licensed under the terms of the -\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} -by the Johns Hopkins University on behalf of its Center for Systems Science in Engineering. -Copyright Johns Hopkins University 2020. - -Modifications: -\itemize{ -\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: -These signals are taken directly from the JHU CSSE \href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} without changes. -\item Furthermore, the data has been limited to a very small number of rows, -formatted into an \code{epi_df}, and the signal names slightly altered. -} -} -\usage{ -jhu_confirmed_cumulative_num -} -\description{ -Data set for 4 states containing COVID-19 Cumulative Cases as reported by -JHU-CSSE and downloaded from the CMU Delphi COVIDcast Epidata API. -This example data is a snapshot as of March 20, 2024, and -ranges from March 1, 2020 to January 31, 2022. It is limited -to California, Florida, New York, and Texas. - -It is used in the {epiprocess} "Getting Started" vignette. -} -\section{Data dictionary}{ - - -The data has columns: -\describe{ -\item{geo_value}{the geographic value associated with each row of measurements.} -\item{signal}{name of metric, derived from upstream data.} -\item{source}{name of upstream data source.} -\item{geo_type}{spatial resolution of the signal.} -\item{time_type}{temporal resolution of the signal.} -\item{time_value}{the time value associated with each row of measurements.} -\item{issue}{time unit (e.g., date) when the signal data were published.} -\item{lag}{time delta (e.g. days) between when the underlying events happened and when the data were published.} -\item{missing_value}{an integer code that is zero when the value field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} -\item{missing_stderr}{an integer code that is zero when the stderr field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} -\item{missing_sample_size}{an integer code that is zero when the sample_size field is present and non-zero when the data is missing (see \href{https://cmu-delphi.github.io/delphi-epidata/api/missing_codes.html}{missing codes}).} -\item{value}{cumulative number of confirmed COVID-19 cases, derived from the underlying data source.} -\item{stderr}{approximate standard error of the statistic with respect to its sampling distribution, NA when not applicable.} -\item{sample_size}{number of “data points” used in computing the statistic, NA when not applicable.} -} - -} - -\examples{ -# Since this is a re-exported dataset, it cannot be loaded using -# the `data()` function. `data()` looks for a file of the same name -# in the `data/` directory, which doesn't exist in this package. -# works -epiprocess::jhu_confirmed_cumulative_num - -# works -library(epiprocess) -jhu_confirmed_cumulative_num - -# fails -\dontrun{ -data(jhu_confirmed_cumulative_num, package = "epiprocess") -} -} -\keyword{datasets} diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index fbe33920..3b36cc96 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -22,7 +22,7 @@ library(covidcast) x <- covid_incidence_county_subset ``` -The data can also be fetched from the Delphi API with the following query: +The data can also be fetched from the Delphi Epidata API with the following query: ```{r, message = FALSE, eval = FALSE, warning = FALSE} library(epidatr) diff --git a/vignettes/archive.Rmd b/vignettes/archive.Rmd index a33a1d9f..86fc2c2b 100644 --- a/vignettes/archive.Rmd +++ b/vignettes/archive.Rmd @@ -36,10 +36,10 @@ library(ggplot2) # This fetches the raw data backing the archive_cases_dv_subset object. dv <- archive_cases_dv_subset$DT %>% - tibble() + as_tibble() ``` -The data can also be fetched from the Delphi API with the following query: +The data can also be fetched from the Delphi Epidata API with the following query: ```{r, message = FALSE, warning = FALSE, eval = FALSE} library(epidatr) @@ -84,8 +84,8 @@ print(x) ``` An `epi_archive` is consists of a primary field `DT`, which is a data table -(from the `data.table` package) that has the columns `geo_value`, `time_value`, -`version` (and possibly additional ones), and other metadata fields, such as +(from the `data.table` package) that has at least the required columns +`geo_value`, `time_value`, and `version`; and other metadata fields, such as `geo_type`. The variables `geo_value`, `time_value`, `version` serve as **key variables** diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index 931564a6..adb5fe86 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -23,11 +23,12 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} x <- cases_deaths_subset %>% - select(geo_value, time_value, case_rate = case_rate_7d_av, death_rate = death_rate_7d_av) %>% + select(geo_value, time_value, + case_rate = case_rate_7d_av, death_rate = death_rate_7d_av) %>% arrange(geo_value, time_value) ``` -The data can also be fetched from the Delphi API with the following query: +The data can also be fetched from the Delphi Epidata API with the following query: ```{r, eval = FALSE} library(epidatr) @@ -36,10 +37,10 @@ d <- as.Date("2024-03-20") x <- pub_covidcast( source = "jhu-csse", signals = "confirmed_7dav_incidence_prop", - time_type = "day", geo_type = "state", - time_values = epirange(20200301, 20211231), + time_type = "day", geo_values = "*", + time_values = epirange(20200301, 20211231), as_of = d ) %>% select(geo_value, time_value, case_rate = value) @@ -47,10 +48,10 @@ x <- pub_covidcast( y <- pub_covidcast( source = "jhu-csse", signals = "deaths_7dav_incidence_prop", - time_type = "day", geo_type = "state", - time_values = epirange(20200301, 20211231), + time_type = "day", geo_values = "*", + time_values = epirange(20200301, 20211231), as_of = d ) %>% select(geo_value, time_value, death_rate = value) diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 8572c808..96ed725f 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -98,12 +98,12 @@ which we also broadly refer to as signal variables. The documentation for A data frame or tibble that has `geo_value` and `time_value` columns can be converted into an `epi_df` object, using the function `as_epi_df()`. As an example, we'll work with daily cumulative COVID-19 cases from four U.S. states: -CA, FL, NY, and TX, over time span from mid 2020 to early 2022, and we'll use -the [`epidatr`](https://github.com/cmu-delphi/epidatr) package -to fetch this data from the [COVIDcast -API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast.html). +CA, FL, NY, and TX, over time span from mid 2020 to early 2022. We have included +this example data in the `epidatasets::jhu_confirmed_cumulative_num` object, +which we prepared by downloading the data using `epidatr::pub_covidcast()`. ```{r, message = FALSE} +library(epidatasets) library(epiprocess) library(dplyr) library(tidyr) @@ -111,6 +111,7 @@ library(withr) cases <- jhu_confirmed_cumulative_num +class(cases) colnames(cases) ``` diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index 214776f1..326a07c4 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -29,7 +29,7 @@ x <- cases_deaths_subset %>% arrange(geo_value, time_value) ``` -The data can also be fetched from the Delphi API with the following query: +The data can also be fetched from the Delphi Epidata API with the following query: ```{r, message = FALSE, eval = FALSE} library(epidatr) diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index 48da8f80..1c00ff6e 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -16,7 +16,7 @@ reported COVID-19 case counts from FL and NJ. The dataset has 730 rows and 3 columns. -```{r, echo=FALSE, warning=FALSE, message=FALSE} +```{r, echo=TRUE, warning=FALSE, message=FALSE} library(epiprocess) library(dplyr) library(tidyr) diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd index 935daac6..0257b3ee 100644 --- a/vignettes/slide.Rmd +++ b/vignettes/slide.Rmd @@ -38,7 +38,7 @@ edf <- cases_deaths_subset %>% arrange(geo_value, time_value) ``` -The data can also be fetched from the Delphi API with the following query: +The data can also be fetched from the Delphi Epidata API with the following query: ```{r, message = FALSE, eval = FALSE} library(epidatr) From dfbed13867b75cd8b1ccdd2d862dd82ead2073e4 Mon Sep 17 00:00:00 2001 From: brookslogan Date: Thu, 3 Oct 2024 20:41:44 +0000 Subject: [PATCH 44/59] style: styler (GHA) --- vignettes/correlation.Rmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index adb5fe86..5f363c8f 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -24,7 +24,8 @@ The data is included in this package (via the [`epidatasets` package](https://cm ```{r} x <- cases_deaths_subset %>% select(geo_value, time_value, - case_rate = case_rate_7d_av, death_rate = death_rate_7d_av) %>% + case_rate = case_rate_7d_av, death_rate = death_rate_7d_av + ) %>% arrange(geo_value, time_value) ``` From e0cfdb0681b0308e61e33113ebe7c7533e436599 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Thu, 3 Oct 2024 14:26:24 -0700 Subject: [PATCH 45/59] Fix _pkgdown.yml --- _pkgdown.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 7e0582b9..c3af76d0 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -94,7 +94,6 @@ reference: - archive_cases_dv_subset - covid_incidence_county_subset - covid_incidence_outliers - - jhu_confirmed_cumulative_num - title: Basic automatic plotting - contents: - autoplot.epi_df From 635ff4dd3a2effa9ea06be0bcee1853e8eb3addd Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Fri, 4 Oct 2024 08:20:45 -0700 Subject: [PATCH 46/59] Remove comment referencing now-unused covidcast::county_census --- vignettes/aggregation.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 07f3bba6..1350faac 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -17,7 +17,7 @@ library(epidatr) library(epiprocess) library(dplyr) -# Use covidcast::county_census to get the county and state names +# Get mapping between FIPS codes and county&state names: y <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% # nolint: line_length_linter filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) From 746347560cf70f852bf370e7bf4055fb7425b62a Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Fri, 4 Oct 2024 08:58:40 -0700 Subject: [PATCH 47/59] docs(aggregation.Rmd): tweak readr & join usage * Rename state_census -> state_naming. * Provide col_types specs for all & only columns used; avoid message spam. * Don't select unused cols; especially avoid the numeric state FIPS. * Bump dependency on dplyr and update joins; avoid message spam. --- DESCRIPTION | 2 +- data-raw/jhu_csse_county_level_subset.R | 11 +++++++++-- vignettes/aggregation.Rmd | 22 ++++++++++++++-------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3da259cf..3b8f9a68 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -35,7 +35,7 @@ Imports: checkmate, cli, data.table, - dplyr (>= 1.0.8), + dplyr (>= 1.1.0), genlasso, ggplot2, glue, diff --git a/data-raw/jhu_csse_county_level_subset.R b/data-raw/jhu_csse_county_level_subset.R index e28ba66b..4d27c20e 100644 --- a/data-raw/jhu_csse_county_level_subset.R +++ b/data-raw/jhu_csse_county_level_subset.R @@ -1,8 +1,15 @@ +library(readr) library(epidatr) library(epiprocess) library(dplyr) -y <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% # nolint: line_length_linter +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 = cols( + FIPS = col_character(), + STNAME = col_character(), + CTYNAME = col_character() + ) +) %>% filter(STNAME %in% c("Massachusetts", "Vermont"), STNAME != CTYNAME) %>% select(geo_value = FIPS, county_name = CTYNAME, state_name = STNAME) @@ -16,7 +23,7 @@ jhu_csse_county_level_subset <- pub_covidcast( time_values = epirange(20200601, 20211231), ) %>% select(geo_value, time_value, cases = value) %>% - full_join(y, by = "geo_value") %>% + inner_join(y, by = "geo_value", relationship = "many-to-one", unmatched = "error") %>% as_epi_df() usethis::use_data(jhu_csse_county_level_subset, overwrite = TRUE) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 1350faac..0a0e755e 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -13,12 +13,19 @@ 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 <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/county_census.csv") %>% # nolint: line_length_linter +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) @@ -39,6 +46,7 @@ x <- pub_covidcast( The data contains 16,212 rows and 5 columns. ```{r, echo=FALSE, warning=FALSE, message=FALSE} +library(readr) library(epidatr) library(epiprocess) library(dplyr) @@ -108,17 +116,15 @@ help avoid bugs in further downstream data processing tasks. Let's first remove certain dates from our data set to create gaps: ```{r} -state_census <- readr::read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d295550ba1540d64d2cc991badf63ad04e5/Python-packages/covidcast-py/covidcast/geo_mappings/state_census.csv") %>% # nolint: line_length_linter - select(STATE, NAME, POPESTIMATE2019, ABBR) %>% - rename(abbr = ABBR, name = NAME, pop = POPESTIMATE2019, fips = STATE) %>% - mutate(abbr = tolower(abbr)) %>% +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_census %>% select(state_name = name, abbr) - ) %>% + inner_join(state_naming, by = "state_name", relationship = "many-to-one", unmatched = "error") %>% mutate(geo_value = paste(substr(county_name, 1, nchar(county_name) - 7), state_name, sep = ", ")) %>% select(geo_value, time_value, cases) From f77b68da5adf0c213b8ea78bef3511c572eb0dc7 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Fri, 4 Oct 2024 10:01:53 -0700 Subject: [PATCH 48/59] Bump version and update NEWS.md --- DESCRIPTION | 2 +- NEWS.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8b1d043c..456e7a5e 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: epiprocess Title: Tools for basic signal processing in epidemiology -Version: 0.9.3 +Version: 0.9.4 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), diff --git a/NEWS.md b/NEWS.md index dd562c9b..b68dd7cc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,10 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicat syntax. - Improved validation of `.window_size` arguments. +## Cleanup + +- Removed vignette dependency on `covidcast`. + # epiprocess 0.9 ## Breaking changes From a2ef5a201f7c41a403b07db2121f415f0dae6500 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Fri, 4 Oct 2024 10:17:31 -0700 Subject: [PATCH 49/59] Fix `unmatched` setting --- data-raw/jhu_csse_county_level_subset.R | 2 +- vignettes/aggregation.Rmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data-raw/jhu_csse_county_level_subset.R b/data-raw/jhu_csse_county_level_subset.R index 4d27c20e..90843951 100644 --- a/data-raw/jhu_csse_county_level_subset.R +++ b/data-raw/jhu_csse_county_level_subset.R @@ -23,7 +23,7 @@ jhu_csse_county_level_subset <- pub_covidcast( time_values = epirange(20200601, 20211231), ) %>% select(geo_value, time_value, cases = value) %>% - inner_join(y, by = "geo_value", relationship = "many-to-one", unmatched = "error") %>% + inner_join(y, by = "geo_value", relationship = "many-to-one", unmatched = c("error", "drop")) %>% as_epi_df() usethis::use_data(jhu_csse_county_level_subset, overwrite = TRUE) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 0a0e755e..45747523 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -124,7 +124,7 @@ state_naming <- read_csv("https://github.com/cmu-delphi/covidcast/raw/c89e4d2955 # First make geo value more readable for tables, plots, etc. x <- x %>% - inner_join(state_naming, by = "state_name", relationship = "many-to-one", unmatched = "error") %>% + 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) From 9213b2397d951ec64791dcf1d539106b9124e7ab Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Fri, 4 Oct 2024 10:36:51 -0700 Subject: [PATCH 50/59] Update another join in aggregation.Rmd --- vignettes/aggregation.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index 45747523..4a415a42 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -39,7 +39,7 @@ x <- pub_covidcast( time_values = epirange(20200601, 20211231), ) %>% select(geo_value, time_value, cases = value) %>% - full_join(y, by = "geo_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")) ``` From a7a34e6363bf6f236889830420fc24bd2d02dd23 Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:43:02 -0400 Subject: [PATCH 51/59] switch correlation vignette data to one with all states --- NAMESPACE | 1 + R/reexports.R | 21 ++++++++ man/covid_case_death_rates_extended.Rd | 74 ++++++++++++++++++++++++++ vignettes/correlation.Rmd | 5 +- vignettes/epiprocess.Rmd | 4 +- 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 man/covid_case_death_rates_extended.Rd diff --git a/NAMESPACE b/NAMESPACE index f422b627..aa136af5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -62,6 +62,7 @@ export(autoplot) export(cases_deaths_subset) export(clone) export(complete) +export(covid_case_death_rates_extended) export(covid_incidence_county_subset) export(covid_incidence_outliers) export(detect_outlr) diff --git a/R/reexports.R b/R/reexports.R index 7358fcf4..e091ce12 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -159,3 +159,24 @@ delayedAssign("covid_incidence_outliers", epidatasets::covid_incidence_outliers) #' #' @export delayedAssign("archive_cases_dv_subset", epidatasets::archive_cases_dv_subset) + +#' @inherit epidatasets::covid_case_death_rates_extended description source references title +#' @inheritSection epidatasets::covid_case_death_rates_extended Data dictionary +#' @examples +#' # Since this is a re-exported dataset, it cannot be loaded using +#' # the `data()` function. `data()` looks for a file of the same name +#' # in the `data/` directory, which doesn't exist in this package. +#' # works +#' epiprocess::covid_case_death_rates_extended +#' +#' # works +#' library(epiprocess) +#' covid_case_death_rates_extended +#' +#' # fails +#' \dontrun{ +#' data(covid_case_death_rates_extended, package = "epiprocess") +#' } +#' +#' @export +delayedAssign("covid_case_death_rates_extended", epidatasets::covid_case_death_rates_extended) diff --git a/man/covid_case_death_rates_extended.Rd b/man/covid_case_death_rates_extended.Rd new file mode 100644 index 00000000..72482edd --- /dev/null +++ b/man/covid_case_death_rates_extended.Rd @@ -0,0 +1,74 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{data} +\name{covid_case_death_rates_extended} +\alias{covid_case_death_rates_extended} +\title{JHU daily COVID-19 cases and deaths rates from all states} +\format{ +An object of class \code{epi_df} (inherits from \code{tbl_df}, \code{tbl}, \code{data.frame}) with 37576 rows and 4 columns. +} +\source{ +This object contains a modified part of the +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University} +as \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{republished in the COVIDcast Epidata API}. +This data set is licensed under the terms of the +\href{https://creativecommons.org/licenses/by/4.0/}{Creative Commons Attribution 4.0 International license} +by the Johns Hopkins University on behalf of its Center for Systems Science +in Engineering. Copyright Johns Hopkins University 2020. + +Modifications: +\itemize{ +\item \href{https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/jhu-csse.html}{From the COVIDcast Epidata API}: +These signals are taken directly from the JHU CSSE +\href{https://github.com/CSSEGISandData/COVID-19}{COVID-19 GitHub repository} +without changes. The 7-day average signals are computed by Delphi by +calculating moving averages of the preceding 7 days, so the signal for +June 7 is the average of the underlying data for June 1 through 7, +inclusive. +} +} +\usage{ +covid_case_death_rates_extended +} +\description{ +This data source of confirmed COVID-19 cases and deaths is based on reports +made available by the Center for Systems Science and Engineering at Johns +Hopkins University, as downloaded from the CMU Delphi COVIDcast Epidata +API. This example data is a snapshot as of May 31, 2022, and +ranges from March 1, 2020 to December 31, 2021. It +includes all states. +} +\section{Data dictionary}{ + + +The data has columns: +\describe{ +\item{geo_value}{the geographic value associated with each row +of measurements.} +\item{time_value}{the time value associated with each row of measurements.} +\item{case_rate}{7-day average signal of number of new +confirmed COVID-19 cases per 100,000 population, daily} +\item{death_rate}{7-day average signal of number of new confirmed +deaths due to COVID-19 per 100,000 population, daily} +} + +} + +\examples{ +# Since this is a re-exported dataset, it cannot be loaded using +# the `data()` function. `data()` looks for a file of the same name +# in the `data/` directory, which doesn't exist in this package. +# works +epiprocess::covid_case_death_rates_extended + +# works +library(epiprocess) +covid_case_death_rates_extended + +# fails +\dontrun{ +data(covid_case_death_rates_extended, package = "epiprocess") +} + +} +\keyword{datasets} diff --git a/vignettes/correlation.Rmd b/vignettes/correlation.Rmd index 5f363c8f..073812b3 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -22,10 +22,7 @@ library(dplyr) The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: ```{r} -x <- cases_deaths_subset %>% - select(geo_value, time_value, - case_rate = case_rate_7d_av, death_rate = death_rate_7d_av - ) %>% +x <- covid_case_death_rates_extended %>% arrange(geo_value, time_value) ``` diff --git a/vignettes/epiprocess.Rmd b/vignettes/epiprocess.Rmd index 96ed725f..66c098ae 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -99,7 +99,7 @@ A data frame or tibble that has `geo_value` and `time_value` columns can be converted into an `epi_df` object, using the function `as_epi_df()`. As an example, we'll work with daily cumulative COVID-19 cases from four U.S. states: CA, FL, NY, and TX, over time span from mid 2020 to early 2022. We have included -this example data in the `epidatasets::jhu_confirmed_cumulative_num` object, +this example data in the `epidatasets::covid_confirmed_cumulative_num` object, which we prepared by downloading the data using `epidatr::pub_covidcast()`. ```{r, message = FALSE} @@ -109,7 +109,7 @@ library(dplyr) library(tidyr) library(withr) -cases <- jhu_confirmed_cumulative_num +cases <- covid_confirmed_cumulative_num class(cases) colnames(cases) From 9b33b0931df4cd903aedec0b2c30c1ee8787153e Mon Sep 17 00:00:00 2001 From: Nat DeFries <42820733+nmdefries@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:09:42 -0400 Subject: [PATCH 52/59] import readr in aggregation vignette --- vignettes/aggregation.Rmd | 1 + 1 file changed, 1 insertion(+) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index faac00cd..a5d8c1b4 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -18,6 +18,7 @@ The data is included in this package (via the [`epidatasets` package](https://cm library(epiprocess) library(dplyr) library(covidcast) +library(readr) x <- covid_incidence_county_subset ``` From fc3650db49c5fe5bd83439e975c0df430877d79f Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Mon, 14 Oct 2024 14:21:33 -0700 Subject: [PATCH 53/59] Note some other breaking changes in epidatasets migration --- NEWS.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2c633234..100c3cdd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,8 +8,12 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicat - Moved example datasets from being hosted in the package to being reexported from the `epidatasets` package. The datasets can no longer be loaded with - `data()` but can be accessed with `epiprocess::` or, after loading the package, - just the name of the dataset (#520). + `data()` but can be accessed with `epiprocess::` or, after loading the + package, just the name of the dataset (#520). Those with names starting with + `jhu` have been renamed to a more uniform scheme and now have names starting + with `covid`. The data set previously named `jhu_confirmed_cumulative_num` has + been removed from the package, but a renamed version is has been removed from + the package, but a renamed version is still available in `epidatasets`. ## Bug fixes From a32166b69be326b63a04e1f12269d5efafdb2016 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Mon, 14 Oct 2024 14:51:39 -0700 Subject: [PATCH 54/59] Address some no-visible-binding CHECKS --- R/epiprocess.R | 4 +++- R/grouped_epi_archive.R | 4 ++-- R/slide.R | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/R/epiprocess.R b/R/epiprocess.R index 283c4a46..147d4ef9 100644 --- a/R/epiprocess.R +++ b/R/epiprocess.R @@ -18,5 +18,7 @@ utils::globalVariables(c( ".x", ".group_key", ".ref_time_value", "resid", "fitted", ".response", "geo_value", "time_value", - "value", ".real" + "value", ".real", "lag", "max_value", "min_value", + "median_value", "spread", "rel_spread", "time_to", + "time_near_latest", "n_revisions" )) diff --git a/R/grouped_epi_archive.R b/R/grouped_epi_archive.R index bec8c9c2..08eb2d25 100644 --- a/R/grouped_epi_archive.R +++ b/R/grouped_epi_archive.R @@ -398,8 +398,8 @@ epix_slide.grouped_epi_archive <- function( )), capture.output(print(waldo::compare( res[[comp_nms[[comp_i]]]], comp_value[[comp_i]], - x_arg = rlang::expr_deparse(dplyr::expr(`$`(label, !!sym(comp_nms[[comp_i]])))), # nolint: object_usage_linter - y_arg = rlang::expr_deparse(dplyr::expr(`$`(comp_value, !!sym(comp_nms[[comp_i]])))) + x_arg = rlang::expr_deparse(rlang::expr(`$`(!!"label", !!sym(comp_nms[[comp_i]])))), + y_arg = rlang::expr_deparse(rlang::expr(`$`(!!"comp_value", !!sym(comp_nms[[comp_i]])))) ))), cli::format_message(c( "You likely want to rename or remove this column in your output, or debug why it has a different value." diff --git a/R/slide.R b/R/slide.R index 959c4168..c792187e 100644 --- a/R/slide.R +++ b/R/slide.R @@ -424,8 +424,8 @@ epi_slide_one_group <- function( )), capture.output(print(waldo::compare( res[[comp_nms[[comp_i]]]], slide_values[[comp_i]], - x_arg = rlang::expr_deparse(dplyr::expr(`$`(existing, !!sym(comp_nms[[comp_i]])))), # nolint: object_usage_linter - y_arg = rlang::expr_deparse(dplyr::expr(`$`(comp_value, !!sym(comp_nms[[comp_i]])))) # nolint: object_usage_linter + x_arg = rlang::expr_deparse(dplyr::expr(`$`(!!"existing", !!sym(comp_nms[[comp_i]])))), # nolint: object_usage_linter + y_arg = rlang::expr_deparse(dplyr::expr(`$`(!!"comp_value", !!sym(comp_nms[[comp_i]])))) # nolint: object_usage_linter ))), cli::format_message(c( ">" = "You likely want to rename or remove this column from your slide From 7df79baa6507b5215c2efc12d18049850b060e59 Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Mon, 14 Oct 2024 14:53:27 -0700 Subject: [PATCH 55/59] Remove now-unnecessary `library(covidcast)` --- vignettes/aggregation.Rmd | 1 - 1 file changed, 1 deletion(-) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd index a5d8c1b4..0b65c71f 100644 --- a/vignettes/aggregation.Rmd +++ b/vignettes/aggregation.Rmd @@ -17,7 +17,6 @@ The data is included in this package (via the [`epidatasets` package](https://cm ```{r, warning = FALSE, message = FALSE} library(epiprocess) library(dplyr) -library(covidcast) library(readr) x <- covid_incidence_county_subset From c9d187766fe21eb52b343d892ec710a314cb1a2a Mon Sep 17 00:00:00 2001 From: "Logan C. Brooks" Date: Tue, 15 Oct 2024 04:16:10 -0700 Subject: [PATCH 56/59] Add missing topic to _pkgdown.yml --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index c3af76d0..e8c05a65 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -94,6 +94,7 @@ reference: - archive_cases_dv_subset - covid_incidence_county_subset - covid_incidence_outliers + - covid_case_death_rates_extended - title: Basic automatic plotting - contents: - autoplot.epi_df From 62879852f850b180e644c097a24566d11b9d459e Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Tue, 1 Oct 2024 22:00:59 -0700 Subject: [PATCH 57/59] doc: big doc overhaul * clean up and reorganize reference * unify detect_outlr, epi_slide_* reference pages * add README.Rmd and rewrite landing page * rewrite Getting Started vignette * unify aggregation and slide vignette into epi_df * rewrite archive vignette * improve slide, epi_df and epi_archive reference pages * update epix_fill_through_version #419 * add .editorconfig * update DEVELOPMENT.md * bump version and update NEWS --- .Rbuildignore | 6 +- .editorconfig | 21 + .gitignore | 7 +- DESCRIPTION | 17 +- DEVELOPMENT.md | 34 +- NAMESPACE | 5 +- NEWS.md | 2 + R/archive.R | 131 ++-- R/epi_df.R | 41 +- R/epiprocess-package.R | 28 + R/epiprocess.R | 24 - R/growth_rate.R | 9 +- R/methods-epi_archive.R | 114 +-- R/methods-epi_df.R | 20 +- R/outliers.R | 30 +- R/revision_analysis.R | 23 +- R/slide.R | 305 ++++---- R/utils.R | 9 +- README.Rmd | 121 ++++ README.md | 203 ++++-- _pkgdown.yml | 72 +- man-roxygen/basic-slide-details.R | 69 -- man-roxygen/basic-slide-params.R | 63 +- man-roxygen/detect-outlr-return.R | 3 - man-roxygen/opt-slide-details.R | 33 - man-roxygen/opt-slide-params.R | 10 - man-roxygen/x-y.R | 4 - man-roxygen/x.R | 1 - man/apply_compactify.Rd | 4 +- man/assert_sufficient_f_args.Rd | 22 + man/compactify.Rd | 28 - man/complete.epi_df.Rd | 4 +- man/decay_epi_df.Rd | 22 + man/deprecated_quo_is_present.Rd | 59 ++ man/detect_outlr.Rd | 111 +++ man/detect_outlr_rm.Rd | 69 -- man/detect_outlr_stl.Rd | 101 --- man/dplyr_reconstruct.epi_df.Rd | 21 + man/epi_archive.Rd | 81 ++- man/epi_df.Rd | 57 +- man/epi_slide.Rd | 248 +++---- man/epi_slide_mean.Rd | 166 ----- man/epi_slide_opt.Rd | 181 +++-- man/epi_slide_sum.Rd | 129 ---- man/{epiprocess.Rd => epiprocess-package.Rd} | 11 +- man/epix_fill_through_version.Rd | 53 +- man/epix_merge.Rd | 63 +- man/figures/README-unnamed-chunk-6-1.png | Bin 0 -> 36830 bytes man/figures/README-unnamed-chunk-6-1.svg | 383 ++++++++++ man/figures/README-unnamed-chunk-7-1.svg | 399 +++++++++++ man/figures/README-unnamed-chunk-8-1.svg | 365 ++++++++++ man/full_date_seq.Rd | 15 + man/geo_column_names.Rd | 1 + man/growth_rate.Rd | 9 +- 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/revision_summary.Rd | 20 +- man/summary.epi_df.Rd | 18 - man/time_column_names.Rd | 1 + man/validate_version_bound.Rd | 32 + man/version_column_names.Rd | 1 + pkgdown-watch.R | 61 ++ tests/testthat/test-archive.R | 12 - vignettes/.gitignore | 3 - vignettes/_common.R | 24 + vignettes/aggregation.Rmd | 235 ------- vignettes/archive.Rmd | 694 ------------------- vignettes/compactify.Rmd | 4 + vignettes/correlation.Rmd | 12 +- vignettes/epi_archive.Rmd | 472 +++++++++++++ vignettes/epi_df.Rmd | 518 ++++++++++++++ vignettes/epiprocess.Rmd | 438 ++++-------- vignettes/growth_rate.Rmd | 5 +- vignettes/outliers.Rmd | 11 +- vignettes/slide.Rmd | 381 ---------- 78 files changed, 3781 insertions(+), 3199 deletions(-) create mode 100644 .editorconfig create mode 100644 R/epiprocess-package.R delete mode 100644 R/epiprocess.R create mode 100644 README.Rmd delete mode 100644 man-roxygen/basic-slide-details.R delete mode 100644 man-roxygen/detect-outlr-return.R delete mode 100644 man-roxygen/opt-slide-details.R delete mode 100644 man-roxygen/opt-slide-params.R delete mode 100644 man-roxygen/x-y.R delete mode 100644 man-roxygen/x.R 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 delete mode 100644 man/detect_outlr_rm.Rd delete mode 100644 man/detect_outlr_stl.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 rename man/{epiprocess.Rd => epiprocess-package.Rd} (77%) create mode 100644 man/figures/README-unnamed-chunk-6-1.png create mode 100644 man/figures/README-unnamed-chunk-6-1.svg create mode 100644 man/figures/README-unnamed-chunk-7-1.svg create mode 100644 man/figures/README-unnamed-chunk-8-1.svg 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 create mode 100644 pkgdown-watch.R delete mode 100644 vignettes/.gitignore create mode 100644 vignettes/_common.R delete mode 100644 vignettes/aggregation.Rmd delete mode 100644 vignettes/archive.Rmd create mode 100644 vignettes/epi_archive.Rmd create mode 100644 vignettes/epi_df.Rmd delete mode 100644 vignettes/slide.Rmd diff --git a/.Rbuildignore b/.Rbuildignore index cb0b7ed2..c844f557 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,4 +17,8 @@ ^DEVELOPMENT.md$ man-roxygen ^.venv$ -^sandbox.R$ \ No newline at end of file +^sandbox.R$ +^README.Rmd$ +^README_cache$ +^pkgdown-watch.R$ +^.editorconfig$ \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5ed5ba34 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8dc001be..4ac3061c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,9 @@ docs renv/ renv.lock .Rprofile -sandbox.R \ No newline at end of file +sandbox.R +# Vignette caches +*_cache/ +vignettes/*.html +vignettes/*.R +!vignettes/_common.R diff --git a/DESCRIPTION b/DESCRIPTION index b021ec3d..fd09aa57 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ -Type: Package Package: epiprocess +Type: Package Title: Tools for basic signal processing in epidemiology -Version: 0.9.5 +Version: 0.9.6 Authors@R: c( person("Jacob", "Bien", role = "ctb"), person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = c("aut", "cre")), @@ -28,11 +28,11 @@ Authors@R: c( person("Carnegie Mellon University Delphi Group", role = "dtc", comment = "Owner of claims-based CLI data from the Delphi Epidata API") ) -Description: This package introduces a common data structure for - epidemiological data reported by location and time, provides another - data structure to work with revisions to these data sets over time, - and offers associated utilities to perform basic signal processing - tasks. +Description: This package introduces common data structures for working with + epidemiological data reported by location and time and offers associated + utilities to perform basic signal processing tasks. The package is designed + to be used in conjunction with `epipredict` for building and evaluating + epidemiological models. License: MIT + file LICENSE URL: https://cmu-delphi.github.io/epiprocess/ Depends: @@ -62,6 +62,7 @@ Imports: Suggests: devtools, epidatr, + here, knitr, outbreaks, readr, @@ -88,7 +89,7 @@ Collate: 'correlation.R' 'epi_df.R' 'epi_df_forbidden_methods.R' - 'epiprocess.R' + 'epiprocess-package.R' 'group_by_epi_df_methods.R' 'methods-epi_archive.R' 'grouped_epi_archive.R' diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 46fa3125..60265a84 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,10 +1,8 @@ ## Setting up the development environment ```r -install.packages(c('devtools', 'pkgdown', 'styler', 'lintr')) # install dev dependencies -devtools::install_deps(dependencies = TRUE) # install package dependencies -devtools::document() # generate package meta data and man files -devtools::build() # build package +install.packages(c('devtools', 'pkgdown', 'styler', 'lintr', 'pak')) # install dev dependencies +pak::pkg_install(".") # install package and dependencies ``` ## Validating the package @@ -13,8 +11,12 @@ devtools::build() # build package styler::style_pkg() # format code lintr::lint_package() # lint code +devtools::check() # run R CMD check, which runs everything below +devtools::document() # generate package meta data and man files devtools::test() # test package -devtools::check() # check package for errors +devtools::build_vignettes() # build vignettes only +devtools::run_examples() # run doc examples +devtools::check(vignettes = FALSE) # check package without vignettes ``` ## Developing the documentation site @@ -24,20 +26,16 @@ Our CI builds two version of the documentation: - https://cmu-delphi.github.io/epiprocess/ from the `main` branch and - https://cmu-delphi.github.io/epiprocess/dev from the `dev` branch. -The documentation site can be previewed locally by running in R: - -```r -# Should automatically open a browser -pkgdown::build_site(preview=TRUE) -``` - -If the above does not open a browser, you can try using a Python server from the -command line: +We include the script `pkgdown-watch.R` that will automatically rebuild the +documentation locally and preview it. It can be used with: -```bash -R -e 'devtools::document()' -R -e 'pkgdown::build_site()' -python -m http.server -d docs +```sh +# Make sure you have servr installed +R -e 'renv::install("servr")' +# Will start a local server +Rscript pkgdown-watch.R +# You may need to first build the site with +R -e 'pkgdown::build_site(".", examples = FALSE, devel = TRUE, preview = FALSE)' ``` ## Versioning diff --git a/NAMESPACE b/NAMESPACE index aa136af5..1fd65d37 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) @@ -65,6 +63,7 @@ export(complete) export(covid_case_death_rates_extended) export(covid_incidence_county_subset) export(covid_incidence_outliers) +export(deprecated_quo_is_present) export(detect_outlr) export(detect_outlr_rm) export(detect_outlr_stl) @@ -89,11 +88,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/NEWS.md b/NEWS.md index 100c3cdd..8ab77ee4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,6 +27,8 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicat using a formula to specify the slide computation, and other bits of forgotten syntax. - Improved validation of `.window_size` arguments. +- Rewrote a lot of the package documentation to be more consistent and + informative. Simplified and streamlined the vignettes. ## Cleanup diff --git a/R/archive.R b/R/archive.R index d8102165..3557c991 100644 --- a/R/archive.R +++ b/R/archive.R @@ -9,8 +9,9 @@ #' Validate a version bound arg #' -#' Expected to be used on `clobberable_versions_start`, `versions_end`, -#' and similar arguments. Some additional context-specific checks may be needed. +#' Expected to be used on `clobberable_versions_start`, `versions_end`, and +#' similar arguments. Some additional context-specific checks may be needed. +#' Side effects: raises an error if version bound appears invalid. #' #' @param version_bound the version bound to validate #' @param x a data frame containing a version column with which to check @@ -20,9 +21,7 @@ #' @param version_bound_arg optional string; what to call the version bound in #' error messages #' -#' @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)) { @@ -75,9 +74,7 @@ validate_version_bound <- function(version_bound, x, na_ok = FALSE, #' @return `max(x$version)` if it has any rows; raises error if it has 0 rows or #' an `NA` version value #' -#' @importFrom checkmate check_names -#' -#' @export +#' @keywords internal max_version_with_row_in <- function(x) { if (nrow(x) == 0L) { cli_abort( @@ -108,72 +105,71 @@ 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. +#' `epi_archive` object #' -#' @name compactify -NULL - - -#' Epi Archive -#' -#' @title `epi_archive` object +#' @description The second main data structure for storing time series in +#' `epiprocess`. It is similar to `epi_df` in that it fundamentally a table with +#' a few required columns that stores epidemiological time series data. An +#' `epi_archive` requires a `geo_value`, `time_value`, and `version` column (and +#' possibly other key columns) along with measurement values. In brief, an +#' `epi_archive` is a history of the time series data, where the `version` +#' column tracks the time at which the data was available. This allows for +#' version-aware forecasting. #' -#' @description An `epi_archive` is an S3 class which contains a data table -#' along with several relevant pieces of metadata. The data table can be seen -#' as the full archive (version history) for some signal variables of -#' interest. +#' `new_epi_archive` is the constructor for `epi_archive` objects that assumes +#' all arguments have been validated. Most users should use `as_epi_archive`. #' -#' @details An `epi_archive` contains a data table `DT`, of class `data.table` -#' from the `data.table` package, with (at least) the following columns: +#' @details An `epi_archive` contains a `data.table` object `DT` (from the +#' `{data.table}` package), with (at least) the following columns: #' -#' * `geo_value`: the geographic value associated with each row of measurements. -#' * `time_value`: the time value associated with each row of measurements. +#' * `geo_value`: the geographic value associated with each row of measurements, +#' * `time_value`: the time value associated with each row of measurements, #' * `version`: the time value specifying the version for each row of #' measurements. For example, if in a given row the `version` is January 15, #' 2022 and `time_value` is January 14, 2022, then this row contains the #' measurements of the data for January 14, 2022 that were available one day #' later. #' -#' The data table `DT` has key variables `geo_value`, `time_value`, `version`, -#' as well as any others (these can be specified when instantiating the -#' `epi_archive` object via the `other_keys` argument, and/or set by operating -#' on `DT` directly). Note that there can only be a single row per unique -#' combination of key variables. +#' The variables `geo_value`, `time_value`, `version` serve as key variables for +#' the data table (in addition to any other keys specified in the metadata). +#' There can only be a single row per unique combination of key variables. The +#' keys for an `epi_archive` can be viewed with `key(epi_archive$DT)`. +#' +#' ## Compactification +#' +#' By default, an `epi_archive` will compactify the data table to remove +#' redundant rows. This is done by not storing rows that have the same value, +#' except for the `version` column (this is essentially a last observation +#' carried forward, but along the version index). This is done to save space and +#' improve performance. If you do not want to compactify the data, you can set +#' `compactify = FALSE` in `as_epi_archive()`. +#' +#' Note that in some data scenarios, LOCF may not be appropriate. For instance, +#' if you expected data to be updated on a given day, but your data source did +#' not update, then it could be reasonable to code the data as `NA` for that +#' day, instead of assuming LOCF. +#' +#' `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. +#' +#' ## Metadata #' -#' @section Metadata: #' The following pieces of metadata are included as fields in an `epi_archive` #' object: #' @@ -187,20 +183,6 @@ NULL #' archive. Unexpected behavior may result from modifying the metadata #' directly. #' -#' @section Generating Snapshots: -#' An `epi_archive` object can be used to generate a snapshot of the data in -#' `epi_df` format, which represents the most up-to-date time series values up -#' to a point in time. This is accomplished by calling `epix_as_of()`. -#' -#' @section Sliding Computations: -#' We can run a sliding computation over an `epi_archive` object, much like -#' `epi_slide()` does for an `epi_df` object. This is accomplished by calling -#' the `slide()` method for an `epi_archive` object, which works similarly to -#' the way `epi_slide()` works for an `epi_df` object, but with one key -#' difference: it is version-aware. That is, for an `epi_archive` object, the -#' sliding computation at any given reference time point t is performed on -#' **data that would have been available as of t**. -#' #' @param x A data.frame, data.table, or tibble, with columns `geo_value`, #' `time_value`, `version`, and then any additional number of columns. #' @param geo_type DEPRECATED Has no effect. Geo value type is inferred from the @@ -239,10 +221,11 @@ 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 +#' @seealso [`epix_as_of`] [`epix_merge`] [`epix_slide`] #' @importFrom dplyr if_any if_all everything #' @importFrom utils capture.output #' @@ -356,12 +339,13 @@ new_epi_archive <- function( ) } -#' given a tibble as would be found in an epi_archive, remove duplicate entries. -#' @description -#' works by shifting all rows except the version, then comparing values to see +#' Given a tibble as would be found in an epi_archive, remove duplicate entries. +#' +#' Works by shifting all rows except the version, then comparing values to see #' if they've changed. We need to arrange in descending order, but note that #' we don't need to group, since at least one column other than version has #' changed, and so is kept. +#' #' @keywords internal #' @importFrom dplyr filter apply_compactify <- function(df, keys, tolerance = .Machine$double.eps^.5) { @@ -466,6 +450,7 @@ validate_epi_archive <- function( #' `as_epi_archive` converts a data frame, data table, or tibble into an #' `epi_archive` object. +#' #' @param ... used for specifying column names, as in [`dplyr::rename`]. For #' example `version = release_date` #' @param .versions_end location based versions_end, used to avoid prefix diff --git a/R/epi_df.R b/R/epi_df.R index 070ddb06..bcf9e56f 100644 --- a/R/epi_df.R +++ b/R/epi_df.R @@ -1,8 +1,10 @@ #' `epi_df` object #' -#' An `epi_df` is a tibble with certain minimal column structure and metadata. -#' It can be seen as a snapshot of a data set that contains the most -#' up-to-date values of some signal variables of interest, as of a given time. +#' One of the two main data structures for storing time series in `epiprocess`. +#' It is simply tibble with at least two columns, `geo_value` and `time_value`, +#' that provide the keys for the time series. It can have any other columns, +#' which can be seen as measured variables at each key. In brief, an `epi_df` +#' represents a snapshot of an epidemiological data set at a point in time. #' #' @details An `epi_df` is a tibble with (at least) the following columns: #' @@ -40,6 +42,9 @@ #' single snapshot of a data set that contains the most up-to-date values of #' the signals variables, as of the time specified in the `as_of` field. #' +#' If an `epi_df` ever loses its `geo_value` or `time_value` columns, it will +#' decay into a regular tibble. +#' #' A companion object is the `epi_archive` object, which contains the full #' version history of a given data set. Revisions are common in many types of #' epidemiological data streams, and paying attention to data revisions can be @@ -49,7 +54,8 @@ #' generate `epi_df` objects, as data snapshots, from an `epi_archive` #' object). #' -#' @section Geo Types: +#' ## Geo Types +#' #' The following geo types are recognized in an `epi_df`. #' #' * `"county"`: each observation corresponds to a U.S. county; coded by 5-digit @@ -67,7 +73,8 @@ #' #' An unrecognizable geo type is labeled "custom". #' -#' @section Time Types: +#' ## Time Types +#' #' The following time types are recognized in an `epi_df`. #' #' * `"day"`: each observation corresponds to a day; coded as a `Date` object, @@ -85,33 +92,30 @@ #' @examples #' # Convert a `tsibble` that has county code as an extra key #' # Notice that county code should be a character string to preserve any leading zeroes -#' #' ex1_input <- tibble::tibble( -#' geo_value = rep(c("ca", "fl", "pa"), each = 3), -#' county_code = c( +#' geo_value = c( #' "06059", "06061", "06067", #' "12111", "12113", "12117", #' "42101", "42103", "42105" #' ), +#' state_name = rep(c("ca", "fl", "pa"), each = 3), #' time_value = rep(seq(as.Date("2020-06-01"), as.Date("2020-06-03"), #' by = "day" #' ), length.out = length(geo_value)), #' value = 1:length(geo_value) + 0.01 * rnorm(length(geo_value)) #' ) %>% -#' tsibble::as_tsibble(index = time_value, key = c(geo_value, county_code)) +#' tsibble::as_tsibble(index = time_value, key = c(geo_value, state_name)) #' -#' # The `other_keys` metadata (`"county_code"` in this case) is automatically +#' # The `other_keys` metadata (`"state_name"` in this case) is automatically #' # inferred from the `tsibble`'s `key`: #' ex1 <- as_epi_df(x = ex1_input, as_of = "2020-06-03") #' attr(ex1, "metadata")[["other_keys"]] #' -#' #' # Dealing with misspecified column names: #' # Geographical and temporal information must be provided in columns named #' # `geo_value` and `time_value`; if we start from a data frame with a #' # different format, it must be converted to use `geo_value` and `time_value` #' # before calling `as_epi_df`. -#' #' ex2_input <- tibble::tibble( #' state = rep(c("ca", "fl", "pa"), each = 3), # misnamed #' pol = rep(c("blue", "swing", "swing"), each = 3), # extra key @@ -120,7 +124,6 @@ #' ), length.out = length(state)), # misnamed #' value = 1:length(state) + 0.01 * rnorm(length(state)) #' ) -#' #' print(ex2_input) #' #' ex2 <- ex2_input %>% @@ -129,12 +132,9 @@ #' as_of = "2020-06-03", #' other_keys = "pol" #' ) -#' #' attr(ex2, "metadata") #' -#' #' # Adding additional keys to an `epi_df` object -#' #' ex3_input <- covid_incidence_county_subset %>% #' dplyr::filter(time_value > "2021-12-01", state_name == "Massachusetts") %>% #' dplyr::slice_tail(n = 6) @@ -149,6 +149,10 @@ #' as_epi_df(other_keys = c("state", "pol")) #' #' attr(ex3, "metadata") +#' +#' # Decays to a tibble +#' covid_incidence_county_subset %>% +#' dplyr::select(-geo_value) NULL #' @describeIn epi_df Lower-level constructor for `epi_df` object @@ -298,7 +302,7 @@ as_epi_df.tbl_df <- function( #' @method as_epi_df data.frame #' @export as_epi_df.data.frame <- function(x, as_of, other_keys = character(), ...) { - as_epi_df.tbl_df(x = tibble::as_tibble(x), as_of = as_of, other_keys = other_keys, ...) + as_epi_df(x = tibble::as_tibble(x), as_of = as_of, other_keys = other_keys, ...) } #' @rdname epi_df @@ -310,7 +314,7 @@ as_epi_df.tbl_ts <- function(x, as_of, other_keys = character(), ...) { if (length(tsibble_other_keys) > 0) { other_keys <- unique(c(other_keys, tsibble_other_keys)) } - as_epi_df.tbl_df(x = tibble::as_tibble(x), as_of = as_of, other_keys = other_keys, ...) + as_epi_df(x = tibble::as_tibble(x), as_of = as_of, other_keys = other_keys, ...) } #' Test for `epi_df` format @@ -318,6 +322,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-package.R b/R/epiprocess-package.R new file mode 100644 index 00000000..e28cec0f --- /dev/null +++ b/R/epiprocess-package.R @@ -0,0 +1,28 @@ +#' @keywords internal +"_PACKAGE" + +## usethis namespace: start +#' @import epidatasets +#' @importFrom checkmate anyInfinite anyMissing assert assert_character +#' @importFrom checkmate assert_class assert_data_frame assert_int assert_list +#' @importFrom checkmate assert_logical assert_numeric assert_scalar checkInt +#' @importFrom checkmate check_atomic check_data_frame expect_class test_int +#' @importFrom checkmate check_names +#' @importFrom checkmate test_subset test_set_equal vname +#' @importFrom cli cli_abort cli_warn +#' @importFrom data.table as.data.table +#' @importFrom data.table key +#' @importFrom data.table setkeyv +#' @importFrom dplyr select +#' @importFrom lifecycle deprecated +#' @importFrom rlang %||% +## usethis namespace: end +NULL + +utils::globalVariables(c( + ".x", ".group_key", ".ref_time_value", "resid", + "fitted", ".response", "geo_value", "time_value", + "value", ".real", "lag", "max_value", "min_value", + "median_value", "spread", "rel_spread", "time_to", + "time_near_latest", "n_revisions" +)) diff --git a/R/epiprocess.R b/R/epiprocess.R deleted file mode 100644 index 147d4ef9..00000000 --- a/R/epiprocess.R +++ /dev/null @@ -1,24 +0,0 @@ -#' 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. -#' -#' @importFrom checkmate assert assert_scalar assert_data_frame anyMissing -#' assert_logical assert_list assert_character assert_class -#' assert_int assert_numeric check_data_frame vname check_atomic -#' anyInfinite test_subset test_set_equal checkInt expect_class -#' test_int -#' @importFrom cli cli_abort cli_warn -#' @importFrom rlang %||% -#' @importFrom lifecycle deprecated -#' @import epidatasets -#' @name epiprocess -"_PACKAGE" -utils::globalVariables(c( - ".x", ".group_key", ".ref_time_value", "resid", - "fitted", ".response", "geo_value", "time_value", - "value", ".real", "lag", "max_value", "min_value", - "median_value", "spread", "rel_spread", "time_to", - "time_near_latest", "n_revisions" -)) diff --git a/R/growth_rate.R b/R/growth_rate.R index b9b9a440..307309b5 100644 --- a/R/growth_rate.R +++ b/R/growth_rate.R @@ -56,7 +56,8 @@ #' `genlasso::trendfilter()`, divided by the fitted value of the discrete #' spline at `x0`. #' -#' @section Log Scale: +#' ## Log Scale +#' #' An alternative view for the growth rate of a function f in general is given #' by defining g(t) = log(f(t)), and then observing that g'(t) = f'(t) / #' f(t). Therefore, any method that estimates the derivative can be simply @@ -65,7 +66,8 @@ #' "trend_filter") has a log scale analog, which can be used by setting #' `log_scale = TRUE`. #' -#' @section Sliding Windows: +#' ## Sliding Windows +#' #' For the local methods, "rel_change" and "linear_reg", we use a sliding window #' centered at the reference point of bandiwidth `h`. In other words, the #' sliding window consists of all points in `x` whose distance to the @@ -75,7 +77,8 @@ #' sliding window contains all data in between January 1 and 14 (matching the #' behavior of `epi_slide()` with `before = h - 1` and `after = h`). #' -#' @section Additional Arguments: +#' ## Additional Arguments +#' #' For the global methods, "smooth_spline" and "trend_filter", additional #' arguments can be specified via `...` for the underlying estimation #' function. For the smoothing spline case, these additional arguments are diff --git a/R/methods-epi_archive.R b/R/methods-epi_archive.R index 0304d9a6..d16b1485 100644 --- a/R/methods-epi_archive.R +++ b/R/methods-epi_archive.R @@ -138,33 +138,45 @@ epix_as_of <- function(x, version, min_time_value = -Inf, all_versions = FALSE, #' Fill `epi_archive` unobserved history #' #' @description -#' Sometimes, due to upstream data pipeline issues, we have to work with a -#' version history that isn't completely up to date, but with functions that -#' expect archives that are completely up to date, or equally as up-to-date as -#' another archive. This function provides one way to approach such mismatches: -#' pretend that we've "observed" additional versions, filling in these versions -#' with NAs or extrapolated values. +#' This function fills in missing version history in an `epi_archive` object up +#' to a specified version, updating the `versions_end` field as necessary. Note +#' that the filling is done in a compactified way, see details. #' #' @param x An `epi_archive` -#' @param fill_versions_end Length-1, same class&type as `x$version`: the -#' version through which to fill in missing version history; this will be the -#' result's `$versions_end` unless it already had a later -#' `$versions_end`. -#' @param how Optional; `"na"` or `"locf"`: `"na"` will fill in any missing -#' required version history with `NA`s, by inserting (if necessary) an update -#' immediately after the current `$versions_end` that revises all -#' existing measurements to be `NA` (this is only supported for `version` -#' classes with a `next_after` implementation); `"locf"` will fill in missing -#' version history with the last version of each observation carried forward -#' (LOCF), by leaving the update `$DT` alone (other `epi_archive` methods are -#' based on LOCF). Default is `"na"`. +#' @param fill_versions_end a scalar of the same class&type as `x$version`: the +#' version through which to fill in missing version history; the +#' `epi_archive`'s `versions_end` attribute will be set to this, unless it +#' already had a later `$versions_end`. +#' @param how Optional; `"na"` or `"locf"`: `"na"` fills missing version history +#' with `NA`s, `"locf"` fills missing version history with the last version of +#' each observation carried forward (LOCF). Default is `"na"`. +#' @return An `epi_archive` +#' @details +#' Note that we generally store `epi_archive`'s in a compacted form, meaning +#' that, implciitly, if a version does not exist, but the `version_end` +#' attribute is greater, then it is understood that all the versions in between +#' had the same value as the last observed version. This affects the behavior of +#' this function in the following ways: +#' +#' - if `how = "na"`, then the function will fill in at most one missing version +#' with `NA` and the rest will be implicit. +#' - if `how = "locf"`, then the function will not fill any values. #' #' @importFrom data.table copy ":=" #' @importFrom rlang arg_match #' @return An `epi_archive` #' @export -epix_fill_through_version <- function(x, fill_versions_end, - how = c("na", "locf")) { +#' @examples +#' test_date <- as.Date("2020-01-01") +#' ea_orig <- as_epi_archive(data.table::data.table( +#' geo_value = "ak", +#' time_value = test_date + c(rep(0L, 5L), 1L), +#' version = test_date + c(1:5, 2L), +#' value = 1:6 +#' )) +#' epix_fill_through_version(ea_orig, test_date + 8, "na") +#' epix_fill_through_version(ea_orig, test_date + 8, "locf") +epix_fill_through_version <- function(x, fill_versions_end, how = c("na", "locf")) { assert_class(x, "epi_archive") validate_version_bound(fill_versions_end, x$DT, na_ok = FALSE) @@ -233,24 +245,38 @@ epix_fill_through_version <- function(x, fill_versions_end, #' parameter controls what is done. #' #' @param x,y Two `epi_archive` objects to join together. -#' @param sync Optional; `"forbid"`, `"na"`, `"locf"`, or `"truncate"`; in the -#' case that `x$versions_end` doesn't match `y$versions_end`, what do we do?: -#' `"forbid"`: emit an error; "na": use `max(x$versions_end, y$versions_end)` -#' as the result's `versions_end`, but ensure that, if we request a snapshot -#' as of a version after `min(x$versions_end, y$versions_end)`, the -#' observation columns from the less up-to-date archive will be all NAs (i.e., -#' imagine there was an update immediately after its `versions_end` which -#' revised all observations to be `NA`); `"locf"`: use `max(x$versions_end, -#' y$versions_end)` as the result's `versions_end`, allowing the last version -#' of each observation to be carried forward to extrapolate unavailable -#' versions for the less up-to-date input archive (i.e., imagining that in the -#' less up-to-date archive's data set remained unchanged between its actual -#' `versions_end` and the other archive's `versions_end`); or `"truncate"`: -#' use `min(x$versions_end, y$versions_end)` as the result's `versions_end`, -#' and discard any rows containing update rows for later versions. -#' @param compactify Optional; `TRUE`, `FALSE`, or `NULL`; should the result be -#' compactified? See `as_epi_archive()` for an explanation of what this means. -#' Default here is `TRUE`. +#' @param sync Optional; character. The argument that decides how to handle the +#' situation when one signal has a more recent revision than another signal +#' for a key that they have both already observed. The options are: +#' +#' - `"forbid"`: the default and the strictest option, throws an error; this +#' is likely not what you want, but it is strict to make the user aware of the +#' issues, +#' - `"locf"`: carry forward the last observed version of the missing signal +#' to the new version and use `max(x$versions_end, y$versions_end)` as the +#' result's `versions_end`, +#' - `"na"`: fill the unobserved values with `NA`'s (this can be handy when +#' you know that source data is truly missing upstream and you want to +#' represent the lack of information accurately, for instance) and use +#' `max(x$versions_end, y$versions_end)` as the result's `versions_end`, +#' - `"truncate"`: discard any rows containing update rows for later versions +#' and use `min(x$versions_end, y$versions_end)` as the result's +#' `versions_end`. +#' +#' @param compactify Optional; `TRUE` (default), `FALSE`, or `NULL`; should the +#' result be compactified? See `as_epi_archive()` for details. +#' @details +#' When merging archives, unless the archives have identical data release +#' patterns, we often have to handle the situation when one signal has a more +#' recent observation for a key than another signal. In this case, we have two +#' options: +#' +#' - if the the other signal has never observed that key, we need to introduce +#' `NA`s in the non-key variables for the missing signal, +#' - if the other signal has observed that key previously, but at an ealier +#' revision date, then we need to decide how to handle the missing value in the +#' more recent signal; the `sync` argument controls this behavior. +#' #' @return the resulting `epi_archive` #' #' @details In all cases, `clobberable_versions_start` will be set to the @@ -265,18 +291,14 @@ epix_fill_through_version <- function(x, fill_versions_end, #' version = as.Date(c("2024-08-01", "2024-08-02", "2024-08-02")), #' signal1 = c(10, 11, 7) #' ) -#' #' s2 <- tibble::tibble( #' geo_value = c("ca", "ca"), #' time_value = as.Date(c("2024-08-01", "2024-08-02")), #' version = as.Date(c("2024-08-03", "2024-08-03")), #' signal2 = c(2, 3) #' ) -#' -#' #' s1 <- s1 %>% as_epi_archive() #' s2 <- s2 %>% as_epi_archive() -#' #' merged <- epix_merge(s1, s2, sync = "locf") #' merged[["DT"]] #' @@ -288,18 +310,14 @@ epix_fill_through_version <- function(x, fill_versions_end, #' version = as.Date(c("2024-08-01", "2024-08-03", "2024-08-03", "2024-08-03")), #' signal1 = c(12, 13, 22, 19) #' ) -#' #' s2 <- tibble::tibble( #' geo_value = c("ca", "ca"), #' time_value = as.Date(c("2024-08-01", "2024-08-02")), #' version = as.Date(c("2024-08-02", "2024-08-02")), #' signal2 = c(4, 5), #' ) -#' -#' #' s1 <- s1 %>% as_epi_archive() #' s2 <- s2 %>% as_epi_archive() -#' #' merged <- epix_merge(s1, s2, sync = "locf") #' merged[["DT"]] #' @@ -311,7 +329,6 @@ epix_fill_through_version <- function(x, fill_versions_end, #' version = as.Date(c("2024-08-01", "2024-08-02", "2024-08-03")), #' signal1 = c(14, 11, 9) #' ) -#' #' # The s2 signal at August 1st gets revised from 3 to 5 on August 3rd #' s2 <- tibble::tibble( #' geo_value = c("ca", "ca", "ca"), @@ -319,11 +336,8 @@ epix_fill_through_version <- function(x, fill_versions_end, #' version = as.Date(c("2024-08-02", "2024-08-03", "2024-08-03")), #' signal2 = c(3, 5, 2), #' ) -#' #' s1 <- s1 %>% as_epi_archive() #' s2 <- s2 %>% as_epi_archive() -#' -#' # Some LOCF for signal 1 as signal 2 gets updated #' merged <- epix_merge(s1, s2, sync = "locf") #' merged[["DT"]] #' @importFrom data.table key set setkeyv diff --git a/R/methods-epi_df.R b/R/methods-epi_df.R index 901b9b32..84a75e46 100644 --- a/R/methods-epi_df.R +++ b/R/methods-epi_df.R @@ -7,10 +7,8 @@ #' `as_tibble()` on `epi_df`s but you actually want them to remain `epi_df`s, #' use `attr(your_epi_df, "decay_to_tibble") <- FALSE` beforehand. #' -#' @template x -#' +#' @param x an `epi_df` #' @inheritParams tibble::as_tibble -#' #' @importFrom tibble as_tibble #' @export as_tibble.epi_df <- function(x, ...) { @@ -34,7 +32,7 @@ as_tibble.epi_df <- function(x, ...) { #' others in the `other_keys` field of the metadata, or else explicitly set. #' #' @method as_tsibble epi_df -#' @template x +#' @param x an `epi_df` #' @param key Optional. Any additional keys (other than `geo_value`) to add to #' the `tsibble`. #' @param ... additional arguments passed on to `tsibble::as_tsibble()` @@ -54,8 +52,7 @@ as_tsibble.epi_df <- function(x, key, ...) { #' #' Print and summary functions for an `epi_df` object. #' -#' @template x -#' +#' @param x an `epi_df` #' @method print epi_df #' @param ... additional arguments to forward to `NextMethod()`, or unused #' @export @@ -89,6 +86,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 +121,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 +138,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 +147,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: @@ -258,9 +258,9 @@ group_modify.epi_df <- function(.data, .f, ..., .keep = FALSE) { #' Complete epi_df #' -#' A ‘tidyr::complete()’ analogue for ‘epi_df’ objects. This function +#' A `tidyr::complete()` analogue for `epi_df`` objects. This function #' can be used, for example, to add rows for missing combinations -#' of ‘geo_value’ and ‘time_value’, filling other columns with `NA`s. +#' of `geo_value` and `time_value`, filling other columns with `NA`s. #' See the examples for usage details. #' #' @param data an `epi_df` diff --git a/R/outliers.R b/R/outliers.R index 43c41d6e..68e921bb 100644 --- a/R/outliers.R +++ b/R/outliers.R @@ -1,12 +1,15 @@ #' Detect outliers #' -#' Applies one or more outlier detection methods to a given signal variable, and +#' @description Applies one or more outlier detection methods to a given signal variable, and #' optionally aggregates the outputs to create a consensus result. See the #' [outliers #' vignette](https://cmu-delphi.github.io/epiprocess/articles/outliers.html) for #' examples. #' -#' @template x-y +#' @param x Design points corresponding to the signal values `y`. Default is +#' `seq_along(y)` (that is, equally-spaced points from 1 to the length of +#' `y`). +#' @param y Signal values. #' @param methods A tibble specifying the method(s) to use for outlier #' detection, with one row per method, and the following columns: #' * `method`: Either "rm" or "stl", or a custom function for outlier @@ -22,7 +25,9 @@ #' summarized results are calculated. Note that if the number of `methods` #' (number of rows) is odd, then "median" is equivalent to a majority vote for #' purposes of determining whether a given observation is an outlier. -#' @template detect-outlr-return +#' @return An tibble with number of rows equal to `length(y)` and columns +#' giving the outlier detection thresholds (`lower` and `upper`) and +#' replacement values from each detection method (`replacement`). #' #' @details Each outlier detection method, one per row of the passed `methods` #' tibble, is a function that must take as its first two arguments `x` and @@ -38,6 +43,7 @@ #' "stl", shorthand for `detect_outlr_stl()`, which detects outliers via an #' STL decomposition. #' +#' @rdname detect_outlr #' @export #' @importFrom dplyr select #' @examples @@ -138,20 +144,18 @@ detect_outlr <- function(x = seq_along(y), y, return(results) } -#' Detect outliers based on a rolling median +#' @description `detect_outlr_rm` detects outliers based on a distance from the +#' rolling median specified in terms of multiples of the rolling interquartile +#' range (IQR). #' -#' Detects outliers based on a distance from the rolling median specified in -#' terms of multiples of the rolling interquartile range (IQR). -#' -#' @template x-y #' @param n Number of time steps to use in the rolling window. Default is 21. #' This value is centrally aligned. When `n` is an odd number, the rolling #' window extends from `(n-1)/2` time steps before each design point to `(n-1)/2` #' time steps after. When `n` is even, then the rolling range extends from #' `n/2-1` time steps before to `n/2` time steps after. #' @template outlier-detection-options -#' @template detect-outlr-return #' +#' @rdname detect_outlr #' @export #' @examples #' # Detect outliers based on a rolling median @@ -208,11 +212,9 @@ detect_outlr_rm <- function(x = seq_along(y), y, n = 21, return(z) } -#' Detect outliers based on an STL decomposition -#' -#' Detects outliers based on a seasonal-trend decomposition using LOESS (STL). +#' @description `detect_outlr_stl` detects outliers based on a seasonal-trend +#' decomposition using LOESS (STL). #' -#' @template x-y #' @param n_trend Number of time steps to use in the rolling window for trend. #' Default is 21. #' @param n_seasonal Number of time steps to use in the rolling window for @@ -233,7 +235,6 @@ detect_outlr_rm <- function(x = seq_along(y), y, n = 21, #' `seasonal_period` will still have an impact on the result, though, by #' impacting the estimation of the trend component. #' @template outlier-detection-options -#' @template detect-outlr-return #' #' @details The STL decomposition is computed using [`stats::stl()`]. Once #' computed, the outlier detection method is analogous to the rolling median @@ -244,6 +245,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/revision_analysis.R b/R/revision_analysis.R index 7be0cd24..27944489 100644 --- a/R/revision_analysis.R +++ b/R/revision_analysis.R @@ -1,9 +1,11 @@ -#' A function to describe revision behavior for an archive +#' A function to describe revision behavior for an archive. +#' #' @description #' `revision_summary` removes all missing values (if requested), and then #' computes some basic statistics about the revision behavior of an archive, -#' returning a tibble summarizing the revisions per time_value+epi_key features. If `print_inform` is true, it -#' prints a concise summary. The columns returned are: +#' returning a tibble summarizing the revisions per time_value+epi_key +#' features. If `print_inform` is true, it prints a concise summary. The +#' columns returned are: #' 1. `n_revisions`: the total number of revisions for that entry #' 2. `min_lag`: the minimum time to any value (if `drop_nas=FALSE`, this #' includes `NA`'s) @@ -17,11 +19,12 @@ #' 8. `rel_spread`: `spread` divided by the largest value (so it will #' always be less than 1). Note that this need not be the final value. It will #' be `NA` whenever `spread` is 0. -#' 9. `time_near_latest`: This gives the lag when the value is within -#' `within_latest` (default 20%) of the value at the latest time. For example, -#' consider the series (0,20, 99, 150, 102, 100); then `time_near_latest` is -#' the 5th index, since even though 99 is within 20%, it is outside the window -#' afterwards at 150. +#' 9. `time_near_latest`: the time taken for the revisions to settle to within +#' `within_latest` (default 20%) of the final value and stay there. For +#' example, consider the series (0, 20, 99, 150, 102, 100); then +#' `time_near_latest` is 5, since even though 99 is within 20%, it is outside +#' the window afterwards at 150. +#' #' @param epi_arch an epi_archive to be analyzed #' @param ... <[`tidyselect`][dplyr_tidy_select]>, used to choose the column to #' summarize. If empty, it chooses the first. Currently only implemented for @@ -57,11 +60,11 @@ #' @param compactify_tol float, used if `drop_nas=TRUE`, it determines the #' threshold for when two floats are considered identical. #' @param should_compactify bool. Compactify if `TRUE`. -#' @examples #' +#' @examples #' revision_example <- revision_summary(archive_cases_dv_subset, percent_cli) -#' #' revision_example %>% arrange(desc(spread)) +#' #' @export #' @importFrom cli cli_inform cli_abort cli_li #' @importFrom rlang list2 syms diff --git a/R/slide.R b/R/slide.R index c792187e..62a0d03e 100644 --- a/R/slide.R +++ b/R/slide.R @@ -1,91 +1,124 @@ #' Slide a function over variables in an `epi_df` object #' -#' Slides a given function over variables in an `epi_df` object. See the -#' [slide vignette](https://cmu-delphi.github.io/epiprocess/articles/slide.html) -#' for examples. +#' @description Slides a given function over variables in an `epi_df` object. +#' This is useful for computations like rolling averages. The function supports +#' many ways to specify the computation, but by far the most common use case is +#' as follows: +#' +#' ``` +#' # To compute the 7-day trailing average of cases +#' epi_slide(edf, cases_7dav = mean(cases), .window_size = 7) +#' ``` +#' +#' This will create the new column `cases_7dav` that contains a 7-day rolling +#' average of values in "cases". See `vignette("epi_df")` for more examples. #' #' @template basic-slide-params #' @param .f Function, formula, or missing; together with `...` specifies the -#' computation to slide. To "slide" means to apply a computation within a -#' sliding (a.k.a. "rolling") time window for each data group. The window is -#' determined by the `.window_size` and `.align` parameters, see the details -#' section for more. If a function, `.f` must have the form `function(x, g, t, -#' ...)`, where +#' computation to slide. The return of the computation should either be a +#' scalar or a 1-row data frame. Data frame returns will be +#' `tidyr::unpack()`-ed, if named, and will be [`tidyr::pack`]-ed columns, if +#' not named. See examples. +#' +#' - If `.f` is missing, then `...` will specify the computation via +#' tidy-evaluation. This is usually the most convenient way to use +#' `epi_slide`. See examples. +#' - If `.f` is a formula, then the formula should use `.x` (not the same as +#' the input `epi_df`) to operate on the columns of the input `epi_df`, e.g. +#' `~mean(.x$var)` to compute a mean of `var`. +#' - If a function, `.f` must have the form `function(x, g, t, ...)`, where: +#' - `x` is a data frame with the same column names as the original object, +#' minus any grouping variables, with only the windowed data for one +#' group-`.ref_time_value` combination +#' - `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 #' -#' - `x` is a data frame with the same column names as the original object, -#' minus any grouping variables, with only the windowed data for one -#' group-`.ref_time_value` combination -#' - `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 -#' -#' If a formula, `.f` can operate directly on columns accessed via `.x$var` or -#' `.$var`, as in `~mean(.x$var)` to compute a mean of a column `var` for each -#' `ref_time_value`-group combination. The group key can be accessed via `.y`. -#' If `.f` is missing, then `...` will specify the computation. #' @param ... Additional arguments to pass to the function or formula specified #' via `.f`. Alternatively, if `.f` is missing, then the `...` is interpreted #' as a ["data-masking"][rlang::args_data_masking] expression or expressions -#' for tidy evaluation; in addition to referring columns directly by name, the -#' expressions have access to `.data` and `.env` pronouns as in `dplyr` verbs, -#' and can also refer to `.x` (not the same as the input epi_df), -#' `.group_key`, and `.ref_time_value`. See details. -#' @param .new_col_name String indicating the name of the new column that will -#' contain the derivative values. The default is "slide_value" unless your -#' slide computations output data frames, in which case they will be unpacked +#' for tidy evaluation. +#' @param .new_col_name Name for the new column that will contain the computed +#' values. The default is "slide_value" unless your slide computations output +#' data frames, in which case they will be unpacked (as in `tidyr::unpack()`) #' into the constituent columns and those names used. New columns should not -#' be given names that clash with the existing columns of `.x`; see details. +#' be given names that clash with the existing columns of `.x`. +#' +#' @details +#' ## Advanced uses of `.f` via tidy evaluation #' -#' @template basic-slide-details +#' If specifying `.f` via tidy evaluation, in addition to the standard [`.data`] +#' and [`.env`], we make some additional "pronoun"-like bindings available: +#' +#' - .x, which is like `.x` in [`dplyr::group_modify`]; an ordinary object +#' like an `epi_df` rather than an rlang [pronoun][rlang::as_data_pronoun] +#' like [`.data`]; this allows you to use additional `dplyr`, `tidyr`, and +#' `epiprocess` operations. If you have multiple expressions in `...`, this +#' won't let you refer to the output of the earlier expressions, but `.data` +#' will. +#' - .group_key, which is like `.y` in [`dplyr::group_modify`]. +#' - .ref_time_value, which is the element of `.ref_time_values` that +#' determined the time window for the current computation. #' #' @importFrom lubridate days weeks #' @importFrom dplyr bind_rows group_map group_vars filter select #' @importFrom rlang .data .env !! enquos sym env missing_arg #' @export -#' @seealso [`epi_slide_opt`] [`epi_slide_mean`] [`epi_slide_sum`] +#' @seealso [`epi_slide_opt`] for optimized slide functions #' @examples -#' # slide a 7-day trailing average formula on cases -#' # Simple sliding means and sums are much faster to do using -#' # the `epi_slide_mean` and `epi_slide_sum` functions instead. -#' cases_deaths_subset %>% -#' group_by(geo_value) %>% -#' epi_slide(cases_7dav = mean(cases), .window_size = 7) %>% -#' dplyr::select(geo_value, time_value, cases, cases_7dav) %>% -#' ungroup() -#' -#' # slide a 7-day leading average +#' # Get the 7-day trailing standard deviation of cases and the 7-day trailing mean of cases #' cases_deaths_subset %>% -#' group_by(geo_value) %>% -#' epi_slide(cases_7dav = mean(cases), .window_size = 7, .align = "left") %>% -#' dplyr::select(geo_value, time_value, cases, cases_7dav) %>% -#' ungroup() +#' epi_slide( +#' cases_7sd = sd(cases, na.rm = TRUE), +#' cases_7dav = mean(cases, na.rm = TRUE), +#' .window_size = 7 +#' ) %>% +#' dplyr::select(geo_value, time_value, cases, cases_7sd, cases_7dav) #' -#' # slide a 7-day centre-aligned average +#' # The same as above, but unpacking using an unnamed data.frame with a formula #' cases_deaths_subset %>% -#' group_by(geo_value) %>% -#' epi_slide(cases_7dav = mean(cases), .window_size = 7, .align = "center") %>% -#' dplyr::select(geo_value, time_value, cases, cases_7dav) %>% -#' ungroup() +#' epi_slide( +#' ~ data.frame( +#' cases_7sd = sd(.x$cases, na.rm = TRUE), +#' cases_7dav = mean(.x$cases, na.rm = TRUE) +#' ), +#' .window_size = 7 +#' ) %>% +#' dplyr::select(geo_value, time_value, cases, cases_7sd, cases_7dav) #' -#' # slide a 14-day centre-aligned average +#' # The same as above, but packing using a named data.frame with a tidy evaluation +#' # expression #' cases_deaths_subset %>% -#' group_by(geo_value) %>% -#' epi_slide(cases_14dav = mean(cases), .window_size = 14, .align = "center") %>% -#' dplyr::select(geo_value, time_value, cases, cases_14dav) %>% -#' ungroup() +#' epi_slide( +#' slide_packed = data.frame( +#' cases_7sd = sd(.x$cases, na.rm = TRUE), +#' cases_7dav = mean(.x$cases, na.rm = TRUE) +#' ), +#' .window_size = 7 +#' ) %>% +#' dplyr::select(geo_value, time_value, cases, slide_packed) #' #' # nested new columns #' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide( -#' cases_2d = list(data.frame( -#' cases_2dav = mean(cases), -#' cases_2dma = mad(cases) -#' )), -#' .window_size = 2 +#' function(x, g, t) { +#' data.frame( +#' cases_7sd = sd(x$cases, na.rm = TRUE), +#' cases_7dav = mean(x$cases, na.rm = TRUE) +#' ) +#' }, +#' .window_size = 7 #' ) %>% -#' ungroup() +#' dplyr::select(geo_value, time_value, cases, cases_7sd, cases_7dav) +#' +#' # Use the geo_value or the ref_time_value in the slide computation +#' cases_deaths_subset %>% +#' epi_slide(~ .x$geo_value[[1]], .window_size = 7) +#' +#' cases_deaths_subset %>% +#' epi_slide(~ .x$time_value[[1]], .window_size = 7) epi_slide <- function( .x, .f, ..., .window_size = NULL, .align = c("right", "center", "left"), @@ -492,16 +525,24 @@ get_before_after_from_window <- function(window_size, align, time_type) { return(list(before = before, after = after)) } -#' Optimized slide function for performing common rolling computations on an -#' `epi_df` object +#' Optimized slide functions for common cases #' -#' Slides an n-timestep [data.table::froll] or [slider::summary-slide] function -#' over variables in an `epi_df` object. See the -#' [slide vignette](https://cmu-delphi.github.io/epiprocess/articles/slide.html) -#' for examples. +#' @description `epi_slide_opt` allows sliding an n-timestep [data.table::froll] +#' or [slider::summary-slide] function over variables in an `epi_df` object. +#' These functions tend to be much faster than `epi_slide()`. See +#' `vignette("epi_df")` for more examples. #' #' @template basic-slide-params -#' @template opt-slide-params +#' @param .col_names <[`tidy-select`][dplyr_tidy_select]> An unquoted column +#' name(e.g., `cases`), multiple column names (e.g., `c(cases, deaths)`), +#' [other tidy-select expression][tidyselect::language], or a vector of +#' characters (e.g. `c("cases", "deaths")`). Variable names can be used as if +#' they were positions in the data frame, so expressions like `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 [`dplyr::rename`] after the slide. #' @param .f Function; together with `...` specifies the computation to slide. #' `.f` must be one of `data.table`'s rolling functions #' (`frollmean`, `frollsum`, `frollapply`. See [data.table::roll]) or one @@ -518,7 +559,6 @@ get_before_after_from_window <- function(window_size, align, time_type) { #' example, `algo` or `na.rm` in data.table functions. You don't need to #' specify `.x`, `.window_size`, or `.align` (or `before`/`after` for slider #' functions). -#' @template opt-slide-details #' #' @importFrom dplyr bind_rows mutate %>% arrange tibble select all_of #' @importFrom rlang enquo quo_get_expr as_label expr_label caller_arg @@ -529,53 +569,24 @@ get_before_after_from_window <- function(window_size, align, time_type) { #' @importFrom checkmate assert_function #' @importFrom slider slide_sum slide_prod slide_mean slide_min slide_max slide_all slide_any #' @export -#' @seealso [`epi_slide`] [`epi_slide_mean`] [`epi_slide_sum`] +#' @seealso [`epi_slide`] for the more general slide function #' @examples -#' # slide a 7-day trailing average formula on cases. This can also be done with `epi_slide_mean` +#' # Compute a 7-day trailing average on cases. #' cases_deaths_subset %>% #' group_by(geo_value) %>% -#' epi_slide_opt( -#' cases, -#' .f = data.table::frollmean, .window_size = 7 -#' ) %>% -#' # Remove a nonessential var. to ensure new col is printed, and rename new col -#' dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) %>% -#' ungroup() +#' epi_slide_opt(cases, .f = data.table::frollmean, .window_size = 7) %>% +#' dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) #' -#' # slide a 7-day trailing average formula on cases. Adjust `frollmean` settings for speed -#' # and accuracy, and to allow partially-missing windows. +#' # Same as above, but adjust `frollmean` settings for speed, accuracy, and +#' # to allow partially-missing windows. #' cases_deaths_subset %>% #' group_by(geo_value) %>% #' epi_slide_opt( #' cases, #' .f = data.table::frollmean, .window_size = 7, -#' # `frollmean` options #' algo = "exact", hasNA = TRUE, na.rm = TRUE #' ) %>% -#' dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) %>% -#' ungroup() -#' -#' # slide a 7-day leading average -#' cases_deaths_subset %>% -#' group_by(geo_value) %>% -#' epi_slide_opt( -#' cases, -#' .f = slider::slide_mean, .window_size = 7, .align = "left" -#' ) %>% -#' # 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 centre-aligned sum. This can also be done with `epi_slide_sum` -#' cases_deaths_subset %>% -#' group_by(geo_value) %>% -#' epi_slide_opt( -#' cases, -#' .f = data.table::frollsum, .window_size = 6, .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() +#' dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) epi_slide_opt <- function( .x, .col_names, .f, ..., .window_size = NULL, .align = c("right", "center", "left"), @@ -808,70 +819,28 @@ epi_slide_opt <- function( return(result) } -#' Optimized slide function for performing rolling averages on an `epi_df` object -#' -#' Slides an n-timestep mean over variables in an `epi_df` object. See the [slide -#' vignette](https://cmu-delphi.github.io/epiprocess/articles/slide.html) for -#' examples. -#' -#' Wrapper around `epi_slide_opt` with `.f = datatable::frollmean`. -#' -#' @template basic-slide-params -#' @template opt-slide-params -#' @param ... Additional arguments to pass to the slide computation `.f`, for -#' example, `algo` or `na.rm` in data.table functions. You don't need to -#' specify `.x`, `.window_size`, or `.align` (or `before`/`after` for slider -#' functions). -#' -#' @template opt-slide-details +#' @rdname epi_slide_opt +#' @description `epi_slide_mean` is a wrapper around `epi_slide_opt` with `.f = +#' datatable::frollmean`. #' #' @export -#' @seealso [`epi_slide`] [`epi_slide_opt`] [`epi_slide_sum`] #' @examples -#' # slide a 7-day trailing average formula on cases +#' # Compute a 7-day trailing average on cases. #' cases_deaths_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() +#' dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) #' -#' # slide a 7-day trailing average formula on cases. Adjust `frollmean` settings for speed -#' # and accuracy, and to allow partially-missing windows. +#' # Same as above, but adjust `frollmean` settings for speed, accuracy, and +#' # to allow partially-missing windows. #' cases_deaths_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 -#' cases_deaths_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 centre-aligned average -#' cases_deaths_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 centre-aligned average -#' cases_deaths_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() +#' dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) epi_slide_mean <- function( .x, .col_names, ..., .window_size = NULL, .align = c("right", "center", "left"), @@ -922,33 +891,17 @@ epi_slide_mean <- function( ) } -#' Optimized slide function for performing rolling sums on an `epi_df` object -#' -#' Slides an n-timestep sum over variables in an `epi_df` object. See the [slide -#' vignette](https://cmu-delphi.github.io/epiprocess/articles/slide.html) for -#' examples. -#' -#' Wrapper around `epi_slide_opt` with `.f = datatable::frollsum`. -#' -#' @template basic-slide-params -#' @template opt-slide-params -#' @param ... Additional arguments to pass to the slide computation `.f`, for -#' example, `algo` or `na.rm` in data.table functions. You don't need to -#' specify `.x`, `.window_size`, or `.align` (or `before`/`after` for slider -#' functions). -#' -#' @template opt-slide-details +#' @rdname epi_slide_opt +#' @description `epi_slide_sum` is a wrapper around `epi_slide_opt` with `.f = +#' datatable::frollsum`. #' #' @export -#' @seealso [`epi_slide`] [`epi_slide_opt`] [`epi_slide_mean`] #' @examples -#' # slide a 7-day trailing sum formula on cases +#' # Compute a 7-day trailing sum on cases. #' cases_deaths_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() +#' dplyr::select(geo_value, time_value, cases, cases_7dsum = slide_value_cases) epi_slide_sum <- function( .x, .col_names, ..., .window_size = NULL, .align = c("right", "center", "left"), @@ -1006,7 +959,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..1bfd2129 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,8 @@ list2var <- function(x) { #' #' @importFrom lifecycle deprecated #' -#' @noRd +#' @export +#' @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 +995,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 new file mode 100644 index 00000000..d99c4fae --- /dev/null +++ b/README.Rmd @@ -0,0 +1,121 @@ +--- +output: github_document +--- + + + +```{r, include = FALSE} +source(here::here("vignettes", "_common.R")) +knitr::opts_chunk$set( + fig.path = "man/figures/README-" +) +``` + +# epiprocess + +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}`, 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}`, for accessing wide range +of epidemiological data sets, including COVID-19 data, flu data, and more. +- `{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, eval=FALSE} +# Stable version +pak::pkg_install("cmu-delphi/epiprocess@main") + +# Dev version +pak::pkg_install("cmu-delphi/epiprocess@dev") +``` + +The package is not yet on CRAN. + +## Usage + +Once `epiprocess` and `epidatr` are installed, you can use the following code to +get started: + +```{r, results=FALSE, warning=FALSE, message=FALSE} +library(epiprocess) +library(epidatr) +library(dplyr) +library(magrittr) +``` + +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 warning=FALSE, message=FALSE} +df <- pub_covidcast( + source = "jhu-csse", + signals = "confirmed_cumulative_num", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200301, 20220131), + as_of = as.Date("2024-01-01") +) %>% + select(geo_value, time_value, cases_cumulative = value) +df +``` + +Convert the data to an epi_df object and sort by geo_value and time_value. You +can work with an `epi_df` like you can with a `{tibble}` by using `{dplyr}` +verbs + +```{r} +edf <- df %>% + as_epi_df(as_of = as.Date("2024-01-01")) %>% + arrange_canonical() %>% + group_by(geo_value) %>% + mutate(cases_daily = cases_cumulative - lag(cases_cumulative, default = 0)) +edf +``` + +Compute the 7 day moving average of the confirmed daily cases for each geo_value + +```{r} +edf <- edf %>% + group_by(geo_value) %>% + epi_slide_mean(cases_daily, .window_size = 7, na.rm = TRUE) %>% + rename(smoothed_cases_daily = slide_value_cases_daily) +edf +``` + +Autoplot the confirmed daily cases for each geo_value + +```{r} +edf %>% + autoplot(smoothed_cases_daily) +``` diff --git a/README.md b/README.md index d0a1c740..820cda6e 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,47 @@ + + + # epiprocess - +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}`, 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/). -[![R-CMD-check](https://github.com/cmu-delphi/epiprocess/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/cmu-delphi/epiprocess/actions/workflows/R-CMD-check.yaml) +You may also be interested in: - + - `{epidatr}`, for accessing wide range of epidemiological data sets, + including COVID-19 data, flu data, and more. + - `{rtestim}`, a package for estimating the time-varying reproduction + number of an epidemic. -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. See the getting started guide and vignettes for -examples. +This package is provided by the [Delphi group](https://delphi.cmu.edu/) +at Carnegie Mellon University. ## Installation -To install (unless you're making changes to the package, use the stable version): +To install: -```r +``` r # Stable version pak::pkg_install("cmu-delphi/epiprocess@main") @@ -23,56 +49,109 @@ pak::pkg_install("cmu-delphi/epiprocess@main") pak::pkg_install("cmu-delphi/epiprocess@dev") ``` -## `epi_df`: snapshot of a data set - -The first main data structure in the `epiprocess` package is called -[`epi_df`](reference/epi_df.html). This is simply a tibble with a couple of -required columns, `geo_value` and `time_value`. It can have any other number of -columns, which can be seen as measured variables, which we also call signal -variables. In brief, an `epi_df` object represents a snapshot of a data set that -contains the most up-to-date values of the signals variables, as of a given -time. - -By convention, functions in the `epiprocess` package that operate on `epi_df` -objects begin with `epi`. For example: - -- `epi_slide()`, for iteratively applying a custom computation to a variable in - an `epi_df` object over sliding windows in time; -- `epi_cor()`, for computing lagged correlations between variables in an - `epi_df` object, (allowing for grouping by geo value, time value, or any other - variables). - -Functions in the package that operate directly on given variables do not begin -with `epi`. For example: - -- `growth_rate()`, for estimating the growth rate of a given signal at given - time values, using various methodologies; -- `detect_outlr()`, for detecting outliers in a given signal over time, using - either built-in or custom methodologies. - -## `epi_archive`: full version history of a data set - -The second main data structure in the package is called -[`epi_archive`](reference/epi_archive.html). This is a special class (R6 format) -wrapped around a data table that stores the archive (version history) of some -signal variables of interest. - -By convention, functions in the `epiprocess` package that operate on -`epi_archive` objects begin with `epix` (the "x" is meant to remind you of -"archive"). These are just wrapper functions around the public methods for the -`epi_archive` R6 class. For example: - -- `epix_as_of()`, for generating a snapshot in `epi_df` format from the data - archive, which represents the most up-to-date values of the signal variables, - as of the specified version; -- `epix_fill_through_version()`, for filling in some fake version data following - simple rules, for use when downstream methods expect an archive that is more - up-to-date (e.g., if it is a forecasting deadline date and one of our data - sources cannot be accessed to provide the latest versions of its data) -- `epix_merge()`, for merging two data archives with each other, with support - for various approaches to handling when one of the archives is more up-to-date - version-wise than the other; -- `epix_slide()`, for sliding a custom computation to a data archive over local - windows in time, much like `epi_slide` for an `epi_df` object, but with one - key difference: the sliding computation at any given reference time t is - performed only on the **data that would have been available as of t**. +The package is not yet on CRAN. + +## Usage + +Once `epiprocess` and `epidatr` are installed, you can use the following +code to get started: + +``` r +library(epiprocess) +library(epidatr) +library(dplyr) +library(magrittr) +``` + +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 +df <- pub_covidcast( + source = "jhu-csse", + signals = "confirmed_cumulative_num", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200301, 20220131), + as_of = as.Date("2024-01-01") +) %>% + select(geo_value, time_value, cases_cumulative = value) +df +#> # A tibble: 2,808 × 3 +#> geo_value time_value cases_cumulative +#> +#> 1 ca 2020-03-01 19 +#> 2 fl 2020-03-01 0 +#> 3 ny 2020-03-01 0 +#> 4 tx 2020-03-01 0 +#> 5 ca 2020-03-02 23 +#> 6 fl 2020-03-02 1 +#> # ℹ 2,802 more rows +``` + +Convert the data to an epi\_df object and sort by geo\_value and +time\_value. You can work with an `epi_df` like you can with a +`{tibble}` by using `{dplyr}` verbs + +``` r +edf <- df %>% + as_epi_df(as_of = as.Date("2024-01-01")) %>% + arrange_canonical() %>% + group_by(geo_value) %>% + mutate(cases_daily = cases_cumulative - lag(cases_cumulative, default = 0)) +edf +#> An `epi_df` object, 2,808 x 4 with metadata: +#> * geo_type = state +#> * time_type = day +#> * as_of = 2024-01-01 +#> +#> # A tibble: 2,808 × 4 +#> # Groups: geo_value [4] +#> geo_value time_value cases_cumulative cases_daily +#> * +#> 1 ca 2020-03-01 19 19 +#> 2 ca 2020-03-02 23 4 +#> 3 ca 2020-03-03 29 6 +#> 4 ca 2020-03-04 40 11 +#> 5 ca 2020-03-05 50 10 +#> 6 ca 2020-03-06 68 18 +#> # ℹ 2,802 more rows +``` + +Compute the 7 day moving average of the confirmed daily cases for each +geo\_value + +``` r +edf <- edf %>% + group_by(geo_value) %>% + epi_slide_mean(cases_daily, .window_size = 7, na.rm = TRUE) %>% + rename(smoothed_cases_daily = slide_value_cases_daily) +edf +#> An `epi_df` object, 2,808 x 5 with metadata: +#> * geo_type = state +#> * time_type = day +#> * as_of = 2024-01-01 +#> +#> # A tibble: 2,808 × 5 +#> # Groups: geo_value [4] +#> geo_value time_value cases_cumulative cases_daily smoothed_cases_daily +#> * +#> 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 +#> # ℹ 2,802 more rows +``` + +Autoplot the confirmed daily cases for each geo\_value + +``` r +edf %>% + autoplot(smoothed_cases_daily) +``` + + diff --git a/_pkgdown.yml b/_pkgdown.yml index e8c05a65..2214df7c 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: @@ -59,35 +61,46 @@ repo: reference: - title: "`epi_df` basics" - desc: Details on `epi_df` format, and basic functionality. + desc: Constructors and information for `epi_df` objects. - contents: - - matches("epi_df") - - matches("column_names") - - title: "`epi_*()` functions" - desc: Functions that act on `epi_df` objects. + - epi_df + - print.epi_df + - group_epi_df + - autoplot.epi_df + + - title: "`epi_df` manipulation" + desc: Functions operating 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. + desc: Constructors and information for `epi_archive` objects. - 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: "`epi_archive` manipulation" + desc: Functions operating on `epi_archive` objects. + - contents: + - epix_as_of + - epix_slide + - epix_merge + - revision_summary + - epix_fill_through_version + - epix_truncate_versions_after + - title: Example data - contents: - cases_deaths_subset @@ -95,16 +108,7 @@ reference: - covid_incidence_county_subset - covid_incidence_outliers - covid_case_death_rates_extended - - title: Basic automatic plotting - - contents: - - autoplot.epi_df - - title: Advanced internals - - contents: - - compactify + - title: internal - contents: - - epiprocess - - max_version_with_row_in - - next_after - - guess_period - - key_colnames + - starts_with("internal") diff --git a/man-roxygen/basic-slide-details.R b/man-roxygen/basic-slide-details.R deleted file mode 100644 index df87f882..00000000 --- a/man-roxygen/basic-slide-details.R +++ /dev/null @@ -1,69 +0,0 @@ -#' @details To "slide" means to apply a function or formula over a rolling -#' window. The `.window_size` arg determines the width of the window -#' (including the reference time) and the `.align` arg governs how the window -#' is aligned (see below for examples). The `.ref_time_values` arg controls -#' which time values to consider for the slide and `.all_rows` allows you to -#' keep NAs around. -#' -#' `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, 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 -#' -#' If `.f` is missing, then ["data-masking"][rlang::args_data_masking] -#' expression(s) for tidy evaluation can be specified, for example, as in: -#' ``` -#' epi_slide(x, cases_7dav = mean(cases), .window_size = 7) -#' ``` -#' which would be equivalent to: -#' ``` -#' epi_slide(x, function(x, g, t) mean(x$cases), .window_size = 7, -#' .new_col_name = "cases_7dav") -#' ``` -#' In a manner similar to [`dplyr::mutate`]: -#' * Expressions evaluating to length-1 vectors will be recycled to -#' appropriate lengths. -#' * `, name_var := value` can be used to set the output column name based on -#' a variable `name_var` rather than requiring you to use a hard-coded -#' name. (The leading comma is needed to make sure that `.f` is treated as -#' missing.) -#' * `= NULL` can be used to remove results from previous expressions (though -#' we don't allow it to remove pre-existing columns). -#' * `, fn_returning_a_data_frame(.x)` will unpack the output of the function -#' into multiple columns in the result. -#' * Named expressions evaluating to data frames will be placed into -#' [`tidyr::pack`]ed columns. -#' -#' In addition to [`.data`] and [`.env`], we make some additional -#' "pronoun"-like bindings available: -#' * .x, which is like `.x` in [`dplyr::group_modify`]; an ordinary object -#' like an `epi_df` rather than an rlang [pronoun][rlang::as_data_pronoun] -#' like [`.data`]; this allows you to use additional `dplyr`, `tidyr`, and -#' `epiprocess` operations. If you have multiple expressions in `...`, this -#' won't let you refer to the output of the earlier expressions, but `.data` -#' will. -#' * .group_key, which is like `.y` in [`dplyr::group_modify`]. -#' * .ref_time_value, which is the element of `.ref_time_values` that -#' determined the time window for the current computation. diff --git a/man-roxygen/basic-slide-params.R b/man-roxygen/basic-slide-params.R index dfa2512f..638307d6 100644 --- a/man-roxygen/basic-slide-params.R +++ b/man-roxygen/basic-slide-params.R @@ -1,36 +1,35 @@ -#' @param .x The `epi_df` object under consideration, [grouped][dplyr::group_by] -#' or ungrouped. If ungrouped, all data in `.x` will be treated as part of a -#' single data group. -#' @param .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 `time_value` column: +#' @param .x An `epi_df` object. If ungrouped, we group by `geo_value` and any +#' columns in `other_keys`. If grouped, we make sure the grouping is by +#' `geo_value` and `other_keys`. +#' @param .window_size The size of the sliding window. The accepted values +#' depend on the type of the `time_value` column in `.x`: #' -#' - if time_type is Date and the cadence is daily, then `.window_size` can be -#' an integer (which will be interpreted in units of days) or a difftime +#' - if time type is `Date` and the cadence is daily, then `.window_size` can +#' be an integer (which will be interpreted in units of days) or a difftime #' with units "days" -#' - if time_type is Date and the cadence is weekly, then `.window_size` must -#' be a difftime with units "weeks" -#' - if time_type is an yearmonth or integer, then `.window_size` must be an +#' - if time type is `Date` and the cadence is weekly, then `.window_size` must +#' be a `difftime` with units "weeks" +#' - if time type is a `yearmonth` or an integer, then `.window_size` must be an #' integer #' -#' @param .align The alignment of the sliding window. If `right` (default), then -#' the window has its end at the reference time; if `center`, then the window is -#' centered at the reference time; if `left`, then the window has its start at -#' the reference time. If the alignment is `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). -#' @param .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. -#' @param .all_rows If `.all_rows = TRUE`, then all rows of `.x` will be kept in -#' the output even with `.ref_time_values` provided, with some type of missing -#' value marker for the slide computation output column(s) for `time_value`s -#' outside `.ref_time_values`; otherwise, there will be one row for each row in -#' `.x` that had a `time_value` in `.ref_time_values`. Default is `FALSE`. The -#' missing value marker is the result of `vctrs::vec_cast`ing `NA` to the type -#' of the slide computation output. -#' @return An `epi_df` object given by appending one or more new columns to `.x`, -#' named according to the `.new_col_name` argument. +#' @param .align The alignment of the sliding window. +#' +#' - If "right" (default), then the window has its end at the reference time. +#' This is likely the most common use case, e.g. `.window_size=7` and +#' `.align="right"` slides over the past week of data. +#' - If "left", then the window has its start at the reference time. +#' - If "center", then the window is centered at the reference time. If 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 more value before the reference time +#' than after. +#' +#' @param .ref_time_values The time values at which to compute the slides +#' values. By default, this is all the unique time values in `.x`. +#' @param .all_rows If `.all_rows = FALSE`, the default, then the output +#' `epi_df` will have only the rows that had a `time_value` in +#' `.ref_time_values`. Otherwise, all the rows from `.x` are included by with +#' a missing value marker (typically NA, but more technically the result of +#' `vctrs::vec_cast`-ing `NA` to the type of the slide computation output). +#' @return An `epi_df` object with one or more new slide computation columns +#' added. diff --git a/man-roxygen/detect-outlr-return.R b/man-roxygen/detect-outlr-return.R deleted file mode 100644 index 50222e0e..00000000 --- a/man-roxygen/detect-outlr-return.R +++ /dev/null @@ -1,3 +0,0 @@ -#' @return An tibble with number of rows equal to `length(y)` and columns -#' giving the outlier detection thresholds (`lower` and `upper`) and -#' replacement values from each detection method (`replacement`). diff --git a/man-roxygen/opt-slide-details.R b/man-roxygen/opt-slide-details.R deleted file mode 100644 index a8d93d93..00000000 --- a/man-roxygen/opt-slide-details.R +++ /dev/null @@ -1,33 +0,0 @@ -#' @details To "slide" means to apply a function or formula over a rolling -#' window. The `.window_size` arg determines the width of the window -#' (including the reference time) and the `.align` arg governs how the window -#' is aligned (see below for examples). The `.ref_time_values` arg controls -#' which time values to consider for the slide and `.all_rows` allows you to -#' keep NAs around. -#' -#' `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 `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 diff --git a/man-roxygen/opt-slide-params.R b/man-roxygen/opt-slide-params.R deleted file mode 100644 index ba4b4877..00000000 --- a/man-roxygen/opt-slide-params.R +++ /dev/null @@ -1,10 +0,0 @@ -#' @param .col_names <[`tidy-select`][dplyr_tidy_select]> An unquoted column -#' name(e.g., `cases`), multiple column names (e.g., `c(cases, deaths)`), -#' [other tidy-select expression][tidyselect::language], or a vector of -#' characters (e.g. `c("cases", "deaths")`). Variable names can be used as if -#' they were positions in the data frame, so expressions like `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 [`dplyr::rename`] after the slide. diff --git a/man-roxygen/x-y.R b/man-roxygen/x-y.R deleted file mode 100644 index a4f9d1d7..00000000 --- a/man-roxygen/x-y.R +++ /dev/null @@ -1,4 +0,0 @@ -#' @param x Design points corresponding to the signal values `y`. Default is -#' `seq_along(y)` (that is, equally-spaced points from 1 to the length of -#' `y`). -#' @param y Signal values. diff --git a/man-roxygen/x.R b/man-roxygen/x.R deleted file mode 100644 index a26f9f25..00000000 --- a/man-roxygen/x.R +++ /dev/null @@ -1 +0,0 @@ -#' @param x an `epi_df` diff --git a/man/apply_compactify.Rd b/man/apply_compactify.Rd index 14b884c6..0e1f0b3c 100644 --- a/man/apply_compactify.Rd +++ b/man/apply_compactify.Rd @@ -2,12 +2,12 @@ % Please edit documentation in R/archive.R \name{apply_compactify} \alias{apply_compactify} -\title{given a tibble as would be found in an epi_archive, remove duplicate entries.} +\title{Given a tibble as would be found in an epi_archive, remove duplicate entries.} \usage{ apply_compactify(df, keys, tolerance = .Machine$double.eps^0.5) } \description{ -works by shifting all rows except the version, then comparing values to see +Works by shifting all rows except the version, then comparing values to see if they've changed. We need to arrange in descending order, but note that we don't need to group, since at least one column other than version has changed, and so is kept. 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/complete.epi_df.Rd b/man/complete.epi_df.Rd index 9d791fb7..71dbcb38 100644 --- a/man/complete.epi_df.Rd +++ b/man/complete.epi_df.Rd @@ -16,9 +16,7 @@ \item{explicit}{see \code{\link[tidyr:complete]{tidyr::complete}}} } \description{ -A ‘tidyr::complete()’ analogue for ‘epi_df’ objects. This function -can be used, for example, to add rows for missing combinations -of ‘geo_value’ and ‘time_value’, filling other columns with \code{NA}s. +A \code{tidyr::complete()} analogue for \verb{epi_df`` objects. This function can be used, for example, to add rows for missing combinations of }geo_value\code{and}time_value\verb{, filling other columns with }NA`s. See the examples for usage details. } \examples{ 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/detect_outlr.Rd b/man/detect_outlr.Rd index 744b9345..bee62aec 100644 --- a/man/detect_outlr.Rd +++ b/man/detect_outlr.Rd @@ -2,6 +2,8 @@ % Please edit documentation in R/outliers.R \name{detect_outlr} \alias{detect_outlr} +\alias{detect_outlr_rm} +\alias{detect_outlr_stl} \title{Detect outliers} \usage{ detect_outlr( @@ -10,6 +12,32 @@ detect_outlr( methods = tibble::tibble(method = "rm", args = list(list()), abbr = "rm"), combiner = c("median", "mean", "none") ) + +detect_outlr_rm( + x = seq_along(y), + y, + n = 21, + log_transform = FALSE, + detect_negatives = FALSE, + detection_multiplier = 2, + min_radius = 0, + replacement_multiplier = 0 +) + +detect_outlr_stl( + x = seq_along(y), + y, + n_trend = 21, + n_seasonal = 21, + n_threshold = 21, + seasonal_period, + seasonal_as_residual = FALSE, + log_transform = FALSE, + detect_negatives = FALSE, + detection_multiplier = 2, + min_radius = 0, + replacement_multiplier = 0 +) } \arguments{ \item{x}{Design points corresponding to the signal values \code{y}. Default is @@ -36,6 +64,56 @@ as well as a replacement value for any outliers. If "none", then no summarized results are calculated. Note that if the number of \code{methods} (number of rows) is odd, then "median" is equivalent to a majority vote for purposes of determining whether a given observation is an outlier.} + +\item{n}{Number of time steps to use in the rolling window. Default is 21. +This value is centrally aligned. When \code{n} is an odd number, the rolling +window extends from \code{(n-1)/2} time steps before each design point to \code{(n-1)/2} +time steps after. When \code{n} is even, then the rolling range extends from +\code{n/2-1} time steps before to \code{n/2} time steps after.} + +\item{log_transform}{Should a log transform be applied before running outlier +detection? Default is \code{FALSE}. If \code{TRUE}, and zeros are present, then the +log transform will be padded by 1.} + +\item{detect_negatives}{Should negative values automatically count as +outliers? Default is \code{FALSE}.} + +\item{detection_multiplier}{Value determining how far the outlier detection +thresholds are from the rolling median, which are calculated as (rolling +median) +/- (detection multiplier) * (rolling IQR). Default is 2.} + +\item{min_radius}{Minimum distance between rolling median and threshold, on +transformed scale. Default is 0.} + +\item{replacement_multiplier}{Value determining how far the replacement +values are from the rolling median. The replacement is the original value +if it is within the detection thresholds, or otherwise it is rounded to the +nearest (rolling median) +/- (replacement multiplier) * (rolling IQR). +Default is 0.} + +\item{n_trend}{Number of time steps to use in the rolling window for trend. +Default is 21.} + +\item{n_seasonal}{Number of time steps to use in the rolling window for +seasonality. Default is 21. Can also be the string "periodic". See +\code{s.window} in \code{\link[stats:stl]{stats::stl}}.} + +\item{n_threshold}{Number of time steps to use in rolling window for the IQR +outlier thresholds.} + +\item{seasonal_period}{Integer specifying period of "seasonality". For +example, for daily data, a period 7 means weekly seasonality. It must be +strictly larger than 1. Also impacts the size of the low-pass filter +window; see \code{l.window} in \code{\link[stats:stl]{stats::stl}}.} + +\item{seasonal_as_residual}{Boolean specifying whether the seasonal(/weekly) +component should be treated as part of the residual component instead of as +part of the predictions. The default, FALSE, treats them as part of the +predictions, so large seasonal(/weekly) components will not lead to +flagging points as outliers. \code{TRUE} may instead consider the extrema of +large seasonal variations to be outliers; \code{n_seasonal} and +\code{seasonal_period} will still have an impact on the result, though, by +impacting the estimation of the trend component.} } \value{ An tibble with number of rows equal to \code{length(y)} and columns @@ -47,6 +125,13 @@ Applies one or more outlier detection methods to a given signal variable, and optionally aggregates the outputs to create a consensus result. See the \href{https://cmu-delphi.github.io/epiprocess/articles/outliers.html}{outliers vignette} for examples. + +\code{detect_outlr_rm} detects outliers based on a distance from the +rolling median specified in terms of multiples of the rolling interquartile +range (IQR). + +\code{detect_outlr_stl} detects outliers based on a seasonal-trend +decomposition using LOESS (STL). } \details{ Each outlier detection method, one per row of the passed \code{methods} @@ -62,6 +147,15 @@ For convenience, the outlier detection method can be specified (in the \code{detect_outlr_rm()}, which detects outliers via a rolling median; or by "stl", shorthand for \code{detect_outlr_stl()}, which detects outliers via an STL decomposition. + +The STL decomposition is computed using \code{\link[stats:stl]{stats::stl()}}. Once +computed, the outlier detection method is analogous to the rolling median +method in \code{\link[=detect_outlr_rm]{detect_outlr_rm()}}, except with the fitted values and residuals +from the STL decomposition taking the place of the rolling median and +residuals to the rolling median, respectively. + +The last set of arguments, \code{log_transform} through \code{replacement_multiplier}, +are exactly as in \code{detect_outlr_rm()}. } \examples{ detection_methods <- dplyr::bind_rows( @@ -104,4 +198,21 @@ x <- covid_incidence_outliers \%>\% combiner = "median" )) \%>\% unnest(outlier_info) +# Detect outliers based on a rolling median +covid_incidence_outliers \%>\% + dplyr::select(geo_value, time_value, cases) \%>\% + as_epi_df() \%>\% + group_by(geo_value) \%>\% + mutate(outlier_info = detect_outlr_rm( + x = time_value, y = cases + )) +# Detects outliers based on a seasonal-trend decomposition using LOESS +covid_incidence_outliers \%>\% + dplyr::select(geo_value, time_value, cases) \%>\% + as_epi_df() \%>\% + group_by(geo_value) \%>\% + mutate(outlier_info = detect_outlr_stl( + x = time_value, y = cases, + seasonal_period = 7 # weekly seasonality for daily data + )) } diff --git a/man/detect_outlr_rm.Rd b/man/detect_outlr_rm.Rd deleted file mode 100644 index 36e784ca..00000000 --- a/man/detect_outlr_rm.Rd +++ /dev/null @@ -1,69 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/outliers.R -\name{detect_outlr_rm} -\alias{detect_outlr_rm} -\title{Detect outliers based on a rolling median} -\usage{ -detect_outlr_rm( - x = seq_along(y), - y, - n = 21, - log_transform = FALSE, - detect_negatives = FALSE, - detection_multiplier = 2, - min_radius = 0, - replacement_multiplier = 0 -) -} -\arguments{ -\item{x}{Design points corresponding to the signal values \code{y}. Default is -\code{seq_along(y)} (that is, equally-spaced points from 1 to the length of -\code{y}).} - -\item{y}{Signal values.} - -\item{n}{Number of time steps to use in the rolling window. Default is 21. -This value is centrally aligned. When \code{n} is an odd number, the rolling -window extends from \code{(n-1)/2} time steps before each design point to \code{(n-1)/2} -time steps after. When \code{n} is even, then the rolling range extends from -\code{n/2-1} time steps before to \code{n/2} time steps after.} - -\item{log_transform}{Should a log transform be applied before running outlier -detection? Default is \code{FALSE}. If \code{TRUE}, and zeros are present, then the -log transform will be padded by 1.} - -\item{detect_negatives}{Should negative values automatically count as -outliers? Default is \code{FALSE}.} - -\item{detection_multiplier}{Value determining how far the outlier detection -thresholds are from the rolling median, which are calculated as (rolling -median) +/- (detection multiplier) * (rolling IQR). Default is 2.} - -\item{min_radius}{Minimum distance between rolling median and threshold, on -transformed scale. Default is 0.} - -\item{replacement_multiplier}{Value determining how far the replacement -values are from the rolling median. The replacement is the original value -if it is within the detection thresholds, or otherwise it is rounded to the -nearest (rolling median) +/- (replacement multiplier) * (rolling IQR). -Default is 0.} -} -\value{ -An tibble with number of rows equal to \code{length(y)} and columns -giving the outlier detection thresholds (\code{lower} and \code{upper}) and -replacement values from each detection method (\code{replacement}). -} -\description{ -Detects outliers based on a distance from the rolling median specified in -terms of multiples of the rolling interquartile range (IQR). -} -\examples{ -# Detect outliers based on a rolling median -covid_incidence_outliers \%>\% - dplyr::select(geo_value, time_value, cases) \%>\% - as_epi_df() \%>\% - group_by(geo_value) \%>\% - mutate(outlier_info = detect_outlr_rm( - x = time_value, y = cases - )) -} diff --git a/man/detect_outlr_stl.Rd b/man/detect_outlr_stl.Rd deleted file mode 100644 index 27204142..00000000 --- a/man/detect_outlr_stl.Rd +++ /dev/null @@ -1,101 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/outliers.R -\name{detect_outlr_stl} -\alias{detect_outlr_stl} -\title{Detect outliers based on an STL decomposition} -\usage{ -detect_outlr_stl( - x = seq_along(y), - y, - n_trend = 21, - n_seasonal = 21, - n_threshold = 21, - seasonal_period, - seasonal_as_residual = FALSE, - log_transform = FALSE, - detect_negatives = FALSE, - detection_multiplier = 2, - min_radius = 0, - replacement_multiplier = 0 -) -} -\arguments{ -\item{x}{Design points corresponding to the signal values \code{y}. Default is -\code{seq_along(y)} (that is, equally-spaced points from 1 to the length of -\code{y}).} - -\item{y}{Signal values.} - -\item{n_trend}{Number of time steps to use in the rolling window for trend. -Default is 21.} - -\item{n_seasonal}{Number of time steps to use in the rolling window for -seasonality. Default is 21. Can also be the string "periodic". See -\code{s.window} in \code{\link[stats:stl]{stats::stl}}.} - -\item{n_threshold}{Number of time steps to use in rolling window for the IQR -outlier thresholds.} - -\item{seasonal_period}{Integer specifying period of "seasonality". For -example, for daily data, a period 7 means weekly seasonality. It must be -strictly larger than 1. Also impacts the size of the low-pass filter -window; see \code{l.window} in \code{\link[stats:stl]{stats::stl}}.} - -\item{seasonal_as_residual}{Boolean specifying whether the seasonal(/weekly) -component should be treated as part of the residual component instead of as -part of the predictions. The default, FALSE, treats them as part of the -predictions, so large seasonal(/weekly) components will not lead to -flagging points as outliers. \code{TRUE} may instead consider the extrema of -large seasonal variations to be outliers; \code{n_seasonal} and -\code{seasonal_period} will still have an impact on the result, though, by -impacting the estimation of the trend component.} - -\item{log_transform}{Should a log transform be applied before running outlier -detection? Default is \code{FALSE}. If \code{TRUE}, and zeros are present, then the -log transform will be padded by 1.} - -\item{detect_negatives}{Should negative values automatically count as -outliers? Default is \code{FALSE}.} - -\item{detection_multiplier}{Value determining how far the outlier detection -thresholds are from the rolling median, which are calculated as (rolling -median) +/- (detection multiplier) * (rolling IQR). Default is 2.} - -\item{min_radius}{Minimum distance between rolling median and threshold, on -transformed scale. Default is 0.} - -\item{replacement_multiplier}{Value determining how far the replacement -values are from the rolling median. The replacement is the original value -if it is within the detection thresholds, or otherwise it is rounded to the -nearest (rolling median) +/- (replacement multiplier) * (rolling IQR). -Default is 0.} -} -\value{ -An tibble with number of rows equal to \code{length(y)} and columns -giving the outlier detection thresholds (\code{lower} and \code{upper}) and -replacement values from each detection method (\code{replacement}). -} -\description{ -Detects outliers based on a seasonal-trend decomposition using LOESS (STL). -} -\details{ -The STL decomposition is computed using \code{\link[stats:stl]{stats::stl()}}. Once -computed, the outlier detection method is analogous to the rolling median -method in \code{\link[=detect_outlr_rm]{detect_outlr_rm()}}, except with the fitted values and residuals -from the STL decomposition taking the place of the rolling median and -residuals to the rolling median, respectively. - -The last set of arguments, \code{log_transform} through \code{replacement_multiplier}, -are exactly as in \code{detect_outlr_rm()}. -} -\examples{ -# Detects outliers based on a seasonal-trend decomposition using LOESS -covid_incidence_outliers \%>\% - dplyr::select(geo_value, time_value, cases) \%>\% - as_epi_df() \%>\% - group_by(geo_value) \%>\% - mutate(outlier_info = detect_outlr_stl( - x = time_value, y = cases, - seasonal_period = 7 # weekly seasonality for daily data - )) -} 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 a5055f4e..4b459d5e 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 @@ -97,19 +98,24 @@ example \code{version = release_date}} An \code{epi_archive} object. } \description{ -An \code{epi_archive} is an S3 class which contains a data table -along with several relevant pieces of metadata. The data table can be seen -as the full archive (version history) for some signal variables of -interest. +The second main data structure for storing time series in +\code{epiprocess}. It is similar to \code{epi_df} in that it fundamentally a table with +a few required columns that stores epidemiological time series data. An +\code{epi_archive} requires a \code{geo_value}, \code{time_value}, and \code{version} column (and +possibly other key columns) along with measurement values. In brief, an +\code{epi_archive} is a history of the time series data, where the \code{version} +column tracks the time at which the data was available. This allows for +version-aware forecasting. + +\code{new_epi_archive} is the constructor for \code{epi_archive} objects that assumes +all arguments have been validated. Most users should use \code{as_epi_archive}. } \details{ -Epi Archive - -An \code{epi_archive} contains a data table \code{DT}, of class \code{data.table} -from the \code{data.table} package, with (at least) the following columns: +An \code{epi_archive} contains a \code{data.table} object \code{DT} (from the +\code{{data.table}} package), with (at least) the following columns: \itemize{ -\item \code{geo_value}: the geographic value associated with each row of measurements. -\item \code{time_value}: the time value associated with each row of measurements. +\item \code{geo_value}: the geographic value associated with each row of measurements, +\item \code{time_value}: the time value associated with each row of measurements, \item \code{version}: the time value specifying the version for each row of measurements. For example, if in a given row the \code{version} is January 15, 2022 and \code{time_value} is January 14, 2022, then this row contains the @@ -117,13 +123,33 @@ measurements of the data for January 14, 2022 that were available one day later. } -The data table \code{DT} has key variables \code{geo_value}, \code{time_value}, \code{version}, -as well as any others (these can be specified when instantiating the -\code{epi_archive} object via the \code{other_keys} argument, and/or set by operating -on \code{DT} directly). Note that there can only be a single row per unique -combination of key variables. +The variables \code{geo_value}, \code{time_value}, \code{version} serve as key variables for +the data table (in addition to any other keys specified in the metadata). +There can only be a single row per unique combination of key variables. The +keys for an \code{epi_archive} can be viewed with \code{key(epi_archive$DT)}. +\subsection{Compactification}{ + +By default, an \code{epi_archive} will compactify the data table to remove +redundant rows. This is done by not storing rows that have the same value, +except for the \code{version} column (this is essentially a last observation +carried forward, but along the version index). This is done to save space and +improve performance. If you do not want to compactify the data, you can set +\code{compactify = FALSE} in \code{as_epi_archive()}. + +Note that in some data scenarios, LOCF may not be appropriate. For instance, +if you expected data to be updated on a given day, but your data source did +not update, then it could be reasonable to code the data as \code{NA} for that +day, instead of assuming LOCF. + +\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}{ + +\subsection{Metadata}{ The following pieces of metadata are included as fields in an \code{epi_archive} object: @@ -139,25 +165,7 @@ as read-only, and to use the \code{epi_archive} methods to interact with the dat archive. Unexpected behavior may result from modifying the metadata directly. } - -\section{Generating Snapshots}{ - -An \code{epi_archive} object can be used to generate a snapshot of the data in -\code{epi_df} format, which represents the most up-to-date time series values up -to a point in time. This is accomplished by calling \code{epix_as_of()}. } - -\section{Sliding Computations}{ - -We can run a sliding computation over an \code{epi_archive} object, much like -\code{epi_slide()} does for an \code{epi_df} object. This is accomplished by calling -the \code{slide()} method for an \code{epi_archive} object, which works similarly to -the way \code{epi_slide()} works for an \code{epi_df} object, but with one key -difference: it is version-aware. That is, for an \code{epi_archive} object, the -sliding computation at any given reference time point t is performed on -\strong{data that would have been available as of t}. -} - \examples{ # Simple ex. with necessary keys tib <- tibble::tibble( @@ -197,3 +205,6 @@ df <- data.frame( x <- df \%>\% as_epi_archive(other_keys = "county") } +\seealso{ +\code{\link{epix_as_of}} \code{\link{epix_merge}} \code{\link{epix_slide}} +} diff --git a/man/epi_df.Rd b/man/epi_df.Rd index d863f655..4c592ab7 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,11 +66,15 @@ 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{ -An \code{epi_df} is a tibble with certain minimal column structure and metadata. -It can be seen as a snapshot of a data set that contains the most -up-to-date values of some signal variables of interest, as of a given time. +One of the two main data structures for storing time series in \code{epiprocess}. +It is simply tibble with at least two columns, \code{geo_value} and \code{time_value}, +that provide the keys for the time series. It can have any other columns, +which can be seen as measured variables at each key. In brief, an \code{epi_df} +represents a snapshot of an epidemiological data set at a point in time. } \details{ An \code{epi_df} is a tibble with (at least) the following columns: @@ -109,6 +115,9 @@ of an \code{epi_df} object. In brief, we can think of an \code{epi_df} object as single snapshot of a data set that contains the most up-to-date values of the signals variables, as of the time specified in the \code{as_of} field. +If an \code{epi_df} ever loses its \code{geo_value} or \code{time_value} columns, it will +decay into a regular tibble. + A companion object is the \code{epi_archive} object, which contains the full version history of a given data set. Revisions are common in many types of epidemiological data streams, and paying attention to data revisions can be @@ -117,15 +126,7 @@ the documentation for \code{\link{epi_archive}} for more details on how data versioning works in the \code{epiprocess} package (including how to generate \code{epi_df} objects, as data snapshots, from an \code{epi_archive} object). -} -\section{Functions}{ -\itemize{ -\item \code{as_epi_df()}: The preferred way of constructing \code{epi_df}s - -\item \code{new_epi_df()}: Lower-level constructor for \code{epi_df} object - -}} -\section{Geo Types}{ +\subsection{Geo Types}{ The following geo types are recognized in an \code{epi_df}. \itemize{ @@ -146,7 +147,7 @@ alpha-2 country codes (lowercase). An unrecognizable geo type is labeled "custom". } -\section{Time Types}{ +\subsection{Time Types}{ The following time types are recognized in an \code{epi_df}. \itemize{ @@ -162,37 +163,41 @@ arbitrary (as to whether a week starts on a Monday, Tuesday); coded as a An unrecognizable time type is labeled "custom". } +} +\section{Functions}{ +\itemize{ +\item \code{as_epi_df()}: The preferred way of constructing \code{epi_df}s + +\item \code{new_epi_df()}: Lower-level constructor for \code{epi_df} object +}} \examples{ # Convert a `tsibble` that has county code as an extra key # Notice that county code should be a character string to preserve any leading zeroes - ex1_input <- tibble::tibble( - geo_value = rep(c("ca", "fl", "pa"), each = 3), - county_code = c( + geo_value = c( "06059", "06061", "06067", "12111", "12113", "12117", "42101", "42103", "42105" ), + state_name = rep(c("ca", "fl", "pa"), each = 3), time_value = rep(seq(as.Date("2020-06-01"), as.Date("2020-06-03"), by = "day" ), length.out = length(geo_value)), value = 1:length(geo_value) + 0.01 * rnorm(length(geo_value)) ) \%>\% - tsibble::as_tsibble(index = time_value, key = c(geo_value, county_code)) + tsibble::as_tsibble(index = time_value, key = c(geo_value, state_name)) -# The `other_keys` metadata (`"county_code"` in this case) is automatically +# The `other_keys` metadata (`"state_name"` in this case) is automatically # inferred from the `tsibble`'s `key`: ex1 <- as_epi_df(x = ex1_input, as_of = "2020-06-03") attr(ex1, "metadata")[["other_keys"]] - # Dealing with misspecified column names: # Geographical and temporal information must be provided in columns named # `geo_value` and `time_value`; if we start from a data frame with a # different format, it must be converted to use `geo_value` and `time_value` # before calling `as_epi_df`. - ex2_input <- tibble::tibble( state = rep(c("ca", "fl", "pa"), each = 3), # misnamed pol = rep(c("blue", "swing", "swing"), each = 3), # extra key @@ -201,7 +206,6 @@ ex2_input <- tibble::tibble( ), length.out = length(state)), # misnamed value = 1:length(state) + 0.01 * rnorm(length(state)) ) - print(ex2_input) ex2 <- ex2_input \%>\% @@ -210,12 +214,9 @@ ex2 <- ex2_input \%>\% as_of = "2020-06-03", other_keys = "pol" ) - attr(ex2, "metadata") - # Adding additional keys to an `epi_df` object - ex3_input <- covid_incidence_county_subset \%>\% dplyr::filter(time_value > "2021-12-01", state_name == "Massachusetts") \%>\% dplyr::slice_tail(n = 6) @@ -230,4 +231,8 @@ ex3 <- ex3_input \%>\% as_epi_df(other_keys = c("state", "pol")) attr(ex3, "metadata") + +# Decays to a tibble +covid_incidence_county_subset \%>\% + dplyr::select(-geo_value) } diff --git a/man/epi_slide.Rd b/man/epi_slide.Rd index 71734cc1..bd3f2f68 100644 --- a/man/epi_slide.Rd +++ b/man/epi_slide.Rd @@ -16,15 +16,23 @@ epi_slide( ) } \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{.x}{An \code{epi_df} object. If ungrouped, we group by \code{geo_value} and any +columns in \code{other_keys}. If grouped, we make sure the grouping is by +\code{geo_value} and \code{other_keys}.} \item{.f}{Function, formula, or missing; together with \code{...} specifies the -computation to slide. To "slide" means to apply a computation within a -sliding (a.k.a. "rolling") time window for each data group. The window is -determined by the \code{.window_size} and \code{.align} parameters, see the details -section for more. If a function, \code{.f} must have the form \verb{function(x, g, t, ...)}, where +computation to slide. The return of the computation should either be a +scalar or a 1-row data frame. Data frame returns will be +\code{tidyr::unpack()}-ed, if named, and will be \code{\link[tidyr:pack]{tidyr::pack}}-ed columns, if +not named. See examples. +\itemize{ +\item If \code{.f} is missing, then \code{...} will specify the computation via +tidy-evaluation. This is usually the most convenient way to use +\code{epi_slide}. See examples. +\item If \code{.f} is a formula, then the formula should use \code{.x} (not the same as +the input \code{epi_df}) to operate on the columns of the input \code{epi_df}, e.g. +\code{~mean(.x$var)} to compute a mean of \code{var}. +\item If a function, \code{.f} must have the form \verb{function(x, g, t, ...)}, where: \itemize{ \item \code{x} is a data frame with the same column names as the original object, minus any grouping variables, with only the windowed data for one @@ -34,135 +42,75 @@ for the associated group \item \code{t} is the \code{.ref_time_value} for the current window \item \code{...} are additional arguments } - -If a formula, \code{.f} can operate directly on columns accessed via \code{.x$var} or -\code{.$var}, as in \code{~mean(.x$var)} to compute a mean of a column \code{var} for each -\code{ref_time_value}-group combination. The group key can be accessed via \code{.y}. -If \code{.f} is missing, then \code{...} will specify the computation.} +}} \item{...}{Additional arguments to pass to the function or formula specified via \code{.f}. Alternatively, if \code{.f} is missing, then the \code{...} is interpreted as a \link[rlang:args_data_masking]{"data-masking"} expression or expressions -for tidy evaluation; in addition to referring columns directly by name, the -expressions have access to \code{.data} and \code{.env} pronouns as in \code{dplyr} verbs, -and can also refer to \code{.x} (not the same as the input epi_df), -\code{.group_key}, and \code{.ref_time_value}. See details.} +for tidy evaluation.} -\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: +\item{.window_size}{The size of the sliding window. The accepted values +depend on the type of the \code{time_value} column in \code{.x}: \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 +\item if time type is \code{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 +\item if time type is \code{Date} and the cadence is weekly, then \code{.window_size} must +be a \code{difftime} with units "weeks" +\item if time type is a \code{yearmonth} or an 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{.align}{The alignment of the sliding window. +\itemize{ +\item If "right" (default), then the window has its end at the reference time. +This is likely the most common use case, e.g. \code{.window_size=7} and +\code{.align="right"} slides over the past week of data. +\item If "left", then the window has its start at the reference time. +\item If "center", then the window is centered at the reference time. If 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 more value before the reference time +than after. +}} -\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{.ref_time_values}{The time values at which to compute the slides +values. By default, this is all the unique time values in \code{.x}.} -\item{.new_col_name}{String indicating the name of the new column that will -contain the derivative values. The default is "slide_value" unless your -slide computations output data frames, in which case they will be unpacked +\item{.new_col_name}{Name for the new column that will contain the computed +values. The default is "slide_value" unless your slide computations output +data frames, in which case they will be unpacked (as in \code{tidyr::unpack()}) into the constituent columns and those names used. New columns should not -be given names that clash with the existing columns of \code{.x}; see details.} +be given names that clash with the existing columns of \code{.x}.} -\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.} +\item{.all_rows}{If \code{.all_rows = FALSE}, the default, then the output +\code{epi_df} will have only the rows that had a \code{time_value} in +\code{.ref_time_values}. Otherwise, all the rows from \code{.x} are included by with +a missing value marker (typically NA, but more technically 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. +An \code{epi_df} object with one or more new slide computation columns +added. } \description{ -Slides a given function 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 -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. - -\code{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, 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 - -If \code{.f} is missing, then \link[rlang:args_data_masking]{"data-masking"} -expression(s) for tidy evaluation can be specified, for example, as in: - -\if{html}{\out{
}}\preformatted{epi_slide(x, cases_7dav = mean(cases), .window_size = 7) -}\if{html}{\out{
}} - -which would be equivalent to: +Slides a given function over variables in an \code{epi_df} object. +This is useful for computations like rolling averages. The function supports +many ways to specify the computation, but by far the most common use case is +as follows: -\if{html}{\out{
}}\preformatted{epi_slide(x, function(x, g, t) mean(x$cases), .window_size = 7, - .new_col_name = "cases_7dav") +\if{html}{\out{
}}\preformatted{# To compute the 7-day trailing average of cases +epi_slide(edf, cases_7dav = mean(cases), .window_size = 7) }\if{html}{\out{
}} -In a manner similar to \code{\link[dplyr:mutate]{dplyr::mutate}}: -\itemize{ -\item Expressions evaluating to length-1 vectors will be recycled to -appropriate lengths. -\item \verb{, name_var := value} can be used to set the output column name based on -a variable \code{name_var} rather than requiring you to use a hard-coded -name. (The leading comma is needed to make sure that \code{.f} is treated as -missing.) -\item \verb{= NULL} can be used to remove results from previous expressions (though -we don't allow it to remove pre-existing columns). -\item \verb{, fn_returning_a_data_frame(.x)} will unpack the output of the function -into multiple columns in the result. -\item Named expressions evaluating to data frames will be placed into -\code{\link[tidyr:pack]{tidyr::pack}}ed columns. +This will create the new column \code{cases_7dav} that contains a 7-day rolling +average of values in "cases". See \code{vignette("epi_df")} for more examples. } +\details{ +\subsection{Advanced uses of \code{.f} via tidy evaluation}{ -In addition to \code{\link{.data}} and \code{\link{.env}}, we make some additional -"pronoun"-like bindings available: +If specifying \code{.f} via tidy evaluation, in addition to the standard \code{\link{.data}} +and \code{\link{.env}}, we make some additional "pronoun"-like bindings available: \itemize{ \item .x, which is like \code{.x} in \code{\link[dplyr:group_map]{dplyr::group_modify}}; an ordinary object like an \code{epi_df} rather than an rlang \link[rlang:as_data_mask]{pronoun} @@ -175,49 +123,61 @@ will. determined the time window for the current computation. } } +} \examples{ -# slide a 7-day trailing average formula on cases -# Simple sliding means and sums are much faster to do using -# the `epi_slide_mean` and `epi_slide_sum` functions instead. -cases_deaths_subset \%>\% - group_by(geo_value) \%>\% - epi_slide(cases_7dav = mean(cases), .window_size = 7) \%>\% - dplyr::select(geo_value, time_value, cases, cases_7dav) \%>\% - ungroup() - -# slide a 7-day leading average +# Get the 7-day trailing standard deviation of cases and the 7-day trailing mean of cases cases_deaths_subset \%>\% - group_by(geo_value) \%>\% - epi_slide(cases_7dav = mean(cases), .window_size = 7, .align = "left") \%>\% - dplyr::select(geo_value, time_value, cases, cases_7dav) \%>\% - ungroup() + epi_slide( + cases_7sd = sd(cases, na.rm = TRUE), + cases_7dav = mean(cases, na.rm = TRUE), + .window_size = 7 + ) \%>\% + dplyr::select(geo_value, time_value, cases, cases_7sd, cases_7dav) -# slide a 7-day centre-aligned average +# The same as above, but unpacking using an unnamed data.frame with a formula cases_deaths_subset \%>\% - group_by(geo_value) \%>\% - epi_slide(cases_7dav = mean(cases), .window_size = 7, .align = "center") \%>\% - dplyr::select(geo_value, time_value, cases, cases_7dav) \%>\% - ungroup() + epi_slide( + ~ data.frame( + cases_7sd = sd(.x$cases, na.rm = TRUE), + cases_7dav = mean(.x$cases, na.rm = TRUE) + ), + .window_size = 7 + ) \%>\% + dplyr::select(geo_value, time_value, cases, cases_7sd, cases_7dav) -# slide a 14-day centre-aligned average +# The same as above, but packing using a named data.frame with a tidy evaluation +# expression cases_deaths_subset \%>\% - group_by(geo_value) \%>\% - epi_slide(cases_14dav = mean(cases), .window_size = 14, .align = "center") \%>\% - dplyr::select(geo_value, time_value, cases, cases_14dav) \%>\% - ungroup() + epi_slide( + slide_packed = data.frame( + cases_7sd = sd(.x$cases, na.rm = TRUE), + cases_7dav = mean(.x$cases, na.rm = TRUE) + ), + .window_size = 7 + ) \%>\% + dplyr::select(geo_value, time_value, cases, slide_packed) # nested new columns cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide( - cases_2d = list(data.frame( - cases_2dav = mean(cases), - cases_2dma = mad(cases) - )), - .window_size = 2 + function(x, g, t) { + data.frame( + cases_7sd = sd(x$cases, na.rm = TRUE), + cases_7dav = mean(x$cases, na.rm = TRUE) + ) + }, + .window_size = 7 ) \%>\% - ungroup() + dplyr::select(geo_value, time_value, cases, cases_7sd, cases_7dav) + +# Use the geo_value or the ref_time_value in the slide computation +cases_deaths_subset \%>\% + epi_slide(~ .x$geo_value[[1]], .window_size = 7) + +cases_deaths_subset \%>\% + epi_slide(~ .x$time_value[[1]], .window_size = 7) } \seealso{ -\code{\link{epi_slide_opt}} \code{\link{epi_slide_mean}} \code{\link{epi_slide_sum}} +\code{\link{epi_slide_opt}} for optimized slide functions } diff --git a/man/epi_slide_mean.Rd b/man/epi_slide_mean.Rd deleted file mode 100644 index e075f759..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 -cases_deaths_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. -cases_deaths_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 -cases_deaths_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 centre-aligned average -cases_deaths_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 centre-aligned average -cases_deaths_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 7ec78828..cd293ee1 100644 --- a/man/epi_slide_opt.Rd +++ b/man/epi_slide_opt.Rd @@ -2,8 +2,9 @@ % Please edit documentation in R/slide.R \name{epi_slide_opt} \alias{epi_slide_opt} -\title{Optimized slide function for performing common rolling computations on an -\code{epi_df} object} +\alias{epi_slide_mean} +\alias{epi_slide_sum} +\title{Optimized slide functions for common cases} \usage{ epi_slide_opt( .x, @@ -15,11 +16,31 @@ 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} -or ungrouped. If ungrouped, all data in \code{.x} will be treated as part of a -single data group.} +\item{.x}{An \code{epi_df} object. If ungrouped, we group by \code{geo_value} and any +columns in \code{other_keys}. If grouped, we make sure the grouping is by +\code{geo_value} and \code{other_keys}.} \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)}), @@ -50,133 +71,93 @@ 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: +\item{.window_size}{The size of the sliding window. The accepted values +depend on the type of the \code{time_value} column in \code{.x}: \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 +\item if time type is \code{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 +\item if time type is \code{Date} and the cadence is weekly, then \code{.window_size} must +be a \code{difftime} with units "weeks" +\item if time type is a \code{yearmonth} or an 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{.align}{The alignment of the sliding window. +\itemize{ +\item If "right" (default), then the window has its end at the reference time. +This is likely the most common use case, e.g. \code{.window_size=7} and +\code{.align="right"} slides over the past week of data. +\item If "left", then the window has its start at the reference time. +\item If "center", then the window is centered at the reference time. If 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 more value before the reference time +than after. +}} -\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{.ref_time_values}{The time values at which to compute the slides +values. By default, this is all the unique time values in \code{.x}.} -\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.} +\item{.all_rows}{If \code{.all_rows = FALSE}, the default, then the output +\code{epi_df} will have only the rows that had a \code{time_value} in +\code{.ref_time_values}. Otherwise, all the rows from \code{.x} are included by with +a missing value marker (typically NA, but more technically 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. +An \code{epi_df} object with one or more new slide computation columns +added. } \description{ -Slides an n-timestep \link[data.table:froll]{data.table::froll} or \link[slider:summary-slide]{slider::summary-slide} function -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 -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 +\code{epi_slide_opt} allows sliding an n-timestep \link[data.table:froll]{data.table::froll} +or \link[slider:summary-slide]{slider::summary-slide} function over variables in an \code{epi_df} object. +These functions tend to be much faster than \code{epi_slide()}. See +\code{vignette("epi_df")} for more examples. -With .align = "center" and .window_size = 3, the window will be: +\code{epi_slide_mean} is a wrapper around \code{epi_slide_opt} with \code{.f = datatable::frollmean}. -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 +\code{epi_slide_sum} is a wrapper around \code{epi_slide_opt} with \code{.f = datatable::frollsum}. } \examples{ -# slide a 7-day trailing average formula on cases. This can also be done with `epi_slide_mean` +# Compute a 7-day trailing average on cases. cases_deaths_subset \%>\% group_by(geo_value) \%>\% - epi_slide_opt( - cases, - .f = data.table::frollmean, .window_size = 7 - ) \%>\% - # Remove a nonessential var. to ensure new col is printed, and rename new col - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() + epi_slide_opt(cases, .f = data.table::frollmean, .window_size = 7) \%>\% + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) -# slide a 7-day trailing average formula on cases. Adjust `frollmean` settings for speed -# and accuracy, and to allow partially-missing windows. +# Same as above, but adjust `frollmean` settings for speed, accuracy, and +# to allow partially-missing windows. cases_deaths_subset \%>\% group_by(geo_value) \%>\% epi_slide_opt( cases, .f = data.table::frollmean, .window_size = 7, - # `frollmean` options algo = "exact", hasNA = TRUE, na.rm = TRUE ) \%>\% - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() - -# slide a 7-day leading average + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) +# Compute a 7-day trailing average on cases. cases_deaths_subset \%>\% group_by(geo_value) \%>\% - epi_slide_opt( - cases, - .f = slider::slide_mean, .window_size = 7, .align = "left" - ) \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() + epi_slide_mean(cases, .window_size = 7) \%>\% + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) -# slide a 7-day centre-aligned sum. This can also be done with `epi_slide_sum` +# Same as above, but adjust `frollmean` settings for speed, accuracy, and +# to allow partially-missing windows. cases_deaths_subset \%>\% group_by(geo_value) \%>\% - epi_slide_opt( + epi_slide_mean( cases, - .f = data.table::frollsum, .window_size = 6, .align = "center" + .window_size = 7, + na.rm = TRUE, algo = "exact", hasNA = TRUE ) \%>\% - # Remove a nonessential var. to ensure new col is printed - dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) \%>\% - ungroup() + dplyr::select(geo_value, time_value, cases, cases_7dav = slide_value_cases) +# Compute a 7-day trailing sum on cases. +cases_deaths_subset \%>\% + group_by(geo_value) \%>\% + epi_slide_sum(cases, .window_size = 7) \%>\% + dplyr::select(geo_value, time_value, cases, cases_7dsum = slide_value_cases) } \seealso{ -\code{\link{epi_slide}} \code{\link{epi_slide_mean}} \code{\link{epi_slide_sum}} +\code{\link{epi_slide}} for the more general slide function } diff --git a/man/epi_slide_sum.Rd b/man/epi_slide_sum.Rd deleted file mode 100644 index 920aa370..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 -cases_deaths_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-package.Rd similarity index 77% rename from man/epiprocess.Rd rename to man/epiprocess-package.Rd index bf5f5279..b4f3e174 100644 --- a/man/epiprocess.Rd +++ b/man/epiprocess-package.Rd @@ -1,14 +1,12 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/epiprocess.R +% Please edit documentation in R/epiprocess-package.R \docType{package} -\name{epiprocess} -\alias{epiprocess-package} +\name{epiprocess-package} \alias{epiprocess} +\alias{epiprocess-package} \title{epiprocess: Tools for basic signal processing in epidemiology} \description{ -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 working with epidemiological data reported by location and time and offers associated utilities to perform basic signal processing tasks. The package is designed to be used in conjunction with `epipredict` for building and evaluating epidemiological models. } \seealso{ Useful links: @@ -46,3 +44,4 @@ Other contributors: } } +\keyword{internal} diff --git a/man/epix_fill_through_version.Rd b/man/epix_fill_through_version.Rd index a6f9c360..bd1bf4de 100644 --- a/man/epix_fill_through_version.Rd +++ b/man/epix_fill_through_version.Rd @@ -9,28 +9,45 @@ epix_fill_through_version(x, fill_versions_end, how = c("na", "locf")) \arguments{ \item{x}{An \code{epi_archive}} -\item{fill_versions_end}{Length-1, same class&type as \code{x$version}: the -version through which to fill in missing version history; this will be the -result's \verb{$versions_end} unless it already had a later -\verb{$versions_end}.} +\item{fill_versions_end}{a scalar of the same class&type as \code{x$version}: the +version through which to fill in missing version history; the +\code{epi_archive}'s \code{versions_end} attribute will be set to this, unless it +already had a later \verb{$versions_end}.} -\item{how}{Optional; \code{"na"} or \code{"locf"}: \code{"na"} will fill in any missing -required version history with \code{NA}s, by inserting (if necessary) an update -immediately after the current \verb{$versions_end} that revises all -existing measurements to be \code{NA} (this is only supported for \code{version} -classes with a \code{next_after} implementation); \code{"locf"} will fill in missing -version history with the last version of each observation carried forward -(LOCF), by leaving the update \verb{$DT} alone (other \code{epi_archive} methods are -based on LOCF). Default is \code{"na"}.} +\item{how}{Optional; \code{"na"} or \code{"locf"}: \code{"na"} fills missing version history +with \code{NA}s, \code{"locf"} fills missing version history with the last version of +each observation carried forward (LOCF). Default is \code{"na"}.} } \value{ +An \code{epi_archive} + An \code{epi_archive} } \description{ -Sometimes, due to upstream data pipeline issues, we have to work with a -version history that isn't completely up to date, but with functions that -expect archives that are completely up to date, or equally as up-to-date as -another archive. This function provides one way to approach such mismatches: -pretend that we've "observed" additional versions, filling in these versions -with NAs or extrapolated values. +This function fills in missing version history in an \code{epi_archive} object up +to a specified version, updating the \code{versions_end} field as necessary. Note +that the filling is done in a compactified way, see details. +} +\details{ +Note that we generally store \code{epi_archive}'s in a compacted form, meaning +that, implciitly, if a version does not exist, but the \code{version_end} +attribute is greater, then it is understood that all the versions in between +had the same value as the last observed version. This affects the behavior of +this function in the following ways: +\itemize{ +\item if \code{how = "na"}, then the function will fill in at most one missing version +with \code{NA} and the rest will be implicit. +\item if \code{how = "locf"}, then the function will not fill any values. +} +} +\examples{ +test_date <- as.Date("2020-01-01") +ea_orig <- as_epi_archive(data.table::data.table( + geo_value = "ak", + time_value = test_date + c(rep(0L, 5L), 1L), + version = test_date + c(1:5, 2L), + value = 1:6 +)) +epix_fill_through_version(ea_orig, test_date + 8, "na") +epix_fill_through_version(ea_orig, test_date + 8, "locf") } diff --git a/man/epix_merge.Rd b/man/epix_merge.Rd index 564a1fdc..3ffebc99 100644 --- a/man/epix_merge.Rd +++ b/man/epix_merge.Rd @@ -14,24 +14,27 @@ epix_merge( \arguments{ \item{x, y}{Two \code{epi_archive} objects to join together.} -\item{sync}{Optional; \code{"forbid"}, \code{"na"}, \code{"locf"}, or \code{"truncate"}; in the -case that \code{x$versions_end} doesn't match \code{y$versions_end}, what do we do?: -\code{"forbid"}: emit an error; "na": use \code{max(x$versions_end, y$versions_end)} -as the result's \code{versions_end}, but ensure that, if we request a snapshot -as of a version after \code{min(x$versions_end, y$versions_end)}, the -observation columns from the less up-to-date archive will be all NAs (i.e., -imagine there was an update immediately after its \code{versions_end} which -revised all observations to be \code{NA}); \code{"locf"}: use \code{max(x$versions_end, y$versions_end)} as the result's \code{versions_end}, allowing the last version -of each observation to be carried forward to extrapolate unavailable -versions for the less up-to-date input archive (i.e., imagining that in the -less up-to-date archive's data set remained unchanged between its actual -\code{versions_end} and the other archive's \code{versions_end}); or \code{"truncate"}: -use \code{min(x$versions_end, y$versions_end)} as the result's \code{versions_end}, -and discard any rows containing update rows for later versions.} - -\item{compactify}{Optional; \code{TRUE}, \code{FALSE}, or \code{NULL}; should the result be -compactified? See \code{as_epi_archive()} for an explanation of what this means. -Default here is \code{TRUE}.} +\item{sync}{Optional; character. The argument that decides how to handle the +situation when one signal has a more recent revision than another signal +for a key that they have both already observed. The options are: +\itemize{ +\item \code{"forbid"}: the default and the strictest option, throws an error; this +is likely not what you want, but it is strict to make the user aware of the +issues, +\item \code{"locf"}: carry forward the last observed version of the missing signal +to the new version and use \code{max(x$versions_end, y$versions_end)} as the +result's \code{versions_end}, +\item \code{"na"}: fill the unobserved values with \code{NA}'s (this can be handy when +you know that source data is truly missing upstream and you want to +represent the lack of information accurately, for instance) and use +\code{max(x$versions_end, y$versions_end)} as the result's \code{versions_end}, +\item \code{"truncate"}: discard any rows containing update rows for later versions +and use \code{min(x$versions_end, y$versions_end)} as the result's +\code{versions_end}. +}} + +\item{compactify}{Optional; \code{TRUE} (default), \code{FALSE}, or \code{NULL}; should the +result be compactified? See \code{as_epi_archive()} for details.} } \value{ the resulting \code{epi_archive} @@ -46,6 +49,18 @@ clobberable versions). If the \code{versions_end} values differ, the \code{sync} parameter controls what is done. } \details{ +When merging archives, unless the archives have identical data release +patterns, we often have to handle the situation when one signal has a more +recent observation for a key than another signal. In this case, we have two +options: +\itemize{ +\item if the the other signal has never observed that key, we need to introduce +\code{NA}s in the non-key variables for the missing signal, +\item if the other signal has observed that key previously, but at an ealier +revision date, then we need to decide how to handle the missing value in the +more recent signal; the \code{sync} argument controls this behavior. +} + In all cases, \code{clobberable_versions_start} will be set to the earliest version that could be clobbered in either input archive. } @@ -58,18 +73,14 @@ s1 <- tibble::tibble( version = as.Date(c("2024-08-01", "2024-08-02", "2024-08-02")), signal1 = c(10, 11, 7) ) - s2 <- tibble::tibble( geo_value = c("ca", "ca"), time_value = as.Date(c("2024-08-01", "2024-08-02")), version = as.Date(c("2024-08-03", "2024-08-03")), signal2 = c(2, 3) ) - - s1 <- s1 \%>\% as_epi_archive() s2 <- s2 \%>\% as_epi_archive() - merged <- epix_merge(s1, s2, sync = "locf") merged[["DT"]] @@ -81,18 +92,14 @@ s1 <- tibble::tibble( version = as.Date(c("2024-08-01", "2024-08-03", "2024-08-03", "2024-08-03")), signal1 = c(12, 13, 22, 19) ) - s2 <- tibble::tibble( geo_value = c("ca", "ca"), time_value = as.Date(c("2024-08-01", "2024-08-02")), version = as.Date(c("2024-08-02", "2024-08-02")), signal2 = c(4, 5), ) - - s1 <- s1 \%>\% as_epi_archive() s2 <- s2 \%>\% as_epi_archive() - merged <- epix_merge(s1, s2, sync = "locf") merged[["DT"]] @@ -104,7 +111,6 @@ s1 <- tibble::tibble( version = as.Date(c("2024-08-01", "2024-08-02", "2024-08-03")), signal1 = c(14, 11, 9) ) - # The s2 signal at August 1st gets revised from 3 to 5 on August 3rd s2 <- tibble::tibble( geo_value = c("ca", "ca", "ca"), @@ -112,11 +118,8 @@ s2 <- tibble::tibble( version = as.Date(c("2024-08-02", "2024-08-03", "2024-08-03")), signal2 = c(3, 5, 2), ) - s1 <- s1 \%>\% as_epi_archive() s2 <- s2 \%>\% as_epi_archive() - -# Some LOCF for signal 1 as signal 2 gets updated merged <- epix_merge(s1, s2, sync = "locf") merged[["DT"]] } diff --git a/man/figures/README-unnamed-chunk-6-1.png b/man/figures/README-unnamed-chunk-6-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b435c65146d50812909fca5fd4781c6ac7e1ff3d GIT binary patch literal 36830 zcmbrm1yq$$7dChS0Rag~1!<*238g_4kd~0{Zjf$732BkeOLuo&LQ)zo-6`GDH3$5@ z`Tv>u|Fve;ybBlJmp9Hi`<%U>y`SgYfR8d_nCQgl5C{ZQ{KI>B2m~<&0zp`KhzQOc z?|#q+ztC(xsM%mPYF=5~0#eeKdjFZ__g2S>Q8u_PAj3y$zZN_gv6v4@mC+c<=L z;FE!W$--mso7V?94E%MHlF9k*YAo)Tq|F95=W9@EvCx-Eh<{!A6Z(vJM9Lov|8<3Z z`zC$kmSP&8Mi-bCi#2TFC+PS(!P0IKjnW5)u;-*7$DW5YsJ*OxMW@`F6_NtqpbOOF zyJ~Pbnwgo2?b8u|eeO4x0{$PP<(exNM%LT$W|4s%qdFwtG2cE_4Uk+2f2z2 zghB1AwsGJMt}rdC@S3_hTdrqW; zI*dq2ckdnzTv>lfXAj$8szSdTl^>(EPMq~hS=+qC*)^uz_qQw2zJdKn%ULL)08f1R zpHr;q&GjL)^X&l}6t5rsyYqJsT^syV>0diEA5xwctyXM}`E5{CVcuWhK;Oj_{i(Y( z8QqZQyQSUBbH-!=kBe-mXI^39&K&IW?*^Qdlr-V^FF355u9avajAaWq82s4vdUG^?d3iY^BBH#kOkQ3d@&%{NX7QI?x~S!B9VZ2a&h5>0R$nj} z)VvwVo@2|coy`{pprPysPa9O29PR8du(46U%)#7@4Gf-CX*YR#9R2B&$ShN@wt{C# zFWYco3h*;C_rc-i;0)xY?Ml~jXBhS~u9wWr($8^4#Ke%A)vaNfAL*nLo`Qk?^t(y( z$qeU@4gA*wZOG)$o)yUF_Cp2d8$B-Bjr$)xc?p5!<>xCGsFd1o45y@|WMpLYCA?`m z>gRQk<@UI+Z}Pl;6xP$-{q-x^ql8E3=nSl^sE|?>CVX^oPk2%iDKYWI$+Y!2zcNik zp?1^Ueoa+X)jNNz;PEDzY<<3W80*KbkeuAyRJyDF+gbS6n?0K>jDttm3VGxKkT&=KbWATU$9kpSmvqG>yDWCHy%*{5o zX5rlZW};L-hDEb``GAj~-~Z85L`ZIK?&HUgTUuHmFWt}feKCl}SkL&~PDThCG+3N$ zU2VqGflZ`nkRZOopBI1m`uO;4z_V%ZkAXP$&Jy4vU>=uMi>3bl{#wPxSGUZRJ_nDO zb)2)uHtefZ_Bo^#@D0-qksz+Hxj(<~gf0F;c_5&7Bp&J6rcuGBPs3+pF@$gRw$&WJK=| z&)IflM>N>tr06PO10f@3m7uE*4xGVe70e7#f$e&qYc;aIaU^IJ$9#B>=$%rEU04wP zfAxedYexyZXXC^;!uaUEK|vUIW}phU;Yy?l4P&WQM}Q>NIlbJug($Z`SJ5-nfW*i1ShDS-KZDx z1&sCO+1V46F$AH{pGy}V8errsD7{AIlNME`?7&@?ku6=mfZ$9Fg@Khm(ALy%zd5oo zoGFQnjLZGLnjzlFw2 z>}RpDu}e!!VB+MBZuTef4#aa}KpJ4MY?-7wi%B+L;m5?pW)l+>uM@5hI>~8iXuxIQ zmbc$f5Fq}o=PMDiuPr7%YHE@a5}xeV@5R4n2SIX6E>Ux7e9nIZ|D^dZ-fZwwFc`6~ z!YqPA?hZ?vTZgT&(c3b+wf=yBfUs>mGP2~zNP}NLK(Hx@IP=W)a?=KG{~N7&8v=M5 zqjJ3R?)2F7nP=CNe-84!n$pgnoo;({w=Z|mxJzxMJBm1pP;4UZ!F{kcm(yfHf;=W{ zpx3zzN@aw8Upv{IBbx8Rm;rN!AG`C_qdEM4!2zS;y|wUtuqyDa(CAn`a&{+!zuiXo z&x5p77XN%3Uz0|<_p+ZTIJm3#9Q)&!sRm;`QNvs{9=5F@!yZZ zIPzK_H$vX6OWy}FoK^z@+e-Ow71ikprRSey@NJ2tlOwM@sV683VSG^vscX~n?BQPQ6ebZ?E#v&*bsj?Qz_`-C5_m_;8uo8}i3+`u|`%n&0{OEW3!{<^NEf)Xz`dzGN!=$mV_2N)>w=;inN$(t=#Yr3@k*UXrviwx7d0$(yP-%Ke zNGVCCQ?JzGAY@=aG=0bfphLK7eCJ?z)tM|Qq(ZGQq;Y(D7s@RS=SeDR+2r{aa06 zHWVl#K(ua>|J)35Jqu+{Zm#g#fhF9nF_J?DWXOW?{-66L`TZ3(`dlAPv6RX`m=pZF zF1~f~bHmrWnfwn*zxo5!Tyqs5+F0+^=lT8*W41jqRXv88bIXPW05Iiok9OLEgO8jL zD6Tg8!J3o3pDwZCpBIY;Tictyu>w2e-P_q{Wv+W~5VjNH=vLSMZ^DQgmbNi=ecQly z#JFD>>c(i>g1{R72LLla z1!R7C(~|J-21)i_+i&oRID-4zCG$C?^!<^oW$p)usZfGxai0tEjWJj!?DxHhR{F%? za7rL|zXts7J=Abs8}1ckZsvXH#!5PnGpwL4tOGvD5)yCynO)yU8?=pr5uJ* zpESC}j{g6=F%tM%A|8Ojq$BQf|NbevL~$zQU!Lm^l@edOI(|9SBydhMWs ziT#^20j!GPr6vpS4~}T}4q4=PyfaGi3~Hi{&AP$O`;-5kWmpw<(ucOd3Eq47**-H2 ze}9rI?nKWaX==K0alBPtUOruE{>FNa&pQRJDQh$$Ir(O%dcNDB%=7v}I+-u7U9a+n z^KJytU^3c_3dE!aVv&~!bls*thVqjy0EWgBCS=jzG8@S$RIf&P@L+qssYz9KeCs%0 zxi~Z|Ojbswzq?yPT-@~9oazoCh^W5s!O<_D8GK(GVf*YMlg8(wRVjBAXmm6UZa`Xk zI>JI>^_)B%zTXbO@OLmpK6iVpaJs@2&h<=2U-tCxU+|Fx68wz&@a$k22^Gs?`>uJD ziTdg?hrTlW&>;6-f%hKh_S_U22BnIQzuS;m34=g>7)ZcaR$l(@-Me|uOG`*jQIS@u zUONCM-@bnR`Sa&%>$$6b-n|k2RghX-UmUaZu`n`Xprf<#Vcp|p>upX=@U*uI%=46E$vxpprH2a;mi34sCz);|jfb)WBSqI&Y zAPD7;vXQ2~ct}}wX{;r96*te?5gLH<+NF13>0^0lmmU{?SMCpPyFVFOS)%XXKR`uAY6i%Ph($xCY2kExQbBp&wsz+C@8A0q zzkiFAAQ-K8vNq>xs`h)tte~LKYVSiEE}v__BaC6nL7YWh z;RF0;{-{*83FWkhTdJvA{-{KcLHnshM=#k)EV&P2)CVWg_6z86#vPKcY=?ijLz746 zSA&CtS}<3H=I@WGadB}WDP?7B?Cf?Y+mn7CeEcNWQX>B|Dy~D(hBuFopB}FE_;rE| z$iczEqc=$1Pl^T)J!F2~)bbw)c5-_2zXZWWMMWVVCWe(Dgn+ui(K&GbRe^!%-a-rO z>s|IgjHrF+@X?84vikPT44>no`L{lP_~5CRF@lhVMz8){e!wP9cwXXcCl7Dz;q}bW zPs->QX{LQnfu*i2-TTI{*j} zqI2t$jiy0 zV`q=Rv0WdeZ0)K^=63@>uUT1Tv*FkKt=L{y2dI$0d-H5W}pV z^^s2a-8raCzMdWmCh^FB`tX9=&@+tLISq#L{@rJ2naXos zql*BmU#+bXKYx-!ha<&QzR>3u z%9)?J+fV+v{u;M)&+%-s`kMOJn9u4UhHeo`s{fp?5pBMrV!(@?dgM2a7?knm__W=& zaoeQ#x0UzQe}X_Psi!#V{9luajoDl(Yu!$=9pbrn2`EgDOURRu#FX+S`g)txKMFL+j^mKODMzj3y|+ZK ziR3TqY)}3-e+%E@2btNRrg6)Dc0=I1nAHx|zP|auAmZ&m_!OfN`Cp%d)N;@D;RkQ= zE@iZMQWyExB?jjZvqf&NWkfwqi)FmG=15zu-CdvkeRB5nt|n<$dohmbn?Yl>iBqKC z`a+sx4Z$REe6MM~E=ie~Y{`FTPVZkcR=W>4u)V5ZYmy{=CF3w{K0&IwrB1Mu%zKV* z7^7o-^rzgs14D};%4)8`<@e7Q5S!%=OyGQP6v49DZduhg^AYMhko&cEV=&%cY(N8`A$AKTB-8#TERfX)SdWk240B9+ zTy&)5V37$op6gGsQycFCugMUVcS9H=y1cZc*Z<+J0dsNuxjr=M^CTxVNgT@bGT13( zp%DXw%2oodr8#M?zl0JeiYkNg-8D33)}UT2Q4*NibB!KuV+E?|q9JTvFTtvQy?vG1 zvncE2_?d_UtfV^o1wKBHrZ{S{rnBr2)B(&L0^-rs1Mkxu(_s875Q8Dj)6?qN@bmL? zP+<^x|NhBq01Jd~KQSubT&&J*(OP4=*X}K!z=;r;3k83%P!~tGr+MC-jfzD0#q+P? zHFp{By9?Z|{Eh&nqH61TM+XN?Z0x;>(saY0cZFJRvrMD@1z+wrhiU#I?J)k8S8lkL zD3FhdMGoq_gwg(IHU7wN8fY-hSg4Wr_V#W!^NVHTIW|Jx%$0l~yX%fcYi}R-o1^io z@)}8~jHu)29AY$M3At8ale)^>t7dq^dGd2nmLXQP3OZ(HW*#0MK0f!=?x^bCk_Uu+deqxpm9(0nhKT*FeeHyZHePM9Y>=(m%0K_8k!>2KwoRj@KL zx4-aw9Ku8&6kjZEH*|S%(Re!NMk|we2Fe=68ntYnm)_fTf<ZY#~k(|qlSl4j#uzp#&oCn1jWx1>*OW(a#B~$))FR# z#a4f8uk+Oy<{yv1%;&6kKHKcTuEL)q@b^LXs4#ThTjcj4lTLc!YtoPO(ZOzFG>2nt z*lu~*HGz3MqU%FT7`foh`cS$nOn`-DH^I8`DYxx&XqW;9rO#kQK2y`uc=5?q`BsCl zJl;@oPcPXL>>+M2cMIb)kBA`UM-W@ux>zm9L%&wHHCf>PY) z6CNKN*I%DvdXze?8*J7Cg;t)Op8Q~e@g6^&+wR&~F@uJqNis00cQ8~^9S1?5Y$ znMCy~v-fsrCiLxYSyqyAzP;nK?cTE)y1F?5#4KkyIXM836@X!9$XlHJf=2mA>TW79 zJUja{af2y#XibFdct~=!38&8D8_$x=rw#u^iWE&`Lw+KuE8>unZEmhjc&*-wTr8ja zh_Q>mZ1@qd+@%tuau!*g?X_ZEfxMP`69+GI^hZceZ+Gkyz}v{85}8$Ee9>IL)s$4zUy5aNbufx zU!>UE5P}Bb)DJQ;C|}aW!iuzDbvM-jSIZ{x74UlZ zSJ*G;E}!XQ4^0i${KGT>j7Yrp4Xd-my{%JW_t_@bqjM}S@xokAjeUtsTTs*X>vX_= zq{1`?&|zP4{<%GmrnIy)D2SH70(2CpFMP=|g#hNLuW`H`eUL+1F7gh)ekgz1Q8qF% zGBHEWD-HL-0ogxbgFZ4unN`Mb;4|w^RmZp~+ZA28ET4_xi;Gtpr(C9tUtl?J|zHZWrw6(Udu&}+oz1hA05;IPU1|L1HZxGR}QjH}B!&ewkW1^*b|94i4 zi#-8x4+tpDMCm~kC9T5L^lssPqD#Ky$>Y*Ru>SLJ&#-ato@PGyHTrkH=j*!<)u zu5uFy5xsHjfEbA9G&kx`thJiuK}Gc+5V*A1n}ZR404!DxC#Y%ARwvC>-+VCDsufzf zYRNLa?mR7(s^kzB8=>8#DS>ZnyF4%~o-vDk0 z6`SlCm(2nI)BJ#Vz$V{aUyuFqqr`4a2Ezv|j}b((%s(gR^+#gNa8rBProqc&Use&U zM@MKUdantFG*If|ZJ(K)9U6t!h-PlgzKESvB#iLf?eD;@7Ib8=XV#Nz+=$APbwpdFeTob$%SBj0dg?S+mPa z`1$c=J74HK=kKiLsZ8l$Sg)2gL;0urZX5&KvCJ|S00mZV@cSp=AwZ?PNUf4VK;Vu} zsp8TAjL|khVYZ$1Ds;-lYJBsXYf|G{d3gGK(&jGXCNas>-(z*=FF7)D4L1;Mzq0JL zD0{3!PuxFHPy&I5X(ISAO&n8penr9p)&O=84I!>{+M5I6WiP+D@$lea$b>`N^E}h= z7nneUlf0HplQ3bNUlqjcf+~$Jp@-itOfr$#mgJs`n%u;-SnRIye?4krc zse95}%sZryKyLox>HK_sbotWRS~9|JG+$Bp*S*2iccP*wls;)WfY-XZKAi_#Noi^6 zeMDSuTKN=Me#xRG80EPXgP-c(184oadn#<*6!w;k)Ce|%l1ayE!bjaO~58Y7-GhN&PKWBwBnGY#H6#Mo(ya z+J(I-eBy?KGRS&IK;GWN{$ue=xs*)3s={vteqeug~okZJo=-2?(BT?LHUb z3;)#u2(s&KmWyf|fMiNCnn{i!3XeugrOM)ILZ4QZ#biea$*Qf3fx!x3Isa@;HyogY z_Cf_b$Zk0_SO_V7m_&==&^f!s&EF(qO}EqRgV*ic)c&1<=8Idf!nU+xh1;g)N01Do zKVnr!L}-b`)3;l^{$YgLw@mKaIn7#xXcM-^UQK65OPcPf@I=ZWC9>NF%eHT!tgHoB zOPQldu9eW6V}o>7ThKhfwgKYi(7C}IlwC-9Dx?!$YkM4z0;=))>PpM&`j{M~uo1EX zrb;D#7+TqI&aF>CTM+k6CjWLT+smU(4O|uxg_{E70scUxOc3$q*b9UOj9)I3;nk!DVFDuL8 zN0pR|BW*eE&GDNKXQZT1K+aZUw1Ff5kaGzM30u3nT1rZya*TVo|Iqhu3oW~<%4tOYhlpRc#8ClSMsx|cEj2$W9@9yEDfi* z?#4YjYZ&!n-F%&ruUq;wE4}gul1I3af0>GvA8Wq3Vx?Ij)C~z4T1cmvaNfDzyzZhY zrh4AxA{b_ zSjs2~Mh1q4h6W#DT@jIXz>}(%@RWzMpMfgVg|?r+Kft?KB(E)g`Jlaa%>;3$K%u|y zD$3fbt1g~&too>qUGkUSyAZ)n8WT7j6`Yr^q3J@;2mx|dgGM%aAp3c0G%wMCe}18~ z(BnML?vnKyWF7%%%NL;mE-UI*0!8y$nv*j`!2tnryiQgY7JzMcUyV`*OOZDG1E|E| z5EBzWd&Xz?rx);yGO;gHfpMk1`ixld*SEGL;T$qX0KjLZDVbHVUV3x}UJJA&`(YeP z(Xvs-k6gWPjx*wqO+6V-`s(1OppisMe*P$#=I`mL?8(VG&emu^Kw{rVn{4NHk}Wp@ zqVpynBI&a-AQ}L?v)XLT?(QzH<95!aZFi*6z-RM}1vnhOK!v#aRqqQ%10>^#n87dOk=4=LGy&JiEaz0AUkBMbKu zF{}Fc^(p?X*Ku`>1iowh$?2JLi*nxTB|1yH)03sW6{E@6z6eK#>v7gonMA>&TW9JB zm>L>2G8BOGMSt?m;|sB>We*@V8bQpgT@BJ!OLBN|nJWjIlT9`g393v8PVS2SF{Mo( z;}UtK9_Bt^^DS|{3F9V*D+JNZ;vQ<~ku|0+&j*F4^k*+5qotDh&VdCS z?8-N_3qWphG23Y4)DlbZEV(r=0%ONK=js+7^J6nC;1A*psg$$g>(k36iKh@gTNE!1 z_w;IG)Hg+yULRTOXA|`t>FDoZK{9dAso6)8 z>0s??Ii7P#edK=w^j99;q=yM?PLwVK<<8e{-{?0NE1JbZpS`jJrG_#*pb}zo*L_i# zEqJy|nj8V^#m$nY-Hb@7m8c-{UZ>Y$dU5rvCQN4Q)1q^dwq0<) zDCg(l=G6759Rm}h7Er4k6mrt68Qo-m)&?ppEeE~S<~~Q-$Ek1K{By!0 zrnJ3_9#jdolOQ(S;8(ML?5n4cV+bFIe!WpNtwU<}oa)gsn)NDB=YApS#rv2DKFPL& zC5kcE*VosQI(ak-D9gIIpQSq6=(1j?hGJ{eYW47xK8j7akTOILx9`Q^8p7&(Zmi5R zYsBna$rJ&q^FfzPwJr5M_A|RxC%3w4hefV9x~k*6$kF~;`;x-q%d3Ds6}_UU;K);; zy1D>miMxsi-MV)9$pSY&@B$qh{6x{$ds_gLGS3OhuIpl$TMv)EzkZV=`&g53ywSd(FENg;A=d z!5+m+rohD4(Ff-c1`)Cu+XEhSaxI9;C29%9vAO#e&q*-D1{-PLxa$XtfWa3L@lGqU#CzfW{ zMAXmMr{*mK7-VOdr|IO>vpI`t!r+7J~D|;YsN@cjK7T$kM#e-f)q{C zj+S0C)O`7U=@=oFb(dqDVd5fn} zUWwfa-r{&ec0@}IT{wwS)&d}RXlN*gRr~d)m%wkv#i^dErZoB^vZ|b_@7opD$JZa9qE7WW29c zgzZ^fqY+n_dF;W*%SPP&;IJnHdTsNZhr?tjBNdK&+EzRn$LHqGD~O$xd~@BrgR4L_sGGBX6ZnJn4-AF)-%jI!;b4G9OB&%7?>vD2Nnmt@J z&sufWTz3)AuQfkD9+PFtGOlH${{CU=*sGbxM7%r~%TQxmQs!648}BM2>2Sq&teb69 z6SW|Y58Jn_8@+9r?F6W0Q%C+{V{hPYE5Bh{)LVtkk&)7s`01iEzkkRy;aZU+f3lny zBZ5c^*~j6rPUK|q>??YswEK~6)K2S}j=fHjDu_QoWO<=2kqIgYF@Ao2Ri5_bfNGF^pct&0}X*|8}r&QIXq1?5#I^RI3XI0fwZS<%w?YyBN=AG+wqmVc~D*ZN#|uL zoNYWBg_#Em>Aljgg+%Qt$FRi|Ko+%^|NCYxW3S z>QI@czoZUi%&)ILDr{Pn`1Bg`*WSAGBNZN(9i#Db%Tci)+}xyCY;E$G5beZMARt^Z z41`5=-=%Qo{{AY?MJkZIXvsyN!56hZ;VXGw9rd;p3s{Eh<5ul*VllRK9I;Fi7TC7wd` z0O9uN$$mSLHWyEo*)L(xCC%CmSWvK`vFX~FGLj{yMnA5c6;&_ z83R-a#(;vZHRj;ppy~Q#8jPaZUx!Z&qab4&`YN=sv2iD9<1ift0$5pig|5D-sj0B= zueJa@JY3uxH#r25Xwn#tpJStX?lReGxw!`TPH8&D6uEnwSm!wBt0arjFAA%hIzi}| zicuii;Ys*>lqKugf*9TT%Ce-UPgIQiKr!KDXL`IqmECmsTNvKb^0IQFTBUv`R(f$g zNH#!5$?*no<=QY;2X}XOTU%QtB_#<7bO_G>Q(jQZ8iXTEQ)+wY$Eu|T&y?+~yk&Ac zwR9$XsgRZrf)CbFWj=qua2#u_TG%J15T;#U zzkj^OQ(fw<{V`Z0Ev9GHa=NOpDbwTf#H{k-V4w~tey=Z2x2$V{6yi@`0-*e>t!4od z?^xt;rxjOIi_gwB24^5TD_!B8VdUgoHag(shv$Kzp(tOf=Ug!cfOI4pB)tMx*9U^t ziLOqw3Hrtzs!KmEH0m~)QzYr<*duKOQu!bjd$W5oZ(Y>MowQ(&zfT6T#SfA`Xbf|S zcBqU0^w*LaX{#V2{3|)1^M0C0P?hz(RcyHuON`e+J5iW6qY_p46;SUgWJ#TaFpa7H zzmo!Ag0ev!&?5VF8X6ipYz?CzBS*!=Abn9`B1HFPR4VA|@5jQ#yi-DNCFhTCfe7^| z>=OgQokpxtRj}#zW8o(QgNS~z@4!|7W*K1(qGX`MH|ny$ytPUQ$}&|Cwj7*Uk?=J; z-TcNIy_c?1ur^P|wh2omJZquX9+9K(T?Xx_7sT_ zgm@cce7&N_nrcNVj<0pDz9^%J#yt;1enNi+l-+`t+vVQg-iwQi0JMU1(9_eCRyNtf z!b|vrp578*6;!>F>p}JR`}gk)3p$V|0`6zOd@;6&t^mQliLS*G!^_Qorr3Hfh(P!v zoLXbE2&A~bfYbw2>Ac^!-O%6;7@r?D2lmDXNMPysNKToXznY7}Y=m;|RSXZpnHRd8OKHw>CW&*0wS# zi8gzO>l1osR@P#mqag$NF_?gAg(m>8KjSou{MP!-7)(~nrM7_MQ$hDL8zCVfMMXt0 zEVCWm-9RBL)jE6#S=iW^oLGE-_}0`kcd=AHm!mhZdF==&VBC&3ks;Kg!5=<+$eEs> zoxKJl_=-=!X*aRFoPD`xW^`=q@Z8-9i0zc}3kwS!K7ZCN4Sv*9B1|0O*RD5uU0y-S zA>FfT3seaS7{&07`w8LvNmFxYWyQdrMMgLxa+b*ID&l5*%!}&6DJhv_vKaP)=2PZ6 zRh|)MCaT51lkxwfjyWt0 z8{+IH_^hYdsCFAzu@Qvl_GrUGZLh!Rm_5B%4>eM@&fN!4dFRvaP;u- zZy}CIxHF*_ZprO2JtyURAe0=0a#NP9^%jzmB3|>0;09#u3T}9aOs+R;Mc=+vVTyKO zS}^CPxlvYkicf|@Z$XWVhL#pdXdsy%@Sg(q>w{mud;vP@xA~*}iQMI0w>N(EdqxAv zFi<>iE&uC!4cNRSpz+7Wwf(y;*Xn-_rZ*_&_4M{W!o=jaU;m+7K?(xhM^mfwQM@GgnUJ{&zf9Mt#r?;%R5Hr=|)FB zaGt}J^;Lbl;fJgO|wG{_PbNT(RG$+Ym{Vs*ZE4VX_jQmfXr zy~e;Al_vjFHROaMiojy}>{s~K6zm96n|el#@8x(mCbg)c=x!_Or}&vz`1K)n>C~m$ zD$yEOpDy;0JbsCyoK*BawzM$87GD3Y_){U;tG=X8xJb@4$~IFf?MJi(!LVBGd&*+@ zE{4@Z2H(6aZ+e~?cMT8WhU=+wER_t~ zh3yI2iS1{ihqIawsEiv2f2*#qY`(Dn?DW|6VWQ@_g})c`;lG_+1;(71^7SEIghC;D;xqzXVBp9e?7tU`d4b zDdnQo9F>#cn)WWi}A=@Pau9G_f#$y+-*?0IORa2 z07Xeelb754k9~?%)OYXTg-_*h+eK5(fFI3D=ZKO>@1f7-;G=oqcoPx*Nm2JqJ}t*# zg{Q#97(Yk^?Yx1-lDH9LH&kkSz{ze=<&q)4&ms5lrmnZuBJfydGt1hobKIdpQ9=KS zN;QRE;*hrkz`lUyAPjg9YN~goD9{6-Zv%WAyThg;x)Ug-o*%8f zd6N+Sm6ef^gv*8klE~*`d$zv_!6776s{TSE+!Z?79T`Vd4(1nNX=Y`6dg^TFZODni zjK?M9us=+}nw|3VL9W~&?y@VJY1UB}i$wkIPS6e&I+%y}S9L&j%+3vmdz>&f8?P=e zU^Bz0L6hr$FXY9M7>CPL3nof_tPiHh$@+5h3e=qJQre#oqQ!e^_{uoc*O|mN^)a=N z;hVu6=PE5VqFV!Z@n&njhP<+ttv|c22ugU}(YDtwI6vd^wn>w2)2^Xd2hwxn`AQ?AM?l7|Ju*?3s`;C9ZK3_q44)TyW?S3 zkdbBhl-I_vVr!`ShVU-vWhJpW*z;dhpBOPpyg5$Z>>z-ADNbKcSg)R|tua0H4Uw1b zb4&7jZLXtfVv?g9=-K*{kt&>yifRc^>420Q2duC<&cKHjz(P6C)O_xVVfEU~&DPVu z+j__2b#ql|K3=r|g>^x&SI4A##4M5SHA71>#>_dRxUt}@PsgrVkFqb0epVt!vz zzuNm-l-8ESShDY-XH?02+WYGB)Y{|a`WgYgjbl68%hxoXQ|){DS5r-MMPB7v`%UJH zZSC!(+_o!#I0t-($y;G+WI?b?((QB?x@kz!XLwsD}DF=+?tJQpM}Dl<_=qcse(fxpUobnvY}uO~Eb^7( zk&w$--Ukm{1-MwfOoc|yZf2z-M)CfuC9m<7CR6dYGq=t7PI8UE$0MJ^t zb)-{qCt~!&BvDdTRaH~FGc>(I79D<|;LTY&GW3MnCpN9;`=$2<(iRs}gyvRYaBzeD#?QqAP+a$W^iUa+%?87+~)L8-~KvVW(|8)n{PfAi-SaJ@*ktDj)x(~K`tvCg>WJMFf=v$JUN$|y^+Y>_gjFb;)eIEC?FWWAwNG~ zLNMY0d}qMF?*+)rYZ6Hxs;OCMP<*IbJUnw)x%XWAoLzc-@oee69x*v~+#+(jIVsE{e(>`+8MAS*w(Da~w!D6Yp4j*(7 z-}S5S)I#?(Q`BL_c(&_Hq~*NmOQd#$E{c99%{v+pEUu^Ux;)td+o3~@i$9)F7nTJ97ac(%W!(97z0^MJaW|Gk~J?Ip^Dbi4;lG&8PJS9?}^GgQN0mgdnb zW1$}IkG7l|PbOq^rjh4Gp1iDFW)JRVm}e8cIVx5u)!UvZotvBEN$MXMD6Xim2OmNC zp+t(y=OaAoPgJqnT76Gv5wQ<@Jg1JwU}+}3;}($QZkmg{4%6t}9`2}jih0cxcx*8c>bdJ!L#Q1s zbrnq8|KkKA2hDK4POx>Hbrls@$W>=?yi?e%>Vcw1wdJ(Yon$Zlq=3J|U4Hlr5j4K( z+QY($F@?IUy?H~Tw&>NxE;_^|^QVMrpCnTHoHNVCv9a5fVSP-#;U{7dx0`f~3BI`R zn@_@}sJrKx?YQnFDe6&tr`fal*rCY++q0F{WfLbaiF>MsFSFtB z1T1Zx(wyC$9m{~f7rWhUkinM8s?LD~Kb8bVFJTZlWtN%jgzg@g@Yii=5j?OFwp|$` zSa#m>`$S0lgK{v6yd-xg67AtD-m$#O-0CBH4v&5Jj`QFKpMZhD2Qq{~rY;+!$cU(7 zUdc(l!t=KFoXm0P7OWB#Y4plYQi#AzT_*-w4X3jWP_c%iXI1r|DAcIbAL7TXx^%5G z?+KFCSDb%ffN&jf9uLZMG4EBIOd@m#{x_w?Nm!U&9aj!P=-}&^1vdHjZtmxw) z{FKw=8m2mGRAr~qmIQLub#t3_o!`sEO&3 z!W}-Zg~J*0Y*gQ=qb{Zdp%Gf~(dY7)c<Nwid(o4(hiJ1#q~m|MX`3t3C%9LkwbG zLc(>i?Rzt)!Kh`STj=BUIwpC3|0T?<2YbXW$ii4;d?r(hqUfG~dgI&$y!zrfd6KL~ z^FD!^1Q6x^s?>X|*A@rMosQmiIW8x<2eHA#LMxuc ziz#;gM|Pw9Xe>%|TMJ8D-HYs`ZM_^Z0Tz!<*w^#MZ$=M*&0UBl8Y4|IhpI^5SS6|z z0{?#ss@&v6M5qw(jSQgJJ6@z|{+Wdu+5&3N$6%a6DSD>Ks|hIe0r~*nG6729j=M9H zOYf+B)Pz2z-x!^Q!TdyJ)c&%;Rg;}BaMdmZZ0bs8yx3jEo=JCYButKS<`sJh;sIxT zv7XHw5&oH2&8cW<*Jq(o3iTCxU1D@UiVRne0~)hSP?4+eY9rzE-2-+aMx;(X6*U>v^{bgG_Sr3cF6Su%z3Y4kQQ;*;2i2_d|bL zqmHjft4`()n+O7T-!CHbURp8>0VkRcZ9;(Q6uOajJM;-pb$9y1j$_(QHZsx{U7#BH zDhbBo21JN*v6iJY4>j^UP{9IuLWSk@Yc@7pU`D_&bcT{H+twCxKNrrawYB=Qr{%HV z&YixW?nLvL*~EG{kW)yxjPC?eFV6mTrki<@*Y;NBFgm|Hy%tF=Y952t(dJ->sbbAV z;k(7-b*u6jFO({52!z*X^@d&ZoLKW5caR`b=VBRkjFX0kXKHp9CJPk+n;QxPj8~j~ zbDEDuMn-PW);o>l$PtHhbav7T2zY|MTKCOSeJDL9bw0*BVZL;AAZ6_GbtuYyG{}CG zF{S=pt>pv%EUd`-DD~mWkkkKzvA2$@>Wlh*4}bR$Sh zN{2|dNVl|fcXz*w-}Bsej625t<2o3eG1Rm6UTd#4_gZs)=V!)G9tdW)>L%Ye`Qwd= z4}^@{Q}@j$$ih2%3(tXEOs_k5yi{irDC&aE3=_G4Gf*soOA_MY{i4ePIwhcvGc`5E zg1!zD1S+9L|Hu91Wo+PNN&#~y#q2UAz=L%`=fMJg7?mx9L*hefSq7S`znoQZ)R3htKE-@u_a(R$3*F4ZO~D_OKE+GsT=zgiO*E ze&kqb=Zp@3odrn7@(28$Gyxa)z3I`F2+8L_i1&ZPPFfULJ|Jj-fb;@L@`H0$(KIXSnb5^zd`<<;@~w*t_I zYhvd=10I3@iAUA=Ts!|4j|wMX`MY~=Gg)FabBqNYj1%6Z|CG8^$`)D993>ZO8RAT+ z?1SJc07-RzC5=i5KhZSX{P$D_Gfg&ApeD05n3NUwlBiRr7jx#vTpc`L+41OVO|T^y zMoEsGm-0S1`<%C~IoE?daH*F#^|My6-oX@Uy4eZfOS-!9BqU726urX&sa|vaT2v=NOvpT%Z@i7>=Bk(ZfJQ&;au;pX0FBr0)9X^WMNrUWJtCWM@I0$6Qx7x_#Pg@0p zEIofC*6`<-f5uRVu@^(^v_hD~o5)<$LKwtC-l@K(457@u#QvKMix~+TOf9Jqy?5ao zUR4q;_aIBB#ZkiWe^yq~X)dgpR88f5lI`cg-$>Biv5`LgGgZk>jEaxQur>W-rm)uu zu|rcO0EJX*M`Cdjy||b?YE~hJ_-flZ`Cc41xy@$Kz_zXy7)VyCLmZ&r+Ii3Ta9}}?t3;0&i225|Hi?^WeDpV97M;!5OLqr zJj6p5GjMm^WQbMe#*oZw#&5xPibP3e_rD6mH#uv-0={07ZIf?Z)$H%+|p4LF17g!a}E=B_GW!;A#1pE^hv-y8UQ@v@k;PD$6`U&T(qdYP9X$BPd3 z2)T_qX^AWIfi=n+A}9P1hfdvR#pQLr^5g^%}A@gVe-^RUxH+ba{+_V(CQ8K>tio+P*`f zgoYHLNED%pzla+UQza6=p@e=yyeIXeY^LYli6FmowElN=MH}BJA|8^u!r{ks_`|V& zk$Mz2T@S4e1D*_$)Zud1dpI1W!>>YdmVh)p9ahIENB;Kg<$x)*tvF~t6pN6nNmD3)$cXFD$kmRqE$0LX9y8?MQN&&@&F(#)vwQYD+~EXvt4$5E>YYi2oV!UPvhMN5A*!F0}>| zkG^xSsK(H-GEnSq`a89utfH{1Lfr{jOo((i3X=m!B`qBEGl% z%*nJdVn+ARE*2Dq^){rNS{yzbiZU7#B^R;p&Mt|clBnj=UGnW9)VJfG%b?Pw`A`PO z!!gqvS5xB4XKyZB}>4PcEP!w;)_W6jjx`syGr08UOJ1gbKdifShC)>8VM9rhA%=<8;i1 zy>l-Nj_bhnh>+f8mREnⅅD2qEK%5Pz+^?~i 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 literal 0 HcmV?d00001 diff --git a/man/figures/README-unnamed-chunk-6-1.svg b/man/figures/README-unnamed-chunk-6-1.svg new file mode 100644 index 00000000..adde4157 --- /dev/null +++ b/man/figures/README-unnamed-chunk-6-1.svg @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/man/figures/README-unnamed-chunk-7-1.svg b/man/figures/README-unnamed-chunk-7-1.svg new file mode 100644 index 00000000..30058a57 --- /dev/null +++ b/man/figures/README-unnamed-chunk-7-1.svg @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/man/figures/README-unnamed-chunk-8-1.svg b/man/figures/README-unnamed-chunk-8-1.svg new file mode 100644 index 00000000..5ec5ba01 --- /dev/null +++ b/man/figures/README-unnamed-chunk-8-1.svg @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/growth_rate.Rd b/man/growth_rate.Rd index c4e82a09..0e22508c 100644 --- a/man/growth_rate.Rd +++ b/man/growth_rate.Rd @@ -84,8 +84,7 @@ filtering (a discrete spline) fit to \code{x} and \code{y}, via \code{genlasso::trendfilter()}, divided by the fitted value of the discrete spline at \code{x0}. } -} -\section{Log Scale}{ +\subsection{Log Scale}{ An alternative view for the growth rate of a function f in general is given by defining g(t) = log(f(t)), and then observing that g'(t) = f'(t) / @@ -96,7 +95,7 @@ method above ("rel_change", "linear_reg", "smooth_spline", and \code{log_scale = TRUE}. } -\section{Sliding Windows}{ +\subsection{Sliding Windows}{ For the local methods, "rel_change" and "linear_reg", we use a sliding window centered at the reference point of bandiwidth \code{h}. In other words, the @@ -108,7 +107,7 @@ sliding window contains all data in between January 1 and 14 (matching the behavior of \code{epi_slide()} with \code{before = h - 1} and \code{after = h}). } -\section{Additional Arguments}{ +\subsection{Additional Arguments}{ For the global methods, "smooth_spline" and "trend_filter", additional arguments can be specified via \code{...} for the underlying estimation @@ -133,7 +132,7 @@ rule, respectively. Default is "min" (going along with the default \code{cv = TR user. } } - +} \examples{ # COVID cases growth rate by state using default method relative change cases_deaths_subset \%>\% 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/revision_summary.Rd b/man/revision_summary.Rd index 590a1ed5..39a72b9a 100644 --- a/man/revision_summary.Rd +++ b/man/revision_summary.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/revision_analysis.R \name{revision_summary} \alias{revision_summary} -\title{A function to describe revision behavior for an archive} +\title{A function to describe revision behavior for an archive.} \usage{ revision_summary( epi_arch, @@ -70,8 +70,9 @@ threshold for when two floats are considered identical.} \description{ \code{revision_summary} removes all missing values (if requested), and then computes some basic statistics about the revision behavior of an archive, -returning a tibble summarizing the revisions per time_value+epi_key features. If \code{print_inform} is true, it -prints a concise summary. The columns returned are: +returning a tibble summarizing the revisions per time_value+epi_key +features. If \code{print_inform} is true, it prints a concise summary. The +columns returned are: \enumerate{ \item \code{n_revisions}: the total number of revisions for that entry \item \code{min_lag}: the minimum time to any value (if \code{drop_nas=FALSE}, this @@ -86,16 +87,15 @@ always excludes \code{NA} values) \item \code{rel_spread}: \code{spread} divided by the largest value (so it will always be less than 1). Note that this need not be the final value. It will be \code{NA} whenever \code{spread} is 0. -\item \code{time_near_latest}: This gives the lag when the value is within -\code{within_latest} (default 20\%) of the value at the latest time. For example, -consider the series (0,20, 99, 150, 102, 100); then \code{time_near_latest} is -the 5th index, since even though 99 is within 20\%, it is outside the window -afterwards at 150. +\item \code{time_near_latest}: the time taken for the revisions to settle to within +\code{within_latest} (default 20\%) of the final value and stay there. For +example, consider the series (0, 20, 99, 150, 102, 100); then +\code{time_near_latest} is 5, since even though 99 is within 20\%, it is outside +the window afterwards at 150. } } \examples{ - revision_example <- revision_summary(archive_cases_dv_subset, percent_cli) - revision_example \%>\% arrange(desc(spread)) + } 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..2eb0be6b --- /dev/null +++ b/man/validate_version_bound.Rd @@ -0,0 +1,32 @@ +% 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. +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/pkgdown-watch.R b/pkgdown-watch.R new file mode 100644 index 00000000..b36a9b1b --- /dev/null +++ b/pkgdown-watch.R @@ -0,0 +1,61 @@ +# Run with: Rscript pkgdown-watch.R +# +# Modifying this: https://gist.github.com/gadenbuie/d22e149e65591b91419e41ea5b2e0621 +# - Removed docopts cli interface and various configs/features I didn't need. +# - Sped up reference building by not running examples. +# +# Note that the `pattern` regex is case sensitive, so make sure your Rmd files +# end in `.Rmd` and not `.rmd`. +# +# Also I had issues with `pkgdown::build_reference()` not working, so I just run +# it manually when I need to. + +rlang::check_installed(c("pkgdown", "servr", "devtools", "here", "cli", "fs")) + +pkg <- pkgdown::as_pkgdown(".") + +servr::httw( + dir = here::here("docs"), + watch = here::here(), + pattern = "[.](Rm?d|y?ml|s[ac]ss|css|js)$", + handler = function(files) { + devtools::load_all() + + files_rel <- fs::path_rel(files, start = getwd()) + cli::cli_inform("{cli::col_yellow('Updated')} {.val {files_rel}}") + + articles <- grep("vignettes.+Rmd$", files, value = TRUE) + + if (length(articles) == 1) { + name <- fs::path_ext_remove(fs::path_rel(articles, fs::path(pkg$src_path, "vignettes"))) + pkgdown::build_article(name, pkg) + } else if (length(articles) > 1) { + pkgdown::build_articles(pkg, preview = FALSE) + } + + refs <- grep("man.+R(m?d)?$", files, value = TRUE) + if (length(refs)) { + # Doesn't work for me, so I run it manually. + # pkgdown::build_reference(pkg, preview = FALSE, examples = FALSE, lazy = FALSE) + } + + pkgdown <- grep("pkgdown", files, value = TRUE) + if (length(pkgdown) && !pkgdown %in% c(articles, refs)) { + pkgdown::init_site(pkg) + } + + pkgdown_index <- grep("index[.]Rmd$", files_rel, value = TRUE) + if (length(pkgdown_index)) { + devtools::build_rmd(pkgdown_index) + pkgdown::build_home(pkg) + } + + readme <- grep("README[.]rmd$", files, value = TRUE, ignore.case = TRUE) + if (length(readme)) { + devtools::build_readme(".") + pkgdown::build_home(pkg) + } + + cli::cli_alert("Site rebuild done!") + } +) diff --git a/tests/testthat/test-archive.R b/tests/testthat/test-archive.R index 1a03141b..0a06cf79 100644 --- a/tests/testthat/test-archive.R +++ b/tests/testthat/test-archive.R @@ -90,22 +90,18 @@ test_that("epi_archives are correctly instantiated with a variety of data types" ea1 <- as_epi_archive(df, compactify = FALSE) expect_equal(key(ea1$DT), c("geo_value", "time_value", "version")) - expect_null(ea1$additional_metadata) ea2 <- as_epi_archive(df, other_keys = "value", compactify = FALSE) expect_equal(key(ea2$DT), c("geo_value", "time_value", "value", "version")) - expect_null(ea2$additional_metadata) # Tibble tib <- tibble::tibble(df, code = "x") ea3 <- as_epi_archive(tib, compactify = FALSE) expect_equal(key(ea3$DT), c("geo_value", "time_value", "version")) - expect_null(ea3$additional_metadata) ea4 <- as_epi_archive(tib, other_keys = "code", compactify = FALSE) expect_equal(key(ea4$DT), c("geo_value", "time_value", "code", "version")) - expect_null(ea4$additional_metadata) # Keyed data.table kdt <- data.table::data.table( @@ -120,12 +116,10 @@ test_that("epi_archives are correctly instantiated with a variety of data types" ea5 <- as_epi_archive(kdt, compactify = FALSE) # Key from data.table isn't absorbed when as_epi_archive is used expect_equal(key(ea5$DT), c("geo_value", "time_value", "version")) - expect_null(ea5$additional_metadata) ea6 <- as_epi_archive(kdt, other_keys = "value", compactify = FALSE) # Mismatched keys, but the one from as_epi_archive overrides expect_equal(key(ea6$DT), c("geo_value", "time_value", "value", "version")) - expect_null(ea6$additional_metadata) # Unkeyed data.table udt <- data.table::data.table( @@ -138,11 +132,9 @@ test_that("epi_archives are correctly instantiated with a variety of data types" ea7 <- as_epi_archive(udt, compactify = FALSE) expect_equal(key(ea7$DT), c("geo_value", "time_value", "version")) - expect_null(ea7$additional_metadata) ea8 <- as_epi_archive(udt, other_keys = "code", compactify = FALSE) expect_equal(key(ea8$DT), c("geo_value", "time_value", "code", "version")) - expect_null(ea8$additional_metadata) # epi_df edf1 <- cases_deaths_subset %>% @@ -151,11 +143,9 @@ test_that("epi_archives are correctly instantiated with a variety of data types" ea9 <- as_epi_archive(edf1, compactify = FALSE) expect_equal(key(ea9$DT), c("geo_value", "time_value", "version")) - expect_null(ea9$additional_metadata) ea10 <- as_epi_archive(edf1, other_keys = "code", compactify = FALSE) expect_equal(key(ea10$DT), c("geo_value", "time_value", "code", "version")) - expect_null(ea10$additional_metadata) # Keyed epi_df edf2 <- data.frame( @@ -172,11 +162,9 @@ test_that("epi_archives are correctly instantiated with a variety of data types" ea11 <- as_epi_archive(edf2, compactify = FALSE) expect_equal(key(ea11$DT), c("geo_value", "time_value", "version")) - expect_null(ea11$additional_metadata) ea12 <- as_epi_archive(edf2, other_keys = "misc", compactify = FALSE) expect_equal(key(ea12$DT), c("geo_value", "time_value", "misc", "version")) - expect_null(ea12$additional_metadata) }) test_that("`epi_archive` rejects nonunique keys", { diff --git a/vignettes/.gitignore b/vignettes/.gitignore deleted file mode 100644 index bc04c23a..00000000 --- a/vignettes/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.html -*.R -.DS_Store diff --git a/vignettes/_common.R b/vignettes/_common.R new file mode 100644 index 00000000..b1882b73 --- /dev/null +++ b/vignettes/_common.R @@ -0,0 +1,24 @@ +knitr::opts_chunk$set( + digits = 3, + comment = "#>", + collapse = TRUE, + cache = TRUE, + dev = "svg", + dev.args = list(bg = "transparent"), + dpi = 300, + cache.lazy = FALSE, + out.width = "90%", + fig.align = "center", + fig.width = 9, + fig.height = 6 +) +ggplot2::theme_set(ggplot2::theme_bw()) +options( + dplyr.print_min = 6, + dplyr.print_max = 6, + pillar.max_footer_lines = 2, + pillar.min_chars = 15, + stringr.view_n = 6, + pillar.bold = TRUE, + width = 77 +) diff --git a/vignettes/aggregation.Rmd b/vignettes/aggregation.Rmd deleted file mode 100644 index 0b65c71f..00000000 --- a/vignettes/aggregation.Rmd +++ /dev/null @@ -1,235 +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. - -The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: - -```{r, warning = FALSE, message = FALSE} -library(epiprocess) -library(dplyr) -library(readr) - -x <- covid_incidence_county_subset -``` - -The data can also be fetched from the Delphi Epidata API with the following query: -```{r, message = FALSE, eval = FALSE, warning = FALSE} -library(epidatr) - -d <- as.Date("2024-03-20") - -# 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), - as_of = d -) %>% - 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 = d) -``` - -The data contains 16,212 rows and 5 columns. - -## 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/archive.Rmd b/vignettes/archive.Rmd deleted file mode 100644 index 86fc2c2b..00000000 --- a/vignettes/archive.Rmd +++ /dev/null @@ -1,694 +0,0 @@ ---- -title: Work with archive objects and data revisions -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Work with archive objects and data revisions} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -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 -up-to-date signal values as of some given time, an `epi_archive` object stores -the full version history of a data set. Many signals of interest for -epidemiological tracking are subject to revision (some more than others) and -paying attention to data revisions can be important for all sorts of downstream -data analysis and modeling tasks. - -This vignette walks through working with `epi_archive()` objects and demonstrates -some of their key functionality. We'll work with a signal on the percentage of -doctor's visits with CLI (COVID-like illness) computed from medical insurance -claims, available through the [COVIDcast -API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast.html). This -signal is subject to very heavy and regular revision; you can read more about it -on its [API documentation -page](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html). - -The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: - -```{r, message = FALSE, warning = FALSE} -library(epiprocess) -library(data.table) -library(dplyr) -library(purrr) -library(ggplot2) - -# This fetches the raw data backing the archive_cases_dv_subset object. -dv <- archive_cases_dv_subset$DT %>% - as_tibble() -``` - -The data can also be fetched from the Delphi Epidata API with the following query: -```{r, message = FALSE, warning = FALSE, eval = FALSE} -library(epidatr) - -dv <- pub_covidcast( - source = "doctor-visits", - signals = "smoothed_adj_cli", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx", - time_values = epirange(20200601, 20211201), - issues = epirange(20200601, 20211201) -) %>% - rename(version = issue, percent_cli = value) -``` - -## Getting data into `epi_archive` format - -An `epi_archive()` object can be constructed from a data frame, data table, or -tibble, provided that it has (at least) the following columns: - -* `geo_value`: the geographic value associated with each row of measurements. -* `time_value`: the time value associated with each row of measurements. -* `version`: the time value specifying the version for each row of measurements. - For example, if in a given row the `version` is January 15, 2022 and - `time_value` is January 14, 2022, then this row contains the measurements of - the data for January 14, 2022 that were available one day later. - -As we can see from the above, the data frame returned by -`epidatr::pub_covidcast()` has the columns required for the `epi_archive` -format, with `issue` playing the role of `version`. We can now use -`as_epi_archive()` to bring it into `epi_archive` format. For removal of -redundant version updates in `as_epi_archive` using compactify, please refer to -the [compactify vignette](articles/compactify.html). - -```{r} -x <- dv %>% - select(geo_value, time_value, version, percent_cli) %>% - as_epi_archive(compactify = TRUE) - -class(x) -print(x) -``` - -An `epi_archive` is consists of a primary field `DT`, which is a data table -(from the `data.table` package) that has at least the required columns -`geo_value`, `time_value`, and `version`; and other metadata fields, such as -`geo_type`. - -The variables `geo_value`, `time_value`, `version` serve as **key variables** -for the data table, as well as any other specified in the metadata (described -below). There can only be a single row per unique combination of key variables, -and therefore the key variables are critical for figuring out how to generate a -snapshot of data from the archive, as of a given version (also described below). - -```{r, error=TRUE} -key(x$DT) -``` - -In general, the last version of each observation is carried forward (LOCF) to -fill in data between recorded versions. - -## Some details on metadata - -The following pieces of metadata are included as fields in an `epi_archive` -object: - -* `geo_type`: the type for the geo values. - -Metadata for an `epi_archive` object `x` can be accessed (and altered) directly, -as in `x$geo_type`, etc. Just like `as_epi_df()`, the function -`as_epi_archive()` attempts to guess metadata fields when an `epi_archive` -object is instantiated, if they are not explicitly specified in the function -call (as it did in the case above). - -## Summarizing Revision Behavior - -There are many ways to examine the ways that signals change across different -revisions. The simplest that is included directly in epiprocess is -`revision_summary()`, which computes simple summary statistics for each key (by -default, `(geo_value,time_value)` pairs), such as the lag to the first value -(latency). In addition to the per key summary, it also returns an overall -summary: - -```{r} -revision_details <- revision_summary(x, print_inform = TRUE) -``` - -So as was mentioned at the top, this is clearly a data set where basically -everything has some amount of revisions, only 0.37% have no revision at all, and -0.92 have fewer than 3. Over 94% change by more than 10%. On the other hand, -most are within plus or minus 20% within 5-9 days, so the revisions converge -relatively quickly, even if the revisions continue for longer. - -To do more detailed analysis than is possible with the above printing, we have -`revision_details`: - -```{r} -revision_details %>% - group_by(geo_value) %>% - summarise( - n_rev = mean(n_revisions), - min_lag = min(min_lag), - max_lag = max(max_lag), - spread = mean(spread), - rel_spread = mean(rel_spread), - time_near_latest = mean(time_near_latest) - ) -``` - -Most of the states have similar stats on most of these features, except for -Florida, which takes nearly double the amount of time to get close to the right -value, with California not too far behind. - -## Producing snapshots in `epi_df` form - -A key method of an `epi_archive` class is `epix_as_of()`, which generates a -snapshot of the archive in `epi_df` format. This represents the most up-to-date -values of the signal variables as of a given version. - -```{r} -x_snapshot <- epix_as_of(x, as.Date("2021-06-01")) -class(x_snapshot) -head(x_snapshot) -max(x_snapshot$time_value) -attributes(x_snapshot)$metadata$as_of -``` - -We can see that the max time value in the `epi_df` object `x_snapshot` that was -generated from the archive is May 29, 2021, even though the specified version -date was June 1, 2021. From this we can infer that the doctor's visits signal -was 2 days latent on June 1. Also, we can see that the metadata in the `epi_df` -object has the version date recorded in the `as_of` field. - -Below, we pull several snapshots from the archive, spaced one month apart. We -overlay the corresponding signal curves as colored lines, with the version dates -marked by dotted vertical lines, and draw the latest curve in black (from the -latest snapshot `x_latest` that the archive can provide). - -```{r, fig.width = 8, fig.height = 7} -theme_set(theme_bw()) - -x_latest <- epix_as_of(x, x$versions_end) -self_max <- max(x$DT$version) -versions <- seq(as.Date("2020-06-01"), self_max - 1, by = "1 month") -snapshots <- map_dfr(versions, function(v) { - epix_as_of(x, v) %>% mutate(version = v) -}) %>% - bind_rows( - x_latest %>% mutate(version = self_max) - ) %>% - mutate(latest = version == self_max) - -ggplot( - snapshots %>% filter(!latest), - aes(x = time_value, y = percent_cli) -) + - geom_line(aes(color = factor(version)), na.rm = TRUE) + - geom_vline(aes(color = factor(version), xintercept = version), lty = 2) + - facet_wrap(~geo_value, scales = "free_y", ncol = 1) + - scale_x_date(minor_breaks = "month", date_labels = "%b %y") + - labs(x = "Date", y = "% of doctor's visits with CLI") + - theme(legend.position = "none") + - geom_line( - data = snapshots %>% filter(latest), - aes(x = time_value, y = percent_cli), - inherit.aes = FALSE, color = "black", na.rm = TRUE - ) -``` - -We can see some interesting and highly nontrivial revision behavior: at some -points in time the provisional data snapshots grossly underestimate the latest -curve (look in particular at Florida close to the end of 2021), and at others -they overestimate it (both states towards the beginning of 2021), though not -quite as dramatically. Modeling the revision process, which is often called -*backfill modeling*, is an important statistical problem in it of itself. - -## Merging `epi_archive` objects - -Now we demonstrate how to merge two `epi_archive` objects together, e.g., so -that grabbing data from multiple sources as of a particular version can be -performed with a single `epix_as_of` call. The function `epix_merge()` is made -for this purpose. Below we merge the working `epi_archive` of versioned -percentage CLI from outpatient visits to another one of versioned COVID-19 case -reporting data, which we fetch the from the [COVIDcast -API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast.html/), on the -rate scale (counts per 100,000 people in the population). - -When merging archives, unless the archives have identical data release patterns, -`NA`s can be introduced in the non-key variables for a few reasons: -- to represent the "value" of an observation before its initial release (when we - need to pair it with additional observations from the other archive that have - been released) -- to represent the "value" of an observation that has no recorded versions at - all (in the same sort of situation) -- if requested via `sync="na"`, to represent potential update data that we do - not yet have access to (e.g., due to encountering issues while attempting to - download the currently available version data for one of the archives, but not - the other). - -```{r, message = FALSE, warning = FALSE, eval=FALSE} -y <- pub_covidcast( - source = "jhu-csse", - signals = "confirmed_7dav_incidence_prop", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl,ny,tx", - time_values = epirange(20200601, 20211201), - issues = epirange(20200601, 20211201) -) %>% - select(geo_value, time_value, version = issue, case_rate_7d_av = value) %>% - as_epi_archive(compactify = TRUE) - -x <- epix_merge(x, y, sync = "locf", compactify = TRUE) -print(x) -head(x$DT) -``` - -```{r, echo=FALSE, message=FALSE, warning=FALSE} -x <- archive_cases_dv_subset -print(x) -head(x$DT) -``` - -## Sliding version-aware computations - -Lastly, we demonstrate another key method for archives, which is the -`epix_slide()`. It works just like `epi_slide()` does for an `epi_df` object, -but with one key difference: it performs version-aware computations. That is, -for the computation at any given reference time t, it only uses **data that -would have been available as of t**. - -For the demonstration, we'll revisit the forecasting example from the [slide -vignette](https://cmu-delphi.github.io/epiprocess/articles/slide.html), and now -we'll build a forecaster that uses properly-versioned data (that would have been -available in real-time) to forecast future COVID-19 case rates from current and -past COVID-19 case rates, as well as current and past values of the outpatient -CLI signal from medical claims. We'll extend the `prob_ar()` function from the -slide vignette to accomodate exogenous variables in the autoregressive model, -which is often referred to as an ARX model. - -```{r} -prob_arx <- function(x, y, lags = c(0, 7, 14), ahead = 7, min_train_window = 20, - lower_level = 0.05, upper_level = 0.95, symmetrize = TRUE, - intercept = FALSE, nonneg = TRUE) { - # Return NA if insufficient training data - if (length(y) < min_train_window + max(lags) + ahead) { - return(data.frame(point = NA, lower = NA, upper = NA)) - } - - # Useful transformations - if (!missing(x)) { - x <- data.frame(x, y) - } else { - x <- data.frame(y) - } - if (!is.list(lags)) lags <- list(lags) - lags <- rep(lags, length.out = ncol(x)) - - # Build features and response for the AR model, and then fit it - dat <- do.call( - data.frame, - unlist( # Below we loop through and build the lagged features - purrr::map(seq_len(ncol(x)), function(i) { - purrr::map(lags[[i]], function(j) lag(x[, i], n = j)) - }), - recursive = FALSE - ) - ) - names(dat) <- paste0("x", seq_len(ncol(dat))) - if (intercept) dat$x0 <- rep(1, nrow(dat)) - dat$y <- lead(y, n = ahead) - obj <- lm(y ~ . + 0, data = dat) - - # Use LOCF to fill NAs in the latest feature values, make a prediction - setDT(dat) - setnafill(dat, type = "locf") - point <- predict(obj, newdata = tail(dat, 1)) - - # Compute a band - r <- residuals(obj) - s <- ifelse(symmetrize, -1, NA) # Should the residuals be symmetrized? - q <- quantile(c(r, s * r), probs = c(lower_level, upper_level), na.rm = TRUE) - lower <- point + q[1] - upper <- point + q[2] - - # Clip at zero if we need to, then return - if (nonneg) { - point <- max(point, 0) - lower <- max(lower, 0) - upper <- max(upper, 0) - } - return(data.frame(point = point, lower = lower, upper = upper)) -} -``` - -Next we slide this forecaster over the working `epi_archive` object, in order to -forecast COVID-19 case rates 7 days into the future. - -```{r} -fc_time_values <- seq(as.Date("2020-08-01"), as.Date("2021-11-30"), by = "1 month") - -z <- x %>% - group_by(geo_value) %>% - epix_slide( - fc = prob_arx(x = percent_cli, y = case_rate_7d_av, ahead = 7), - .before = 119, - .versions = fc_time_values - ) %>% - ungroup() - -head(z, 10) -``` - -We get back a tibble `z` with the grouping variables (here geo value), the -(reference) time values, and a ["packed"][tidyr::pack] data frame column `fc` -containing `fc$point`, `fc$lower`, and `fc$upper` that correspond to the point -forecast, and the lower and upper endpoints of the 95\% prediction band, -respectively. (We could also have used `, prob_ar(cases_7dav)` to get three -separate columns `point`, `lower`, and `upper`, or used `fc = -list(prob_ar(cases_7dav))` to make an `fc` column with a ["nested"][tidyr::nest] -format (list of data frames) instead.) - -On the whole, `epix_slide()` works similarly to `epix_slide()`, though there are -a few notable differences, even apart from the version-aware aspect. You can -read the documentation for `epix_slide()` for details. - -We finish off by comparing version-aware and -unaware forecasts at various -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, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} -x_latest <- epix_as_of(x, x$versions_end) - -# Simple function to produce forecasts k weeks ahead -forecast_k_week_ahead <- function(x, ahead = 7, as_of = TRUE) { - if (as_of) { - x %>% - group_by(geo_value) %>% - epix_slide( - fc = prob_arx(.data$percent_cli, .data$case_rate_7d_av, ahead = ahead), .before = 119, - .versions = fc_time_values - ) %>% - mutate(target_date = .data$version + ahead, as_of = TRUE) %>% - ungroup() - } else { - x_latest %>% - group_by(geo_value) %>% - epi_slide( - fc = prob_arx(.data$percent_cli, .data$case_rate_7d_av, ahead = ahead), .window_size = 120, - .ref_time_values = fc_time_values - ) %>% - mutate(target_date = .data$time_value + ahead, as_of = FALSE) %>% - ungroup() - } -} - -# Generate the forecasts, and bind them together -fc <- bind_rows( - forecast_k_week_ahead(x, ahead = 7, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 14, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 21, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 28, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 7, as_of = FALSE), - forecast_k_week_ahead(x, ahead = 14, as_of = FALSE), - forecast_k_week_ahead(x, ahead = 21, as_of = FALSE), - forecast_k_week_ahead(x, ahead = 28, as_of = FALSE) -) - -# 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) + - geom_line( - data = x_latest, aes(x = time_value, y = case_rate_7d_av), - inherit.aes = FALSE, color = "gray50" - ) + - geom_line(aes(y = fc$point)) + - geom_point(aes(y = fc$point), size = 0.5) + - geom_vline(aes(xintercept = time_value), linetype = 2, alpha = 0.5) + - facet_grid(vars(geo_value), vars(as_of), scales = "free") + - scale_x_date(minor_breaks = "month", date_labels = "%b %y") + - labs(x = "Date", y = "Reported COVID-19 case rates") + - theme(legend.position = "none") -``` - -Each row displays the forecasts for a different location (CA, FL, NY, and TX), and each -column corresponds to whether properly-versioned data is used (`FALSE` means no, -and `TRUE` means yes). We can see that the properly-versioned forecaster is, at -some points in time, more problematic; for example, it massively overpredicts -the peak in both locations in winter wave of 2020. However, performance is -pretty poor across the board here, whether or not properly-versioned data is -used. Similar to what we saw in the [slide -vignette](https://cmu-delphi.github.io/epiprocess/articles/archive.html), the -ARX forecasts can too volatile, overconfident, or both. - -Some of the volatility can be attenuated here by training an ARX model jointly -over locations; the [advanced sliding -vignette](https://cmu-delphi.github.io/epiprocess/articles/advanced.html) gives -a demonstration of how to do this. But really, the -[`epipredict`](https://cmu-delphi.github.io/epipredict/) package, which builds -off the data structures and functionality in the current package, is the place -to look for more robust forecasting methodology. The forecasters that appear in -the vignettes in the current package are only meant to demo the slide -functionality with some of the most basic forecasting methodology possible. - -## Sliding version-aware computations with geo-pooling - -First, we fetch the versioned data and build the archive. - -```{r, message = FALSE, warning = FALSE, eval =FALSE} -library(epidatr) -library(data.table) -library(ggplot2) -theme_set(theme_bw()) - -y1 <- pub_covidcast( - source = "doctor-visits", - signals = "smoothed_adj_cli", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl", - time_values = epirange(20200601, 20211201), - issues = epirange(20200601, 20211201) -) - -y2 <- pub_covidcast( - source = "jhu-csse", - signal = "confirmed_7dav_incidence_prop", - geo_type = "state", - time_type = "day", - geo_values = "ca,fl", - time_values = epirange(20200601, 20211201), - issues = epirange(20200601, 20211201) -) - -x <- y1 %>% - select(geo_value, time_value, - version = issue, - percent_cli = value - ) %>% - as_epi_archive(compactify = FALSE) - -# mutating merge operation: -x <- epix_merge( - x, - y2 %>% - select(geo_value, time_value, - version = issue, - case_rate_7d_av = value - ) %>% - as_epi_archive(compactify = FALSE), - sync = "locf", - compactify = FALSE -) -``` - -```{r, message = FALSE, echo =FALSE} -library(data.table) -library(ggplot2) -theme_set(theme_bw()) - -x <- archive_cases_dv_subset$DT %>% - filter(geo_value %in% c("ca", "fl")) %>% - as_epi_archive(compactify = FALSE) -``` - -Next, we extend the ARX function to handle multiple geo values, since in the -present case, we will not be grouping by geo value and each slide computation -will be run on multiple geo values at once. Note that, because `epix_slide()` -only returns the grouping variables, `time_value`, and the slide computations in -the eventual returned tibble, we need to include `geo_value` as a column in the -output data frame from our ARX computation. - -```{r} -library(tidyr) -library(purrr) - -prob_arx_args <- function(lags = c(0, 7, 14), - ahead = 7, - min_train_window = 20, - lower_level = 0.05, - upper_level = 0.95, - symmetrize = TRUE, - intercept = FALSE, - nonneg = TRUE) { - return(list( - lags = lags, - ahead = ahead, - min_train_window = min_train_window, - lower_level = lower_level, - upper_level = upper_level, - symmetrize = symmetrize, - intercept = intercept, - nonneg = nonneg - )) -} - -prob_arx <- function(x, y, geo_value, time_value, args = prob_arx_args()) { - # Return NA if insufficient training data - if (length(y) < args$min_train_window + max(args$lags) + args$ahead) { - return(data.frame( - geo_value = unique(geo_value), # Return geo value! - point = NA, lower = NA, upper = NA - )) - } - - # Set up x, y, lags list - if (!missing(x)) { - x <- data.frame(x, y) - } else { - x <- data.frame(y) - } - if (!is.list(args$lags)) args$lags <- list(args$lags) - args$lags <- rep(args$lags, length.out = ncol(x)) - - # Build features and response for the AR model, and then fit it - dat <- tibble(i = seq_len(ncol(x)), lag = args$lags) %>% - unnest(lag) %>% - mutate(name = paste0("x", seq_len(nrow(.)))) %>% # nolint: object_usage_linter - # One list element for each lagged feature - pmap(function(i, lag, name) { - tibble( - geo_value = geo_value, - time_value = time_value + lag, # Shift back - !!name := x[, i] - ) - }) %>% - # One list element for the response vector - c(list( - tibble( - geo_value = geo_value, - time_value = time_value - args$ahead, # Shift forward - y = y - ) - )) %>% - # Combine them together into one data frame - reduce(full_join, by = c("geo_value", "time_value")) %>% - arrange(time_value) - if (args$intercept) dat$x0 <- rep(1, nrow(dat)) - obj <- lm(y ~ . + 0, data = select(dat, -geo_value, -time_value)) - - # Use LOCF to fill NAs in the latest feature values (do this by geo value) - setDT(dat) # Convert to a data.table object by reference - cols <- setdiff(names(dat), c("geo_value", "time_value")) - dat[, (cols) := nafill(.SD, type = "locf"), .SDcols = cols, by = "geo_value"] - - # Make predictions - test_time_value <- max(time_value) - point <- predict( - obj, - newdata = dat %>% - dplyr::group_by(geo_value) %>% - dplyr::filter(time_value == test_time_value) - ) - - # Compute bands - r <- residuals(obj) - s <- ifelse(args$symmetrize, -1, NA) # Should the residuals be symmetrized? - q <- quantile(c(r, s * r), probs = c(args$lower, args$upper), na.rm = TRUE) - lower <- point + q[1] - upper <- point + q[2] - - # Clip at zero if we need to, then return - if (args$nonneg) { - point <- pmax(point, 0) - lower <- pmax(lower, 0) - upper <- pmax(upper, 0) - } - return(data.frame( - geo_value = unique(geo_value), # Return geo value! - point = point, lower = lower, upper = upper - )) -} -``` - -We now make forecasts on the archive and compare to forecasts on the latest -data. - -```{r, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} -# Latest snapshot of data, and forecast dates -x_latest <- epix_as_of(x, version = max(x$DT$version)) -fc_time_values <- seq(as.Date("2020-08-01"), - as.Date("2021-11-30"), - by = "1 month" -) - -# Simple function to produce forecasts k weeks ahead -forecast_k_week_ahead <- function(x, ahead = 7, as_of = TRUE) { - if (as_of) { - x %>% - epix_slide( - fc = prob_arx(.data$percent_cli, .data$case_rate_7d_av, .data$geo_value, .data$time_value, - args = prob_arx_args(ahead = ahead) - ), - .before = 219, .versions = fc_time_values - ) %>% - mutate( - target_date = .data$version + ahead, as_of = TRUE, - geo_value = .data$fc$geo_value - ) - } else { - x_latest %>% - epi_slide( - fc = prob_arx(.data$percent_cli, .data$case_rate_7d_av, .data$geo_value, .data$time_value, - args = prob_arx_args(ahead = ahead) - ), - .window_size = 220, .ref_time_values = fc_time_values - ) %>% - mutate(target_date = .data$time_value + ahead, as_of = FALSE) - } -} - -# Generate the forecasts, and bind them together -fc <- bind_rows( - forecast_k_week_ahead(x, ahead = 7, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 14, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 21, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 28, as_of = TRUE), - forecast_k_week_ahead(x, ahead = 7, as_of = FALSE), - forecast_k_week_ahead(x, ahead = 14, as_of = FALSE), - forecast_k_week_ahead(x, ahead = 21, as_of = FALSE), - forecast_k_week_ahead(x, ahead = 28, as_of = FALSE) -) - -# 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) + - geom_line( - data = x_latest, aes(x = time_value, y = case_rate_7d_av), - inherit.aes = FALSE, color = "gray50" - ) + - geom_line(aes(y = fc$point)) + - geom_point(aes(y = fc$point), size = 0.5) + - geom_vline(aes(xintercept = time_value), linetype = 2, alpha = 0.5) + - facet_grid(vars(geo_value), vars(as_of), scales = "free") + - scale_x_date(minor_breaks = "month", date_labels = "%b %y") + - labs(x = "Date", y = "Reported COVID-19 case rates") + - theme(legend.position = "none") -``` - -We can see that these forecasts, which come from training an ARX model jointly -over CA and FL, exhibit generally less variability and wider prediction bands -compared to the ones from the archive vignette, which come from training a -separate ARX model on each state. As in the archive vignette, we can see a -difference between version-aware (right column) and -unaware (left column) -forecasting, as well. - -## 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. - -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. diff --git a/vignettes/compactify.Rmd b/vignettes/compactify.Rmd index 72a2d266..a791f67a 100644 --- a/vignettes/compactify.Rmd +++ b/vignettes/compactify.Rmd @@ -7,6 +7,10 @@ vignette: > %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +source(here::here("vignettes", "_common.R")) +``` + ## 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 073812b3..95c1ac50 100644 --- a/vignettes/correlation.Rmd +++ b/vignettes/correlation.Rmd @@ -1,14 +1,18 @@ --- -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} +source(here::here("vignettes", "_common.R")) +``` + 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 @@ -27,6 +31,7 @@ x <- covid_case_death_rates_extended %>% ``` The data can also be fetched from the Delphi Epidata API with the following query: + ```{r, eval = FALSE} library(epidatr) @@ -73,7 +78,6 @@ time value, and by geo value. The former is obtained via `cor_by = time_value`. ```{r, message = FALSE, warning = FALSE} library(ggplot2) -theme_set(theme_bw()) z1 <- epi_cor(x, case_rate, death_rate, cor_by = "time_value") diff --git a/vignettes/epi_archive.Rmd b/vignettes/epi_archive.Rmd new file mode 100644 index 00000000..38dc65e6 --- /dev/null +++ b/vignettes/epi_archive.Rmd @@ -0,0 +1,472 @@ +--- +title: Working with epi_archive objects and data revisions +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Working with epi_archive objects and data revisions} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +source(here::here("vignettes", "_common.R")) +``` + +The `epi_archive` data structure provided by `epiprocess` provides convenient +ways to work with data sets that are subject to revision (a common occurrence in +the public health space, as situational awareness improves). In comparison to an +`epi_df` object, which can viewed as a data snapshot at a point in time, an +`epi_archive` object stores the full version history of a data set. Paying +attention to data revisions can be important for all sorts of downstream data +analysis and modeling tasks. + +In this vignette we will: + +- construct an `epi_archive` object from a data frame, +- summarize revision behavior in the archive, +- produce snapshots of the data in `epi_df` form, +- merge `epi_archive` objects together, +- run a simple autoregressive forecaster (version-unaware) on a single date, +- slide a simple autoregressive forecaster (version-unaware), +- slide a simple autoregressive forecaster (version-aware), +- compare version-aware and -unaware forecasts. + +## Getting data into `epi_archive` format + +We will work with a signal on the percentage of doctor's visits with CLI +(COVID-like illness) computed from medical insurance claims, available through +the [COVIDcast +API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast.html). This +signal is subject to very heavy and regular revision; you can read more about it +on its [API documentation +page](https://cmu-delphi.github.io/delphi-epidata/api/covidcast-signals/doctor-visits.html). + +The data is included in this package (via the [`epidatasets` +package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r, message = FALSE, warning = FALSE} +library(epiprocess) +library(data.table) +library(dplyr) +library(purrr) +library(ggplot2) + +# This fetches the raw data backing the archive_cases_dv_subset object. +dv <- archive_cases_dv_subset$DT %>% + as_tibble() +``` + +The data can also be fetched from the Delphi Epidata API with the following query: + +```{r, message = FALSE, warning = FALSE, eval = FALSE} +library(epidatr) + +dv <- pub_covidcast( + source = "doctor-visits", + signals = "smoothed_adj_cli", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200601, 20211201), + issues = epirange(20200601, 20211201) +) %>% + rename(version = issue, percent_cli = value) +``` + +An `epi_archive()` object can be constructed from a data frame, data table, or +tibble, provided that it has (at least) the following columns: + +* `geo_value`: the geographic value associated with each row of measurements. +* `time_value`: the time value associated with each row of measurements. +* `version`: the time value specifying the version for each row of measurements. + For example, if in a given row the `version` is January 15, 2022 and + `time_value` is January 14, 2022, then this row contains the measurements of + the data for January 14, 2022 that were available one day later. + +As we can see from the above, the data frame returned by +`epidatr::pub_covidcast()` has the columns required for the `epi_archive` +format, with `issue` playing the role of `version`. We can now use +`as_epi_archive()` to bring it into `epi_archive` format. + +```{r} +dv_archive <- dv %>% + select(geo_value, time_value, version, percent_cli) %>% + as_epi_archive(compactify = TRUE) +dv_archive +``` + +See the `epi_archive()` documentation for more information about its internal +structure. + +## Producing snapshots in `epi_df` form + +A key method of an `epi_archive` class is `epix_as_of()`, which generates a +snapshot of the archive in `epi_df` format. This represents the most up-to-date +values of the signal variables as of a given version. + +```{r} +edf <- epix_as_of(dv_archive, as.Date("2021-06-01")) +print(edf) +print(max(edf$time_value)) +``` + +Note that that the max time value in the `epi_df` object is May 29, 2021, even +though the specified version date was June 1, 2021 (note that the `as_of` field +printed above helps us see the date of the snapshot). From this we can infer +that the doctor's visits signal was 2 days latent on June 1. + +Now, let's investigate how much this data was revised. We will plot the most +up-to-date version of the time series in black (`edf_latest` below) and then +overlay several revisions from the archive, spaced one month apart, as colored +lines (`snapshots` below). We will also mark the version dates with dotted +vertical lines. + +```{r} +edf_latest <- epix_as_of(dv_archive, dv_archive$versions_end) +max_version <- max(dv_archive$DT$version) +versions <- seq(as.Date("2020-06-01"), max_version - 1, by = "1 month") +monthly_snapshots <- map(versions, function(v) { + epix_as_of(dv_archive, v) %>% mutate(version = v) +}) %>% + bind_rows( + edf_latest %>% mutate(version = max_version) + ) %>% + mutate(latest = version == max_version) + +ggplot( + monthly_snapshots %>% filter(!latest), + aes(x = time_value, y = percent_cli) +) + + geom_line(aes(color = factor(version)), na.rm = TRUE) + + geom_vline(aes(color = factor(version), xintercept = version), lty = 2) + + facet_wrap(~geo_value, scales = "free_y", ncol = 1) + + scale_x_date(minor_breaks = "month", date_labels = "%b %y") + + labs(x = "Date", y = "% of doctor's visits with CLI") + + theme(legend.position = "none") + + geom_line( + data = monthly_snapshots %>% filter(latest), + aes(x = time_value, y = percent_cli), + inherit.aes = FALSE, color = "black", na.rm = TRUE + ) +``` + +We can see some interesting and highly nontrivial revision behavior: at some +points in time the provisional data snapshots grossly underestimate the latest +curve (look in particular at Florida close to the end of 2021), and at others +they overestimate it (both states towards the beginning of 2021), though not +quite as dramatically. Modeling the revision process, which is often called +*backfill modeling*, is an important statistical problem in it of itself. + +## Summarizing revision behavior + +There are many ways to examine how signals change across revisions. We provide +the convenient analysis wrapper `revision_summary()`, which computes simple +summary statistics for each key (by default, `(geo_value,time_value)` pairs). In +addition to the per key summary, it also returns an overall summary. Here is an +a sample of the output: + +```{r} +revision_details <- revision_summary(dv_archive, print_inform = TRUE) +``` + +We can see from the output that, as mentioned above, this data set has a lot of +revisions: there are no keys that have no revision at all and 34% of the keys +change by 10% or more when revised. + +To do more detailed analysis than is possible with the above printing, we can +inspect the returned `revision_details` tibble. Here we collect a number of +statistics for each state: + +```{r} +revision_details %>% + group_by(geo_value) %>% + summarise( + n_rev = mean(n_revisions), + min_lag = min(min_lag), + max_lag = max(max_lag), + spread = mean(spread), + rel_spread = mean(rel_spread), + time_near_latest = mean(time_near_latest) + ) +``` + +Most of the states have similar stats on most of these features, except for the +`time_near_latest` stat, which is the amount of time that it takes for the +revisions to converge to within 20% of the final value and stay there. It is the +highest for CA and the lowest for TX. + +## Merging `epi_archive` objects + +A common operation on datasets is merging (or joining) them together, such as +when we grab data from multiple sources for joint analysis or modeling. Merging +two `epi_archive` objects together is a bit tricky however, since we need to handle +datasets that might get revised at different times. The function `epix_merge()` +is made to smooth this out. Below we merge the working `epi_archive` of versioned +percentage CLI from outpatient visits to another one of versioned COVID-19 case +reporting data, which we fetch the from the [COVIDcast +API](https://cmu-delphi.github.io/delphi-epidata/api/covidcast.html/), on the +rate scale (counts per 100,000 people in the population). + +```{r, message = FALSE, warning = FALSE, eval=FALSE} +library(epidatr) + +y <- pub_covidcast( + source = "jhu-csse", + signals = "confirmed_7dav_incidence_prop", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200601, 20211201), + issues = epirange(20200601, 20211201) +) %>% + select(geo_value, time_value, version = issue, case_rate_7d_av = value) %>% + as_epi_archive(compactify = TRUE) + +dv_cases_archive <- epix_merge(dv_archive, y, sync = "locf", compactify = TRUE) +print(dv_cases_archive) +``` + +```{r, echo=FALSE, message=FALSE, warning=FALSE} +dv_cases_archive <- archive_cases_dv_subset +print(dv_cases_archive) +``` + +Note that we have used the `sync = "locf"` argument to specify that we want to +synchronize the two datasets on their disjoint revisions by using the last +observation carried forward (LOCF). For more information, see `epix_merge()`. + +## Backtesting a simple autoregressive forecaster + +One of the most common use cases of the `epi_archive` object is for accurate +model backtesting. In this section we will: + +- develop a simple autoregressive forecaster that predicts the next value of the +signal based on the current and past values of the signal itself, and +- demonstrate how to slide this forecaster over the `epi_archive` object to +produce forecasts at a few dates date, using version-unaware and -aware +computations, +- compare the two approaches. + +Before we get started, we should mention that all the work of constructing the +forecaster that we're about to do is just a simple demo. The `epipredict` +package, which is a companion package to `epiprocess`, offers a suite of +predictive modeling tools that can improve on some of the shortcomings of the +simple AR model we will use here, while also allowing the forecasters to be +built from building blocks. A better version of the function below exists as +`epipredict::arx_forecaster()`. See `vignette("backtesting", +package="epipredict")` for a similar demo, but using the forecasters in +that package. + +First, let's define the forecaster. While AR models can be fit in numerous ways +(using base R or external packages), here we define it "by hand" because we +would like to demonstrate a *probabilistic* forecaster: one that outputs not +just a point prediction, but a notion of uncertainty around this. In particular, +our forecaster will output a point prediction along with an 90\% uncertainty +band, represented by a predictive quantiles at the 5\% and 95\% levels (lower +and upper endpoints of the uncertainty band). + +The function defined below, `prob_ar()`, is our probabilistic AR forecaster. Our +forecasting target will be the `percent_cli` signal. The function is as follows: + +```{r} +#' `ahead` - the number of time units ahead to forecast (in this case days), +#' `lags` - the autoregressive lags to use in the model (in this case, the +#' current value and the values from 7 and 14 days ago), +#' `min_train_window` - the minimum number of observations required to fit the +#' forecaster (used to control for edge cases), +#' `lower_level` and `upper_level` - the quantiles to use for the uncertainty +#' bands +#' `symmetrize` - whether to symmetrize the residuals when computing the +#' uncertainty bands, +#' `intercept` - whether to include an intercept in the model, +#' `nonneg` - whether to clip the forecasts at zero. +prob_ar <- function(y, ahead = 7, lags = c(0, 7, 14), min_train_window = 90, + lower_level = 0.05, upper_level = 0.95, symmetrize = TRUE, + intercept = FALSE, nonneg = TRUE) { + # Return NA if insufficient training data + if (length(y) < min_train_window + max(lags) + ahead) { + return(data.frame(point = NA, lower = NA, upper = NA)) + } + + # Filter down the edge-NAs + y <- y[!is.na(y)] + + # Build features and response for the AR model + dat <- do.call( + data.frame, + purrr::map(lags, function(j) lag(y, n = j)) + ) + names(dat) <- paste0("x", seq_len(ncol(dat))) + if (intercept) dat$x0 <- rep(1, nrow(dat)) + dat$y <- lead(y, n = ahead) + + # Now fit the AR model and make a prediction + obj <- lm(y ~ . + 0, data = dat) + point <- predict(obj, newdata = tail(dat, 1)) + + # Compute a band + r <- residuals(obj) + s <- ifelse(symmetrize, -1, NA) # Should the residuals be symmetrized? + q <- quantile(c(r, s * r), probs = c(lower_level, upper_level), na.rm = TRUE) + lower <- point + q[1] + upper <- point + q[2] + + # Clip at zero if we need to, then return + if (nonneg) { + point <- max(point, 0) + lower <- max(lower, 0) + upper <- max(upper, 0) + } + return(data.frame(point = point, lower = lower, upper = upper)) +} +``` + +To start, let's run this forecaster on a single date (say, the last date in the +archive) to see how it performs. We will use the `epix_as_of()` method to +generate a snapshot of the archive at the last date, and then run the forecaster. + +```{r} +edf_latest <- epix_as_of(dv_archive, dv_archive$versions_end) %>% + tidyr::drop_na() %>% + as_tibble() +edf_latest %>% + group_by(geo_value) %>% + summarize(fc = prob_ar(percent_cli), time_value = last(time_value), percent_cli = last(percent_cli)) +``` + +The resulting epi_df now contains three new columns: `fc$point`, `fc$lower`, and +`fc$upper` corresponding to the point forecast, and the lower and upper +endpoints of the 95\% prediction band, respectively. The forecasts look +reasonable and in line with the data. The point forecast is close to the last +observed value, and the uncertainty bands are wide enough to capture the +variability in the data. + +Note that the same can be achieved wth `epix_slide` using the following code: + +```{r} +dv_archive %>% + group_by(geo_value) %>% + epix_slide(fc = prob_ar(percent_cli), .versions = dv_archive$versions_end) +``` + +Here we used `epix_slide()` to slide the forecaster over the `epi_archive`, but +by specifying the `.versions` argument to be the last version in the archive, we +effectively ran the forecaster on the last date in the archive. + +Now let's go ahead and slide this forecaster in a version unaware way. First, we +need to snapshot the latest version of the data, and then make a faux archive by +setting `version = time_value`. This has the effect of simulating a data set +that receives the final version updates every day. + +```{r} +dv_archive_faux <- edf_latest %>% + mutate(version = time_value) %>% + as_epi_archive() +``` + +Now we can slide the forecaster over the faux archive to produce forecasts at a +number of dates in the past, spaced a month apart. Note that we will use the +`case_rate_7d_av` signal from the merged archive, which is the smoothed COVID-19 +case rate. This is clearly equivalent, up to a constant, to modeling weekly sums +of COVID-19 cases. We will forecast 7, 14, 21, and 28 days ahead, so to reduce +typing, we create the wrapper function `k_week_ahead()`. We also produce +forecasts in a version-aware way, which simply requires us to use the true +`epi_archive` object instead of the faux one. + +```{r} +# Generate a sequence of forecast dates. Starting 3 months into the data, so we have +# enough data to train the AR model. +forecast_dates <- seq(as.Date("2020-10-01"), as.Date("2021-11-01"), by = "1 months") +k_week_ahead <- function(archive, ahead = 7) { + archive %>% + group_by(geo_value) %>% + epix_slide(fc = prob_ar(.data$percent_cli, ahead = ahead), .versions = forecast_dates) %>% + ungroup() %>% + mutate(target_date = version + ahead) +} + +aheads <- 1:28 +forecasts <- bind_rows( + map(aheads, ~ k_week_ahead(dv_archive_faux, ahead = .x) %>% mutate(version_aware = FALSE)), + map(aheads, ~ k_week_ahead(dv_archive, ahead = .x) %>% mutate(version_aware = TRUE)) +) +``` + +Now let's plot the forecasts at each forecast date at multiple horizons: 7, 14, +21, and 28 days ahead. The grey line represents the finalized COVID-19 case +rates, and the colored lines represent the forecasts. The left column shows the +version aware forecasts, and the right column shows the version unaware +forecasts. They grey vertical lines mark the forecast dates. + +```{r} +edf_latest <- epix_as_of(dv_archive, dv_archive$versions_end) +max_version <- max(dv_archive$DT$version) +geo_choose <- "tx" + +forecasts_filtered <- forecasts %>% + filter(geo_value == geo_choose) %>% + mutate(time_value = version) +percent_cli_data <- bind_rows( + # Snapshotted data for the version-aware forecasts + map( + forecast_dates, + ~ epix_as_of(dv_archive, .x) %>% + mutate(version = .x) + ) %>% + bind_rows() %>% + mutate(version_aware = TRUE), + # Latest data for the version-unaware forecasts + edf_latest %>% mutate(version_aware = FALSE) +) %>% + filter(geo_value == geo_choose) + +ggplot(data = forecasts_filtered, aes(x = target_date, group = time_value)) + + geom_ribbon(aes(ymin = fc$lower, ymax = fc$upper, fill = factor(time_value)), alpha = 0.4) + + geom_line(aes(y = fc$point, color = factor(time_value)), linetype = 2L) + + geom_point(aes(y = fc$point, color = factor(time_value)), size = 0.75) + + geom_vline(data = percent_cli_data, aes(color = factor(version), xintercept = version), lty = 2) + + geom_line( + data = percent_cli_data, + aes(x = time_value, y = percent_cli, color = factor(version)), + inherit.aes = FALSE, na.rm = TRUE + ) + + facet_grid(version_aware ~ geo_value, scales = "free") + + scale_x_date(minor_breaks = "month", date_labels = "%b %y") + + scale_y_continuous(expand = expansion(c(0, 0.05))) + + labs(x = "Date", y = "smoothed, day of week adjusted covid-like doctors visits") + + theme(legend.position = "none") +``` + +A few points are worth making. First, it's clear that training and making +predictions on finalized data can lead to an overly optimistic sense of accuracy +(see, for example, [McDonald et al. +(2021)](https://www.pnas.org/content/118/51/e2111453118/), and references +therein). Second, we can see that the properly-versioned forecaster is, at some +points in time, more problematic; for example, it massively overpredicts the +peak in both locations in winter wave of 2020. However, performance is pretty +poor across the board here, whether or not properly-versioned data is used, with +volatile and overconfident forecasts in various places. + +As mentioned previously, this forecaster is meant only as a demo of the slide +functionality with some of the most basic forecasting methodology possible. The +[`epipredict`](https://cmu-delphi.github.io/epipredict/) package, which builds +off the data structures and functionality in the current package, is the place +to look for more robust forecasting methodology. + +## 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. + +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. diff --git a/vignettes/epi_df.Rmd b/vignettes/epi_df.Rmd new file mode 100644 index 00000000..e9855643 --- /dev/null +++ b/vignettes/epi_df.Rmd @@ -0,0 +1,518 @@ +--- +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} +source(here::here("vignettes", "_common.R")) +``` + +The `epi_df` data structure provided by `epiprocess` provides convenient ways to +perform common processing tasks. In this vignette, we will: + +- construct an `epi_df` from a data frame +- perform rolling time-window computations using `epi_slide()` +- perform group-level aggregation using `sum_groups_epi_df()` +- detect and fill time gaps using `complete.epi_df()` and `{tsibble}` +- perform geographic aggregation (not yet implemented) + +## Getting data into `epi_df` format + +As in `vignette("epiprocess")`, we will 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(epiprocess) +library(dplyr) +``` + +The data is included in this package (via the [`epidatasets` +package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r} +edf <- cases_deaths_subset %>% + select(geo_value, time_value, cases) %>% + arrange(geo_value, time_value) +``` + +The data can also be fetched from the Delphi Epidata API with the following query: + +```{r, message = FALSE, eval = FALSE} +library(epidatr) + +d <- as.Date("2024-03-20") + +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), + as_of = d +) %>% + select(geo_value, time_value, cases = value) %>% + arrange(geo_value, time_value) %>% + as_epi_df(as_of = d) +``` + +The data has 2,684 rows and 3 columns. + +## Rolling computations using `epi_slide` + +A very common operation in time series processing is aggregating the values of +the time series by applying some function on a rolling time window of data +points. The key tool that allows this is `epi_slide()`. The function always +first makes sure to group the data by the grouping variables of the `epi_df` +object, which includes the `geo_value` and possibly `other_keys` columns. It +then applies the rolling slide computation inside each group. + +The `epi_slide()` function has three ways to specify the computation to be +performed: + +- by using a tidy evaluation approach +- by passing a formula +- by passing a function + +### Slide the tidy way + +Usually, 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, say, `dplyr::mutate()`. For example: + +```{r} +slide_output <- edf %>% + epi_slide(cases_7sd = sd(cases, na.rm = TRUE), .window_size = 7) +``` + +As a simple sanity check, we visualize the 7-day trailing averages computed on +top of the original counts: + +```{r, message = FALSE, warning = FALSE} +library(ggplot2) + +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_7sd, 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 Texas plot, the state moved to weekly reporting of +COVID-19 cases in summer of 2021. + +Note that without `epi_slide()`, the computation is much less convenient. For +instance, a rough equivalent of the above computation would be the following, +which is easy to get wrong: + +```{r} +edf %>% + complete(geo_value, time_value = seq.Date(min(time_value), max(time_value), by = "day")) %>% + arrange_canonical() %>% + group_by(geo_value) %>% + mutate(cases_7sd = slider::slide_dbl(cases, .f = sd, na.rm = TRUE, .before = 7, .after = 0)) +``` + +Furthermore `epi_slide()` allows for selecting `.ref_time_value`, which the +latter recipe does not support. + +### 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 input epi_df +- `g` is a one-row tibble containing the values of the grouping variables + for the associated group, for instance `g$geo_value` +- `t` is the ref_time_value for the current window +- `...` are additional arguments + +The same computation as above can be done with a function: + +```{r} +edf %>% + epi_slide(.f = function(x, g, t) sd(x$cases, na.rm = TRUE), .window_size = 7) +``` + +### `epi_slide()` with a formula + +The same computation as above can be done with a formula, where all references +to the columns must be made with the prefix `.x$...`, for instance: + +```{r} +edf %>% + epi_slide(~ sd(.x$cases, na.rm = TRUE), .window_size = 7) +``` + +Note that the name of the column defaults to `slide_value` in the unnamed +formula or function case. This can be adjusted with `.new_col_name`. + +### Rolling computations with multiple column outputs + +If your formula (or function) returns a data.frame, then the columns of the +data.frame will be unpacked into the resulting `epi_df` (in the sense of +`tidyr::unpack()`). For example, the following computes the 7-day trailing +average of daily cases as well as the the 7-day trailing standard deviation of +daily cases: + +```{r} +edf %>% + epi_slide( + ~ data.frame(cases_mean = mean(.x$cases, na.rm = TRUE), cases_sd = sd(.x$cases, na.rm = TRUE)), + .window_size = 7 + ) +``` + +### Optimized rolling mean and sums + +For the two most common sliding operations, we offer two optimized versions: +`epi_slide_mean()` and `epi_slide_sum()`. These are much faster than +`epi_slide()`, so we recommend using them when you are only interested in the +mean or sum of a column. The following computes the 7-day trailing mean of daily +cases: + +```{r} +edf %>% + group_by(geo_value) %>% + epi_slide_mean("cases", .window_size = 7, na.rm = TRUE) +edf %>% + group_by(geo_value) %>% + epi_slide_sum("cases", .window_size = 7, na.rm = TRUE) +``` + +### 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 `vignette("epi_archive")`. + +## Adding more keys to an `epi_df` and aggregating groups with `sum_groups_epi_df` + +An `epi_df` object can have more key columns than just `geo_value` and +`time_value`. For example, if we have demographic attributes like age group, we +can add this as a key column. We can then aggregate the data by these key +columns using `sum_groups_epi_df()`. Let's use influenza hospitalization rate +data from the CDC system FluSurv as an example. We can get it from the [Delphi +Epidata API](https://cmu-delphi.github.io/delphi-epidata/api/flusurv.html) + +```{r} +library(epidatr) +flu_data <- pub_flusurv( + locations = "ca", + epiweeks = epirange(201801, 202001), +) %>% + select(location, epiweek, issue, rate_age_0, rate_age_1, rate_age_2, rate_age_3, rate_age_4) %>% + tidyr::pivot_longer(cols = starts_with("rate_age_"), names_to = "age_group", values_to = "rate") +flu_data +``` + +We can now convert this data to an `epi_df` object and set the `age_group` +column as an additional group key: + +```{r} +flu_data <- flu_data %>% as_epi_df(other_keys = "age_group", as_of = as.Date("2024-03-20")) +flu_data +``` + +Note that the `epi_df` object now has an additional key column `age_group`. This +means that there should only be one row for each combination of `geo_value`, +`time_value`, and `age_group` in the dataset (this is enforced at construction +time). + +Now we can aggregate the data by `age_group`, if we want to compute the total: + +```{r} +group_cols <- key_colnames(exclude = "age_group") +flu_data %>% + sum_groups_epi_df("rate", group_cols = group_cols) +``` + +## Detecting and filling time gaps with `complete.epi_df` + +Sometimes you may have missing data in your time series. This can be due to +actual missing data, or it can be due to the fact that the data is only reported +on certain days. In the latter case, it is often useful to fill in the missing +data with explicit zeros. This can be done with the `complete.epi_df()` +function. + +First, let's create a data set with some missing data. We will reuse the dataset +`edf` from above, but modify it slightly. + +```{r} +edf_missing <- edf %>% + filter(geo_value %in% c("ca", "tx")) %>% + group_by(geo_value) %>% + slice(1:3, 5:6) + +edf_missing %>% + print(n = 10) +``` + +Now let's fill in the missing data with explicit zeros: + +```{r} +edf_missing %>% + complete( + time_value = seq.Date(min(time_value), max(time_value), by = "day"), + fill = list(cases = 0) + ) %>% + print(n = 12) +``` + +### Detecting and filling time gaps with `tsibble` + +We can also use the `tsibble` package to detect and fill time gaps. We'll work +with county-level reported COVID-19 cases in MA and VT. + +The data is included in this package (via the [`epidatasets` +package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: + +```{r, warning = FALSE, message = FALSE} +library(epiprocess) +library(dplyr) +library(readr) + +x <- covid_incidence_county_subset +``` + +The data can also be fetched from the Delphi Epidata API with the following query: + +```{r, message = FALSE, eval = FALSE, warning = FALSE} +library(epidatr) + +d <- as.Date("2024-03-20") + +# 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), + as_of = d +) %>% + 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 = d) +``` + +The data contains 16,212 rows and 5 columns. + +## 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"))) +``` + +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) + +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 + +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 66c098ae..d7c1c1e5 100644 --- a/vignettes/epiprocess.Rmd +++ b/vignettes/epiprocess.Rmd @@ -1,357 +1,201 @@ --- -title: Get started with `epiprocess` +title: Get started with epiprocess output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Get started with `epiprocess`} + %\VignetteIndexEntry{Get started with epiprocess} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: chunk_output_type: console --- -The [`{epiprocess}`](https://cmu-delphi.github.io/epiprocess/) package works -with epidemiological time series and version 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; -- `epi_archive`, a class for working with the version history of such time series data; -- sample data in these formats; -- [`{dplyr}`](https://dplyr.tidyverse.org/)-esque "verbs" for common data - transformations (e.g., 7-day averages); -- functions for exploratory data analysis and situational awareness (e.g., - outlier detection and growth rate estimation); and -- [`{dplyr}`](https://dplyr.tidyverse.org/)-esque "verbs" for version-faithful - "pseudoprospective" backtesting of models, and other version history analysis - and transformations. - -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. - -[`{epiprocess}`](https://github.com/cmu-delphi/epiprocess/) also provides tools -to help avoid a particularly common pitfall in analysis and forecasting: -ignoring reporting latency and revisions to a data set. This can, for example, -lead to one retrospectively analyzing a surveillance signal or forecasting model -and concluding that it is much more accurate than it actually was in real time, -or producing always-decreasing forecasts on data sets where initial surveillance -estimates are systematically revised upward. Storing and working with version -history can help avoid these issues. - -## Intended audience - -We expect users to be proficient in R, and familiar with the -[`{dplyr}`](https://dplyr.tidyverse.org/) and -[`{tidyr}`](https://tidyr.tidyverse.org/) packages. - -## Installing - -This package is not on CRAN yet, so it can be installed using the -[`{devtools}`](https://devtools.r-lib.org) package: - -```{r, eval = FALSE} -devtools::install_github("cmu-delphi/epiprocess", ref = "main") +```{r, include = FALSE} +source(here::here("vignettes", "_common.R")) ``` -Building the vignettes, such as this getting started guide, takes a significant -amount of time. They are not included in the package by default. If you want to -include vignettes, then use this modified command: +## Overview -```{r, eval = FALSE} -devtools::install_github("cmu-delphi/epiprocess", - ref = "main", - build_vignettes = TRUE, dependencies = TRUE -) -``` - -## Getting data into `epi_df` format +This vignette provides a brief introduction to the `{epiprocess}` package. We will +do the following: -We'll start by showing how to get data into -epi_df format, which is just -a tibble with a bit of special structure, and is the format assumed by all of -the functions in the `epiprocess` package. An `epi_df` object has (at least) the -following columns: +- Get data into `epi_df()` format and plot the data +- Perform basic signal processing tasks (lagged differences, 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 and perform similar signal processing + tasks -* `geo_value`: the geographic value associated with each row of measurements. -* `time_value`: the time value associated with each row of measurements. - -It can have any number of other columns which can serve as measured variables, -which we also broadly refer to as signal variables. The documentation for - gives more details about this data format. +## Getting data into `epi_df` format -A data frame or tibble that has `geo_value` and `time_value` columns can be -converted into an `epi_df` object, using the function `as_epi_df()`. As an -example, we'll work with daily cumulative COVID-19 cases from four U.S. states: -CA, FL, NY, and TX, over time span from mid 2020 to early 2022. We have included -this example data in the `epidatasets::covid_confirmed_cumulative_num` object, -which we prepared by downloading the data using `epidatr::pub_covidcast()`. +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. We have +included this example data in the `epidatasets::covid_confirmed_cumulative_num` +object, which we prepared by downloading the data using +`epidatr::pub_covidcast()`. -```{r, message = FALSE} +```{r, results=FALSE, warning=FALSE, message=FALSE} library(epidatasets) +library(epidatr) library(epiprocess) library(dplyr) library(tidyr) library(withr) -cases <- covid_confirmed_cumulative_num - -class(cases) -colnames(cases) +covid_confirmed_cumulative_num +class(covid_confirmed_cumulative_num) +colnames(covid_confirmed_cumulative_num) ``` -As we can see, a data frame returned by `epidatr::pub_covidcast()` has the -columns required for an `epi_df` object (along with many others). We can use -`as_epi_df()`, with specification of some relevant metadata, to bring the data -frame into `epi_df` format. - -```{r, message = FALSE} -x <- as_epi_df(cases, as_of = max(cases$issue)) %>% - select(geo_value, time_value, total_cases = value) +The same data can be downloaded with `{epidatr}` as follows: -class(x) -summary(x) -head(x) -attributes(x)$metadata +```{r eval=FALSE} +covid_confirmed_cumulative_num <- pub_covidcast( + source = "jhu-csse", + signals = "confirmed_cumulative_num", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200301, 20220131), +) ``` -## Some details on metadata - -In general, an `epi_df` object has the following fields in its metadata: - -* `geo_type`: the type for the geo values. -* `as_of`: the time value at which the given data were available. +The tibble returned 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()`. -Metadata for an `epi_df` object `x` can be accessed (and altered) via -`attributes(x)$metadata`. The field, `geo_type`,is not currently used by any -downstream functions in the `epiprocess` package, and serve only as useful bits -of information to convey about the data set at hand. The last field here, -`as_of`, is one of the most unique aspects of an `epi_df` object. - -In brief, we can think of an `epi_df` object as a single snapshot of a data set -that contains the most up-to-date values of some signals of interest, as of the -time specified `as_of`. For example, if `as_of` is January 31, 2022, then the -`epi_df` object has the most up-to-date version of the data available as of -January 31, 2022. The `epiprocess` package also provides a companion data -structure called `epi_archive`, which stores the full version history of a given -data set. See the [archive -vignette](https://cmu-delphi.github.io/epiprocess/articles/archive.html) for -more. - -If `geo_type` or `as_of` arguments are missing in a call to `as_epi_df()`, then -this function will try to infer them from the passed object. Usually, `geo_type` -can be inferred from the `geo_value` columns, respectively, but inferring the -`as_of` field is not as easy. See the documentation for `as_epi_df()` more -details. - -```{r} -x <- as_epi_df(cases, as_of = as.Date("2024-03-20")) %>% - select(geo_value, time_value, total_cases = value) - -attributes(x)$metadata +```{r, message = FALSE} +edf <- covid_confirmed_cumulative_num %>% + select(geo_value, time_value, cases_cumulative = value) %>% + as_epi_df() %>% + group_by(geo_value) %>% + mutate(cases_daily = cases_cumulative - lag(cases_cumulative, default = 0)) +edf ``` -## Using additional key columns in `epi_df` +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 the following examples we will show how to create an `epi_df` with additional keys. - -### Converting a `tsibble` that has county code as an extra key - -```{r} -ex1 <- tibble( - geo_value = rep(c("ca", "fl", "pa"), each = 3), - county_code = c( - "06059", "06061", "06067", - "12111", "12113", "12117", - "42101", "42103", "42105" - ), - time_value = rep(seq(as.Date("2020-06-01"), as.Date("2020-06-03"), by = "day"), length.out = length(geo_value)), - value = seq_along(geo_value) + 0.01 * withr::with_rng_version("3.0.0", withr::with_seed(42, length(geo_value))) -) %>% - as_tsibble(index = time_value, key = c(geo_value, county_code)) - -ex1 <- as_epi_df(x = ex1, as_of = "2020-06-03") +```{r, message = FALSE, warning = FALSE} +edf %>% + autoplot(cases_cumulative) ``` -The metadata now includes `county_code` as an extra key. +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("epi_df")`. + ```{r} -attr(ex1, "metadata") +edf %>% + group_by(geo_value) %>% + epi_slide_mean(cases_daily, .window_size = 7, na.rm = TRUE) ``` -### Dealing with misspecified column names - -`epi_df` requires there to be columns `geo_value` and `time_value`, if they do not exist then `as_epi_df()` throws an error. +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, error = TRUE} -data.frame( - # misnamed - state = rep(c("ca", "fl", "pa"), each = 3), - # extra key - pol = rep(c("blue", "swing", "swing"), each = 3), - # misnamed - reported_date = rep(seq(as.Date("2020-06-01"), as.Date("2020-06-03"), by = "day"), length.out = 9), - value = 1:9 + 0.01 * withr::with_rng_version("3.0.0", withr::with_seed(42, 9)) -) %>% as_epi_df(as_of = as.Date("2024-03-20")) +```{r} +edf %>% + group_by(geo_value) %>% + mutate(cases_growth = growth_rate(x = time_value, y = cases_cumulative, method = "rel_change", h = 7)) ``` -The columns can be renamed to match `epi_df` format. In the example below, notice there is also an additional key `pol`. +Detect outliers in daily reported cases for each geo_value. For a more in-depth +guide to outlier detection, see `vignette("outliers")`. ```{r} -ex2 <- tibble( - # misnamed - state = rep(c("ca", "fl", "pa"), each = 3), - # extra key - pol = rep(c("blue", "swing", "swing"), each = 3), - # misnamed - reported_date = rep(seq(as.Date("2020-06-01"), as.Date("2020-06-03"), by = "day"), length.out = length(state)), - value = seq_along(state) + 0.01 * withr::with_rng_version("3.0.0", withr::with_seed(42, length(state))) -) %>% data.frame() - -head(ex2) - -ex2 <- ex2 %>% - rename(geo_value = state, time_value = reported_date) %>% - as_epi_df( - as_of = "2020-06-03", - other_keys = "pol" - ) - -attr(ex2, "metadata") +edf %>% + group_by(geo_value) %>% + mutate(outlier_info = detect_outlr(x = time_value, y = cases_daily)) %>% + ungroup() ``` -### Adding additional keys to an `epi_df` object - -In the above examples, all the keys are added to objects that are not `epi_df` objects. We illustrate how to add keys to an `epi_df` object. - -We use a toy data set included in `epiprocess` prepared using the `covidcast` library and are filtering to a single state for simplicity. +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("correlation")`. ```{r} -ex3 <- covid_incidence_county_subset %>% - filter(time_value > "2021-12-01", state_name == "Massachusetts") %>% - slice_tail(n = 6) - -attr(ex3, "metadata") # geo_type is county currently +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 %>% + group_by(geo_value) %>% + epi_slide_mean(deaths_daily, .window_size = 7, na.rm = TRUE) %>% + epi_cor(cases_daily, deaths_daily) ``` -Now we add `state` (MA) and `pol` as new columns to the data and as new keys to the metadata. Reminder that lower case state name abbreviations are what we would expect if this were a `geo_value` column. +Note that if an epi_df object loses its `geo_value` or `time_value` columns, it +will decay to a regular tibble. ```{r} -ex3 <- ex3 %>% - as_tibble() %>% # needed to add the additional metadata - mutate( - state = rep(tolower("MA"), 6), - pol = rep(c("blue", "swing", "swing"), each = 2) - ) %>% - as_epi_df(other_keys = c("state", "pol"), as_of = as.Date("2024-03-20")) - -attr(ex3, "metadata") +edf %>% select(-time_value) ``` -Note that the two additional keys we added, `state` and `pol`, are specified as a character vector in the `other_keys` argument. They must be specified in this manner so that downstream actions on the `epi_df`, like model fitting and prediction, can recognize and use these keys. - -Currently `other_keys` metadata in `epi_df` doesn't impact `epi_slide()`, contrary to `other_keys` in `as_epi_archive` which affects how the update data is interpreted. - -## Working with `epi_df` objects downstream +## Getting data into `epi_archive` format -Data in `epi_df` format should be easy to work with downstream, since it is a -very standard tabular data format; in the other vignettes, we'll walk through -some basic signal processing tasks using functions provided in the `epiprocess` -package. Of course, we can also write custom code for other downstream uses, -like plotting, which is pretty easy to do `ggplot2`. +We can also get data into `epi_archive()` format, which can be thought of as an +aggregation of many `epi_df` snapshots. We can perform similar signal processing +tasks on `epi_archive` objects as we did on `epi_df` objects, though the +interface is a bit different. -```{r, message = FALSE, warning = FALSE} +```{r, message = FALSE, warning = FALSE, eval=FALSE} +library(epidatr) +library(epiprocess) +library(data.table) +library(dplyr) +library(purrr) library(ggplot2) -theme_set(theme_bw()) -ggplot(x, aes(x = time_value, y = total_cases, color = geo_value)) + - geom_line() + - scale_x_date(minor_breaks = "month", date_labels = "%b %y") + - labs(x = "Date", y = "Cumulative COVID-19 cases", color = "State") -``` - -As a last couple examples, we'll look at some more data sets just to show how -we might get them into `epi_df` format. Data on daily new (not cumulative) SARS -cases in Canada in 2003, from the -[outbreaks](https://github.com/reconverse/outbreaks) package: - -```{r} -x <- outbreaks::sars_canada_2003 %>% - mutate(geo_value = "ca") %>% - select(geo_value, time_value = date, starts_with("cases")) %>% - as_epi_df(as_of = as.Date("2024-03-20")) - -head(x) - -library(tidyr) -x <- x %>% - pivot_longer(starts_with("cases"), names_to = "type") %>% - mutate(type = substring(type, 7)) - -yrange <- range( - x %>% - group_by(time_value) %>% - summarize(value = sum(value)) %>% - pull(value) +dv <- pub_covidcast( + source = "doctor-visits", + signals = "smoothed_adj_cli", + geo_type = "state", + time_type = "day", + geo_values = "ca,fl,ny,tx", + time_values = epirange(20200601, 20211201), + issues = epirange(20200601, 20211201) ) - -ggplot(x, aes(x = time_value, y = value)) + - geom_col(aes(fill = type)) + - scale_x_date(minor_breaks = "month", date_labels = "%b %y") + - scale_y_continuous(breaks = yrange[1]:yrange[2]) + - labs(x = "Date", y = "SARS cases in Canada", fill = "Type") ``` -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} -x <- 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(as_of = as.Date("2024-03-20")) - -ggplot(x, aes(x = time_value, y = cases)) + - geom_col(aes(fill = 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 = "Confirmed cases of Ebola in Sierra Leone") +```{r, echo=FALSE, message=FALSE, warning=FALSE} +library(epidatr) +library(epiprocess) +library(data.table) +library(dplyr) +library(purrr) +library(ggplot2) +dv <- archive_cases_dv_subset$DT %>% + select(-case_rate_7d_av) %>% + rename(issue = version, value = percent_cli) %>% + tibble() ``` -## 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. +## 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. diff --git a/vignettes/growth_rate.Rmd b/vignettes/growth_rate.Rmd index 326a07c4..4e1269fb 100644 --- a/vignettes/growth_rate.Rmd +++ b/vignettes/growth_rate.Rmd @@ -7,6 +7,10 @@ vignette: > %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +source(here::here("vignettes", "_common.R")) +``` + 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 @@ -105,7 +109,6 @@ red) and below -1% (in blue), faceting by geo value. ```{r, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 4} library(ggplot2) -theme_set(theme_bw()) upper <- 0.01 lower <- -0.01 diff --git a/vignettes/outliers.Rmd b/vignettes/outliers.Rmd index 1c00ff6e..03b7015c 100644 --- a/vignettes/outliers.Rmd +++ b/vignettes/outliers.Rmd @@ -7,9 +7,13 @@ vignette: > %\VignetteEncoding{UTF-8} --- +```{r, include = FALSE} +source(here::here("vignettes", "_common.R")) +``` + 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. @@ -24,9 +28,8 @@ library(tidyr) x <- covid_incidence_outliers ``` -```{r, fig.width = 8, fig.height = 7, warning=FALSE,message=FALSE} +```{r, warning=FALSE,message=FALSE} library(ggplot2) -theme_set(theme_bw()) ggplot(x, aes(x = time_value, y = cases)) + geom_line() + @@ -203,7 +206,7 @@ returned by each outlier detection method. Below we use the replacement value from the combined method, which is defined by the median of replacement values from the base methods at each time point. -```{r, fig.width = 8, fig.height = 7} +```{r} y <- x %>% mutate(cases_corrected = combined_replacement) %>% select(geo_value, time_value, cases, cases_corrected) diff --git a/vignettes/slide.Rmd b/vignettes/slide.Rmd deleted file mode 100644 index 0257b3ee..00000000 --- a/vignettes/slide.Rmd +++ /dev/null @@ -1,381 +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(epiprocess) -library(dplyr) -``` - -The data is included in this package (via the [`epidatasets` package](https://cmu-delphi.github.io/epidatasets/)) and can be loaded with: - -```{r} -edf <- cases_deaths_subset %>% - select(geo_value, time_value, cases) %>% - arrange(geo_value, time_value) -``` - -The data can also be fetched from the Delphi Epidata API with the following query: -```{r, message = FALSE, eval = FALSE} -library(epidatr) - -d <- as.Date("2024-03-20") - -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), - as_of = d -) %>% - select(geo_value, time_value, cases = value) %>% - arrange(geo_value, time_value) %>% - as_epi_df(as_of = d) -``` - -The data has 2,684 rows and 3 columns. - -## 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 simple autoregressive forecaster - -As a more complex example, we create a forecaster based on an autoregression or -AR model. AR models can be fit in numerous ways (using base R functions and -various packages), but here we define it "by hand" both because it provides a -more advanced example of sliding a function over an `epi_df` object, and because -it allows us to be a bit more flexible in defining a *probabilistic* forecaster: -one that outputs not just a point prediction, but a notion of uncertainty around -this. In particular, our forecaster will output a point prediction along with an -90\% uncertainty band, represented by a predictive quantiles at the 5\% and 95\% -levels (lower and upper endpoints of the uncertainty band). - -The function defined below, `prob_ar()`, is our probabilistic AR forecaster. The -`lags`argument indicates which lags to use in the model, and `ahead` indicates -how far ahead in the future to make forecasts (both are encoded in terms of the -units of the `time_value` column; so, days, in the working `epi_df` being -considered in this vignette). - -```{r} -prob_ar <- function(y, lags = c(0, 7, 14), ahead = 6, min_train_window = 20, - lower_level = 0.05, upper_level = 0.95, symmetrize = TRUE, - intercept = FALSE, nonneg = TRUE) { - # Return NA if insufficient training data - if (length(y) < min_train_window + max(lags) + ahead) { - return(data.frame(point = NA, lower = NA, upper = NA)) - } - - # Filter down the edge-NAs - y <- y[!is.na(y)] - - # Build features and response for the AR model - dat <- do.call( - data.frame, - purrr::map(lags, function(j) lag(y, n = j)) - ) - names(dat) <- paste0("x", seq_len(ncol(dat))) - if (intercept) dat$x0 <- rep(1, nrow(dat)) - dat$y <- lead(y, n = ahead) - - # Now fit the AR model and make a prediction - obj <- lm(y ~ . + 0, data = dat) - point <- predict(obj, newdata = tail(dat, 1)) - - # Compute a band - r <- residuals(obj) - s <- ifelse(symmetrize, -1, NA) # Should the residuals be symmetrized? - q <- quantile(c(r, s * r), probs = c(lower_level, upper_level), na.rm = TRUE) - lower <- point + q[1] - upper <- point + q[2] - - # Clip at zero if we need to, then return - if (nonneg) { - point <- max(point, 0) - lower <- max(lower, 0) - upper <- max(upper, 0) - } - return(data.frame(point = point, lower = lower, upper = upper)) -} -``` - -We go ahead and slide this AR forecaster over the working `epi_df` of COVID-19 -cases. Note that we actually model the `cases_7dav` column, to operate on the -scale of smoothed COVID-19 cases. This is clearly equivalent, up to a constant, -to modeling weekly sums of COVID-19 cases. - -```{r} -fc_time_values <- seq(as.Date("2020-06-01"), as.Date("2021-12-01"), by = "1 months") -edf %>% - group_by(geo_value) %>% - epi_slide(cases_7dav = mean(.data$cases, na.rm = TRUE), .window_size = 7) %>% - epi_slide(fc = prob_ar(.data$cases_7dav), .window_size = 120, .ref_time_values = fc_time_values) %>% - ungroup() %>% - head(10) -``` - -Note that here we have utilized an argument `.ref_time_values` to perform the -sliding computation (here, compute a forecast) at a specific subset of reference -time values (the start of every month from mid 2020 to the end of 2021). The -resulting epi_df now contains three new columns: `fc$point`, `fc$lower`, and -`fc$upper` corresponding to the point forecast, and the lower and upper -endpoints of the 95\% prediction band, respectively. - -To finish off, we plot the forecasts at some times (spaced out by a few months) -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, message = FALSE, warning = FALSE, fig.width = 9, fig.height = 6} -# Note the use of .all_rows = TRUE (keeps all original rows in the output) -k_week_ahead <- function(x, ahead = 7) { - x %>% - group_by(geo_value) %>% - epi_slide(cases_7dav = mean(.data$cases, na.rm = TRUE), .window_size = 7) %>% - epi_slide( - fc = prob_ar(.data$cases_7dav, ahead = ahead), - .window_size = 120, - .ref_time_values = fc_time_values, - .all_rows = TRUE - ) %>% - ungroup() %>% - mutate(target_date = .data$time_value + ahead) -} - -# First generate the forecasts, and bind them together -z <- bind_rows( - k_week_ahead(edf, ahead = 7), - k_week_ahead(edf, ahead = 14), - k_week_ahead(edf, ahead = 21), - k_week_ahead(edf, ahead = 28) -) - -# Now plot them, on top of actual COVID-19 case counts -ggplot(z) + - geom_line(aes(x = time_value, y = cases_7dav), color = "gray50") + - 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 = 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") -``` - -Two points are worth making. First, the AR model's performance here is pretty -spotty. At various points in time, we can see that its forecasts are volatile -(its point predictions are all over the place), or overconfident (its bands are -too narrow), or both at the same time. This is only meant as a simple demo and -not entirely unexpected given the way the AR model is set up. The -[`epipredict`](https://cmu-delphi.github.io/epipredict) package, which is a -companion package to `epiprocess`, offers a suite of predictive modeling tools -that can improve on some of the shortcomings of the above simple AR model. - -Second, the AR forecaster here is using finalized data, meaning, it uses the -latest versions of signal values (reported COVID-19 cases) available, for both -training models and making predictions historically. However, this is not -reflective of the provisional nature of the data that it must cope with in a -true forecast task. Training and making predictions on finalized data can lead -to an overly optimistic sense of accuracy; see, for example, [McDonald et al. -(2021)](https://www.pnas.org/content/118/51/e2111453118/), and references -therein. Fortunately, the `epiprocess` package provides a data structure called -`epi_archive` that can be used to store all data revisions, and furthermore, an -`epi_archive` object knows how to slide computations in the correct -version-aware sense (for the computation at each reference time $t$, it uses -only data that would have been available as of $t$). We will revisit this -example in the [archive -vignette](https://cmu-delphi.github.io/epiprocess/articles/archive.html). - -## 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. From 1eae200fa8920c77840728cf83226698bb62fe24 Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Mon, 21 Oct 2024 12:48:11 -0700 Subject: [PATCH 58/59] fix: remove svg setting --- README.md | 2 +- man/figures/README-unnamed-chunk-7-1.png | Bin 0 -> 242429 bytes vignettes/_common.R | 1 - vignettes/epiprocess.Rmd | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 man/figures/README-unnamed-chunk-7-1.png diff --git a/README.md b/README.md index 820cda6e..2cf3b8c7 100644 --- a/README.md +++ b/README.md @@ -154,4 +154,4 @@ edf %>% autoplot(smoothed_cases_daily) ``` - + diff --git a/man/figures/README-unnamed-chunk-7-1.png b/man/figures/README-unnamed-chunk-7-1.png new file mode 100644 index 0000000000000000000000000000000000000000..58c00edab16afd329f2cd4ba404c38bfa19911c0 GIT binary patch literal 242429 zcmdSBc|6qX8$Ya6DwR%IDls|PLR5$>Ls}R_mTZMiWr-sDI;Ks9EQgT9gt4y`30Ycf z$u>d}iY#N7u?@rT`WQ2u@AJp=dOiO<&*Swvujb5rKKFgy*L~gB`o1r}^IEFgx9;1@ z#Kg26qjp-CiD~O&CMK56o7clP_X7Gd;h!zH)Qp{&n8Xhv|E+URRB~lvI>3ZEeNx~3 z`EZ9v$a5>K(0G1w@Cg&i&>Pje-sA)dc&mT=`R%>M-u*(CE+&aYU*24O#p&8VqT$TK zNBAD4kH0E^#qs7d_vR4h&kH9X9}PIfEuyuTla&Qkbvwr*=kblXP_gTTs75lGT!51z zc@#Oag&GG&qEJ3*=XBF#2==yQT$JU_PGPngd9SB>A( zb3sw`G<>a_`ZXDiei=H)x4v8HY_IW+*+LLpBH+x(G~tFN2d_1=HAYa7ERe$qD8_#Hzuei2%EgN49wnZ@XF86 z?~=G7x6m#>J*^XoujlV5KYsl9iF)FVTysIXFz$IY*_WS9XniIm3H2>$&*u1*ng&RusL1tQ0a|_*52L<`1;n0 z>T8a*&!l5I*!aALUsf&-*Dq!?72OV+D7e#m)!Mqsep^(qdZMmK_}}J|8Sbh2X^V$L z)c7Afcpz7V+I)KC=HJn^(=#(}o)QfnzAAj^sR_UE41JLcBH?jye0++c1|VDy$v$^) z3r}N@N=fk(9t!(=dU_H`&NrWH3Kkn534ryG4l4^?cl-a6eEk1Qn*X0si*fN}HqAE4 z@5|tv9PE>FM1s6raIcLal zC>EQ|sm(91R$3ZQe$~^ac63oY6lZ=+w&JL*wyjyFKUizKKnt9c>Bt4bccY#?f4-29 zo6k1|9(;RwwI<@Qsl#sC__r;t`^I9HKJC^PlZy+E7sl6D?xvjn`{nhfBn^pTa&xxN zRAQu=oRpM4&bmHU5k(zPt*ornwkdK-HAu%(hpmPRX3NUTY|SR04P}=u&fu12>ScQ# zx0V0;^-CK&sCO=L$#>H+A7Bi_+|$$kIzSeKTWSOL@Rf#W9ci;d~1G;QoFi*Oyn9BODjx$KFlG zJ>*RbG@pJIiJkq3s&up_!3}w7VljGPx zLkatr%oL9mkKDB9-m~XvZ|td4r-oWGFaE@-vY%=E{rh)jey0hy{kNQdDuZ~iSnNcm znOld;LpIdns9P!bnZ}Q7N()s7gD*KZo_WvU&fbf0Bcn^dwHJSX7R}BOBD3Z>^i=T3 zyJ|)^wBGD|HwQA68CcW%M;k|JDlcw%?mGIzc&r#loocOgnkeM9dmHCN4U!+-JSRw7 zBU8<{t=mcMJ%4H>vV+atfB6b{hR=_cmLA`;Z(m`zzi5)F9H}t?grnZLCLM6lIp&q>b-gOpf)m zk&6-M4A5a$q>`IfCCx(*Q>uSfRdo!GeB11Er@9k8k0mY5OW`=o7EDzJqi_Rp4@+jt zB}#@&$0&<4rAke&ckn70p+FV*M9hN#fI^6w&3Iu)7npac1alFPZXCX**QQ5lu}8^_ zjIF4sK+hD^D^7jYv&YT1YkNCixe|{y%d=@7&QFtgq$HXWtvRG&P9YZ0bjsJ<&9iN7 z*#r`yAsFV=xpNTc+wFHc*=-$<9MVqJC+i9;nkwE*D(J>d2M&C@bE*Kmrhxc9KVpHf>_ zmpQ85INZ#9C(hu!OjOH8u1iPe@8Ceq&rgq}Z-0)d_tsBuZsYn#FwDVZkEk(tATr_V z{JWfZ4c3dEerW{GMymej{;iMVQ*={5MO)6zeo{9OW(Pr#iy0;9jF)Y_F#lAadxlIF zF24P&y1Kfml7E(MLWZ@}?uu)Pg3+)0e@w1;!^gnm{lh3n% z|Bp8}+wZZV_nc|$EcQxowpnO0Q~1q`LYe2=b%^E5`7F%()o&dXY$_Zr9Lg#hEi~p- zpbR9qb$VE_p8@sl6IJJ}FA@$aUfBQD_YB0ZyClxFB$|@6`@-dNIoDyML|xLzNK8Eo zhtc#_Ulj$y3^-6X+}+=rWP)2YZ}GQiOI4Mcq8lbr7jdHeVO@s_DA1&n+s+$p2|_8E zrJr~lpOTzh8NKl}81Dfx=Zmm~DL32UuF|ChFhbwH8L^*<((Jp6n*Zj}AJdu^ZQqug zdR#cNrl+T8;-QxONG@fzA@0=cuKucj59Ympu$}wEJGyQEBS^5nlZ^NAgrk^{kf6h! z*hDIwi!S|M=wiz{hFUsEb?4AvKO^BY5iryPO3NoAdOzCxZRfR;Sjt0Jx{d^mhkF%^ zo&F9gdcVA@n1B4WV{>tA*a41g_cONU@n&wkXBrzC&M(9Ta?4ClPq$PAD^7nClkrpX zoc?aLs%w#Pba(8R&AD0`1g8D0&ex`<$vbS`KS6KXk7$6HfalR#0p`V4j_FvGrkVFd zdEP`l_!5o<3QbsEpV?n+FY@#AQyY|4od$aqO1alBUoP~Ye}lh%{;}KdlULV#C+L7z zz1gooA>3nOYpIxGR~QY?_O`frrE1zuyt`Nw_cYRq}Z@TfM%5je_6 z{hQ~6<2B&f!yP>S@xuSAEtH!tls6li7r&5)_e{1diov;@nXAW@po{yS*=k*S4aNn| zU2BCob18X&WeLtH_noy+<`*t?AO4L4H zUOP`pRaI3Gk34Yad~^xvoPFW%P>mh+HaBi$kOYAWN|O?`DcZhd&Pr+iyI<`uAwfaA zQtC9f{W}M5?{}`fL=A}!kaWML!(fA3P2EgydjBlD6Kz*`D6*y*m1&UPwa_Os1|r@@ z?hcUHvB0oLMrlpl1CPgiW{N;7Eh#i5ARl|0z(c%fUo7oo0iIAjdj1BM0?X*(?wYL_ z&USCUV2B;Rhq_T{B*r;b0^!eCiF{b~bI@Nyt=VH^V-lPJKyq7=n>`%%Yz44KolA=| z_0E&F*~PBlw1IEtA3uPsM80dTZUiE&A8#FQ+!%h(g7OAMXj9XYVLTa$n~VI_wZ@;~ z!&-y#hEojgq5McfZqR_4P1geUaZM)td>op2vs*D+A8q5rTpZwue4+l#ux;E=#z8wfZl}a`B z44ZQ&H>!f4NfGuREnQl?cI^9~`PU0{UDT3gI%NAGTr3it^ny0of7PCQ4yHm3xS<=o z-bCF)(Y4~`{hR}GzAFDXJLfJ(b%;{ZP2IMAyT!36t+9=ASFc?o7>Tu1L}{MgzUj1% zex`w~i@I$~1}0Je(@E!r*$Hp7Ik!+)HJEaUKkpC!tIAsd1|VM8^XDBF2m$4ad8}pd z+;S2zTK3;xS%9m3xZq;u;^GHZiA%f;erPRNAZZok@BcoVfJ8L3XB*Zl%|_0T&QCK( zOQ7c=?6@g%a3k~jUc36ws@kvXiy?@FPeriIySiI+$}eV8oi$(0vHic?ih6W8-hY6A}?v8)$Z$&SGH za{<(IhR*L;?ymiApy`~UV?sgD@P^A;jG5cRf)$#aEiMTt?i8UDRqFRCT1#@u9q%pV0YiH#M7OuI;6m3}1$?emRL6jhZg}5AEu66V;Wt zKc}(J)c6OlhLe?Z7$YYtDlGyrS=#jlvl$fk|FY=%OtD_^C@B42dv_14+|h_^gS41$ z>}C5`Uw2o-O}B8_E6nFoa!oR|_Sd^z(PMJjxsnfSz^a%3iJeDfYv-G~yf14|_P!G| zqx_WdI^G=p*6o;#G2X7j=Y83EocBpP=lGH6^-rVe>luAL|In+uDYAI*!wo03Y*^~Q zt3P@5>#N}?ecO{`OWqW8P#=Y?`BhvmQy*147CA+FV`#eP%)hQP%7zTGET$KoyC$yo zz>gzbr*)o~b{JtK2rPH`>C#m*ywF0OAiYT!e-v+hIY;SVwXYI#VZ58e=#$&&W*Xqe zr%=tw66i5sv6F!EY*2friB3;LW5vI~l^euWKn1)m-U!x;ss#_}HTL$0%}J2lGh=4Lq<- zb?sxYO?FDSkH$kitlfOnx4_O&`g%umaP^W2suhDx>0OQ%O?QU}Q(b3(iVqBy$v;MM z2W!`tdTTV@%>eQH$(Vgd!Ao!NHl=S}xi4_dU&H-+=4|rXf^M!8yU!Rsgxglu(&y*> z5VVgVHt|w*u=bm)7nxWYT0s7Hu#5pQd(hS-gvaL5h*QGuzmzXC#OR^B0QT>H2WqB| zAKvpQ27@}IWB2tXS^3uL1AayV*fHfKq3`=$wo4fhgVTZXgY?ao|6Q4^LfH*ROenVq zh0=lJtV%!5ZjbVd#!$mb4tGF+W>*JvFPgtDN{Vjl=+7-$JCx?*bG~HerVB(XLzxRi zR$v1?Aw00d$$%)NSKWE1lj3}-$m@UtXT)qWrZ9<)* z`|@3`*Lova#%q_mXHUb1pAx{TgDf9se1D+T2-BscCxC4O?bfh5T9PE`0+HEtRfj&^ zV{7fE)D%U&4&LcpV!m9Q3l*SH=TBCK|{_iVGM)_;W- z=kQ%C4tM+}QbS5`4~|jPfIfv)`nYV}l#YB;YI-#rMNYg@V3I06@`j01jWKLhWn0Hg z_Oq9v=y0v_WVue}IC|#4VWZ>K3D=p)G(vEEN=lF7&OMA#FJJKqCzC2dIzc3@R~PN) zi^rh0knW5#1Y|w%vc$NKV84|V&@1p`ci9KV5n5mHK;3g>^o||F*0KKXcN4?{FLl(| zHk@Tp2n(OFvQh{`ln7%(;?fP$!U|NdZAwl>^Vj6|;?oE$Ds=Hb;Jh0-kS6##;^=?h zir!OGnM}PW`KCx4=**!Lv$^BbL`8^CpE{*4q(m4F61`#pBVFA+K`Q&QyhC?(uV}cP zg=Y+jT(AL*1AAW_5d;~UK7URZ_6F$-#CqZ=I?4c~_Z)pi1JCLR_{5H4bF`K`7UJQH2!Bj+j~f%IAZqIqLau%=H2 zF#LMel&>I)j9s}e-Z@%=%fU^TPXx_?lrgqAhT*h4l=nvi9{pRWyG>l+?qAH3scE%PgEN%kW!lMKUuA0oQ z-DOu+e^el7&m>tI8z%rSqZlmX5YkQyEzuXQ%iZ;Yy2`qj9mQanM;Vd@dlZL`HB`fE zRU``lA-foah#r5ADgF6B)QM_}XLMjqU^CZ9+YQ+;G^|8FiLXk6@`-p?a|@ZVHH+cV+|*ztWzF_Uaa(YsnUU z-u|$(ki`&Jt;(U*25GF=HDa zEktbTBYqeQsP5y;X2&6p{ubBO^!x7{X@A`(lwC@OOAMQMcOiHOaXUYPxl@J`3)tx`|Iaj|9XvlIC6(R2Nk zLbg5nBGUNehU4&7Oy_#%P+(r98o$jweUaXTBa=F*`Zo8j9V0<(opHzlljtpSTbL3Y z%n6m{l0yglvA5DDCbooD6J$0UWKirc+g$Bp-Rb9iXo!vlDBqz!y=k5Fgt<)KbZxw@ z&FpuAQo9m9x>A%Rn&>~)^kSA2%AvJ=C9UyGPdXRM>-pnp79%4=SqZ^68}gZ_1apDc(=l0!b{~_$ z{dfjJ#F`xvO4fRUUy33(JpencI5Wh2_}lI>Ov5M*W^wx#VqE2c`N_qL9~M8tBb2ey z!l~C?S{AzY^WR*oOy81z%WOGaP9cBcqaIYESR95z=s<&{YRN?DsBAUQdNBx4msbvy#Da$F ze{GpYP+AxsU#yG`G%pyjFLfgStcuTP$vMxUK>9@EXGxN1?`H|uQ9tu2+1RDFbNPoo zy_i_(p5TE?<<;M!$Wb2oMff=~1-~@jm?Y|kC5>94a6W%|F&k~5Yjrpf5a(>vC)PKN zqW$9`4bv2^`*I;X7>oVSb9YwMs%cpiSu(c>Uk#yQaOuu6B12qL-1lPH&yYL}iOE)3 z1iV425!y#A|3xyB7$bC1=J?|~D4X~ zRl%>%+06L9nVo0_l~cq4Vl41{d390ws4%3BjL)x4sWn*rU zc3nYVVz#Uh`T~Y6_aU9v%F=j@QZQxq*<5as(u^)pv5qm@g-gRxWRk~yG6eCDk-$t} zZS9*e4Wm9KRz#ux#?eLcVeZ%IEQ3uGjT0M3wqJHwom|$~U;E-k3?w2dLcoD;;9MR4 z>80OMWTCA6dxnFxy;wZ8JsS7H{61?Qu(oki>Ix|l`W`lb`ILQW{BvYQvV@$~slWe) zWW|UO-JLvNUT#2C4dfXEFz^1ks=TG1bKIR%`Abu* z=e(F?F{`sw$!z#NnGKPNFb6B?Vas+qxf$|oLbCmLDBNg7QI6omWtuOaI?2_^uj%7&RZ>%JZ^*1QJzx0XuG1pyP56v!TUr>LPA>;RSx;b zAFSue+-~z6HofJ;;$d2j^Y)t)QRBnQaXTv$m;D6jP) z`kB5XQUNxucqEO0dzBj63Bimg!#ULd=T*nZDCmy3WVT8P7_Vh@yep6bii+q`ic+7p z@EU6BsjOgAlD{Mp(POI)anIq^v*)`&L?o}A+>=mnFOY?(3f9kn5!x-&B~czl*3nZ_ z3YBkgKLC2690SH=4H*u8^@!{h$z4PR2mjtNEFXv1trv-_8xCZuIX_srv6XVP1w?vukE6R)4lGdawmu0b9w+e*(rfsjuAY>E=_t7drs z$*b9_Uyaw_K>o;tk;-||-TyxMn8vOf<)Wm@2f;u}()WsMhxT3a=VCGVdzGOaAi40= z_HA0eyUilQ&)e#$qa z(voW>&{^{ED* zgMZiSYzINb29?#(*aIbW1hB`#hk2@`OclI_q9!1#r_9QbARVu^4%H?e4czx3iR<60 z2|(Z8Rr<7x=B*2_k|exLuOgXQYo67g{41J@Ndlg6ptp-We=cq$r98iXN*csl| zJX&&yP&zvpK-uKkV+djVnt7AkI?NrXT25>K9i?8EyZd$+Jrj7~^tqAIFw$>LQEgo( z0qPi3D0GtS(~5i?bU}4LrYrrO(#_dc5yP64=Ann(J&`x$C2fYY%!RGp);`10?jlG{ zUmW)*;}$lS<&Alq`}^NV@xnaw(M%BKa+;*!YG_;W^V*X(4x)X@@8RerYLk4a%TiQ7 z-eC%5YFzYmG{&=_o1pIb!h*qYVctLhetZo@GLema8g-rC)!!#XN#D9QK%v1zjqWR` zum@K}WYfQh8uZ0BhpQJ5`?fRHFbX6);q*7Xta_(NEZ&aeeDRdmnu8&>{{7uCH{DPt zBL2g`8%6U;ZPac=__wb<%9Wj88oGEt1kq&Dt6L8#8TC9xg8jnl+o8bnfK?8rehepX z&t5hU3_tKERONMFUcw(g(|7YlhholY`am0!Q;WR|N5C%!Nlau2zifHM`FGTjQOVkH zGVDEL-VMoLNg*Yv?@M6HbjFlgdcB@PZ5@y}M;Nv`DM(?2D19aGucuwI$1SI1%ZLnJ z^;|bD&as#|oE(^KL+=ucUs6{XJ)4^GZ4 z%gWSqZaCcRI0A7wPO^WzMRhL1wF7VKnG+`>J#(xil78myeii4T_k82$X1g5=_=so zSLV&6rtFJNqr3xa#Lw0ZFURd8;PM=U=f?=vFd0PMC~?iD@M9cjI^2}T%a{R)1K8CL zsF?#c5CEswLIt#F0q1YM!?z_#HQ{esGqsyvQwo`Kp8b{~WVa5L0)TIEzI16Gumb?} z^YWQWH<#WQ*r#Vc`+*C<4v~}mm_GuO6uZtr?v+|<@cPuD6y)6RD-Eut33A4=i@PWz6O6zw|F z=0Zx01dxQEpm78LGMkj^nwe7wV~>7A=8$|^tDdk#(#92%@1rrHfGB+zewK}m4G+X} znYsT0+*1C{_BVa^01{R-{HjK7>*sUFmT2QJkxnb5t5fPi&=-EN;)k!}NJC@WqRiA$!2Q^dX2y8~`s0;QPMbGhC)*UkUGDfLis7w{~_W`-^6| z0|Lk@R9uL*ps%a^0WQCiv}^x%q^>~zs58ot^>k}uhR zL>f%cz&bwal}`=})G=J?3G_&A(A<2~WKxAr0I2OPvF24bw zpKNLL^tY@11(PWWK_cP2)Sza7e%gl4(YX>Y>=*X-uY7Thy}smoz0=DDwBiFW+e2Q@ z6zNWoeg!wqt-iI|jr>U8Xpu(b;`dYS= z@W-k%!oCb5SR@4V=VAA#N3|5=Haq_7&Ye34wKiiyrC0#u2$vb!%atG4pXC}}b$nimuyBkSt@=w|)9_ zKNzz!Z>iuACRCur_%Z5+#?Eg3Xg9n`!bnfoxAz}AH+aQI-`{Kic(E%4nIOQZE!Adn zo2b5tzLtftoWd{7;pN>%pZ7o5ttA_mw~0@Yl~{|r6S5lmuhl z4^p|>-@iZT-5BSx=vit1M=6 zmBvDvwLGo!H6@RyRNClsQdkz`r+Z&?P_JYdzfO`yUpeeP^nk1#b*%+snFlC&IKj0} z9|5O^Ao7I$H*MOKTa>I(dyl-pa;mP};fI1rUUlhaYn#O;Va@Xh3v2T@vGrob69){QREWn^bw@^Wd1_y~I0|r6*HtoEpf2KnK@*ZV3_SayNaP z5R5LAUYyFtDK6F4YG2o3zu8gP%s8JrYavhjc|a3P^XF4d=1tGjc;h0ED9ZplCf-&m zW=VIxwyIZfgG(Cu&26yO5y2?juI0T1bZJtXHV*YgoFK71+~g|cadrbZ(tY?U>SC(? zvmSuydBw^%)$`&OCf2?*s5eEnco0qwmu?CthwTRY$O4lcJ#yq$#a=6^O1B>lstJux zhw1>FDZ>)3-V66Iq^lRgR^&(W6h+;Yk9sRnniCW(XcI!R;>h*d_jj0C?24yajK}t( zP)jZOCC&Ts01$*6eb1(_FVAVJ??nqQku5^QsiWWrF`SH8W-gf7x$Z>&jUR7co>(L& zD=D5habhyoHme}XB=dMB0#rRmtX;7jpp4Ruv`$eV9M3luJrNXd-Bp4mB?B~5#_2&^ zTwG)cIk9#E(7&?)d&dDza_UAXzxhHpSLvVdY$*xk_C}MId@X;6CQUV-kIqPxV0q0- zs)3lptmcMlP0MzfALG^9K8{&dpl)Q6BpyC^kX1b07L{K-a7LmHpmBg^j2EW7XY&~^ zY%LxIZS4W<_8MqFF3yYqHqy594IG_(5Lu_<2n>y9a$l_7P^Sg3F5VI1ADCFfn;^Re zbr0k&2Le{FlOKB=54XD(hRl~dD-{nOJcwX2^KX2=5<0q~Tpex4sPjGxOVrk4r2-}D z<%p5dhoYG;!Fv?CPu4i5ps-+wn=%A6e&phm`O*|X_b^HqQuUD=AV8%Hi1U$Z75Z2M zu^mcFJyz86Peyq?6Uxij46x{@D3kR{OMqV-tJe2b`GuHG_&)?g%EIe@8H^g@!@$`D zAEwZQT^ zVNW~Gz3(3LJ!ao4|MYS-Nl885y6j`A939$7W0o?6&k3DR=Psv)S~On24seKus|>$4 zjCRs}a{O9QS%f9%&kzR zFS$&Yktq3)5l*(1*^#}VZt@usZ$^UbKEIY6@1Y&+`1#LP*IqqM3R*@@MTpl-AE50( z15xb)5kLL75?bGd~b%L}ccl4vc_H8@1MQj?(J3FZ>pqS&#Afh2f#-yi58~X$G zu18?6cv7N*epajCv!3d~Yi|P98LS%_rTJx>RVbR2Ze`)41{UsxJrMqv5q)13(S8eY zg5d1q7E(>>RU~ka5P#CKeVLlu&u!BQ5)XD=y#NJ97SBU#zQ6x{)aP8HG%!`nRJe$n zM*In`z9{m|kt(1xfgkd56zV)a#d zAc{#RsQmBlS}rFd?AMMBa1=wLc96E^+g+DkHe0yS621reKV%THw;7rM6yrZj4LouR z0W&Cad>*Bx@RaKW9Ekh4{Np|uh4aBL#q5kWUrw*<;W zb~e!W(5~E}Y}qcxLa9^i+?p}qjtvOd^)>H@&;)>e^<_x3G4uJ7Nz1XRz}8io_Komi z5AEh5QkP)eR zc$+Rhj-p7(3rUn3#jk#uevlx+j;#3MMe{mNCBGg4Bjv0@xFYJ&;XUH)2PV-rojQd{|Y1;;W=PiJMm!tg?e7$^+^0 zDX8VR)0JO{baEGS|x7hc>%0P%9BN6*{6R%#LMZLIi%RRX#U0Lf#?Sh zL^B;+0Qj8a{{H5z6=t0;3`Ktm@wq9oETSiGaqp-7aDv@at<;WUL>YVvT$#Ar{*(57 zRh+Y$($33jrLJ-WH^}0!uNmsO=0mdY6~2$*Uu@1@To#^gy4|QU8yFcyvL0${X7EkR?eE;WYneY)C%?2kbuORhv8rSVu8gCGV>$U{(E* zZMM6&qIn;-iIP~3U8>HIQ=#}Btt2uBFjBi-*sr5#1Yz1Fv6PGgP7S!^2^CW z&O>jv?^x#2?fvS2N%Z&KW_T?9&-v?@Z@q9KX?k96T;E09?|8+qscs+3{L;$muucfHsi|CPupy3 z3*j6e1*tr<)q&1TdrDj=x?>dCf8Et#0K|4cXw48lDTtUgF!R~bCHCAbA9Wv+B-y+= z6p@dr9KwgB{Yg zU48Rcev$cWyJf-9YhD%%!&MT)c957e3EFvJ9#cGxD!ncrT|@6#6cp~r7E?JvGi6uS z(1HP4fIA$lt3r>4 z3C@G7o4uv91N+rX(;R{F2mCcaKa^Qyb2TW~+l#;sL=b#Lm8;3))=c zK(soA6ykFQeCGdrFEKLOngK~vqcmli6&3L(!CVD#V*iI8=Lh6+Wyy^H>kpqCLQA&2 z4Mk+~!kQUnATCO7@6g_#C5SmzU(57*t214SmB6^0houp;yRU((bnB=x{=dI1EA6in zyhiue5Vc}*79O;KeQwS$SWlLq%jI5M@;L#l02b15YhO9&H=hvdbIxn!)E{9yJ`^cQ zYE`l{rTqEXdCxd=!+`lcV+p2+v_@+w7Dx~?Gc$h|e^3&_k8!#MH~Ccf8YHfM*!oDX z>zr2THD_n3kLAj;&rpX21qIVCnBRX$V$(W@M*nM7i`#Ma>eWS#kc*_Q12%cjx(*x| z`T71H^f$4=y?)f=9%s*)kTBmAxx3l#NSEE#o4({mtdiGHTyShiqY333T#bHZw*DGM zt$wgkeJD=D!E1)U>O_$+!6W##@m%t{MGj@XP=23#x25cN9%=f!Q*1ELvwfS+3wxQ5 z+^yTT?T{J_&brxkZ~o&YtDg(_vvpfYB(Kb|6u8)?C7{@$7Wx}nKrG*S^I0X8rwpuBs0eEhDDcM5U3@7Oz6 z_l`mrr;g{D+oM~9(X-YaOP}7`v$`6^1OX5~Qas|vS3a&{2 zTI}SLtt8QKe|v|M>}RGti#dp}PTvw^IImQBXcHs+EkC+a!BtO`pp@$-_RZzNmwp>Z z1~_nU0Ju@c`!~yc-1ogORhkz&pbWkqMgJ}RK%MwYrkiZ>jgMN<;!cjE?#s_JP%O_^J=M; zMe@?NhTsp3qwOaAerhx0VajP5shzU^u>7GJR z3W4Yu&9O>_Gx)aa9+J6b?Dy>1Z*2-rx9^&l7!IzgzA)!;JF~9B{aB+Sugm{1RBT2t z)ckdF54ngTenQ;cPP*>7hv=R=56PsfBvlk*(={9D%<^a$06;>5u;-Vn0FGxv=G@{* zCzOqn-huT(oW`;b8;a@6V!?6}QHrJ*oOPE`aZ44bh^{rS$ep5GY1nH3rUnzrT#A1B z>&oqt-DEnadC7(X)TGR4V&pvU{#qLWWu>jFvvtd2%s258p`3IOToG3G|GssVx7`3! zY7Uz0^cw2TVVi5YH*I1uiA-P{ssCSMR48|HPEk}|=KM-=$*gHa^f>Fz)p4aDx&r5_ zS-nu|l}lj{wS)zEfRQS>N>Znc?}m)yS>4KIt<9*JJ{Fj8RztaG&nNwx$$hbh%7#=I zl~KbE%RUNIh8?_l1$Q4Q(w`Z;r?}#}G-rAUVs#%0E{*+5UFv^YS~@Qp7HHBE@T|V7 z5exByeh2BJott@D?n8t}Sq!(@ zXuq2Qu{AVjIq(Z$fSIla8$g{gA-xRz)f$M_IN=h(GOLv<7KI37aQqMu`58tO4AgbC zjH1cvl%VCS1^KIoml{ zLS-?VSb3^1RPl^yK!qP%?EkL1Jn-##5od)xj$rPJmyuFljE!uu{^U>~Nnk4oEjcd+ zCLvcGM6F|$oyMZ1Q#;@wid;wvKvI2cNb3GBLONyV29T7?X+79txCBtR${2^|r+e#u zr18Q*XemhvI zyZj0`vNNmSkA76AI0`f`1FED~1((}F3if6^1h8;r-Sj+Pl~F{IQFfcwx>70pQ0$ug&}R3d|v&?rscAgm(;uYMO^v}^3XQR zmGHJ#56LL#HKdK6`)#E-WV}SyUV=N{k@LN@qdXuVcy@odJZjrJ1ch+f!62oBid(3} zy`wZ|cS36cXihK+ZB)WNbZF00RDe-Fkc%?})Rb6-e=MdHY9~;kE>(gaG0-z$7%5^x zAx?B|koLy?L!&dueT{}Z3pP@{!sM4IHlO+SE+6O~<@M!gJWG#P+qR2k{nB^2;6 zYKr&J=3LKS>7WmyYQzR$@6-%%eD{a~RQ8az$L9-%rg>KNu}BTkD#oSd3wOzU$5_d) z?e?;LPp^uX%yvVcg3Pi|+Bln4VYQA{YUZp(p(U9eRDjwHw0w@iltK-fsLPa&i@1~&k+fc#w(w)>)sF=u@jT5=4~2bfUL z!=R9U85M+-^yW5c&5u7+S}0>?yW~_WUGLo|Y1KJiJcb>Et_^6Qt8@H;jQ31Ym+2e_ zZJ%>OyOC{kN=L)JKDcUL7;#dqOG-+LoMu=&7!6$?I^4Rb$v8?fG=3tz{RO-+G{;UE zTg6O4*?R)v+^wI=fKNQCrE@a@zRAqNV5l| zu!)~}2kq-(Vl;iA5d^YxC!iKAQ8*BfG@6MLTPaBO?w09J`5JYx83b569|^l4lpD~R z=ELe0T=Mx|W9AA?8;~jwwza6!1!Tba#}PDoF%{Zr4Zp5O3fX8%V{lMlpv5aK8uL8G z{lW-e=Pu*&iXgUjKJ)~bc7M0lY#9rB9ID?-&`Tj^>H*Tb3rAMP(a5&=5G*p;`?a7SsA0C2^_~k%u^!_g55~a1Y1bPi2&m!$CkS<}+V}|k_D$$cr#z8uf1(d(ps6=i! z#dYb{dsS#s5^gF+TS_rIFsDm}Qnw6EE0lEqeK7)@Yncw`rb_hmOPD*$Aljn8tpqy{ zg&>O3!bc@&W^@Uu0O zELA`k!`bhgjGb9n=vP{Tj%05U)Wy9iPb%Onj7Uqgke!LFvP%f_g#VtTC<)ALO-wlX zWH#sKN=RYI>o3Ik-vbH3B?IvRo#Z5$m|GvVIGxi?)khYAG%l<6 zY)%;aa$3 zr!uTep=%5<`y^Z|vix9$o{U1u)o?_!Rdt-ZurpJRnY2+ep6~~S5yeLEV1zQ%Yj}yR zHhD4fZaz@ID}p9;J?;_rPC-!_X(1ILQG#G2%m#iZ$GDnE5>P1A*n1P&(v2J(4hC6@ z{>otCMbbh>Pe8dr>&QiDmRC>t3J1*v$XR-iT6q6V+LlvhF6mr!M2-}VgjcQBU(7J( zW7)1VV1q`*AWD>sgSK2$QL_nunbNshXuDM5SSm+*`VJ_4{b#)sKbQ0AMvueg!=Ubk zEZ*L;DcTIA81O7vLc13`p}!myLA{P~&|Zvs1TV(sh5AH+i5r))UsqH zCH$1{WQ#~HGHA7d^t3454S5BJi=g0TeSr=1wLZodxscsKzZ?>hD66$K@jE`P{A6S@ zc;U#nQgHl&exE{78i+!q3NUsCeZI6s86AZU?oT0P3!DUhAslg>TkeG{hH8i#Q}{dR z^^Xj+L&8x*0j%OMz@EcNF0FuG>1d?i>k+Hf!=M1mdT+V5w1&*D)}MUef5_0@2A2h$nK7H9eAe~ghb-1)1#?llm)1O0rVITJM zVSj=H0hf_8EZ**V5e(qRT`ric z;gS0&SYMi`G4AIWKNBKk`j}I(y zX~7~$;udmw;%m3)M0>|l>Cy!C;n_+9cg2pON55^uPPS5+@6bgoB=Ky*U)#U%5>d}n ze5SixGsis(TcJ3Hl)Y6MH)b{TCds)Hf^1`KZ$nEGI;htUr-w6P8Q92(q|gF5av*o2 zZzFt3H;-37U&`T9~esXGOD*!)-W+ zS9fiVpRO3f97jGX;*^!n29&;qu59}P3ucUItaeP|%xnG}8E{W;N$(*-htP|0VGrq1Xr*Fjjp`rx3k9@>5UvXl41L?txwtgS@hyxGqzEsia97WRX~s_rxn zT7lm`9^e;0Uja~LFfBcle{R@HiMw(Fv2*X=a}0i=9r-f#G}SBDoUkSp;sHyXD+gI0a94dUl`2%9MX?!=x-VetJD>30)Dlt zP*jMI!otnYBbqHB=N%sv0plPo)K0*^<_iQN#ngE`7WoMNTiS_|Kc7Xxg4s`dd(fDB z%}(UHsIs}dtQ1y9Qw1WL3rOJb?au>pewUAztvAx?)aByPC&~_h9@ZcZrgS9=96QgV z96>4pLz@@>9&t}7Z1S6vE@(wX>fiRA$s_MlafN9B5 z^%IX#c~boR{0c1H`y}z-_XEl@X3RUem6c}54qVj>)K}|z^tt>Fsee5iPOLtTo(3B>?eQ2jc#O;R$#V_N_=`Qm2J-37?}F z%cUJ(5$S1p+oQjtUJdS;{3Az@5MX8|F{SVEESlF9;2>(tt#C$L_Sb#Df66Rl%qm|4 zikCI|9>CP*QA-mjtHoJ}^jyn140FZtl&{I+^~Y8OQnUC7&&SBnJMUF4-fmXAy8}`s zO|($>st4juj00PXPAr1{X_*+V0WYy-_OQK2O;P7m7J^&#@PLGRu=nqS_1IA-=+ze; z{M?y{5sGmpzRIHM^xNTv5?Ynxz1BJ3crQ!q){Zmliy*Z$OBi0*-v|1$=AU>=oo*k4 z#%yH0(y5E3Q=sq)Ph_xf^M}s{epxN*ARY$dWD$`!kdEop|pIhmCM@_Ll zO$-f;LiqzpCgc>|{bGVQ*BR(O>UN)Y3Om)%vYasRMWSQ?V5ukWI-?WHFX`2`G}5&= z;!w#G=>ru2GIYzkA=Os0YYKsdXUftyFTvCO*dY#+$X=D}FV0&RC4_wKxdkrLo8_S8 zAh*Jw!y2!l+b*(pXw7?z{R|Qqx=$l6`CMN#uHVh4A7Z>%+Xsg4x;YT<`cKwMG$*F{ z_(H;GXOU9N9DVC-G^!VpWpY7uUCIg_kG>ha;|gU=Y8I% zisS#6iQks-%je25QpBy6q$#u7NQe6*JMjGNBhu?C^(jJFSK)qtdiUR03UJ8DY@o4V zA?RrkExmC7L|#G7FlqD=JF0Rmzxbf;Z!W;F;B?Ph=V>CIRLdLrMF^<*!2;#hfM6(k zMc(~Q%}<6*(VO$yPsMmiK{|i0e)l22n_g}=i_)8MP z^m(c}gaiAJz~o9H@oxFyP%j$%E1>aiE>vxFoL5a`|2!ORM*Q+IFfgzgRJ9T1LqsVT zqRjdCU?>oY>XV>q{ku4!w!h|(MmB!E{N+W+eA0a68u}Z;k_EkBEC4TJsF1G)!${m1 zsb|`GLK@_P6E;VrSBs=QL5^0sc|T0Qv!n~CI3uFFjkBbTq>U!GN@rmAH`c>+{G@?m zhKn7>hZI_1xj}w$7L=z2XMR%!FS#$@r8uq6+vfTG?X%?JyB3+}TIdO!3x0Bl?;0+P{!xdfBM1r@n)+|phWUUU z(N)@no6jauY?-5cILc^oy07m9qCibDt{6tbqehWF<2>C|t}bvj8muMae|?Sdv7}sJ zlkQR;M0v8CJsty3Mi5DtUIm{~k=X-$|6HlfMX2Dlz7RX;+$`y~V)_%rGS!@4nb$l* z)F_LHLJ|^Qz?AK|*Q)yWH+b6GW*Z9x{^Ed#@!o8ILLR>M`al3tCg$7kuAPBmQv z{};%KUjuG@qtP#k6}@*@0TDk2+062*1qW+qm#Q|`N0tGFVmc8&Z^Q_i#Fow=vf)ck zWeNEGV39#ByXRgO$=XU{-Mza(EyzOb5NZIc;ehzaz%`Hlh}ghh@C+JfivSS;EJVQf zm2M6IE6JwQi|;T{ZE(S;7NT)*)|;J2L5_9#+cLNx7B7AsJ`bUD?VVGVbO5^t$hJ|x z?e)(7TfOFM0CO>}4+KEzr(??QrQPpmN_>k4R#B z%>kYYx7=#aXcPL4XRG&8Z^v;K=&>;0;0s0_Qj~^y3xd+2<1CcQxc4~L zy3A`yuk{~jjfMm;cvP#CWqliO!OZV>aIbMroR2r~9RvM%+h;%|KHt0s!l3gbjh>4W zq~)ySGuwVZHCN^K+{`AakfgURyf~F;q6k4d==hCo@0G5}Z@VbJ$NI3`M3Br4(|0lt z3jW%5k>|!)>%Ut3lIeDSP&jx?i>aPMHyXD^+KHw44pC>T zCg!hpYg6;TC`!pM9arUKDeO5W{%7XCth7#Z0D>mq^q7@nn!JW{MdnEZQ1|P)pg10(RrJy zYgGH6<>r`0Sj4bIQPEQhuHL6oytZ9-^GPz7a$}Xjo4-ugw>4tSWZ2~Y_!aN5DK`oepA92?GCG!uwJxQxL5t`PUe3IFrR3XK%(rX2dEaTuK2(_~x z5(t{I=r&~!508{Gsuuw%Txn@(Pl!9OpJooNt*Bbr{-h>UT}G97oA$yEpQ@z3Q&f_9 zwlscMqK0FAi|+3BlHIXdv&z#W5Us+63FGP8%VrQsC_LZlvou@J>cGv#$E9q46}M_T z6M1NVeE&r{$QI;^z1iGY6$B8_P_jEmui(ic&PRDSoifyuV9OV@&%%`gXa4x0C9Zb^ ztE{P@s{`AT)4|Kud8bNS7#P~jEQ?wP?1jF)pZ80@*BEPUR+oL|9*VN;T z@^@T#LlN~q4j2`cwRMH^*`cVzpY31o`m6A($XSg1sq4f?SA-Rnw!M_j1^+_+tT1*H zMgVmvxgoVrk7)%SDbAohDTZjIznN?}3+D4H`h-sRI1wt1Y}u*=P&3xy&7Nxxbr4bPyf5N2)Ds;MBX&>8ya54&o8gqv_5c7gF zoFH#xn?opJf~p-``rjY<_3JYKdl}*xqX4?XTxx~AMAki3e2P-m2jMFpnpcTjYfqp` zKEHUC($iyX7mvYc1|A`+lk|i#AOSKwJe)Q7{^4#F%;D9ISS5A!>GqrsK!pA2z#3^D zR;bFA8WIo`1W9t6r)gild^w$TzWua@%7>p(1sCxmYrT`X z`AfRfLPFu2R$s$(w6(1Rvz0MzZyX0dOzl4VWMJS%RbNtUY&Krwpmkk^X~9MtzmJ!! zoUXn;SG^M&U0&0#L^k#Nz{Sno63ZrbA_Tf4A^&l^g2vvxd;RH-9Xr;ZxNYx+9WZeF zGNY45dEXiu8e%1e=a+BwP9LK+wXUiAgO{qBBb?Lrfy=*^xtT=QZrUzq))<33I`H=7&3fce*OmMeKnc zf5FRaY;3wzAeQO)1ZM^$twUd{`S?_d!I=)&DEDA^!UJ_Iu2BW++soNejvhC62)K7` za;`}~+$Auv=__^8@2r<5)#)w~8mWt4-_06OSuYz?k(YOElh>(frg3|#h}uge(dhyX zrNC7<jLR48kZiZ9E$zWd=cv#Ub*Dc{*M2C3^j)f|G?Bh$}#H7pws!L`^nFwHYZV#@^k9tZZ8cI$r zpc(n}lT~pp;|yg9$F8@gs~RqPA|Fgn{5GS(!bx zgDMgZ3V4ezBb8a86Apw}34HahSz5S)=6*apFx6eAo&Y5{BVaT{Zbdt$ER?IGP{+<* zm#K)-IXnz*t7a{P6#K=g2(~4rPM`^RKl>#?x^ zcq|vIUf2~tx6Ob-*wugaNq8pk<0~EyP8zx(D(kE|Xl>5dyU>tEXp92Gym zYI@W7eHPKfwylTJP@nUd8-iSheq6pOUQAqk6-hBr7RG~vf~xHWcz6sU^8huRaR~`{ zuw;{zkBnv#9&@)+%g_Q+FmyZP8q=jCM~(ypwBNH>fglk)Y7RRz;Lr+0XV8SVxOFRY zkV7R(UQ+7^xV`sqy<_g+gjt&Y!u+YIE}Y2!s!X}%iJ?24%=JYitD4aiZ!naT+0od3 zRbEM*L(+XIgtsx}#WvbU^1YY- zixobxGrMXy9?4R%Kpgu_gqV5Jzhh_w`TOKvsuC>#8tk8&nuMJmj*h`%adBr&6r8_R z7Zxl1bAAQZ0qLmowf}B0lwTPXS;EC9l>}cu{q?1skMFCK%1|eZJU9E>TPWN{l9SNh zN0a(B6fPr`H6t#1_8=xfm94|Vil;jbTMG_W ztgv0zgPk;9mB;b->+H*<=jzyi5s-ul4}V4^igvv+e<%hw_rqQLvupO67A(OBz?~9A z8G|lNlH4);L44l8ti5`;HYR{nydCuaw1MQjdkNPZ^WL;4~?fGu0%z1;XO z4}{v-!u};>@;=)#%I;_beuO_=kRGF@=wViFS?U78wG;gx2rMe`rCDddRDWee{LTdeTBD1NxGtW zuCV>|P=XSgb=%bpmh+^l_!`)YF^NK8VA;%By({9SyeV)Zv}=*3HaA56$@9bL{9<{X zapYF)V`X%wRSOG|Pt+o<$F@`%4OK+j+CF)(5Ri-nU`u1a3-I7S{g61wOwdb2vFsyp zc;h@-{pILom)A%3k^st;$zY>;PkitY(SKuY?xzZ6EF;@e{Vrb?J2RAYO~XG8cQG6t{%D*yTT-XD^DK zk|^xO)5*H0{rCDOaZ*Zk>x zwDbthXTa+H&SJsuPHlrd-ycENgYE8X&4J&zivFBVokQXw0lV-&XM`&Md|3+YDA_`k zF1-ed1q7g}KhvM>iy;Hu{Tj{hj2%2wiOLNz#s-mc*-y)dUn!&2trLf@d2I_1Ak#wf z_dh>a2E^7e+Q(c1%Wwy1NC`pB4n)@_Q;>1g{w>M}Po&!OgU6Xh@hsL&zgU?P=DJkP zgt0==cHWC5mxoFHpy3KSIA}@rn?wn}|MmSZbU2Joj-8?dWh{4o*_M+k(_!n|oOKA| z_pAuKK?6&KS!Nl&e~;V`Qj?2c&}0}Dp3lG1iO9XWK9YxJly&a~Mh_j$Z{`@h;$U)d z^3|U_B&`#uu~Wd*qMmej(DOQzgN4t?!tSPH`j6k{zLCIUh$%V*#alCp*RdR;7D+J4 zc9g6RIk;=}|GibHECMWK842QfI>k6f+1Z~>ST~*#8j>6j$(tGG<-_lMDvZHz2Ny4& z-aQbxYI;O=lIOq=v7u4XJ7M>*#s>+~-8F2$I77}?Chx^?uwFum zUAHU51^@N`-WXqsyRBu_9!ah~bdaDz+c|pR(xm0*w~(|=)@_*ks8Ry$g#al+8rM;` zKuQJL?PP(s_=I@)_uHuk8ce2UdABL-HErqY-HjaVg2U(C>MCXAOS}jC1IVVZH6MST z39H0nfnXw~Q44PI1b`ZC-4M=vdd5@+mDTGXL62n#Y3!Gx5F9Jwz`Ve{_5AoyWt#fK zib~}Cl7a*BAcF^HVE4WrV}586kgU7!04Z9)M9gtO-UV?46!()LCPgxo;0@G86CvJ* zjzX4z1F6PdIOm$W_0&v3i-6(kV+Zk^1}Dd4LL|b8oKO7Nb0hol5$th%*wL%7GS{;< zqMVDsPjm^3iV_BtK69zz#W^-x*#a@Zx0YuIpMC$87zd%JL`<|WC3gZ;np1`!TPxR@>`0yAAP#v9} zon0S+vjPna-+#^9^1_md5l5nUo8Ww&Yl1VwWp9!l$-dgCjhY6%D!(oyejkZ*06?f6 zc{E+G?`xuJu?oVofUIi%Nq3A!Tf|BvU|n8#2{h7*s?Uqvgp zox$JbVXk`n;O_csvZ-Jsnd-(lVQdGwmxdJp$I}kq!G~$qTX7kKX^LX!o_K`N*t*<7 zOJUc?J>!N&I`W=OB{BxxtY;=%%cYQ5hb&* z(HK1tpRU;P^5x4CBLC!jU^n>f;O6Dg!=%Kc1bLO)*hm50*-1y->tWkY29*A79)e%9 z;-y{`WImpIln#Vdd8PwL&z_ol6cKSaufo(U`ky2>@}zk^8}qk4?_a7;GTdhKEX*8} zLDM^(!PBLD)w3U(vTdZ-L^v3hrXH>6>~H{krakYKy|gOU3_99^Ys3$oqB3p5HHVP2@m*V1RKYaY5#H} zhnYGW`AQDAJkSu#r>*@!Cv+rWKba+5>19Z3jU}^*L&QXWB$XEYfl#k!TDj`VFGjQI`4c%b)KmGr0K0|s6$6uM+Y0AO(5%u6 z!kVr~EEWqoPvruWl-@*t)zT2a#-<-bhua!9o?RGZMnEq zV=alTwGr4K_sNXnhStbqCEqLZ_`j7!d`U<1*>Z0!hZh$oqpWz98y4QWnxB<`d4ZX> z1QGT_oN8B9R9elZd%-TVc%238K2x1L=)CWSmz`nz%Z06>U#glASW}ap?95CMrfr+J zicX@|-chmZPF@JWW{fWwnY|`|l*%O)5I24t(F$d`^!(?fvGtwyzJmuK6oU4(1V9Xc znF(P5cVNx)goIwmHysUyuo61;8X_DJ=Z(m*s+wyRBX_puk`Jr=2O?D zAaN|^LZ4PFhVbFV{%?lXh=Uj3rz^F&(e*Jh5W^zvKKJ_ehSldX!E{S3yQ>}}2ECW0 z12MCnsfWdW2mAKyb_kmH^==(cs5vyLZ&7&Byu6&jTKiRbENyJ2qJ)NZP1FH*#kBKDGG7Wum@v^LV+&t{OI59UdMffwst|LNYJ%PzDwDvcAh+h^Q~l&4^U@K z-Cs)WDRVM57XwJnrK&vR8CVa|F6~04s?AZhJh3vRA5j9=7mgDE{<=HW^VXqa_EGKN z9?z`IOsu2+yh|RPWV#BKIk*kSsb2hIj=RVW)H9I-doYV(+ zy+Sa9!WpuL3yJWNbZ-ht*@v8jZK-)HXlpn4&DSqJV54N&=_#Ofvp19{l+hngHk3BB zPZ5u49#c6d*~I2iJ8$(gc5rD|sLdPOp(CkDM-$)io6L-HW0TIBSKkbk9GP#Q8gF3> zl}MrOv0nnPXWw_mt=SuE4q3adn!|7(qROpQIz zV_{tvYL+OnZaHnogCam#0Msw2fw(v6#N&B}aUMPdX$YPFKy#qd^{(bkJpAudR4Dn&+TE;n zvBXPulpxM<&4fKYnsV2nMH%^L3z)XstYpQ{@E8W^_g+E6y# ze6`i=srMPY6j-r?%vvLtlnR&ol)J>&$>67o;`%K&$kxtJ&wV1t7b2KP-m#dK5DFc*XzIG+c$jABTrlR7lwMz+n-~O?C(cOmg5A=hidZ zaK>)WeQZ}M&zq^+MWj>-)JlaDSu{!Gv@bYlzz2^P)D>{|og6q}$76u)+nj%{n_$F# zS7m<~I8Pl+UaOjR>Zy814j;B26uVddXGM?2y9PWWOY4q|H${X|Og49Fpr)Z?LVEH4 zSjzY3g|UCPoBdWhdC?6}>&%?38{~%sCg%VNAEa#@Q`Rk~?p%7P=y-N51*mHLaE7WP0T+ zR7{^sd~g}AMkiXpwlkhVwZytc>Xr5}$Ev=nTb~cQ#6x>uh7^2X8UTII^vJOpwYHz%tdN?E?8 zKa0gmuQ!F82dcJ78$`KHe0Ky^2rO$^H@H8YfX0CrV`*XW(f!Nx64JpO4nT8bqsv=k zNLBG079WL71o$@NcB7+pFfjmeL&BRfmQ&zQ2X5MWYtn{xPE{;iu)~le7+VJXwy=Bh zoWq~xL=BhkEqOg$d;O({Jfu{Cge$GTd^Lq@_f^>! zUs2)pG3W>)zzIMglIiP!IJJ*VYIP_lo!P#0lJgTtY|2{uy4ja`LRM1BOtp&Ln?lNf z#2+EOYJJjSuu61mfsd-|Mo6eX5iW_}v+GKTp)P@Un^5B40o%Z6-BjxQnTnLEfvlM| z$7LaLX-YP2&gA;UyPWX8HnX>GiwWKZ)81-E5+X>qSwdPp5Ubb@p;_fm(R~Le2!gnp zeBYV(y9Yl#TZM`sri(8bW$!>R1E~nm{Pa)Jp0{A{e~Q2>2-ky#f$x&bSOnbw&^?b! zc`XAU6?J{-oK=0p9KH|_`lN-PTAUy!MZ?7>Mi8`FB<17 z0}h=3b|e;fz;I7k(gw1Lh`2!{&*C+6`4kKI7^(s)Z z$Tuw>*|~L9pFi~$4$jLQ4G}=6S^XQ$TMIpaH1O<1Skkf8HBzdFhMCSd$*NL(xbQvR zwlKD1(i2UNPhklz;Y~o7`Fgc{*s-esDspm?vZP4S1EnD53K67Vj(Qs4T}qnCY8 z!KuOM#T0kT;XVW(2TTmvpn|fYX-numBn`&k^bP0kE|abc-0ds1zS7^`Sfslu79n8Q&MAW$j*f-9msv`U0o%x z?7B-Klj;V??1a0KIwf4+dcIWYR!)TKX1vo*UJ{1_0ZRH92zo%a+2h(MqJi_Ir&#r0 zMYp~GWDytLY?7x6IK@CopNZ$HpD0t3J%L9BXIe8)(i3pstPv5m(F$%qzt9+Hun1(nkqz~A1_ceKEPs%%U@Nqy67MXe4PbIN3mZbHlnHE4b>aa)10UkhUIIX!a?>(Sl|ZT zXyePII#FUKWSi{c($2iZRqHGIYMat*8zNWKsGG=IK2=+FxO|qd@tljmGO?u}c&qCmgHYm34rKcx!WMhbS z(7&y#H`RW~PhvHH=UYX!+j@rgtQP~*LJf0ShrijXZ4Ui7w!%r%&TFeCJr9@L{;9Oy zk^kW;vp=_nn@evu9K5{w)Awb5fW3qiY&+;*A|1dn7)(%5P^KFLBV$HdTKmfOW7(B* z=x6LGxXqx|m)=6z)?)wgN_TKe(&n*QF~7xuH}>Yh3t*$YgRZeZUnXn#2SRotHJw4# z|=Ev>bWhV*DD2I4q?~mcAtn|$1P0h`Ey8+M1?cN(F z;*faO`~kya?T9OGd{JZUa`nH`92a(^N$I_n{$AUCtYJJg9>tQ0_^E+~Del{SGA>Gx z)S<`Euj62p3dbR~c=>+5x0sHL?*v?$|9I2(rTYB8qOP;{n;W#l)5>^~iemaTmOPoZNmxx?LF8Uc{p2 z>ooK9fmURafvuWv2aBR$%!b2yLe)xr!g`?T@jl6;;oM+(%q5q$SKcq-7Dlxr?!PfQNR2h27&M!^4m zL6-u%4(ejHfQfMk{|7#8>&^NKo?FcIyb3n%fve~=C{f_JfL+>AF^8^8#~py$jGZ0^m|v0@NRHfX3!#5%;)1 z1N^}4_FC9JCO)g+e3@u9jdBk3;)V2*0d(#uBoGQUg<{jq+Oia`6kT~jq;0r8SlC!U zy7Qq1Akh3i$+wf#T=bRmG8}r&YvFFZHcT{J)X!<+A9&T`&LrxDZMW5_bslNRYA*`X zTv<$jv~~8BD3ZS(t{?wrUaS5)jc^kdL?;gsK7s@~)9YXImN49-lM(rtjT9_jf%s8^ z;%zrGI}Y!<1_{>nn>5pAF`J>o(gHq%UxS7E5->j}-(mmz;EYvV#|0ZnFSYDj@Is?7 zKH_f1edC@<;{wl+{0-0svj@S_n$$nPodU;q>^j6H_hnAa?J|hsy!zsYE3Vl)Y2h6p z$x*|91IXhARZ^f|G?yr3)l>mhv|Sf2UUd2Jm>#J%Ua;DOIA&nKAKCvZT!isoscTO_ z^_>=#GefcD^?JKl5$ez6VRvWiV9dn4hl&#S!rd{wfcKq`7H@X!bY(2tSS7Bg7sOZG z?T9yGVRjA4IW`9R|Md;IxvSuccbfr?r0#=B)G&w~{{WtIt62r`CEMR41mNYt(E6AB zB0<5yh)W4Z*of~@M`mrnYb%29e4_|3I>+=^gK3KHbQT5$?RDu%Q;7zwcEOu2C+t zQFWHa*8Jk1UniC}(QP-L$>qn#K0kk5e$&~k)*8|mNF3yVn-s3Nf|AXi zp(-Idn$sWrzE6O?9-Ozz*$?sc|H{ggsO`kCOM*B)0;ixBfo?PFE}@puTDQJ+N1aM^ zQuEDATD0q#l}@}~L*a@fw?i*vjCJ|;%53``S1np1M9T7d(^L~ahmh(yEiR}68gXzS z%E;aTEh_sY_ba zx$eJyhXel&E(iLB0(Y>`Qf9-917PgobUwz@Fhk*SfIsg7`|U@aljgz)Bou6cr1F-JEKq_hhs zChjaeN?IG@go~HS`zoAWBuO=xnJ@<5ll`m!7%_EBV)uR~Kcm^Zci`>6B2-eUmKpEq zqc8T`w#Yp{QYDyd@>&s?A(@xojYRwB%?Wq))(k1re6a+$GHb?KkFt;C-JE-FPY=f9 z`<+r?KOr%Ee!JG+ESI$4%SPEO5FmH#o(BTma}lU5;OugL=f^e80PuOsm+=Lcm8}DI z_HC&8?&kCMQEi!umP|<<1y!tt7}I<>V65z--{WEc4vRSvX4O}6YN^gt+e0{uwR{?zuz>m$km)_}22`MTn0>_B?t*nQ~+i%a`_1`^Oq`LL84+fPP zBJF3!K;SH(>yl)`rrDM+(R6@GeLB+R$!c7|5E2iegt(Uwzo3wHCs>k`0S<#j*bh6q;D8c%RS=a+-jI{|h z@gWmZVh+{36y7p*=5sS4T(!Qb+Yo5$TW%-991THgBJ8>@J{3$qG`i0#Lr1n z2(-_n|;L0<^Gdh-Jed5mK@%NH3I&0+jvOkrorhIZ|x=p?8dITUNw{APPIY* zRkow(oCLN^pASyn82C{-1xN)jSoCLJ9-XKg z=o(%UV6x-Q@C58DJ*~EDW{hOqUsBs%TxlYJuaO5hHoL0fnoNgO0y34?yf5fjx288j zJa%FNua_Xt9b znu!0}8rz<99k|N7rV@9#u&@v*83U*iynykxEG@uT#Kmy{$(&>U+}LrrUw}!4{0680g34s3# zABmLNw%dY~;iIW7n2&1LEHA}%$t4Tx_B{n~{*CKO(~;}j6uy1ieNgs)EdH0xn8yz` z|6JO1z7%Cjwj4#@iXcAm?Ui2mNg&o8rWVa1s-BZPX!pOav+G+ebYW9GuHunJC{VN| z-rjXutdmm}9>@O%6DPRZL04a#mHWcWz^&F1hBkrzg%#9=PqMt`xdM#U?^xFoCnr zXR$Xy)T~$N_SKZK?M1&O;ovzk@c{wkQfn&$0C&$Wxkx3$uxw~hg%iTTy$mJ2l2!z^ zS$0(U%`g0rg0g>2s6Wx3IUTkEBw#bRH%60uD}6UNAQDeWIbBee?qDzGJo5CV=hg^i zjCTxW^N=3iFK;vt$A-Nz@h`!Z1tJ>UgjJvtp0OwisG8pKSlgF%9o)`evAoQwYwN!R z#bJb(99HC-pB(^DbpIpr^W6j*Rx)R&xGXQpPB*95mR^?YeweaFne+v$o|kf`#7CT} z)0(!juQs);7S)KRI_o;%nCBb^K0r`TS@|Wv3r|%_mf7EgEmJ(91Z)946O==lL31hY zfCqan#+2LFfO`{c%IqXPMn~DRSil!pj#s_oAJ0I@@Qg(|(f^;;!0?#l`_yeeuA&u- z!=#EaQWvmghIhtn`d&i5{UuP`h<5KCSNarw$n!7JQhhpKfsvAyMwpggItwy@ql$ox z$(J97ut-Y*F==4{h{!xVJbP(q5RC{pZNR8nS&y7fDThKh7U;H8H?IV*{O9kwp^qr= z$+VA^jmuFoC#SYMwX@fZJqAYFetPW8liQRk8B0GcBTE1>1YQ9lQhXI|8kuHAt~}?90zP}@9`8GOh9&5g;b_1-O#*9J{q^B`P7QU^-vaC2dDIkWo1vzaoaCjr z#tG{{UJY(E^ibGixn6w)sE_nc8b2{X#+0XYK~z-Ea6d}JodFS2`z+{st&+1odPY1t zz_J1A8FD8#Cmnpdi*2-e%KjJ;Hh(>WB5;j?5t_?Y&PGbrC4d#wJms$I1U(<$vR5!w z(p8tK%zA0(tZ(;oe7YY|GRl9Z7;?g(-Hz4F-UTwrLbvMZTo5fN(aJv?-tY37lai8y zI1|CMmlB3Vj(mZ)u9!!Xh2z@oCFAT?p!Hm2^-rd`J9(c=wA%r?IRQYVPBY5)UDN5G{o~Do3Ztn-U0)H~&)>tO*x2NlqNAd87YM+q(QDr;Wj2#z>*(lc?Dl5` z<(9_}6Ly0cm=w;*Kc)TMPB%{1Xu2S~R}z|Go@m%`Ya-svknVK8H6f%DnmEwP*#wJ- z(9poYmE9PGHcn6)sCp8Vbw+jaUiohN*qzV~-pbHZInUL@DNap5O+SVfHDNVsh0%PP zXB!AWmUnA)3)ChsD)faJAJ+6fkZfBoJmuw9Z|dSjzSl45+$Z4daS)eA^Yk^VHw5n` zvrsg)APC&TF-@=aq*M z0Uu<^#kvJ(6BHfX1m$Jw$ai*SmyOSSUYYjFt~$Rly55ROc+%Cp6K1UvTwSQ&(W!-) zYLBN2G~O8l)L=XK-?l@8@Zp~Q0gS~YaN_J6gtt5aI&~k8VvK1J$ne?WJcAVqNwVm; zcHqg<_|BBq7aSnvWCA{XmrwFqLTf-;I79y8%EP3p4wG*d6JeEmdI-pip|%H@S=obN zt^)94mJLVk)}Y*IqyK>Ka(^I8n~kI6EWq)XK+z>MqtIE_75OWDi34WpyMglV-&}xy zHjRw2|Hb?=1yJ)YcU{7K%%=grAx;hsD&0Ep_k0&CO458T27tKkiwdbtY(Nf;GH)@j ziIGTxsC_2;M|Wx$*#Cn5Z-A`O{vL$C)Z;I`^vyCVvxm9Ssr9Myg>Vx=BYWP=Lbtp& zEGisn!B$+Xjavb1{yaKoYwJFy@degT9ETy!QxRE`3_gY|qjAfwVxu@qSAOi$xGcIe z<8cwkGBVB*=bvNH6PK@OGofhdyNzA!^VkbZ8~vZ@PYT|h0+y8nenB^*mD~T?VUEI# zb+EBrWex>Zn)>?gV{KV7r?5M}KuWXZmv99#gfoneP)+`3svWq}m=pc#0SPFFlB-Of z-kTGpjpqqILgcyk(Jn6EOOz=;>=4-2s>oL%`a?JFQ7-6`Yunj0HMx~dw5KzmDD%Bl z)~$R(_l?T^0^I1I;GRBx3h)L1jiIuSp>!hvk3@<A%;CYA$tB(4geQOH6929m4WQwO~5+4 zTmowxp^8cp25DI1wY4PijL>(RAEV&ESk5T>ovq8d9tgDNBG!{rkuv&9)AfgK_Z1ku z$8hjHQ}{9A*Z>Q1A%jMRlizr9es`a~bFYQTCt6b6-z*x?nvd$W_a(O|f|+fpuC4}l z8W^-&mX@wRzp?^8++y^{g~?kP=CUHJ-h+U!IE=^wEAxH7YYu454soczMAESILf8Xs7rka=axmQLbwipDQ;Wm&(C?_fSsE| zWD4|9dJH;Sk!0IqmN}(jkg%o8_fw~S>Hmm~;ZV_!wdUn<+V+}_&T_HJd68K>PgO`W ze1s@1Av&=C!sCJ&LA$MKdM64-s!SR5a#I#3r@+0;V^A6XD5F$290Cn*VRl?{pxnY= zKelMTm*2A_O98XtwItYep&nsk0Pw%{pbs%-v{B_b7G&f(CA;MkHN}a6AJ(h+SN$I! zCy4A(0|)MFr_1utgUF<@0tv72_U-6j{?c<#kexVRC0#f z_gq@|_VHD6cPndXi8^6ut`?YY>5| zP*0Kqj-PIhPIfhtzC?FxdS{0Uk)rvH7^rvn(>*(Wu^g0JZg2d6L<^)l1L$0F>mlM( zXKlfO`}OxY=auWySaO##MF#3EH~=eu$*eLRl!-kcuT99AtpH9>P#8@>noZ-zj(0oj z$LG}z-;^g8%i;(nF0C|1-mMK$Ap+B9b5(oa;r6}h8L_&PRw0&oeDnEuiit}wrH?YLp{eUVp5GeqPzC(BE zB*eRjP!01K_(dN0NlsR{b&n_*fPkzRKo&p1>T2iI8Wmfg1F=4P*eW$o(-+ycJ;kWXABAbSCvBFP1)jXELf#BMLXb+Y};*IXpR!Eq-ZEa_eE zY>nUkK7JfpaF_3sLMtViJ;ZNkOy9coOa$_Ax%J;!y~jP63`dZ{eDnsq2!0N!_tsA? zo-Fdv32q?=;hoZUR|hCat>%}5c)+CBj#Il7vesA&Dk_YF7KN`S-=A5PBbFK)8pcA= zaQZ=H=Q4+KPB`zdelhg*AOlk5oxZ``4_!2w)l-)KVt+)ri2=&YJkyS3JyTQr*GGLy z`L6p&nVt2Ew&j`(e;I&|95bu;8nDuf3j)&2rV6QAwZ5gT4g$ zCpo#r#TPiPCF6fCWS~`40_eC;8yw945Od#MuAm`N`NM%u*Ic8@?ah~7)+JQ`CeQGmb$rd}f`U)$>jeG+y}#bOzwQ~GMJsD6 z-1RPyZe;WHr$e2tM+;-4*R9%umH2$#=RVoVD{WHfgQFXWylAvZ!O^edN+hG(E&122 z`(C$pU0}OtGqqk5e&W-kj~W4yJ1$)Pw1FS*TaS3I5xwLF*T5B%{a>xqBmf)oCui>A ze+mWK5f5InwpxW@*hLeXkV5-CK!@Q9YP2lsJUGL<_quQ)-}q*)KTFcru&lmQCunyE zV60z3+5Td47DMpI{=4k1(tfBnz4oj-`c6?rHjlQ76)0iY4IU;j3g=wp=0;Joam*rG=@sr<1+ZEc`io?S1_rbn)m|&YP;&k(^2&>vnI*C_y9K zVtZ3pqhVa&0)+fN1*aLu9K!hwtn_S7RPc;;STD&89FE4Z_zp`(bFT3nDeKt$SwH=) zG;!meISIU1?+riK;YU85Hv`GfnoV!>v9aBRTV>20xISaozb0Rv?AqDq{54fE6iw9m z3u-LNtXpK65Edd}E+r=BASclE25^Ge&R+tYU`U@1bp()wRV;F+T8Be~1NrCV`3-c* zT5%V@7#wuX`xp%0}YKIb;hdSjtm5Pjv zY;UEw_^_9cZ^KlKv!#_KXd$>p^@6NX^JwvkdyT+n@5a^jU3))VSXc<<-*DBlBjlXS z%Qz6gR>WfZ1}8wgG2n-%^j`AT>}W1z0s(`;P-$lIQdtVfJC8L8HBXG?-8-;yGTeFS zxuRiS9inc?N4)L}^cvdw#?q}zBut%N$n@=P9RjrXD=2F5zS?%D3PT)!qDL^|j7wp4 z)Fr6QDN_vT6)#GE&0CvITOyhjy*J$Dl_)N-;nP=mWBh7vqE9D9HKtz_SF_S#(D>(J`iJpW~!H5A)E_>2G^x>p>W^N16L;aWGIWY4=kN1k!5Q{ zbMJ^#L})Pc(p_nKmUBM|`b`V487TF2O__G`l2Xmj!Yofe|(ALg%rh&+eo(g#J_xi6?&9#xYV6ER1&~f zFMIL4HaO3+_9-c!^GElqTrfR1K|L}#ITi4l{4@QV1JSYmfyktlfvlw4Mhjo%0_s9YaZneJ>hb{UziecCHn8+WC24tUnF7wfbA3JMA~LFH^7 zyb|lYR*94nAMn2s;luLs^6xPaLISlja^+vdLZ@m2RNujzzz3}3*xFIGIqe&`Rd!UA z4yu3+bUek@vP!h`mp|BBKxmwDOeJ(GBb{}*K9EtkbL8>~QFIb-^Q9(3SFVgcT^*6t z{(yd^wcC7~jx&$vE8VPp7DMg~Nh0KIKp4tO?pRyD8N#Ss31#-~FmdBItCTnqvZ+B6 zSW_P@m?Z7H0l8FAWlNz@(Bp))m3fefn8W|c5DfVP8N6Fb6E%&J3YRh+wc(1q!|n|s z4s?(!yP!Oz(yh9?HZ%*XxMUeiq0}-sbQ;`DI7TSDGo_%lZ}d&zK4;3cOXp#Txbfq8 zLc1s0r=#Z8QIuAHS=8Lgw0LnSlw4w1^j!a_+&35s{Z zcYWMUJx(}}W^s$B=okO^jDwp4vbw$aNp7}I8T4zwU-bwqKl-MueWzasl0%fymV{K? z+tFX=fFd!46A;Cxuzy`_(pQrnKOi`fX_Ms|GE81tn|R80rR-kli`9wl+rpm|r6U+u zN1MeY!zOC??~GVdro4dU^#*87*{^zY=DDbKwu68C5$@m{3*E* zrEe)!rhIci@6!PM##ZwZlV8N0^^Bj>KZ%Bx7ZRHQI-JS4LiOk}0MTGep{xkCDq<$wJS2SLx813;@%8isoZ@4RDfcTM z52?1eOx`*9lV@Cb`prs~$b^-p*YvB;fzOmT+kCe3vQT`j7WjEfn?5*EYNPmYqaUlf zrz_?f21AbF%%&ED<4GxDQX`-zX9RdFXhED&jg7GVMa}LR@KO{gognV5+&2O=n|VRE#NI2>AUYs%_aJ#niIRs<_;6w3jU zk^T2RGoyi^s?Kv^Qdc@<(LAJ<{V&ohr>e}l3yVI973o#Y^_i`CvwdV)ExoZoj$Z2z zFe%w@R}gZdYx7%72BDy$n@9h|)U105$O}V3OX0OY<+3t~mv~5npl)FOJ+8RdyVjCW z#vV2p3LvDbTYcC+&i1S_D;^7JGGmH1b2TUWi`V|h#?)=itRinmKTZ`b0J zjk)1=Td4pUL1FGFPC z#ibN(xmCR(%YwR4fdo_%7|K!U=G!_y@mtd)1r`2rQ7$RnkyFT)#eW=YaPwaA&nV&%03>8{~udl z0uJ^1_TO^KX;ZXFLiSJ;O7_Z55m~YqA=$EL7*w)Ewn7q;v2RJnI#h_rHg;oXLYBdd zFxIjB?>X=9sNes6UDr8RSIf+Id7k^Zm(LQa`nk5pkX)8h!e##v9=t&-+CF@ zy2eFo5{rD-$5PjuRHwTOUpOnW{7LeU=(ok1kSU6v7q243&DEVzS*WEtVUn`a*P}BZ zEcBC~ypXkmF%+NN-ui!!{Cp3v>G^;YmA%k?H<(dy&vLc+oz2!71~aQj=l_Vb!{rB1 z_$|>U^r(fD9q_UR6R4V+ubv%z$u8>2MTXd^w-XOyd;(-E|5wnuQO2Hn`;S>$l+~gJ z!poN*S=C(HKwm(#Sn6z)dDy)RpKwQN#!m1G8U3^?M6oadiO0R9zPU$!p{JooS1>`` zYHk9#`^W>$)2D#a`|lGy)iCYiQ-w!Xij%-a=w=Hl657EU@32Jna0~I>{KV^)Y8C>z z35c1?5&6Wbk+@94#}p}l?9hvYZ2DR2N8-@yu4FnxCarlzG@ zVM#>Lh}8X;a@|0xN~o1xPpI(9^UJN> z5`X!x&^JFpWw^0}X6o{_XxAX~-haKB=vQBUcg91M+i8?JTJ5xD;oF4)8*B|`u2})I z>i0#{v|w=nAEu_a;Dy8C1rCy8g^J@T_s!yEHyK=8{&{l*`bHKkZ&!l|K&paP(W|Ueb&2 zD26$w*$*ZEbu=9D*XT%W|BvOsKevCRTZbkFjPyq>O7PHN_FGE-{DQ)g}*Qd{!o>070e;?{HV|0&y?M7WUpYP zYh=mZixVO(aSkr!aGs1G8x2a`ub7Z=EV`OQRjp9>DRr3n=pj5poJ@(fy>Q^Og?!by z`D&9n{dhDBrJySwf!k5>G%Leje*HQ`mp|ERtgh0;su8 z%<4e_1m_F84O)231PsI=DVcEEKOXR>5q^Gp2d*Fk) z(j+}XLWub&k0Y`iBzXm4^@W60`Q*6d<}BC8*h^&>GS-bNKG}O=%Ez#8lGY8rEu6^H`OC$v+Vxfm>YWf z`9__z+s_GP!KIPD9Zq}x`$NASakTh4fH!n34yK|XQLkU2wIo1?^C|+)M}NsoDe}~z)q*;ZZimP!gn0wFeWbL5r{qGMGjOn8g_`p`>&F0c%&VRXJ0_xh%n>u|7ufRB+eWIVVmgI2u+MK_b{-R< z{+<}VZxAn8`6W5ChPl7Kp}bzxyLpP14%#Zkcnl_nz=X9Gdm(S2uJHEbXFpe;ONYXP zWL2Ks>gu5YSMR5R;yEfMr!eooUQ!FFY5coB16qx%$eo7h!o0nbXA1j&wSO%o$D1~_ z8~0N3N?P@(+Ggr0E|oOD)+j&QOYKJvr2Bp~S1)|>u2yj7FX^nl8RV*DCq4Z4&8?{i zYs)+6-GL1N`RKo&zs!i)%=}Gz z($UpX3Epc;?wG75Pldl5Zxj(_{Ci-q*J2If)4iUx;TS?@g7U|)&~9Xx(}!=;FUcS9 z%W%E@)WeiG8wC%7SR=8azwKr&KAQ0<)q^{b+Q^rFckE|v8;y_}l)4wg;_l_+3tZz3 z8W9xJp8ghBxm*~>kMa_34pvvzYiz2t#pNhJAR~dKr)TQ#Awhd?`kqF!Ae|EM zBmsN+5t1LjH8mzL#!vOqDZ84}Z}qa#XFbS{XBmdH!#8F^dMXwrE9|1nmL!EXPEmG> z_3z$xH@MJs>#@Eqh5MP{@mqrC{1EzW#Zx<|BOU)?DfJB zCB6@YJdD%}xowww7MK5cH%Cm1S=BwhrHALR&qh_3RW(jUE`9V0q$cQN8wT(PPlp9l zWsI+`hTCFs6TeREIU8BXV{!Kb!?s3a#;dEK9p9L zlk%KWeG^R|R!m6Oh28lhSZ;s*l+lH<1L?HbI>l8o*$^9_UUkGgzczPR(=7fQ$F@`U zQh$>mZ|z=*-@41&ZQZTc5GIwhrbn$&@?jc?cpam`pPmUl2}yTqP;lnQX9Y}Tpjq4} zkf9qmG-DX!v!;ou9owRI+57>pDF(8TWDc-8SNGDoSHA-Ao%xr|oRN8HuD^QP`uB+U z3hNw7N_y&Z^NWGTU4d?~2o7Ou-dI3DWJ;4jfPE}1`08M?O0Wj*zk zvES(|F>65JUWKxfDK~ebm)_=V{Su{em|>5$U>dLfbL5WvDSaDwV#nqP6mxrIkCAOX z^C~nl=_5TlKLQ&_0L4TX;FF1fs()h?7xx_9Nd>&rdIU}WBd!f^=xr1>kSb!W=a`F5 z$v%GQAc>5j@QrvbrP0oOh$v|kmtnkTRA!R1QaYVyM|+v|9XxPs5CZLP-A|twhuoXz zp5a=Df@Q9>X8pR7EJqu_GlAzFKlB8~%}79YW6TRf=sniWb>e1HgV}FroXPcQ=wxDg zF_%E#{=}0-O=~zeBG>FM&o=Z-*5aSkINbM6buA%S@@pQ`ccF*vI>UQ36!CXo zd?vug-9jclXKVtSEN4Ph-)F$rg8)u@sx5r|5=f1o8Ty@_O&DOoBCa6EaSs>rpGc>H zz%MIkT(d8~U3(7~Tyl3l!%o9Q<*PX9I&=a`6PH$mQ+gfzoM{6IoNIQ9wGmgHGc&(A ztJMI13BUwGp1z=-XfwNeDjT5Dt}U-GNc}-BcSV6eVMG*xK&kFAQnwgz;=?#~T`#7Yb#szIz)1!xdf67Iy~H(}S#HybL~D z>+rHxdZM~>Sq{2hm?R82+yaz1Am-w(g?`b7T0))NT$Vt4JPdSUo$c1T^1?tDY0IEs@r zcary`SX}h5l1~S9ZmM^Ab|2C))-}K`d7QzgDZLb;C7pkee*Sj0l|1C0hy;Cowi%Ps zKEJVN(U~Xlk$We7VYd{%tbm4M-YAd*>7rLoges{Pp7RikR_M4GxXxR)fWAl{aCZ59 zEd|G}$-Jme&FC*IrTH z-%y7b|LODA7I!SK+-CIgd)Cb_&0Y)K(|_yyB7$a(8A0c1!y0pz$bRnDW)5YM3vb?oTT z3l}dM=<7S;D@w?0yAPb8!oR4WT0mpA@F@Y0?KRBQ3*WeuH_WK{PRbaoJLkF-9^~Ig zGUMjX=rPue;b>{f+%eAUUwkJQ)!Xu}_MI$whM}*vg|-&kWnJ$ghHI`ca6;{Ba-~XuLkz9K*8`0lmzM%(uV=``! z0*7%|W`M~g9~@`$HeFdd^cDdyW8{*r)wunfeQ48`ybgk@G3{He2zT4(c2^PO?#p#i zgdR#Y?~^F=#90;U!ndmkml8dSYTCE2*vvaA>Z0yds%@n?_eYRZ$8c??=jUFg(Vm~_ z@?JAl9{PI_GTFq7j<03BD1mzpn~E4>ai$`@@XN8*q)KQx10rAi(BJO;jKA;o zDeS^MWC{k1K!(&lN}Viju(#mdQ6L5uk?j) zuK9ZX=(DSh5dIN|h%LD1f7vkg(uaQmDu% zg(@p63-j6%G7QMOpsX8A%}B%Ip*XY`PK)1fi=Da%{jEK*)U&&&8I)UUg?icR+w-Re zv_3K*f_}$GyZ^+;K!pV}RIVaI6WXu9pR&l(-ooKsE9>v$(eaW`NDpOv;VG}Dm#uVP98vE+=_BkHDGef;5wkE(kr95 zy59zC-|@WX-T9wPOzMrp`3Y2h@UQ0mKVzAw9;kNy{-FKWq2@^{9+LBm40&Z-xaQhS zH1NAq)8$v`gTLMObLxpaQ*>4_Z^;Feq>StfaD$d3=HxtI$~SRPaI0^;89|Z!aDztr zFCkXBn^R-=c)Pn%vD6yhdqb5eez6a1)!$;AaIMFv3tWXE6Lp?TpzOq;h;Rv;3u8q4 zK{btd?fCh%y@ANziL)u+W*AS(IQF?fh^j6qpaA}Y?#5bV*O`H@^y=;Dxt~7+ z3_YOtd#>AT-Ay~|74bYiYwiN>P;pYPBcbDBs-cF^vz=t$nBocy*KvEVnTIv5B@XGe zWQwv<>{rEq`pIYTMT?0+Ndkt;5`Yw+?*6teoInk<^K{F$-w|j)b z%*%`5Z3~oH_yOf-X#l%{zC~A!!;8AlI@@0%6xSZ6W+g$LkKA0Fr4$zX;oR7Wn^SSI z5Tw55ANCjHz0u!q<~ax*%>0+S8rM_g&6G~Cv4L(}6NKs%m#?gb;Peb)hmt^YrJGcN zk|-T;jBCiemcZ<@RyHy)gRMc*JPUi-DmNc7a5S#hToZ8DQS<-C7f*N9Js8}%5#clf zBbx~Jzn**sA3jmOR-Nxf+wXaj;no;QdlNlHlYngTn!G(v8%51eyT_Kd>}b-3>{I1Wbw<1W*G z6C`_B!!pWD`ZaMxY!aHMVM)MC|&Hy>%Gu&w!AuGV27w-v%+=!EcE)Id2;v;p_^q5J?KdMGYKb z>|m0v07B!9vo)8;VOmNA9@vcEl8n7Kxc(r4xJNJbfIAP|p!f`bbXL{`D7Qwi)7iMv zVHO!%TfQ+9ZTBvT72R8|>2{iVzmy_E`zseFe;4Lh_woDQH~NXa(l4ru$rPB7a=jX; za_OEoNz92cC*PApHiny4x?a&gz41i+oqPKN!}<-_=X$*l;T!1!@IZ0R1MLOCOVG`t z={Ahundbw^-}oCht?6W#eb>MtvX}|A8Qi921IXSeNu0Ru>zqqB<$&6yXauaj&M9m% z_c!~f`8S^}4ZM1`=C!ue=u4Ojdx2q2f&zU?-M|Qnf&dxM7@XN^e{D-T6plJT;)%H0 zcl(lMA<9rgmhv6CGe%aYhcC4Byb*qVmmkv)TMR}JsWBW+9}p{g75Z3bhB+R1JEuH! zvandwwh5jjIa|zQ$1f!2)8j*dV(Y##I7&!j9rxV9;*GBT2=9nI4oeI)-!6jLM&BGH z0$LB-Cm+^;d@Dd^dAe!&Vyp1XXHYM+n)<8Vcx$+j5+{(P^f>Q5z*hyBgl!ceVm;Gu zA~o2!!7-C@>&3i%KLFwO@8-lLF6cBGB=k}TuC9({5v4C)#|fK4v5{t^hZTRp@$}=} zshnMGamJc_lcfynzkHs2m26)I=fYn+Vi&K|HHlpRu4+z~59^z4EB z0axR{i5>?eX6E(8V=^1ytjAmcf+=$VK8|EJQTKg(``j{=AEr1yf?oZm9{9K3?Bj9OsQ4opZ% zNtxE{wIC|sHQz@BA7G1vs+T;Q`1-Z$AJS_-wN^C{>@I=S&ks;iclVJn?0ZQg+1_;@ zpHjcsU{yc7f~=cMj(w(s@0ugw{bc#s=au>5Pj<$ZYAqL~o>X@VlKQ=eYsFv>Umt1H z!D5W7LCWZKOE8iYyh1qO!aY;+ESnqr?vV= zbAsV|^q7QU+R#TbMFxgXqK=V$iil1F?!gnQwoF)I{oz}QdRU&BI0v%@YEXKQZfM2q z+hUdY1L_|Bt8X^?pU>1(tXNIOq=uXDuRXk{ld?9Ai2{=6!$z*)x(IST)p&}|r7s!* zqW9|h#tsLOov`*8_8-5^UT9n*6!woFNNL0poYyC(x3ZmWL717GGFLOp8}F9+QEWn{z;tdvFFd_Y}mDeJ(F{M`~|&RoAa?xGz6NB4-r}ir#Fr zS=4}hM*X6UD5v*a$GXbcgIatO*rc44x%K|`qi*7Qk{5x_Av-EVK|@cC$hCeanB9hb z6Sfd}VkBCvZZ$*E#ieNdH*S4`5!%w)f0LdJWkHXmhQ2ZI$gU0w;n>6SlGA6oZH{qa zKy9}|%dw0!pndEwM+JFRD!a={;|pDJpn_STj`7V8-9wbT#7;AY?30LYhd{f-YMyT=c2YN zf}>RkJEzy63&3+raa*O=Wq}LIoxp$NgZ~DgsmuO1>3YX=v~pF9xoVQ+35^el>V>b= zBSkFl&r7Dr7N?(=7x688Cts>9+j}D+qttPVL}onAE^E6M`7#S-=o17oAap%c&y&q# z_V)G`zD4QcpXoU1_es{LYM>Ma35pAzhxYHLW6B>~!h2;;)AhGB6p|&D5`SOW z_(uTSJFAGc45^*2OrY9e)MULRWApJCag?FRStF1{51soO|FKe)Fjz@hi1b9-(Kp0k z=uIeN7%?bEIpnnjfd>trJJ$-5EjArIsw_YSkFKBr@Hoy21;B zP6vm3D{AI)C9ST))5Lx?OZ=B>LW~(7Cge2E-p(s>?bZj6?#}-?cD_DoJz;i{_kYk8 zFI{kTZo$VEzI|4y!~jar4=QGE=I1IYpeE43;d91wBV4?7qBGNE)8f<&7g9&31BBHa z&&HoI5_A382v3f#iU%$9MVafJYisjuGf;v;CS(pAJcFwsNhJ!Vy4YUiD)_tijd1w(6(;`bJ;Us}eTL+%h*9fpg`SOK)nkS)c zrI7%IQzhgape7-O&dbhD01z4g&&cI8x)URmR|1Yf{@}=R5?mO~{?Kc4*YhD$*jpw_ z;wrvA%$jMgiXpfMQfZ6>xT$`QsW&j`yBptCS6E&WqEwuVlGGS!nTPOL&>hI;Uow~d zmaIAkd9Vnlx2u3fC0z1&#HK&z%m$+Hatc%W6X_MSTr&_!Hxr3#{3jVr@Pl*>9rYLd zM?DS6O)#7O4%8+m;5x-O&sv|(AE`N6>WJ&PnCexDr&C?6_8R%!M2SmPv6;wYJeFO+;ZnaJE)k%C}3@@0VFazQ|ebj(ZnD4i)B-&$C6*k2B<(>wH#+KYg?M-^qO3%e~T8XQ%5Umgd@m6%qFBrbuPL;a=WL!2)bcdB{N zRE-p7k2eiX!ERSs)0bYIaUg6%J(!{Ab>#%(Rnue{#$#1UXs1lPenTH!q5AANXIp1qB_Y^ZeS$ve0Ph zn+4=$5{RfB^;Afhc$Pr*)W{aRnQ<*aOxd2UOhzBofRYGgGT`_HVrq2r$m#dBzVa3< zGru2?(YyO11G6C&@7k9McKSJKFZSzl!1g$~&hi z$6O0Qz&J1Ofipz3%Wv<*Qx@~apXeuy(rUJY`>lY92W_WtcDhrc5hc&w!$ZwkM+3-c zAP(;G^gkZu^lVi+TX4X^Vz((FkZD09b8701wOZjbGWp$MApkSDqXB(@jFLOvHFV)w z6i3ocHm3^cgx;3e@QCACjVPNAP^b5Y=j#q@E2Zf$35tp;Oyw2t#UzEh1KM>f8K&}H z*Ma?=(46w%NyJ!I3;|LcwD}VEup}T+uEYK|RG&G)tFpN)RwbPnzb*g&$amE2+-fCS zHdj(ML+P+GP;NZpslYm{@O$ueS1>iEdhq7U$ySsvVOog94g$5K7hgUrlmR(HCGW3w z3NPRe-2Trc^$OVb8^ELtVFx^I?)$`VC;tFAeCRsjB2g;pCIIl}fr5GYQsGW*tjDaC z{{5PFO5O)d`evA!Lhp9Nm(;o4Si3YAJCSjQ_{7}#TaUiB^R4@5W3$)}O53~lM?up# z*a!r(V6Mk5BTxR=x%gInYFFYPV}CibtCci(=ElP6`8#8&&;@J)I}xQd5og~quW=z# zz2iO-pHQ^NaKfw6-1ab}PU!-=TXdl9jpSOO`jZBh`wDVj3(P7Z3*}|%2hKaa#oDJ{ z*h`}kH>=;$CSQFCq;}l(qC+bBKxNZOIWTZPf;p5R)t!Vi=On)q<5M}L`vR}UNS&8)ae zosCc!i6V4RB32N1GQ8#7oSXH*oG$76JJfGnfR?$HG;UMNZHV8UiqaT>J=*@q~=jG=7xYH8Ri7~bxaP)-sjnK{~E z$5Zjh7!8D>B_C2^DuK$~RW!u32&T?7ccE76p2T zT$r|y8dX+frFTi$B_5nVmnRw#v*B$UjuRKEvv3I9Bn#QLmCf%9>hs#rm3NSD(8a{q z*x2%|foMg4P7uFNKK$_rvhkqL6Tkbza{9%rMG=@FoP8OyQ#%QVx4ra&kjGN=lmbvo z+~id&IV; z<6_~}ncMg0gY?@R?FJo_SWjkEd3=dHpjo~qEDEF36i-q*|k~M7_gdv$^15e z_X1|h`{&O5C{<%+C*t7-Qx`bSt(xb9QqDmJ^#!0;rhYg)PIFvXSa^Q*w*t|GCBO=u z3SB=|8tSCqTq<7jal8_1l%Ke1Sgsl}{+73N649U=%Uj)a44d292K&^fs2aXR<{7@) zJNTN<+*FMyZlT+n;hGTDvmHMwiQ>HI@XEvVX6=mVztb=0^=9 z4bDPTWS*p##i=pIv+tUm^prafJCob5Axe)i6&C}1`p_rgV)>l%WYrwCK@gk({myr-_@npcd8M-n+* z`K;BrBlEy?SZbQI7Z=8fnSHVg*KS{?HWB@3Z9|Y1xRs#Ccv8j>R5kwl@c`gYuE{#< z;hDl~IzrYOGfQRlgHDuWJ3cBmcSruaC!wVSiik9cZ@K#cq-e_g-17q-S#G7?-~)x? zQlaZ0*mZ(?u@X!MM3`3o=%tp(MBQQG0Hc+q|#p>R*x zzgU3R5c-tsY=!R|G9_ii_oExyGUCG!M?Lb)q)W+^3t-FiGoo_cWB_at-g(YQ4Ttd1lR9{l4!~7cSox++A7KIV z#u_C0=yGqgbymN20Lk=}CvCmpQ}Hlecd`RvNTv%OlNzhs77{B@)d1~Fj-}>+e6D4P zoo5c+)MDG50Jx(=hmX*q3bKE{+7%CaPzCYjXx^=--gp)=?Ytm@xwW=809~=J!c0%zy;c2F=lnxPmzXrm^vnmtZrv|@WsicAUJm9{z zKB%v_4uLp z*zr;3b@V7W6<((NJcY8T^mH7z*m}$Xa$(3*n2$Z}oI3JrwUdP=t-ga@c~$Dk;btDe zfHdL>jr8Rq^h6QNRYbW})NzI@;dKerjQ;*8^X$2Y=jm}_#;WI;j!;Ym+wKpsPyV=R z%#Evj$TNZpC7TZmn~V9&7T(L(ZrXQnQqRX`b1aoRaAAC$Lml25NLQ$TyOY0fTz2_x zELA?WOu^p50KZR zVfKZ~02}kj0*EaOQw?SW!$92};_TNhzMv}*5Yq>|Bif{h0%8-3Ik0F zMn^4fePUlw^9IB(G#mS_oI8Ha-&Pk&pPt{ulXfI;AXPs05H?szDV>pYKvj+NP?}h^ zgja~u5q;j_H|m9J@2b>^V_yBOm37~*@IlD5>j??7w4~mH7hHYyyVDz(a*Z_;tG8 z1%^Kg^caKO_>mvsU}^$J|3_Hm+8Jz{d5Pxllg{a___}Q9aT`|ns5ztkn!lZ^p#8Rd zL&Vy2UMw?yX;MCIXa$1bU$O#vnSO-{G4~Q83I?Kkihw@0`V*$)F8r8R(&L~Dl7!@H zcYJCvQQ^IKIEQN3QTMc98I3N2`5TJxh zkI_q12b_Woj$*D?6p}BwjZEp_A=wnK?mh2S-&UrYJtxENcs9x&FO34@trdVm9F~#) z^=aq6O4hA!=>rPq`eO7iZp)HNZGH6_tt^x%Gt(#mC!LyRVjLlssBSSP_^jr;y^2Ss zB0?v^5&;#STvS04&rsX2ecE8w+Rf&8n<&XpqFvJf*!URa-HILy0uv!n&c67<@aff5 za4IS=EuW)fL3y!r`&P5kBAPynY#oltYhxc=II=%9L<_d|-z0razV)c(?1|E| z42#U3qYhV`I~(kUKP*jTMP&4QXN0?+_bNSk7@OA6@!Y@Xx@e!)@f_dkwA5X5Jhoix zkjqQMKt@7x5OigYY6j1RgCCFW`lYQ~c5O9RpP9PNhm2N$+UK}WecJ^8`P{m!BW9cm z39xP>+m2y{*%y9tqe}e+0z8){?pzgi$IH_8=&uTo%D^Ooq+j7t>C+7MaN@qpzpl<~ z1@LVyE$9SbLo{4n6{{&`N8&UPOrcIpcwT9T%CpvL#NMgnhR$Q$C_^YZLE@Uq`_Hys zLL32`M`M^Zf8!^Bo)oHWhUiM``xC^?<{n@Gh| zRshJ1L?yON3fB!DRT$w&Z<;wm`gw4_SZr~WA9R^yX6p{)lKsIIz={E&vu8xLrTFXK zy_}=1kB*MgfS8yX%pnc-4a393rjBC4k3av2^rNQjclP`7h#`oP{q@-H`~{dX&A}`j zoGvD;d+%vM zqOC%zhJU)Rz>)hh7z2VW7JO2Qik@6U;Ac|(7IH0~vx@V#9DoWnTLc4>=%P4AZZW^L zWxB|_fzReX$Rwd7_7Fzt%BY;2NtpH~irZ>AKinGD^zr=V7)!0bcwuVjwwlt*7?h58 zwB$w}rVZ^%PB&q5v3Cn|1>~KEq)uty-x9Ar$JrvK@4vQlY7KGyYEq%1SJPU3`1H#O zgW^?Jn%DK0{vU*@-v`J{mH;VR)T}~+$f@%HXvARUJCola1L~}lI zrnEpPy;W-b4MZRj^Qv1)1`vk;wFs^9#px}ieSq9kI9(DoRw0Og6DEcEqI2A7=NxSL zFwiPt69hgWs_vJwbIWKf@%t4u=Szx+VwL?@5$^2xwFsq)r#F}CO1#F2)&>?FDSTYq zlnyvEnY#NW(?;%jS*U^YPkuo`1bf@7^R*myCansazEf)upaw>MXMKxD0B{^Di|Q3* z{=^-L!6S9n`brGf`tqVD3~-80s&9xY*>@@@o57o|`yz^=;#ooEO~V$fhg(Zh=2ZKp z$D(72q}oFdUq`*;u?JC`kFIVU!m?nYO>$i3w6L%Y%!*zRZtCf|LpZFDFf1@--TRc; zY2wcB(BbpUMw|odqi?x6cHcjJo#ZNW*2hBr7%vuS0ynHGDEsY{SWoFYdIG2a7TyeY6vcX?|MV)8KVzM zB`%%UB}3I59mybYXk`l_q5@X69^Ki-5stm-R_HE&p7xy&FVKA-qi} zCfnZNTEJ2PIl^wvt`h8~c0c*tHw&%9&Os>xI-@EJPtAX%|hs~>y ztKVVjP??J&j7Ko6H366P(n#vWMO->CTT97u8itaR`=jT&N^L(Gsf&Uw$G%tHHr;JX zl6=X%*DEG^*NYb!okqX?2l2HT&?((EAX&ZP1_bQSfmF&n#UY0&+`BeoQa^ zq(vWq!L8qEDEF2il}ZdDvl00q>J>EENhHdTGNoPnJ`iV3#kE!+x~Hnw`#o+V?k9`K zi-OmDu?Gd6=RS8jQk*a579MglTnqPkDO4jj8fi^99yAMRkg!G&kvg%D1oQ4yU!?cnu05f+nK%#QpczXja-sTB0%y}fh;)s?wXNEfLvtX{*<_(_s(;?VUqE8T!q zk(e>m0AQChXu0pvUZ{%a1&&R3U*D^--S&r+0XO8vcDqO==9lemjOmw$9DdvZ$MZ`< zUS)hcxa-uK_4dABEPoxM3BHe5G1^wQ==Ry-&_u}z#Va)IIG7r%q@`v=osJb^q*e^4 zV~p1_*2f!<=e<+Eipbaa{!zgxefV_pV834C6HaZbS7L_sj4kc9U)p>`rmqy&R=$FW zCAleAZLnQm-msUzZ!=Z*E267&YJiQ`q51sty=ik-iX;mrz5;L;l<6;k+AY8fS65d7 zuyhupd{0MJPR@Ne5sPXp=fByz3NrC8Dx>if{0?M&6Dp8Mh^>eUU3RYZNA8%M zk}(V2Mg4o|!K=+(+xuoMUbNdg9+cYn#b&d{W(E@h_*dCnuX1`ZArDPwH%a)DVkaE@ z%U{0UDf^+C%+tcCtj#V(^S!|W~Pc2oY3HQSTv0l z0B;7_5WOG9?FxX!y4SuStqL*p1L!5ar+vR_7)S-b-()$!vm$4Fh)^xe!|p!rb8`pN z3hZ$O7>dK+o&(W=kJVS^Au7N)X9dFa^y7Tb5Z%d7fa-*=0DS%hP|<=1dgJs*m{LNL zH4PoD;(;1E(g+}W^j`E~j?&ENzye6IkY$9 zZ*mF#grK?(2+G##4G?0v51Rbl-N{!3`%|Zl`djxuRS>CE(&2ZE-U(B@P`z#X`~2ew@IG&BA#c^oILs~|!42&A5_ zzUG!O%Upziee0+(DyN2&+@AS{FpywZq2wT0bu+qCf0RW7O~Y14}NuDT~cXnT9<%%DhQhnpd>`DzNbQh2Kh_Po8FG)plEX zPugr%c+uL2KLU9&<8g%A3~Nl1WOylGjNYc{L(L4kzCDs__tja_V$Auc+7A82+vaO~ z`}Ud271ttkh3KopdgRTJhncbrjFLe_fv@+Lj#kz>t$E3mW8GdSHAw}Khf?mQC_yudA&C{LE@K9LD)+WuUq*45x5ob<& zX|)$hFFVEbLV9xStg+sYKtL7PrVM2j6xi^~A!^a&Y3*MSEvMVuf3&|;<9)-~sF4kq zGe==K&TUzAi~j|T59PFeZD-s?ayL$KsJBRYa2nt}#f~y3o^zBDw{PFsMIbmCCCNSC z`RKB9b@z5gL~H3a@Wk*H4^_N*ioAj^=bmtX%2InyNLV=kBOiW^nQ2dk^3*-fh^}3= zPKuVX$wK)Xn$wNmu^k*5eYwIoUMDrfTH9R~Ppn?Qlr=v{$AMIW^;xrPg-fi+94y#uICG8&u{WX*7pYN@mDpNNH6TjCN z>GYAkvore{t>dgok@|qY+##?tI0AmI@gM&CuVv0% zr?@o)~%{s z26`4`i>%EQCtjq)&+IP;|H_Ts;EezAGF%j8IP~egC_^~M#?B+5f#g71LVm(>(*h1X zZ71v9^OAIUFCWX({vjz(AAtYdn%X%OP*cZ>tRv>Kd#;_A@r+!2tb0LH{nvz(=;>FT zIP%f}yMI^fMg5k8^>6f^B^?x7CnnrJcL_CyT2i#LKLw^#l)N@5vHS*y014abQ@XPr zd;^R3HGi-ydbQCr(cvWi4CAwOtXdnL6z~r#=~VSr{(9*~-C6DSWROQr(xB-gigVvr z_w-8l8;;qB9~w>2EfkU`ljiUTYR~afzK;9Iysq(X;8EC^R}d*9Jz6kXI#TwY;jKu-qh*|d3@f*aw#&3BSeD@J9UTr^P8Oesc-h+Q`w4WOWwXlJ#%Q)yGBne1p9zlfJi(Gy3icjToop*o$KQr_UsyHx@NEZNCXS8xJC`)^($XG3Bm*Djh-u>hyU#@J$S2j7rT0=vC+|MhEv1Y z^JotaEh)AHgP5EO)5r5l`;TtC&)GnTmfk(yr1h}2@O{#{?V051(d%>P`7Sj}z3&X9 z9zR{VU?WrhMb3aJZBf9oi%J$LU!WSPJlFBEr)=g0?Z`afEt=R(PmdtUboNBnA2mE< zRzZca+6fuYybx3;3Hl_TBp<9@))|O0cedF8`d>Py7rj)PCy)bvfiKcD0V7lCxx)bu z%32@7pd$tD!J`;;A0HZ>t1-O_2YWYdob#pRR=bbkFKGAa1!c=-LsViF9gV~77D&H=eTunbfVmWJkBvk3c6clxk0Vm^>``M3U@n-6ddyf*(6`?F<2hgdFR%~hoF26 zYZ&RL`|6G@81a2R>#GaddO4pG z(2o9Kfz@L3q2nGE{6f5P{M%&bzta(`mDNzdLI-b%nNlVJn@u&XgI=~&IypIkMY@r5 za%kgyM@KoxpWmGEC1KM$CT@_vy$5%8Q6x!|4#*}x9CYN`Y7|%`b*#* z;@X&GWn~-iffncu2OuVK&wXVBn9n|oTU8Hdfn{Q1VqyUbjG&Kit(}{nzdq1g?HMw- zwOsZi_cUKuw&1@jKl zyuC%}%74;40B|!=@^kc;Q^FpkFw2a+vKnhkNjY%Xxq4-cbA3V;5^Ok<`@QHCD*(Ac zv$4<|2@ehxS_OoaQ`^*)uISyhSesB)c#YIy300!!eURp!Pq=nbLc*8gmf{2CxY#9z z)*A+MQGS4PqIZV?Xz)_b6pU&>Lmde4u3+{cYFhRSCMaSpt^VXQ&Bma{m!qG{0Wc!^ zdqF#n^Mi`#)cIb~_`71|P(NQ!oKrY=0YnzSUiv$0K~(d(aC%!F9-%*U@s&{+%sQYp ze2!A}b&~P^FxB(U>?Hd}1OgskWpu56iZ&}Yc3+z8i`aYJV zJoQP41OWA2^_2S@w@Vo(oG$V9=S$*3$MIH2&);`GnpWT|BNPcDf#cBR06X4DcfR~q zlYSN%X=&()uH6+AzRp z;e#@(tNnn{)USv`)5QnjS!pR<>pXeU0diyOGso=h?7lg&i*kdFb|qxo40lp6l5}sr zRvBl=(m-<3dnxZFq-#A8QZNsOzHo5%15b=@yTAz^hCtx{n3{UUU-%?wG0lCmSpLES z-o2h`zl(?cE^^;d;I}hV5F?)!OsyA8V_{Nc<+cX8oTD+_ zdcGwFEKG@gv#R7ixq*Ee-GjWJ)0)w#b5gL12*A*zbBN(B+$wc4yLs~__mG{t3mhVO zCZ!^k^oJq^$`KT&g%)Q&H|YKN0~1gArwMVj2OQ200+jp-7zB59$pY_TW!ytt_7JVzwMjz^IBcciIu zy3{cpJ&IfweWp8kIAytx4&nx1V?Y=JcwXPY0Ju-C;6MQZvHf?}bG&IdIf1&-_-!%| zwN#a*kF_UL^gBN66i;l;{3x*VvD5h2Em9UUF>y8v5FZGu$|9x*!}T^YEm0NhI$`O$?DPkS}@5CLu)miTW*5WppJXTel` z281JygTM}GL_Fle*oB0kKC15g7*(CmbN+1z|L%OIT6ik%$|f zDs7oZ)K49vGb3SNBA}}cY+WL&@{HB%F+jM}!Ju>HL%IV`swGclgqnhc5t4z ztOKx{2e?YqTNVL6=q&DZb$)(6w8_wDPXHS?uzQ(WTaTXl1M7^`EsKG?IxJ)kP%{6I ztT&H`a{d3uRi~AbHe`#Ekcg!0McJclAzMNcijWzDHniBIkfjW=PSz>YP)SNz8f%!D zL>SB1hGC}R`?~3z_viQcoyYm>oSOT-ulu@R*K2t$vNR(ST{E@_YdVC(`b0_j77ugR zrZru4x3#>bbn8&SXk);yAm;f~kGk#ZUdmFV74|(YIzuci^@Tw6n|0&g#pB}9l}HHa zaBdYl?n+9E>>`IVya{Mah9Rqb?s<+}xSXKm8cecO|vOmm&l`^ohKb)O|N^ z{5X17+-36rs1g3md=4f?RX|rKmlN(cC~R&{6J+zL>%zi*7sY?r)ctj<*CbQNSVT zDtZylI~;yY|Nj?CKg23^iF3dn2B8B`L8=G>8aYkLtGgT#OmJ%p&QH<*~YBn3Q$|EBifV$E{uYM6Jq{JiX;ll~B-raHZ9jF7yV}1CPV)A;zNEJ>-7ICwd=opeeb)<@1;5))p+_#EfpqvDtaHJkjoxR}SDY67L}q_I?J zS^!ggDNBrVAngn@xDr0kb9pcV{S6e+?ka!JX7~S|&FR(f8W4iGI-jcI4#F!wlEgTT z^6e5y>?>zwX=}or**dJIh>aJgR)JxYvaweAc+#!j&Fi19G^f;_@h?|bg^$!e#3~Dj zV{~+C1>K)bb=dj*uC}zXIS#F8;9#72cC4hdRFQcX=&JodCF-pS4W;4#OTfY$?z_Jg z$G{UcY@5OpcrVG)Dt4G-AuBUfG*qMys$X z^f1Pnz`oUlwFaS98QSv_JDogTI&8yF^C2M7LOkA?n!JQ<2Urd9 z0I3~hQ?H%g-xkHG0ww`z%|b3TK>QCw#MSUwB|(#9qAwr5D@istp0b@(@B&ZZk1J2g zS^vzCb?Y9$6G?6=J$D(yFJz+Pc=IN;0!8eRV`m~-;bS5I59%&L6>}lL9&#Y)%YlN~Mc$KFGS1#&fqlgET7u0umJMntXB z^hpc|w!|A9`ce$v84S)IzSa6KVleC_`jZXBj;^WRUI<`ex~!YECD98XbY`{@CH7SN zDAv@I@4U4n(Tm_hbELlJ#Ap0j7{xES)>aZRuL;$bZlqe%yaxG#zQFrWe^WMwSCPUQ zJ$RqM4uoZ{MZX43#UbMVpkobn)&^n?d05k2l7vLK&?$dpKKe)nt0I$qQ?-YTmZ6bd z52o*k`&1c?qs+B5_}eW`4Wge=SZ0b|vn)D{7xnfrhHc;}iD+^}3=nvFW7L4CQ)dHl z(*391f8dIDT>j?`IIkqOPNz9#bE1B4&YdOtq@dxuEt_Rkijvk}bx#&CT53uni`;LD z;F(jduN?Op`Q4b9AJ74MDXSRP9y%yYi++xyjwYjkLHKX`XI|(gieuWoZKRod@Rfkc zR2X=;c?9}Ac(Cib%V~E48fxdwsj9LxyPjF2Uk0Y5i?;(iWoaIPyqOtL&&7ypblX76 z{6;LW1xK3rAB)cSyDTm7Wn^*>ozhS}q!1N6PLH}Goe%G%8$9d%#hO7hr~Vg9^1cO5 z2UQeLp~5Cb6_tx~pe2?zi);-Xb_<=Z8Lu)5m z)4m$U>X1Fj5u#6L!Eol_^x}W}s3i~uyo;?(8t)+}T=Xn^L^edJ4W?VaFl0SeSv@*( zlWJ)|eO{A1j6qgNvvofy!={g92LAjH1-zIWyfsWspF)QaN{Gl-& z0ve|Cm()##xU_NbHS)*#U`FklP!LxT3;;wn)LW!#L}pJg6V71%4}o_eD?qldFw3pl zkkypT2$G?B0aa86WMw2JlFL=F6ITMMpupcT5x}I7-G*=SW@;G%Zp5Z^0MtqRw{$`@ zz4Rp>5glw&n+OIro$63rWMrhP4RWp`v_?>8H-!a8fya6p9>jer!GY-9{}GiU@|hCM zsgQOIfegVnY-j2CFFZj-SC^E&;oF8|;+S)~yD{T*wv9pac@!6Chrr$D*HEd9jE(|R zEmfo3i;Wa6$n5Fkkw&1lL5BV5Vo6fcE{(E41n>YSf#_Y#f1gXHiaON!K%(&sr0#s? z-V2sfaJ90SwOE#!0hOK1Xb^B4txrx0p>KQlyl5biA#u*!dMl5nvR-!Uzv;5{Py5?x?xx_{&FFX%oRiVoUS&&|o*L z`rnRc+z;H3flSNLUH2_Wco&~HHK37`>Rsgll+qlJL5;>EjSDkFNL@sQ4iyX=g*Nb<)(QD* z`1HjLfd9dL_!WiedyG1tKx+HxZbe;Hz3-!m43m=A6Y-jN84@q@$+1v^aj{(gKk4-z zr-vf=b4|{R$qZ?uk4g!ev75(~C{Bc~M8+xzfD5tO?zUnNrG)I(n;FPcO29*T%`pFp zF7mOk3Ota>3j#y^`$8A#IT2nx^o8Y(DDdiTVpEsk&?|`vNG2tJ8N4V}rdQ%Yspg6_ z^0Z0`)iW7_J(M@pj6$h+Og^^r6QaPOUGunT1w-uglRiI)OQLehBMU=4T*il;-9wx` z-UK2Iho3(?oh2EyF9n-4q(DuuX*6YPj9^dwD(Y*<}6uurOIQ^Wu1^M($_;JG1bWo=-!dB zz2AzJY-itWy5Hb8uKo0Mx*=-k*0TYX3ybvqp$q7YZZg`9`iOqg)kXdK?u}A?X~MKZoV!*r;TEo>*^xrC#x&d ztbz^=`w`G^Xr8&Bwly)gM~iER*>p0)%gZY_9VN3;N$>c+m=;waicx0HVjg89zioyR zNm9Y`4tNG;RO){0h<~YF`AH+I5t!lU4dko!HDkYM953(@C*oKcAa0AX2Im}5bDabH zYKLR;vFpF2bDt>!LKHG={e9;~HX~2*?)?xhkV7n!sVi`1FG}l)9i3+*`|Fy%2Od?7 zZ4PJ1m?E|56D%164Y7>@XoDpH3GQo^`Seor!WJ$-+e2RqU|kuAJO|~LQ znJ0M}$=Awe=W8g9=fOJ;gbq6*us1h1H&&ICGYGh=r(oM>pv8w=VGF8caBee>_=G99 z@-g4h&M**DzGFB&&;xvy!;DzUR=zKnm1t4Fnz4c18pT zvleq|8`cZRNp7&kwuoIf?w7o!Z=-S zLWqnfJkgBGRvFQq5RUa2S+wB#jDw%HOjI+x1T7^=&fl$BxX4azV!d3ev_;MLw%=8!~bfDAF1#2{CV^1nV*ks5UrxiAvH#+TNY_+_dWxh+Alq zd-6YMlx~FF6UlD{mE4Yv!n6kD(~y-H-jp(T#3#r-)$f=OZ7gnkxElEEB0)poANY=g znrXnHkvzM~{61OyMO|M$0Q#*4i=(n}d0&Nke?3|ErMsL96xjJSW~*$$w3cyfdwpzmll^uG_XNlwp;v+Q4NFInYUkhO+EEzN zJ?<}7yg@98!~=&#{N<jx1c&XstMU#`NS(?e(0t7gx+oW<+7SMY$sNovvvP!tfwn$-U`GH6weo(9vFS`$4372EQdM z!Qi>CHo`r%kNL?&g=|4GK@Q}1Tk#&V=%%XtO5aa^@+7uB_@^Mky5h)Fr zuxT5v9*5-BOQD-=mozK3G$jT+#W4h#abkX%zj5sR_>W)Vj4Y`b-;TXfJb@kTEiaQ9 zIQ8m5%EX_L+Jf2N6l9ZzQ4G)19;IF1A`8t==+om9yI}tStLQ=Grd=3xj^<4VNTR(Z zb7I7PkS)L52$iLQM;I5Y5VB^N`wc2(EFb{P5ng18!|qVL<*;H(KR5v%A8!egv;yk4 zYgNym;aeDIin2N|j%vTnA2ty8B5SSD3UfGpEIWW{*!=YrkL%5Da=|ZNAf(k!T9DL6 zEk3CR2isT6>axy*Xs@lETDekGevjX(Ju$F}Yv$|ZgEtb-T>+e;p73>DIQsT&lb&5J zKY#vgA+>aW-xau}-Z+3D9W6W&&e*Ir2aRU-p{pAVrCOQxOZ8*jhIT5U^p5WDwS#G@ zgtr9b?pyXc>O011VWCCJ(=5L~2e4c|J8C+KH+#sVz)Za~5c$9*C)$opIm0JtZrv{Y zQ0+(Xk~@hTjCfdu<>EwF8-Vf>{RFl|0F!(g7yMfmp@YzdmN(yfcl=Q#_rK9f*Az6Z zI8xsJ=%9gtA)f~49XC7hsA@!>?nt-kAEsP0CUE3%!q?Zb%ZqfyREkl1dCxO+bRzVp z$1$j`BRoC?cK*4mrjYL1rEf0J-Hvxa(K8%wDkYaT+1zBTRncf^j&%Tw4S`sQSp`-_ z-PIWMkks1Mh;10?9@vshk!T)vClOJ&{&ePx7`%ttxo~k&QcoqPekrJpd9m;fr$>x9 z72UZ+aBF*2I)>A?&{X=VX zv*_uQ-o{Cvz0ZHF{Cz9e(eaJ<*S8-1TA`md=JdVM>2$Yv_*5e8Nu-HBZ>G;Q=aO_f zWPQg*k-6u-WlN(L9~7W=c58eAOLlHr#Nv5YXT}BgVyf6cb+r$BQ7V+v`*Q+GX|yLQ z?9vTcZ$XgT>a^}h5J@qs%q3$$LbU>9)w}|!bgwqCO;4M;^RS_l3o0S=^+Q%~i3@Xj zR=vbOzr8he@1N}#1)Gb^7-(gv9IYzi)|$F+`j}CMEd9@)IS~xytnhoXy-D$V_djZB z7G{Za?%-d$G7R(dv)S%8OAR@`W6Ocw#~;W-fxyY^*&a6pQ&8Yafmr=pjWJ9Vs}@ds zYbyImhu3w7VE@_`f}MyBxW$VDS+>CA%(Qc70K)(9+9UfL|^kSJc&wLGxx#+Hbi z`ZaTJ0v$qqi~Meq^oBC#L!-0y4?F6MwVY(#aMz~_JXDka#Lbg0gIrbTFxOa?+o;idytmrJTF*|M+K&9i@P8az=ieqW z?8oUE#~(WjC~g2@pJay2?;l~fv!N72)-E}$sr%B7g#(sDg3o03W8rI=mH`5DLE}iBV0i_Wr>e5>iK`VeKCIew=C@O!sB9z40DZ zR&yB=u||5DPLYw2|83iO^!A$}r1+Eh^0Bv{WI;-W74L?k2zqmECt1ZW76#8p%v@CU z@@F50Btsw!ZXiL*G{Bby;-6S-#!6jLa^4SZm%w)I=z10l2OE`8)K|M3Ks$(@@++&<^XLkfJpIKA^^9>IEHGIPhk8iz! z@g^t+0qPMMVZwwDAa_}98OSE%TAx8qWe!JVafdT>{PuH3YH;nmx!=-nG)^2!Rrh=m z`6%Vs3%5)DGu0hqcC$VTNuuy~1gZe|!Kc&~r;nLR(kfhX0!7raXe&y<6oAeBPI`2c z*Yo(?%Xf|Z@n*AQRtPX^6kYG@qa_mC@`qZJ9l{xn5|xo7bCsm({zgW8y6a6=5TM}iW*qlhP#W^D3|oRV1{|MH9_Z|@x+Am9VoIRbE7){#R5p!)}tLI zF$bJfN5_m&K>LowZUPMmTYdSYOSbGXYp#fVI1?nMYXPfHGLqjJ2f%)WoCUt&j#qm` z?=~ZEIPA`+;&d6B-eqX^kX~h#44_9Iej+y#R0zCY^6Kon*N$K_G6Ua+E;aHNuBP5Y zzKa908rYbWK4N3ZYN^rN;ki(`Ke*^!@Q{X4e0S!JIOf|l$igl3kX7(#5L9U=07s@I zm0e=V6;T+ECxnib)awpBQfj(<_3B&TaDZzv0F*+5fzIIm?E0tPH-12%X zQZ0C_wtSb*J)75if6%z9>wn6>E%1Xrm@x#})y~sg*1Nb9XPO=a&eqy+$4sUvc+q#b zrw!{PRVuCwSx$u;H*UIFb_DPB(6z=lE(okzoson=x%bvY7~K89(e>GB)a#2 z1rk@4OfHIc#E@4XI|76%E$G_&ITqq@(9Y{&qhv>xGfUyp01ww;7vF~AQ!stL?zmIxllK|n+$!KA9|Z@-`=U+^bIKPY3a5Z2n?Av9 za4{@<4Udn$6dcjN64Cdwt13UPcKXTZ^_wS*LHq*5%)!#lr~u1^m$%*za=UeAP(2Y( zuTZVmhmkd4{Nz6Q%x;ddT$m-cDa6R@nVI z%p|syfbQ8Po3Qcef}w+@zJn6u|wnMR!H8OJHQ~@)}xpD(F`$Ui@&)ovZ*wZfp9WU3G9hzL`at3BB+e?aBG- zt?#H*it26q9yFogMcKi56|%Vw$tK>Jwb3`zw!@LI2W$qluquyt-FlMKNgOCA@I&E^ zU5v)XYbKvj!3TlBiN0zAZ7zW)@RtCx6n9rdA;57rHGmdDzQ@>GPY~xU+M5U#uqWaT zf1>&STw1UX4PC(DsFOJSG5gW7j-u|Lg)z3*?w*qwy22M%8%_JifdlwDYH>@1??TDejbp;_*kEZxaqT zC|5i#ldHdDK4%gD%60CnNISd7r{{InKg+>JVb4a{v=Z|A+p?>lzqT^Sh$0f;vj}G_ zgyIB`IZ6`K<}S^lAoS{snP%HvT#VJU|4DPN<#6hdcAeu2D&UIJnb;YZHZfj3lTST{OI_vX#$@xy-Pd~! zn1HCe%iANipPMH8%}hPdn#BLiK;Ap83PfQrX9#GRE7=xcd4pgMQ>Z>p=e8t`ifbU- zV$YIUtgj6y=}{Nw@G$9!{0zRX&8nk?FtU4sHizfpGCMO-ZHu#hAr^EIFV56=)Ypnk zWJD1-GyIMY)vPSX#i0quo1W-`o|ftI=8)GH&zHm6Gzm-;3I!YqD6h-1jQiz{7;?PR zhN$^c&=59j%s*=kKh8PI7(${1uB9p%aUx*y4vv4Cgpp3pdA)P&vKtNnhcTFd`R8^u zNtsaYRa}$OLHbk@X&Qtt-8*6Cr2)vNne@ugrA34g0m9#{j&Ffgh@jGu(KuuHdp1Q1 zc>o@hQRLt@%v+r}{5@aa)#Oy+n;B0WMs1xumRj@GQF*$7YK5o!pxJe^O|n`0qv!EU zi&y*D!_aByp_F)ZLYDLZzYLdNlWqOtq5i2o!lpUTYOlT9!I!=7ashU_o^fX<2@S5I z>9qkyki3ofjC)=AO0-iYFQ21!x>3V}7F?4^cmxvL&#^-wJ9dTQr>d%&bjW_hXt+!? zXdn)e6xzJ`PA7IbScN~{jB8gL-Kg}*s(t6k#=Mwpv9@Fe8iwgOtv7SkqhdFQYa*NL{x$JZ{*F$$QY*+R+S}S8-11CsAif&qW@~VEKLO(UzHHB0fIE z$NV7Einba6TZuz0-V=lBT$8duIs(?NJ3nv-GZwzNPpA8V;A1t^{$#Q<1W{nv3>lUz zPjduV6iiS;*&+27`?oH%^}wQbIisI3te;cy>KDr8t0_ST;7#(dthZR9vs9u%4%tIRdPd7Fuc z;>V}!)$@u9Wdb5SIGsMzLtU&S62T{hL2HG9BNLXE3hKAO&?EreHLW!S0KiXf6d>DE z;U6NqVLxZ`^LF$G+Shh^^p;~M;%m~lELkpaG_EuK)+ zfdcDbA;_;Mp-%!D9-ywJqhAuFGC&lbMKH{hhiv$Vzq%-bWi_f?j#2^-Iy~3Vaz&Mn z@1Q*wwTJP71ivwHo;tKtZ?wcl863@}6_Yu9^`>k_XnIesoe5M^N#Pyv@2YXcb58p# zEs?SF)-^rF>>8ns{)giZ7j+|3+8mzW__YX|C z&5V&kk>@b#v%N<+=6xmbXQZ&WM>qFQg7+&gGos4&Z~u5 z_cAN1)+VC1uXj6eoM?s6HAz8#0QAiLCy@d@rGW20mS)C@mh zn-<>F+u{5K**XtSfZ*J^llLD>p?Li@um!%Qxu2A zHQ8dKL8M2b@_G41>c1ULLk98k^A7R~O4WLeXuCDyWu-YiJ}tpSy1U%7g*Ow6o;6zD z3~bzUeFedZt5ua={m~)R<&MyI01~X}yNi$4Y_l}**~j@2VEM6=Uj0~{*cae^^l|=? ziq|q}ZOuGP0TMr=HD%cs&JaWv*~$ZYYI1b^)gQmmDslQiYLp<{xk(opnw(b}l*!)oY(l}f|FNkYixCT!KFBsq=mWR?sun7Rk?bm@As$eD-!D%J11E{)& z6tzOf8j6irK$mk}uQ2>77mu@#=F*uLWX|;PeCyE8W5?w+4*HA$+%0XEkmj$N96~GU zL~?~ELL}MjE2lS)*1OXn7gakt?#n7zE|1j!5&VItE+KvY+!^N0#L3gs5{cEZlmpI^ zL?oYE_iaA08yV(lo7KwSmaGsMTK%eAriz|NvhqVw+hv;bqr2{W~B5$%qeWQN>kFIHcJ zo?|1rJESN+r0esDwl&ST#^fnIoFU>h#ZhDbC=i1O*8X0BmZfcnu9R8kXy>ih?hHbt z#Xqyh9Pd9!a=QcOVc)$7=umy?j4|sZfYm6xVbG%|S|Rh8Lsi2P?ugAJv}5`cGX4yJ zE)613GBgW{7VRB_J`wW(=X2)=^_S(Wl52|yG}^mun$?|IulO+OOO`61Hq%n)7*bdt zPsWbhZ|4SViLbRL8b<;4&GP#7ZHm>$_VE^+*FY9_$0kho)g9DQkV1y-DJHbgsUVHxzzXdS>KA?B5SJk}Vtex6<%j{$UHs zj_p;)kywW}rkrpjA>8Nordg1oo}g*}k_@oyRSeTFFpWiq-^e695f_}DpKln-dFm2v z1KJYEESV|OQyI$05<4D2-du!qVZ!b-7nr_YLn|1OaZhi_=yR;d>W!_=l1s;%W1OKW zD^WTfXI=CR_mZK=sXBaW1B}atPw5>S^)EQ;wuv8yJgLBr>#`mXQ;F73`=YU_KSnKS zR^9QBhXNx&uMv=w;>71L*8As?A=pu*({-S#hYg^C1^lG8&+KGY?s-6CdP5rrFu<*mjDU^`F0d2;d$fgQV8OexVK zx#&}MB#Cq7tB^h66?>9ZsK2iiP^=E=>Y$$_k#%WmXKk(oX?YUIOl{jhE_Ba%;JcKR}2scP9nA?h!@+ zM~S76E~)PI-bjExecrYR1_*MlawM<=#?Rg{GZiN$(q@fOYKJb~e@f2S%$2q-YXy;$ z$%Ivyt-Y`JXq1Y%qKAIa4c~OoL5wHRBlv>;JTdjTSwT6@Fd^VX%mGgel}N{<-Gjo_ zWh3Mly~yDEopw1JB*smloB4CF72YdWiW2?LIw<`$Jv4VuM=%Z-#aGvZy3z-bf=c4L z@pgTS+Db%GD4!qcgn=bT42ln-v6G~|#e@^G&z!}vjqTS?+zMadM)*`Jc zp;==JX2jtL}3K z&GI|$2$C|GeL;OrKifCk@e3<26s|4%Wn~y@y<{GIxv5X;oBCK{Aij#tv-ZP#ZNG!* z8phe&cmlHPL;rRnT7KcM6NnvWzKDGp%!$UFTPfA|7}Z|SW-64@7iYP>OwxF_mMhmS z1~e;$5{8m{O~!-4V=leofGN7^!s&H$Ke*kLM9urI0c>i!9YK_aawm%I3qg^ zYM0$+Jrnp}1m! zU2@k9W+(HtTMk}3nIU}r!wru23W%qn38Z-VHq=7JladIxvpf&?eIgM6PWbMJEJJ?wd1o)v%GNLBu=-uwWD zf;+i0fLX0MXX&4)Wcn{$Sb zwSeWJI0jzfT$1yp+io^scOzmXW>n9eA=6?+SAKX8Ep?_>gkDZc0_nxSe~iQ??x6f)l4w zHq_r^0?)T$twiEt)k3nAaSi>s?9xn@O3t=4M|@OPvnIN zTtdA~iy|To&d(cZa(su&@iHf?V?$nn&o4sK@=0Xe(_y`g6esy_K>{os7Rzf1`|u&q z6Xm_|UTSQU&Mq%c$sgIqqO`#h&F zj8&2P5FLvA5QYTQr1dBB%3WQ1d7y0Np(*_snZdHH+JHq3wG56VOG9s>LDQYPlE5!% z=&1mm_W$h&Y0$SHAO6D`Dk`5H773LN_A=7JRnarxTV&RRvgMi`0+~HpyN^5HT$=kK z;`$P@kAN*yJ3{trCUV@iNFf391CLa>1M`I?Dc3wH^5w}y#&1wb#3wUoAH<2w=8LU# zQElA6LE=R-7FR4{w*B*JonKF=v&;_(G`n*5wK9Y}Y;(S#^3|qzo4(60#ni^Wm%p*q zUs|_};V?S`%X@0i4pWp4M6Fe*%KJ}5NqS!?bV{2J>t8%>jvIa~Vf_ozV#a-$|ibj2Xr^7m1`}lbk zn%QUfg#UGA_WLvg$Jg=I*c>QsC8jl(VM1Zk9!!80?$h{=J~j*h|9wgl0YhH^>f!P# zwC?3(Il;8sUg#@Ay&Ez_jW@@GkKKQZwso85ba1Y@@+)YZc<_vB4kDHJj9$pN&_i6% z&n3=}rumu|Bm&*KLw9{?)U9K|P5ZKYhj>P4H<6O_u_?M63^no;15dI*yt~9Jam4s_ z*85U`rOmui4A}1tMUwClvLVZ&Dk9e5JG?CF+6E$$2y=xMWWPkf*(y1eL5=_%hiR8z zPk5RS?xu>1>!Ep^E6c7ZijFsG_g_OkU1MpXS8pOh%nAvdnfm_N#DZxY_Ws z7H~tU;#6bSDJP#cf3XC?lE`4=^iTLjqYR__*)m|3un_rGDa+##{M`u&3ue-n4YxDCG z@~o2a->~L__(JvF8|vVT%X;n<;Ot-VKIUzOl3G&8d5Z!)c|CncXB6ZOeX-TRHv+~1 zp6TMlwdKU$EaUF5$zbAh8P9dBamykVY{ty}@;TWC!ix zoAbF})$nj`eFZlRsOx3R7K(TqUL$(BKV!3>e1E8)Ef@`ps##D_Twe zeBMZ>jzl0D+amJUe6@jH+7o~1f-zDYR{uSp#IY6M)5IJt?eZSo+H#~&==Vn2P@1Zq zuA8vR><3S+*$J(;lOxn%lch-XVhxo}?%tzQ7$1_-0q=m4)sMhM&9+qUn)p>Uh#yX$}_khZEruE;mYQuL3 z>HK?li)j|0(NYhfUFA%^T&oxSr(mi6_}Jx#S_`8?`=FInEOfb(b4x(U)uQgzOTY6q z0e%Z}NSY&D)$Ios3*+?K6H-_vw)<9Q9`lyfYbM z)>bIs510%}i75BMYI258DVZMgqxkdMC--P++3rkMz!ds*!7K+#&Rdy+d7F%EOxjf` zH8mjr3D?vXoR>jHCnr4v#polDjwdqQ-Z2=rV2g<HQ@=pS~V` zXyVFy<=wwn0Ow3+buTt=j-FSNi)jqmH$!R7Q_atN1?>bBJ_`sHZ7|4wVVK=r>f7A) z1iklHlUSVp1lr|m;kU@we96>i#|fe9Gb3^aiOGC5H`CW2r4BCl+_=_B;=ObGUHXY0 zic3iuHEcPp(7-3$ZH6pk5MPhxGtfO!bi7;aRe_KC<)RtdNjJ$SPsdKAE{7<)P793a z)Y^8?*U;0S(61nd-~>7WijkqA@xXQ9H-`?`wMIq;8yZfU`B zPmzhU`ZamjZKT{MigNYWoSiNuU?OtcK?&;={tU8_#%!{$_(@`HJUo3i!*C+MZvzbz zl95$?!EZ=1yn8An!4O5=X+iRYeQ@pf-@O|-Agn}NzmsGtRp{>t-FOlwHe{)yY$Rau z$ntTfaJkObQM(^Y!JYbC-cDUCKH`|GG+E26rywI!Te^0Yr z6<3NL%i8sv_th51TqG4^<*^SfvvR^2C6C$S9EPR2sH@Dt2ifJF{4O(3?iVpd>gSZD zD*TqG3C!k<*@@A1;8-S(VFoa*&lynv)I0Cj2$Nd@MTa@Znev;omRC-l$nL*FK(`(# zleKZzpJ-CMER#W1^S}0?+hN7oK;c?l3Yo6?i~B*OYJ#S~Wh$N0KYQ8s)W?u)B_87^sz~q4UtzCq_#> zhF-3LyqXLx>;Cf%G^R*;Nykhs{{?mARDY!c3!)-Ji<(PhROH*E=ricLgU7uGAfNqg z>qDYbMz03ed@)AVTn<4SK_L9!s`%%{S4(4&8$Elt(CY{dC|i6#0;5Do@{Z>%O%Y&_ z6PK+ltv!G#UO~pRhxGQqbM)q61HWN${xC$oU_e#I@(f_QEE#f*T?IRh=PqQHv{VGYT>83&}Jps@zpP^NCnUl6Q>t}ONQX> z*lM;h3bz-lQU#+3_jud{w0MxG{K>B_5Yo=`R!;<|} zkk%<*H)8DUx_&QQ;qj%@PZ|>GTJwM0KBtIT;ePXk7<%6-4*V1Q&ny0|B$ol3noUD9 zK_^ic;x5Kj>%3dndSqtd&tT^&^y%z#c@|YBxocSah|fwoN-kZBr9N}%AGaHo%K3Hc zT4qTAr!zOXAlD&i?y3u>FXG93-@W&H6aU$r-!239Y?)`_%ii#A&r82vjQ^LRnVphY z{xUZKA@T%l#*|KT>D=~XGLA2ke|>1?i|Ly$|Lq$fx1*!m!A4qbxY2BLWqwJkU6q@p>1H7sg#YZ!Ehx@lM+f6d05&Qar$6a^Y@M5z7clrPAKAU(pI%Mw zpP-B9(yj-7uq7pP8T8e2J6exeTU(#nCmNIR#ksQ})vd04bWrNzeY20>qh%8XxYyl@ zYSK~8CSw$KY>4=HscDdv9Lofnjc?(TKm)52*_PBuw-qIG?| zz2R+h<<6;_Y@0RjL{9`Q166q*{z1LBtv4?@v6!_zVZ1DfR?r*7E-d;{kXz)R4SXV3 zdv#<&_$qgYXCmu&l8nT*xpgw(F4xd4KA{%bIqJV5{#lT&%aQ8M19r~dYb)v|*4*#x zpE@A6p}1ott++Q+I>TCl_G^oi-@hTN)N^j&!~DFq4r}1Xi}C&hzwLL_PY1GbX_j~h zv%RXzVYc(q4ZXQ%8+040_F{S0KL)cGER&UT64kl8^VHkuB>lQ3n6BI;2HbW#KxX&M z91uel;~v@!2TOBB{w)wEKjV7DF$V?5eO;R+)f%zZU?1d7?GyATOg6l*TRAII9`*6i zIB~vD3{n33iBwsC$?^ceMIcfX^X;)At#BmIHsrYNZnj^bagE9SeS#e_lJ@d;e!JI0 z7uQ@r^m1f3*;M4&q;&i7nOvaBK)_n{Wq0%~`o>T=@6Cz;sRRA#tVaquL15YUaGse7-tW^@notr`>j_xBaQMsFNVJ2#{00WKG<2=VO^SHI%GP zYFVvfK6Wq!{R?f#e7IUXjwId0n*GjZdy-akT%$jh7@IFX!e zVX~S69$wi)+0W7%^b+_EkK@{V3m}1s+(tq!Fw-3>oKXlte&62N_8VeJWggT zlxy78OnQ_8_Ta^hSbhln&u!_x*>Gy_+7?(NLV9SkqyM*;Fz-9cUn3qc9)5fle?RXi zXNa9&@qY1JySW$c#&^aEwtm5rj)HZ+rQt#l;6Ro9R%GO3e;uMW^q3ssD7h( z8E7$Md)qx{+aG!!5=8(CU23I<1oqt;K9Wi!miiKl8?nr~sI}ARwCiOV+FnXnMN?oA zwuS`w==F6jOwPrF8e#PRF3>p^tEsqaZav5T5Iv&9kVVs}wKfR-JoV;Q_Kd7|8Zx8l zGFOxXZkSJs0bF9JHJUa%)wcB>;^|@weUgxRW0su%q;GUKBz!HKSG*>9?lF8jY^C<6 z&P=b;ue2aFZ*eBDb7B`)tSA*Wz$!<@IwTuROkM8S%DkP2<;A0mb$kOVL zCskJ-s92EI5Dg>#+k-n9aB3sX?m_o@r+scDQ&9mXYwW?U<&)h>L9t4tJZ9|&@Y@lq zx&TG;y+=w|)6*XrhN)v8d|BfE?Un{(*2hc1lk4?W zzcMu(ng@D6{*3vaRzI5Dl~G#xm49XLpSTRoLMs_4NYNLp54X0_HTS%@+gof!(t8`A z@7Rltrz~EZOp>Mf-1tKiFyhTr`kQlXYKo$aGOj?Id}PLw zusfs>VyYQPwWJt`8L4I+klRNGWrnZHS>6H0R1UmYxh5?C)Ro1~$U*)kFIxt28)|fp zcasx-#w!E~TU?n-ieCjiH3khOiGb|>*4}zYtJca`WZ!bPcAbp1T)RnY_x?Xo0n94^ z%TrOdB-L*d{UXhNjwm(Qyd1e6%I4iXpLp2L?kdbZY=3Eaq+buzisW+I2klqn0(2!4 zC43UT^8QAC8Cn8p3Sc^UJoIr^B>8mvfT?}(C*Fz1DI1Pdeh0N1aDWd}O`ueF@lUS3 z9nPwtR}!pY4kq2!kXFo)PGs1DVVDGkvyZ`5xiVnj>b3s7qafV=+2NynLVpBPy2;HK zl$Ga|uGTSn?^YagGg|AO49ad?Vd-Coqp4N(z^NT)Xkg0;@`JE-(;qybZI~y6Rpc=p z)*%-frvY%fRllJ|t^Dgn*89Z%EL-d7(C<5ODF*+X4Dm!yb}hcoaF%U@l-owr6b$TO z4Qmv`_hL=|k&rldv@L)+XLSE$l~a++m%&&EKM12|2OWlmNoqhl?xw>pN3N8>y-#eu zKqaViS1Te3Mp4XKz)E8rbAc&Yz z-8biatdDmf6%x|J2}ln|W3cDXG8kSv!!<>QCe>do+>cr6Q41Xit=fxEA__6^I`1Lf;nT>{7vJT%ZEhW=1oMNz3TY)nIT@^fA>4KT8F?sS9uju=$B>jl)Wh~ zGJk%W{4?Hn(f%YVLouP+HVb|)=HK6g_l;jiiixG^T#@9I{;k|rSCBbRL$h{NUo3~u zp`-C))0y8c8{PGB_jE5T>Xrii8YD;VAWhT1JvKVz6y_WGO%=E0!2S!F{biem|AyRe zNP~5Ea_J*jHVtaC!taN1ZhK}M5zsg? zostQjc@8U$FD_>GlZ`Z7*lrTH%kpup`LzaNtI^sh4G^!okv7u3J#uaws7a@;z&+aFYz%-8;csXT0(lifB3 z84tDqu-6=4kT~Z;gYJdIIoD6S!qO#{Lk`yy%cvAv@A#*g@gdvI{kEs*l~u01xTQ-` zFQn(5E}SN?&mba21{n3ENBK&$3h@mzsXNj97t%hF*ogqvz6b;qZPULm?klu9TFa?3 zhGqsef{yQ0G*ip}i^?Hi1fqL2`eHsq*C7=SB>jvnZ~V90aU(2|77*Hp7sK_lbS%H$ z{`p}ylFxg_b7@b|(t!Wkq9sUE5kzI;ZKOHH8B6&Yy(0n8ET>)G5LcfOZI z){ma;A@jnZy;p1#iqiof5Z8sj3B)1KO(2C+v-666jqmmLmAr&t_vxAiNoqGV3dZU3 z%~*Mem?ah#x`_3j?x!_}J&C`Q8Sq%B`zs;nLK1k!adrx2nAnWY))}W&A#>tPtx`jM z-2GkD%YhSx*cxHdP+~IdALvHO(jp|V85W;NJ|)X8lBkFO{S2tatPu}Aw##=)A?xBr zNO(FuXJ_jkJ&Zsa904VdK-O-An{@;S4=6*E2iTAsWSAzBL^gtjJqZB9qX{qu=lb6- z+E-9_zUbua%0vEue@J7|a^gj>JB{~a9$q>v9h_^(I{7Jjpg-@azKJop+WQzpz1tw; zkmw5|M>u6s;qL=XGA^b@0oTjJgWn z=6(Di9&n$pm#BHLEw36TSIak}a*xuG_XaqZXB6DoB`X!~YxKvD0qlZaxx!=7as zuJc6wv~N|~6+W#u^l&Re|R z-*}q>ZZK|y?~-k~r0irD zY#rGR#yK%o{Os0>x4#-xw9k7Q@ms3&B*VI_=K@UCt6d}DSb?oB9fj*i{*Z>`Q3tib ztVpG4;2fpv;rdb{Saz(2UiBlu9Pc;5>e#brZs_lR_&Uuz5AF@} z{KbQkptv}(r3CSyxoJ?|RCi#i?2qa$@W{PTS}+*jEND@>Eqk0j5nLfV6gaNVzt%wd z(=@2J#l_z;jtTFv;w~X$h~T>zyg|(^|CJECHX1m&NaZeT$%_j45JW%g-ULENuz2GT zs}#pJP1MRpDLpD9(1&`gr!M>d8fT?)LN8@N!^e=N-cJN<>r|Vhk7LKiGnGm=Vmc-t zye4B?YzyMKQnbsx;vysp{Y}~>+a)>rI=wg5BNaFk1zwbKyoLtGU{_Q;UBZ=M6X$#9tX}Th7 z6>RUcbe1$Dq*39MdgEsGPinmRiKK1r{@M7aD8z*>yH>dZoRipURW;dkL}bHAZ;N za!m5YaOxLxdIDXlrgz3q0j?trtw5LMkK?#NnO*{3TbCuz5kgPRtDL1bfI(NIr=GG)#W#U|@ID5`&p^5G^4rVDrP+CDh6v2AXId_<@QuSeeAyb|NcFKb?7zy zd$qL>WSy=c-EIpJrP^+7qh*o+di7Yk7W02^&gEFulv|~q^bwMC(miw*RA!6hs>e3L zR)X#2EOZKspj6ulM|bGznJ6p5ybhD^ZODnCC?CPSayyUegG@8UR6XUw8)Ffs0H>{x zhkbR*Fr~0-cD9=Z;hF?)&oqj;uJghsT+DUL7MHV~n5c|QOVtN2SJB`%aRq4&&(ffl zou!4XVq;tJlMI@a#O6s8wYyg9wK%nSi7G#c%&Bk6DJI2r!0NP$ z7dunxnw@c1?Ar?-e-I#<9QocAr0_i~nPb*{xH% z+qG zOeCF)Zg~8QD+YYvb-0r_0$VT0lLbsS$k;Cw;~RrpHZ%WkH&5xOHZTj z{GgAU(N2I4<`4Lqa&HlrkJH1}A_< zcsxJ~Seq|0oVVf@HDdeR5F%x@vk&o?g0FH>6jw@AaHUBJbJ?@M+vr=F2;@(TR66qV zMWvskF|Jc!@q@m$BPX|EBza7O%Cnca{dIU1^gO5kAjtHH^d!r=iy3B9==YcwnYLho zXDhmUxmRb`Kl|R*NBtEXpdHn2@0Y{PgvaTfBO1D<+k2E9gQfm*6b_1n2FU%i#jGvz z^6L9=a3pyw?e_nk(<9bn#OQYNZg-r1z0_$$uMkNh(S9&ZZ`(5FU4vAdr_fc^AGNKO z>w{IaVe*41g^CwO>gG9Ju8yR^JB&2z!BvBWx0vMpp;eH%#)~c4{ zI7ejzb#mJ$y;BZAwF8!_;@zN3)F%<+rlVB9^GrTWTlW5KJ{o~UpW>5HKRWfc|%dBwhZnATB9ISx8L@NU?5c)1r8YXX;{$dL-F!khaE z>VOvcTsT)kPApi~f%bAg(kZ8D$2sM>ObT@zWiE+6Hj^%Uo+f)2Fv$YKozlp%Jj> zN|?iDJZD8KBITm|k~YFti+WJTfEcyY>1q}l51Ej+02ZRWy$h58Yy?GBAt5O`Pv6x^ zBvI?)nu*XI@X6g<;qXMkyr6JGy1f%C;SxV8j9-8hu2hkbgP$}NW?;D=v(m$rCKht~ zG=T2jB0&rkM=#kWqX+XNL7Y!%V(IFYZ8UK(w&`PA#)e-xG(QQP3dPu4K;UqfsOYU) zWi&i|IqF`37U6h-nSp{dMCf(lW^Xyk@{B6By7hSm3h}=s!>>fam3OHD;no@bDzGOe zvfXM@x_KkQw6LWhwFx^FAx?$WNtY}N7O40H4}+9nob97B71DTKaV4AeHUmhwKU!^i zo?qqwu=KalxkN<1BejyC%iq3k!!G1g@ht9rP*fI1hlM;x`PEy)Z6UoUhT@ncL=+s- zV91%nmyuZ*Ip-z+Bp>1vOgbKgIL*KWBF#Q*xZdsvF#3Rc+dOSuN;YiBa3k1XyS7%b zrOOLx^q>(O%-#)Fl>8QAgVM|C8q*Om>U)YOGj>s5S1o08(E3LS7yyJN4ZHXz^MaKCY51y>v#?-xY%lcC_3(g-uQgL(SYpNb@ z$xQU*uKMZnx0F2)O+l2zne}Ktu@h*8uvm`Jj3EWjWGo?yAX$8kcFr3UX;#qXn&vvF zv(v(Gi?^gy{&()GyCy#>CDV$bkqc`u4e;aX*~PnDjP8ZxDHbJ zfZxEXSO3GF6AGd5`0AC-}W8>N0>cq7Aos%j#b z7Sc>7sOl~L^-_%9qtfYQ zHSO%cNWvKQt_=PW&_EI5q;Bu%%+jo$E$4;PcG<>{7Ks*`)J+)h6Tk`_VbkqlwFj;*$Bp$z_Ngc}?!UAT?*lN_ zkiK(nW_PPq2e5*SF`6y1%yy3iJ{o z;MwP*CemU_z0UAESI(xJg9!yB!D7L`3GmCB?8H3(gh@)^LzCIzq}V6)90}gYy-B*t z$m;r>ARDK>&7_&B(5om$*6ye>0i5!rWvf@K?JMJfP-dX$Af{ulVLzo9^s?O+(l;6Us=pPtj((yUz zU<3KFajq{Yn-p-5ceivw9{2?%!K`>^tLh=P1#vOW*Cf$EiXUwx2}BGUyi9g8_Sj;x z*0NjMOaw3d>`}(2*ulE z?A`WX>GhY0gBp3>r`|RS;z&=;>zirYM0|QYWl|Tmo|iWv{W)*TF|(YDrOMEW_Kb2I zYsKvYrTCk9qZ*@YTzkLdZ=GX$(W8O=M8RYJ``qLM8=j^bkUvT} zSq5|SE0uTS=Kr$jo`V)4Ho9{F5(m`mGy=8(7U?wwt6NVC^3o$51X{`NEyIpYb8G5K z2(KE9+&njzTqfLox_y5QXQEx8XY{0F%WtWN9#6T4JeJN@^P$McaDpfAtbH>o3A6p@ zW8Wsqd8zt*ia7=VZu*-9_1jYmS2x4>z6?BzBLVU?!z;+J0Wsi6*bC4_4RP{Us3C1t zDt-dTS(!<#LcZ%i)Bf*Lp(0G1W9B?a4V15EAyQ(`I%Io8N6ndKtTaEf`by}B}ZZmmEZGGZi$jU(c*gwlYjX=LuC#lWO z%DCu5B(XMzIPgu@X)0B4OvE%7jrIoHDJof#Pp{q$vl+K0?uVLrzsE=bMoF24;%)+d7!18W?Fqn$a=AJMpC;CW2aoxoT!lhG_s8uSqkq(pd4X(3H}_K`0k*=FsM z#i~)?7&ngV6o!_Ev2R>5e;>#7Mf$(TNj|6OG7Eymb3vsi+j#TN=}X7wbQ!S__Z~5I zx|2rah{YJ-96ucHXPpHo9gVQelektNz9tp84q}0;2=&21|mdzEaeq=0>&Sxgf^_hd1j%*{-vvqr{|IwwotN#uTrn% zp)>GwCJ3wwE^J0z_jS_%6*l8k6B%lo3azz?q13EJSWBUGrU(MI1ds1CUh6|Sd&_T< zAnks%m6f0_t063YjBQ9hL7_Y`TG6P_hXGVz&rQ% zBOoeZh%1GN9uw&|(MO>1b;+4u;8-xq5E888WzLw!bGC}T;rddt<=M#>%x_CgrDM_} z{X}nR)YBHOLYxE6l_XlLr#l%??~=Foh%)w8yF9$6EiUX9eB-^487m^mk_zT3k~>Ou zKhIPV>oTv&up}l!yFWi8%L$&%pNs{2bfNgO>AQR=M*4)E@TrM7XzBQ2#)^V*631~F z3*YXE4iJZo3kd5Ci0WUr$2DR~QFGObUh7l0W?!ndzvg^f{^ysLf9oC#!>vqqKz_X_ z6~?AH+7je`BoF2OcO`L_7xz@iMOED}u)%S-lU!*ZL8}Wwdxnd2{P8Bds;SnKOFq-y z(rm4my^)%tG{-sBGl=R-9PlQSP#5_EOrLMUM!$U6bxe!83iTw#dJ1=KSBy^ZfHFsV z&!{Ei1D%NOsyaSW=vE;ftZHfTs#>K_1?_GDT4yKkqzBef&EGITLLzQ~n&f0=U-*g!NIpzlT8QSbMin{5J=WC=uqZ+;#%HSCACG1_20^k&-_! z(kaXixdDd1Fw`mlih?1CIUU4tELy*Za!A6N&{;S;tVlXAUVCjQL+R#oFE|RQIPOZ? zVD+q&l8alH2htByKfe9r)Rv`$b!KB~4P$dj{m@dnO}+Var&9uC5)kypNXP|JR*DGU z&Dy8B7)g=9iX#yye!2$+$dDh5CQoSLUT^{c<2H9m6oLid!!qM5jb zXC+xFs!IJASza_g?pUz|1691gmD(V+;F$BeK`aicppHcMqGg4Vj@W0oU7d0*QRZTSa=Qk5`2CvnE zaz1x-ZPHPP-t1|f+clYJ&ccNH1pq|>5QjrTU34S7+;$eI_mpujLf{^G>m0EZIu6|L zt)nrZ#`7he!Cqu+{O5!0Iz2Iz;o={5ytq+Iih+Z`%|@3>9yKU!Sd8S_ydtj-uKUQD zAB^x-zFkd@`9<0+sc4BA%(3@lxMYre( zT48062M}Iv+(S$gg|84MAG?@eLt=H>=Y~Vwj8}El#_xPrn0+b{3YuF{mUYi`houou@eWJ60sCig9)a8w6O$`a9-C0AgGsbnK&18&9s`T z7SJARw4BbBf`tr-D1Q6Hc*+QCmOBC2R)CT{P%bFW&nUIQW-{7S55w9DBh3;(=IuQJ z)$hVxbW38Nt^8wc22?q>2O{i8TN+AOy|ct7_UTfq{U>|Ihwd((V)!Y(=5whoXY{j^ z^8XE3YfIw6n)mG@a$;w$N}~Ac1}X`3I}j#aJG>%vK0Pe(sqXHIYDQfgki?Au(hNW` zi!Rboq%1%J1`fUX=nZ#zOiG&vbw@@HE=_u>CxjDwh_!nduz}-{)@O``4r@2N*!t@$ z$cIPUQ(z}AZB;yfX|nr!sS~)~5$G21_A!xWKi`y_>|^@!@lLC(@GiEo}@!Nk1Y>9P2U<6v?c$0vGNDF{Y0s-60*@1>SK+w5=2H5K)}snGXTN@ zXdS?;nMF$G6}H53wt?Cw$1Al;NYx>s-t~;~Fl9KrFAF_r3zOT5sy-)AeT8f2*48zo z38n_j@2@{BF8+o!eAjXR79qcVmWxUBR)P@G9hUU-mrQ)F`1E^7G-7;Q2EGO?K1G|cY)6YBkCEE+`CvX1Lpj`@gem^0f-?PS1qie1X1${n$x1*07zIB6vhwFF0O_vvJXD2pw-X>gk?An{QsUP{Pt~Wmz(NtBD0fa z3e+Ae7$Nqt<7vS+AMSq-goOa1kk>reRHXr{`_CvO8^#Yb6)T^e-0NUzJKbmJ$<4AO zJ?fz&jd7IawpKOytc|9F|H5{t4l!Riw)NzA` zLDCW<4f50p_j8Svk=nWrGpeTGX`i0&ReE9-Y_1#2!UJ*bRWy9e7Zzg}A28J=evk|h zMuK1$YhIj)Uas@%@zORZT&)d1bqNi8swBc+#3cQ7a~jh`{Jh{;C+YoE*4qbPm#d;N zW)+O`r^UV8(FA2``pgoO=rJJ1Rv*NTYkHt6$LAn^+g+b%<)s~c(H{VF;CK1?fH>Ff zxMy5n4E`oH4WMr{@%=k5{*BO9JqR$5V^mXWlZC3 zED$_v1BvLFar+Z2!qWdcgv3sm3g&EoeaMiMIS`F8Cmwm0<{VY(QX z?hDoOUnh5g=qtR58n<2_!4poS5W|bB0*j*_a(|0gTb2k_W9n*_X4jj?6zBMETbLj+ zWn&{dHFOv7^K!7`G=@Ex{3?vBx~ED`7y!8$BXz_T9){zAPrhWn71X+%uPo1vC3)h; z8J&&PK^0sJYu?`i#_ftt%0!f6a4#G7v0E3tB9R_n9-!3_+|RHMQjxJda8NKnp$ zQtpGtW{|}eU_5}8;$m`OzgK1Rk@vlsp!bZ>xshq0B_`EoqQDwp8WQf8q1A3;VxoYe zQhIxhvh-kPPL|#z2(!SvMdze(n9nmmKR-1!)n@m|!C`VN(PjU0qw%jHbquku05(Wk z@sdt1Afb4*_%A!h6gBeBn*4>i>!mH?Z6`PCTE|#GpLg45hpe|NbwhgtKSaH-Z%qWS z!JM$Xv(xz$3j9RGj~$kNv`ndDv+Uz+E?;WrnYwF%IFMzKqd5OlVsNF!lQCmwVL_U_ zYyyM+SV234zBE{1&xx?n7mj7fvboHrEXTL{+BiGsH|*d?--}rZ9^64>I_UpYdltVP#h{A|0_8!^dId`!b=WutKS9q%|P%$XI z#`+5gQljT@ko5AyH|%S)l$~$!ry`7fDvc$GO-PJUj&T_L7vF3N55pGZc(ARryuvQg zU{I9ZJai8b*jyiTS+kKNkW4d*ZClrfDQiT==rV?o8MHw)#VFo97o!B@=@r*k=ks** z&&3p=NRB!PI%Rga9>zI~AKaL0e66kQkOx2g>lEu}uY>H-+-@^MBoU5X+U>U_e$*B> zg_5Ye3=oU&otaEA;IO}w3tA9;9(yYE)l)=d{eIvnp$@bgxy>0-z)j_wpS5O~%t+3t zz4~YOV@wss^Kl36UoA5+I0QBbn*EH)Eizc5Hbb8SW*9AWz+_)aTbSRc8)qmB3hDN1 zKT=kMVqU58MlfW^{g)~R>Fo%5JM;egl@gyzN)|ybt0?Oe>Y@w7M}(Yl7oZQEP;)u@ zv++=iYJ$XzZJNyjd7GDexN!7jdJn}S2!iLGqk7KoNyAXSr>l-L3Dw-?xro8xBXna-@FERUtp0%^n$ zn9#CPMWmk-C|5Im*>&56o52J8a;ign-sC-s*7ti#+m91ef~h>1nXaJ^{)bhCIq+W-hy2M@(;Vt5RP^jRFjyGa%DFt7!xWVQ24Fp8+ey zwFbVhee;`s!u`*vw|hmP4KC4JcRk|`Ghsi`mJ=EXHJ2>FN3$?o;iIfiX8D+Pz3}Q` zEo!LC4bnMcQYhw{=SE`}g@p&jO>oDXb|LMvP^ocAv{?U@*gDJm_orE0yp;PKc_SJW zm-^dpBfw*XQrG@$OkoxM(30{=hzLZMj{av*YCa9SmpHcQTw1|x2#Ma2)NtW)FDTlT zSSsQL81%hs80qC2rfx^Mh4R=I(7EmT~IQwU)jN!UPo793JP@|Hy{L0#Olg<$puf;+VdeQ$8+`c`t$#BK2 zAHXfOH3lnwwf6P(6*9Lxc1<8E%b>KkZW0FGj?UGw2EqL2s-_sQ_oo;opT+41n}%RI zZeAipWaa8352^2LJReW>>{U1~Tpce3eGPcV?WbG-T1fAK#{T$XAB5|_dos_r#!OF` z=5(c1iQPJP(q1^)C0-qI3mn|qFrWmWDU$wOM{ebjI4Reygev)(bCwJPj} zF;xCAb+zAf5akqDAp=9MzZ#JT2T7ZjoE`<`q zow!6Q0z0RZ@N26Fe`9}MS!A?k1S9zd{Y7wx3hdIu+S^n*20DmGeiZ{Dh|dS;%=YDd0q(1^;ST-Rs;i zYTd=oJUgLRv8tSm9*@@IJCGZcS3otnjg*WwdFTd6P zi7_<;hRZ2IUI7_9e@dHb9@iXWCLnk_zd1qwBG7ycmp`!U5hBL@ zv6O;ew=>S5%%sJp*i<+!F3#?}R=b|L@IQ$|{>LuW4(@ul+yCr?t7AGniXSAk6J-@J zLhI}fROOYQhq&B&{0u%M92RzI%wOkF(-O=L_jBSIrh0#r|BErZ^#s55x{+h1o~r>TlebKBt>X#idP~T2 zt5iw1hkdJKhbFszt!aPptt{+96W&q3YBYx=`P{WiGDE9uXA+&pJzqp93hiWYnDHxf zMzOZrlS9`qwZU|f8c^^RZd5!`A%DLzq*#1B)o{kYVSt17qcoY|At6xu=|Zh8jK^!C7uu>9gT2sj~c}1 zoiwFqzc1goKR*a3f%{qFj(49u{d7cT{f80$lVf%-rpx2l73zrAH=~T$a(5V;@x|R5 z>!9c8l#+dn#Ldo@YT!TpgL@4zNd4;Y`ldEh3E8B~m>;tA%tj%D>063Y=&-Z|=HJ;O z%v!UrLU3`tnfU$0SNcolc&6^WOf)%7H{&{Lb(q`b(}S>UsW7x#F!v#`Z!vkPavWxa z8w@8iT!Q?-?EZzG2N&<>$>eF23g4)9$aKbafuSa8q?Mc8D8RSAm2LmbAweuWl5Q`( zcK!Jk9-w_F?f-qEMl?4}-#+2!9#YT;9ShwUjWn|Y8huSWar?IH=k1zZ)ZYutC3CN4 z)G|9ACJUCnTKpRBS}T#rNjN(=1w8)Dapwd<6D zl+U9H*g5sD?{UYrXRjsv8}jy5%%fP_;Xi5@4=p~Jh&@+i;!_9x)KmD-Q@+Jv|OIz6cG2_^an+5pokH6SqgP1&z z6sNhTB~m+OPWDR;>bZ|BJi-6R1u!izXxZ6ZueDoWVO{v3`W&NW?k{;PvBWwmW;Ife%(dhc9(W8CfGjV{f(Cv2Yn1MM*7NB)A<@_wF#k> z>pGpa?TdqpM)}Jm?zi^2#kGwtsI|5*ENv~Na-QnP$wrDx@7YDH`6J3bUppaZI0+Kk zA)!~F-emg`3j~(bzUSQS%;}X?O(KW1iVboA#Ys4NB9Hs{aGRLB<4){N|L=J<)6SK% zPZVE=tyAjC+{TrtZDC`svTVEm{m1_=v;TwX?m{zyFXS+VU^36ITt+z{k-bJDA|jsE zt&@{=tiAE^@mqmSa7(rCO+pJN>0-OXh+Un`!3E};g>|7@>+QEm+ZPX|J0D%o!7iNU ze~@U`vaA36tDL$eSvglI&s$~)ZDV?1DrY`P(d!ZIRq`9^kF@tUyH0T+Ew9r{S^Y^_ z$tf&fX5L6?IF{38d9)Mc!>Ec%Y*~pmS(Y+AxR6jlnD^ePk z0}oc*Spo>b;Z@8nDG2}7<=OkvLcD&}>r4LYODvmwZr1lSI5_9Nt%v5 zX36`$IC~0^mu*~~D$9z5CuW1x-sk3U?>lIfTB_k&=?xS{JE+t1Nn41G?IXJBL*xOq zn`&Kl5i^jb^O8$C%2t4cw$8HKVt8_ zq)kGRU*xzbJtA-}?knghW(p8?y;|Setkxjhdv*#~n&wJ_>G9TBXFn2= zdvWnTlSk>^s{oneb_8^Kylad-^YVlFO&^2iEws+I`nn#yO@S4oY9?F)LWY6rh}}=ybAwwLy-1 zd8!FeY*5& zEqgits67jj;4d+i;Wz5%XxbRdy$;%63`}K$H%AZ0>^q?Mb7nsl6v7xZ4a0k7mZgJ# z?UEA@GI!dd67Z?sDyPj|ec!0H%kTz(JOJhyfRL4wJ*O-%0R<{dU!pyI2m3LEZ;57K8m`v%ar&->B(2Ay+V zDoD~8|Fw?<2K07J7Wz6gcjEB)UW6H{?fiP)MB_)}L3h|8`|}tme-HVo`IFaIuPtcT zRIl_8#zR>LINkk``sawl5jQUWE-rCWrut^N-sN|4s<~W^S^Jt3dlWyP2T;9Jp8hoX z*zn_t$7&iV>EDE9k7$N;grb33N^r@5?xE442+-n}HN%hO2z&0@{?V8X=h6QS%fo_`R&_S>H#6jPyo@b7}3O%?g-11fyrjGm8^$vh+b{-xk_bLXdd?;LNs$Ol< zNNZ*d3FP+sY%<{WbUMRtGULUH0DR*YJ27pywP?jCKj+weX!2hry@hAd)!EaE$%Q(> zHfjI7_8inIWuzjaCqJ(%x|Eba3c1`bWBz~)L&hFOU+Cs`)>Kh`AHZ22+l#3{JoLLm z|J@@}IV$bwlrXno3*i4peW4hjC+@1Vy`sL*6EN94ns16$!8 z0`g(Ls2?X0e!h3_zAvPgrO?ViuiQN?$3z(aC~9B!+;8H2AY%}4*Vx~*Tf`;^ z3YlT(+m-jfyhdKj&wL(zV{F8FXwP%dMlY4>+Pime3bi96_^MCK2m1tu&@8>%Dom<` zfvE(pZhroqvX2JB-7bdBrlqa5HI!icNm5E?LEMoFtIDHPvEBW`jRW4X@4Y0Z9-Eh$ zw@W(GWK!tzjDY`=phE?%P)+pH9e#RK3ruYAns*dD$LrT|jtKfF(-JWQ{ z{|^RI#Xu~ySx>x}1)*^SDEL3V^!wL)Wq{-UJm?k;frWv%Mn7kfJjlHNK{Sj6=7ec@OJzTpW|6YH#h+?#^~z5aJ_!`^u|kY?5y zU`k%+!CoA6-rGOym7PVPAJ79@Nq-xvbiMn6j^Y}};y#a!m3UkY?Quk(cU~ zN#8fFpqvgh$?J9L2McAY7kfoej>F@G;*2pv@f}hn%mLR8VxW)K!bHX9N>K6hm^!{~ zVT}4k;yEI)Y7{y>1hC*2^s$B{|OFr(EhK`Po|Gthr`Z1-*5qiij znB$)mc+Lbutw_ah=xgb19_&pqGt{3|_fl?;B{e9W+=S}%)h9=4@&|f8MXq&!wP!0M zmrUh`S2-sPdd+k|0)?T=2?v;}B&nzD`L1$L`fU+pqkS64_728{*ab|2K0?@SsBeE? zeLt#EQpiXQmEIGuj_K*?0{~ERDu&&sXpm2HDk2~0Id8yd!Shg|RZ`;8)Cw*0iW_(cRb^cM{#JRufgH}T zeNZ}{&Y$WaW!aj!6HhTtDwHkPNr(HY!z0ybeO? zNh!0u7ku)NP4-yfV4u!TX|uf}O^c5IhMBSV*e#1mw+TZ%nf-9;F&u4LVm#!ox9N6^ z*!Pis@@DH)JYueaPrg$9^g(ZY#lS+oC$>p61Q1R$n0JmsVNHZ-zE!u+ODig(V8{gM zyDo4zBrFy)5W0B@#5_!GYHn@KXeZi9%E`)l!w~Vn8e@{6gu*7;`R#dBZ0>?E(OslD zh9X50NLeY+jbMNbB?N=}H!z*q0lWUEhW_Ak8K!i>UX0!RWHz2>`F~t=+a9U4R&KJJY zVRtN7ov$3u4mGDiaUh1Q>12y@d=2Fa_C4R}KzCzs(bi6(JyQnB2-lH@2tHM$tBiI# z1ayo6kPkgKL1kBVc!@I86S#GT^3~gW`re4O@&4W%n&l&`^)uB^-Vho`gHH=<>6%Gj26ZApw7;vvex<2xlbq~{c(%Z91%%&lP?IBKhU-z z7vu}4n#VoUUNC!{OlT*;9T3N_f8nGAZq%_d|D~uFSzV;7T=0W_Qj`;at67B}D=sIY z3vuFhZ`X}T&h3}d4shTO-ApYGeraG(I#^9C!M|WVXN8fRhzp!8N&=X203o)LO zJ*&Dc2Kwltn$CoGadIWsBr#pupQ(hr``gUQJCPL}s@B#dCKMDA-U+(jsSL`tucCSdf=B$qtpwI9<9U6IE z*W2;X=V^zB?E@{1@EuHQ8J=c3_O3d9A)h`wiWq;o9QNzbO;Jl zWur)xpS}JoKZRqQs^W@F(f>SQP!Hg5hg^B7;l3q<>jK!0sDbpN;hzP4N28i2gZ@3M zhAgc!j-71enFNWI-FkSk3V6*F%>CBBKdF;@G zACoWU>;vvYgZ0 zZGk!Rp^sW$I234$%`EXoaWSP!@%~Fxl+M7L4F2#Mlu^5|plk1PC&*m(lX2 zl00~N3;oC6m-X$14ejw#6ZRxW!3v#85Ir{#_U|`KXKc5-+0EO!d4}A)P;YHNaS(}< zDHl&~J9P6)Hb@33A8F6=p7?BXvTEN`N^^dKQQ+)DBolPk-uP$N2GS4BxkR?6)`fax(Bp>Lr)swM4JOqz!w$y9H4^ZRitrvM zWIoKP2G-Ta9+m10t{F4ps~h8HJ>nM2bR+0R+9{O#n|K~EHSWYcI6MvJfMz6USSZZaC^#}4V1(M}XJfsloaI5BL$=4I;Zx)Tq(&wol&)j=}Fbv@|cg_E&n z#qAxPIct9pm(i*c_r%d2sq!_#D5p@eh;nv+jrniqf?=qcgH8%gj!{Pdokox7mLiH(ipI?z^~sQRuo! zHg^~$^!5G_+jvmq5lsY}$*7rm)U7zCh2&Ek6uO7&JDiqxc~3TG*JWfhlu+$i_9_xN zl8!&6%&Leh9f=dtsUhUpzI&t?|}(~Xxf zzC7Yx1Ygl|_iH%Sg~v-fZ;j3bok=&j%)(ksm2c>ZpG94%R&NpXzo@WwkabW8&1R4G znD4gXTK42x#z)>)C_W9tv#H&o9Xz&q11=)G2vx`DA99KQ^(vVIJ}7T8 z-k9DUAo#ovu4&Ofau_{sIrB;)OeXk_I2Btq;`d7E8Ch`Q#sG&d^Uu;%*Aic#NyaJl zS1YdP0P`vO+NB0`!EAX@Lr0X-L2z?^d{F}(;mIRfNbTX8ZJ*MZ148PC|2(r9sh6?Y zjZ+o>mzd{+OMF6^p~%h;24@>wt4m>JenNKxh0IK~|5^BzTz8!#{ZYv$?+7G_<+v_Q zhIi`4FuSz6_l(FZETfzzESsyAQz>Q+i6m!w^w{{3?O9vqP`z7fg-Sa%)j zRaa$X?01q# z-s|5KFG>%)F7<{l{Cms$@9X2Kpz}=cniPf7C+E5C#!m-^FWT z+Ji+29NHX315lax;dOgm?NX;Cef7gNH)!+Nc^TP4B-L!E`OMPTEZ!e`Bf&6tZh7ei zv|-UA+E&YJBv$t2eTkltlLz~7+$UHPxtJwx+G#9_-EFSZR^`>je!cJv`V6rEDU|~FuEwC>c$v4Ot?WJ+| z%CtJ!?WU0cb|7kP;@4*{znf8I(wJ5fr96~*dR3O0D4WseH%ALYO$|7%kIs-0%f2l> z%mL!kCnc14j};W=^NBADD9&^ugSF7l@OJe^W z&hS#ZGh<|~uE1Aed6`d2&g27wpU>p= zE3#oaPE=2?4woslm$;j^&bw$W!hlwy)7rz1AIzD)mIA1xc1PRex&zATu1kfv@swL- zG}k`W$r#wQ;%?{yW_x+KkhU zk1~Vnyz*Bv<$I8-dg6gs<54)3dsYKs$804L`~zO)QH3{A#XXvEgkyC zQSUtw>pdO%@};!N$Lk0#?md;Xu|K|sU^!>F{B+%+*oztKnQ0+YW$Pn;z%DMZHxokU?jdh!f{?nxv{P9yT`AFIMvXWmc1@bbzLza9GirzB(3bI0ADN@iR&aq2Ed&@S10Ld_U&W@5c^n`b$5 ztwh0$?9ZFdJKy1g6p4vNJBiz`=pc{kE(}j1bEFqza8b!Gr7 z9f4E3argo%ISDS&a`(*#Y&`GXAH4m>ORSM_h%GEq`ZU(r@f2^7dnD@l1B`8&%=4Lp z1HbPLa6G$vWZuMnpA_Gll++e|&I_6els{b=0}Y-GS2dKdtN#04jOSczmTCh!?ECpf zf`~(c5(tY1U43Rmjjt>je<@Hu5-9kM85tM^}z1M@MU#BzV}7YNSa-XbPZs)m8r7wv;^9rsE;mWh8uNCo!l2!Ij{_S};?1#(90tS@QX| z%zFuX;lf+#X@v1Tm!DObM*`j{mKA z&}F2UL~NcEg|wI@^>3B3e==iP`Tw`92$NwxR4VnO_s!vx z&gsiuZ=Y4TzkOR)9donGb?K+_PkHZYh4{lfpT2BAnj_nKigs>Z{c67PyZyJ4rAJK~&gDG`Zn@zvActGz@Oc-q4A9bnXcu-e;!J%FH2rVHo=VtJT}MUcQBoSWqlu661N=$n zolNImpr~gz4jR*`zn+??u0(kuAvF8&oEJ$K;#Ij1u{jB3qJo?OA3|E)7mvMPLebVV+DIS=o^b-dEJkvdl*@G&9FMq?|9B=c6-?98@&PpHLp*Ch`??IR9`3LtW4Te#J$cdrE7XABYq<{zP zX2E+;rI_-9ayqtxRmD=rxlgD{NH>ob{igh!qFM^JADeAoP~D!#+In#17sY8ACMlRF-=K-{> zSSrc@!dsz`3yac|m#I|^c8e=aOl4mbH%5gs1+4D#*!$SFF5##Y#OT2~`*Gu)Q3EI2 zWERk+KlquWV%{bG7YX_wjRtlPp}^4c0ZH!?549W24- zI;<#Y(VF!5?eLWDwD#7&uets$k!-fsX^vj27~&>V@jfZnkF@G||6Y-)d!F&HbiP^9 z!-dH6LhB=}B1_BlMOsV&u7B9UOl|Yrxr6vA|8SgUM1P}^>gKyIBY7wPc1+d7m3isr zsjW;RJmn7~7H!ufJu+rbykP8F=F>QCZkultkAe6ihuS~lB%gLOt2!zAz6^7yXV4My z-bUC?$Mu;#iG+04^(AB0BPF0X_hla=3Vg0bWvLf#nadBYx89p3gI}Qx-{$pxWT&uT zUg|&nm3`pzicdA?30Af?uQSvrL{)CYr%4i~mzd1e|Tnf)d zMf%jZZVjq$f7N0`NxN`QINcN;cvU8XC(5tAH_aRb+rui$N{5^Sow%G<3U_`7j4LjdiBta_lctJJiR1&R=F0i>i9(x^x!r zPi(ZE@qVlmGqXx$Cl5Sm3-SoOK&ybS3)cLJX5Tnstwyx3#a%}~j;%}?u1-f7e$ajV z=@E+Wkc=NqX;w=avm_CFUyIZH^WD+s90tJ!Rh6{A=Jdaw)W1Hg_mv#AW9>_sZkWA5 zyDlC5<6$bwi>Q^4Q8yp7>GXUWnD)M`!=UV-1TiR6L_8SM?4Ji33E-aKZ^SkeOi|`S z{-;xlGUHVvhtM+3u}>*K#nsH200O$Mkl^hgZR#l3S~;YL^71llU8hg99d~AdI<$E_ z64&}&EN{Vcoz9X{Mxlh6`)02V>f07H-Kt+J=Dt|m7&SX-d(tx`P7a~Kw)RI^pvhnAwCeZOmY527k|C@?f z31V5)+_^KY{m3An{6#g9o?yzQr+HUTI4Iw1>cYsp(($LI7ATf0is@flp7F0*Uu8|{ z{AHH1VM4W$YT1@jw8^2-bP*R96ThD0=~1?ML~5gHSh{U@flSX}nt7E6Vg!pgR1TPJ2v6Yqa{H1+<4FE*qbXr{pCYJwJN_f2&3b z+^j`i_@?$GQ+VK?0XFSU%3aMJ@Zu*1=X_I0N8PMDlvdJSJdxqQkQ(=d@^ps?#?G3i zUuyP&6`I8tJC?93lQ!`ty9#gi*k5V%Jij@f5Xw+Hxd%XiTb>A20r~tIbr8z)mqQ-tGU=p z7Y5i;|KsYtKNkWmm;+R=Qa*&ZNWkiVV?2x@GWbeJs2}kx` z*|N7BduKalhr_WBzsvjcsqgRm^>016@B4aPukjq$<=$P=-YbQl;VgD+(FHDIV|jER z0BAa;W=L5-hWhq5lFaEfuQz^(F%RjJ%}QdQ?3BBQHd2LMAbX~_{ypoVyG*Nr!}3*> ze6DF1`v43R!&vj}?hb(oxVU$~n9sIiRp8t!Lu@H!4#z{>Yb*i!i0S>?igu-yUztRI z38vnoRi>}~vqXR1r{U~5U!y2Q7r$70Kci^+L)wsm`oz&PnU3439ONpSIH=tsWh25K zZ&>|(?Xe1yEDJGdjG`oHO0iv$@Cx@3Z+@_gntK0D6a?Pb2(R9|qvhF8Sf*D==;AwE zuc6xPvFS|LP>DJU-S9V{^G(IhmyN>ewdotq)($*%h`*~WriqlNVbO!h@}f^$I04y# z0P#T^T6Pz~GY)na-0R%@m^V_LermU$W}5)-rT`mAJh~${dq)3ATJ`cNOSv)0@VTbU z7y0lH&-3x4PMD3TR{xvJ@ctz$gO^~Fmyb-Ox>j1j&k+owi%XpwGh9-Iw>S+WU;#0v zi%+Wk!*Kd+D7-+=GwD=ug!WrdM zpiD%ZnGi5FSBHAr>nh&v!2f@q_2-;522s?ijeQVXYj!)`>dj+v{+X|8NjK+S9Y6OC z!a^$(WzXfh@S%sybcbFBgoWbX1qn*$$Q^z0!*&C6nFXSG7lLrnx8r``8KuGaEt}!! zvwGBSqxM2)B!_tJ$#|Bzixbhe-hk!p5w_c0roUYqelJ*8oa)G#-a=CBf4-&@ylhTa<0DJQVbw+KeZ*lz+rM+N zvV(}Pu(NB$7w6)#1n|tWVG-j~uf+3;ZAZw%onPTngNL6R=DDITy`uyBZZ$$4E?e!+ z1n|agcIP>p+sF)Vkfg8w1&3Vki#<)J2TA$|=(Db(_5gSgBP?gjxsFd1%NZxft&W)!U82xqmrI(UINu3O!~`dGv&uDk9WmJj1Yz-( zS-M6_q~+)ZYj@e5>ET??`tgdaq7HW^ZqBZH46WyAB#@|WfZ-a?^xrh9%d*%tD3Rv5 zxDW&iSI-Ouc=UPh=iJUb`v{?VJL}CNIZnjkl5B&0MM+3I{BXpf-Ez{`oS*nCK#u!2 z2UrpIpK@FbOIejdG0G8M@``VbLRILup}#~fFkp|Pj7y=^Z#k0odJyniNHg#!+>j2r zDcV?RW3x)~`OjayE+E^)f3uLLl&tKJUyc>kWlZkK;N|Vqqejn%Ju@mu8J8t*LalvF zkIJ^L$?%TnF^uAHcuNsRj>uZ_qI3Q6>Ed2(1PyJRSM|q7v9c=1F=TNS8yzA59c1G? zOTf$zMX9jXr(+Se6}OQBU_x3w65kbRtZ)wv!geAEK2b{kopLpOWWTl=_#IqC%^oq* zjLmt#YAbG)uqD4|MgW&x{72Ym^GkT0Ed6bo-NyX|SkAqUq1C;>A~k#4OB7(YGgO27 zH8fP*n7|2CnLE^p_wN(+BmKy1|8(Lb4hE3RcpQ=99G-`dYRg0!TZR47C96+b)g#1= zSnIX6FEH|~a*&B0yMg0!Z~ejm)i{s zAeGTEM>mM`vpRvMp_AfoOjqw>L)JQil)J_QO6me7ju(uP0;^qyN$w;F6QsZxs`um- z`c^o3l+v7=(%xj5=0-~&wvi^BH6Y-J1_=3K8?gi7bmL27m+?MbeQ^sZ^xsGoFn0QJ zI_h=RP$98d)`BSUc~Afu37R1s`h-yPO12!x1qCe|r6wnTNC2rv5}YTkK9?kX1oIDV z>0Sr3;g)2CSRsr5;%DLMTX-=de`hEAj`1 zam+zyXx|>b!Bk4G7`BKZgYL>QReS;pQ|!-@UwPh>3Q$(^`|n`HLFYYp(yi0~z{8EvpW&5oS9x`J8 zHg#A2)-pK)QoVo!V_Q$-aj>%Y$sAU!tX_R=kz_%_C6_cR`oH>~!<#(h%JNmW6IWVnh1++^I7T-DM=wQIz=y_@tbaY^^Ix_oo zSU+kb$ICT8((T6sip$yWQ+|kZ#MC-3Yi{cWLISfH9je`tAUtBp5mkJdgBqE{1il@# zU;^{|OQgME`uYA{D_iMg-4970%yS-R{UDG@dmV`KPIy*uY;>WDe?n;Qy;i?l-%N8gV0qV1);mrkT7z}?2u~vl$~X} zqe`nzphQi4DX;)lq&aewL?N`^g8pfPGRbsW*N%@c@^Rs&bAO7(0fQ3pCjHo5yK2=M`tnXBUbqsF6K02W@deHq3nv^o?GvrBJ%1jtm)3vj;T zCedY34?ZPc(4!YW3=l#VIi8r>)vGbJLRV3V!89ADb(wX7FO|W*Cjpd%gN9t2qPAxD zmiAN*y?^^M43n=du4>Vs)EW}s+}P*b2=){&EiL~SOvo!8sW$+*%+4z?SX^h}6badR zzH<=&0EE}HDEKaZ_d^s^u2;MOSUQI@{3RuXDR&@2exMCt>UCGQ?DWXI4RLE=-Vpfo zZ?RjyV$^?1xRp%fdVMfqWQ1}Za7I@0ag_#>!8kpownawd*Iu%Kn$d@Y;foRmVh-}> zG97`vo@pgoo32O&vP7_`-2hXv%|}ABEV`#2&u7p z_LNH;XOkez&|OcD_N@Vh1yw*$W`KSGd;k0ZE&U{yb>)Kq-pkSMlOE9efS;y(H1>Xl z&RacAWh2M9E)y%$KEC$sM9mOMUoQ!0!JS0(b;eG z{^{tSCGRXfH7q(aR9>B4wa+(!<(b6@m`2Klkb5U{vf5> z#Rl9ra_plNt60i1;w*>MW3*bHeLTId?PXtP>G?n1xLa`d(Llh|HtdR*E@E!w5ZE(mnTpfINRnYlaC`KgTUT8?sK^9*I_ z;CXEo?ai+_c0=MAqvTd9fMscCD~XOGCj?^S%OB5IQio*L0<$yhlI(}};9A?TfZ45N ztFvY)=QW7y;Th#yW}MIy5?LL$Pf);ofV*;3q$L=m^#WNy_EIZwXIfx1@8l?*t{0B> zDs;Mxw|x6=+D!>a#Pp@^J&`LS+6^q)L-VK_n$7OSQX$D!!!8#NOO|h6n%;#+3 z=!U|_4)68*5m^eG^7=a%Vc?+qJ5HMr{e5_pUB7>L*jmd<+upvZ+wLd9Q%1V8$Kj`N z!QC6G?tAXFGzcEB(c4BP4ot2#RI#OGQFTC-0%zKJ3o^Jh!?g+rA8Vi+6abT)Z=>*Z zG`gTii+8v9l@-`bK28clxGWogBkq+IC#vdfSDp8v%@mBqsbX$#Jj&#-t>ttmq{y8@tH+B2~H*K-9^iYg*W!oSV9O;wAKX| z8X?pq3(wiM22*bCJsmuC9sR+*OLV96OV75xA3E75olIY(V&-co744EWfq2-%hVFp% zy!xIg>U$bMvb`_P-Uu8`^Uw4sJQRhZt)=9Jlt^bohQI_nF(N3sHAgy(~Dxq}|dSK6(mnCkL1z-IsNq$8u;?!Q+brNvZ()^K+8g68D&(zi@ ziuXOrfru7?enWbAUGU}^@iz$2FWmeW^F(x%r2c!^5KsFe!H?|WXY4~8&{JyUSAqH@ z!CJ`+%2HT`FRX&3o?EmkH@>HUW7awUy+aLcgGFedu$w}=2lGn9`2B2I_7cv4i#_^$ zKIO8(B88=LT=DDYTBEbir1dlx)RHU3JT{6_t(x5QPOBwf52$ZnNGuqL0o7*SGef|P z)F;ScsPBkBlF(z1hH2PTyD!dv4WJ@^!1RU{AJ6#9|0eM!%r*BA22W+93Xz`FoQYQE z$#MN;TOH26UiXYA{{GwDuFr*|VIrpG^}=l_s&o1o+=UkR1d6*6RTI{!c@!FBESY7A zAo3G7m{37{4~9TEyia?tG!pWay&@JSKc}^5nNxvVA0nQ%KeKe4&HfVwBY~HOY8uJEe@+T+Uj|JREDUBRIk-8%&EyRgEk_j5*Eu7mk0m2ow zm>CA`@(~#4Z)#qk;QtSvN@kTb?nY$&kddB%X|Bk;aLv3>!X;+_bPPI57|p z{dYLo16^4XgZRRaQU=sGfBn<8WySH^s%BIvldv zqbpmOD@2(kpYWFSJPF%*)O|Up9(roIaj`;`3|wo+Yr>9Ndjx@8@*TZVmj&zKjRbyFPH)SNgNODsMd2=(SA}mLgHGN$ zLXe7JL}d&GNDTK7Vu9qif-E zcB64`w&mf_CaEfFDY{=WV(i~N%W?-4Qs0D9Kzp%WAw8`5U;66OyJv=sBVZ^gI-J_~ zMBSnaPLb_la@>=BbVVim3cx^G> z11R%%yWram35qd+c4Tgo1aF32B zrg|$!AHE9-?8wUJRC#pOOpA6=7z*5kK+8POE9QQjeD$tfU4Bb0zh2f_nLq=%FNKDf zTS8WNzfw*jMc%d-wEzCGwG}@rT)g-)j*x%V9C5#b+y#Sm-M%P+z6tXBTaJ8jCN?a; z13ecaCNxP07gn_4Q}STmL+5;oo0PTChEVklFgM1&j`P7-f!_2F{&Ua;(g2DiFnb~v z=nwB8G#9#tKldbqB10#5oAasM1zl`JMzwlTW3>JPgYT&736A%Tl4nNC*&K+D3>W9! z*&DcfuB;p}uqW;m@$u|=HXnm@Hk~QvC<-PC#?sXv10P$+(s<4nB5|_*2Oo*O=FPlg zN$}CDN$ReA^OM;WTXuy?97KX`GNa`YgFV>1gO@hq$SW`p2(u$j8 zV3Q=k^b5OT?9pOWDldF9a?WM&+ZS`W#1j4Bpw+sGEb~*{7E8-vM*qH0PITY>3pov~ ziM(HOsh}G&E}Jq%HBa&qdkAgZYOka3X}GrM6|(s@8vlf2{$77{ZEsl{1FjdjHitTZ zL`7GPwG1BqY?x2X7bNTTEQztof>%8jSj}mF0!9pXuGxW52G94pjOnZ1C#Byl$OZOe zMK$FJr6Vp9;T@2TEjbzh7$)I%qJWFvS??$3zlsW!qh?XClq*s@aov1lPzrdS~+kI?|l%Fd}_Z%(kX>kVl0xNsp_Zz(xflIJ_@&pz0 zQ`$V-?Y#3GY-YApFm^=s(QmPUDa z8$g+wz@mOJO7om5y@pR?+11dj#dys7s`48EW7Qv&OnZh_?fPJJvuRprIQJJdQv0MI z(M8gq4YdaZbO8RWUXf#-%*U-=6fF(ZKiA9oF!oEDBZR$lxe;2^DiUB39Hi}gA_bb#9`1y~D`u8rs|rYiIdv-mR93o0e4cTnRY ziEe@tJ>!TXyG?AYNT4Zc*R8h!BY&;pR@8UsfO^>}e!(&D|AWT_Bj0_r`X%=ir8Syl zVo!qpW7|5_*NeST%56AHOf9h4lW3{d(8J%My+}$96k*X`01XEOk&lrB6;P2~Ma7yj zMMa@1ZqRx5LX`>E0PN|;GMDT$=A|e2jpR=qw1Vr!ZcWh<{j+AXqqIAq6ofE_kZa@dM&> zkv@S1RrgZ#?J=$v<^y-l~tx|42 zc*-rL_ooCq{K52v`ajG!UyQ2~*g_jcCZmgjv{rucP2Z2#+@O6ptk9@b*GGzQYpK)4t)? ze^O}%0)Ws5*9v-mxsR~TJ~(TUeW-+UcI$nOP2M{AiDxo^SVcKN!g_8GzEWzJ4x^80H8i=I9&X;N~gKNgh5T0Ybj&m!@$6 zOE63ufUE}0LDR^G^;;3BBxhoJ-8LOhWkxoI;KdKWLn*}Rqqe+lG(Rpg7x@0 zN+J&K7Fz>Rs+zn8p={_}ZiPdv{D73ll8p5`pA~Vqzt`*Lq(2L`Dw%Uc+gcP~ToEDktG_<+8ZrDut09*S`=0{t}P0H)blDObapN^GjnygUb)jVa=q)lPa9 z;RQf(_>_`cPbH??__M-F{cC8S)Va~ErFSwPa)AuV+S{}y2wS;~ObB1DR0Eaf%AzFT zo^um3U>N8hdg$3rlE}#x{JFu?~e76V^APQ^6bm2*k&w<;w=&W zdr#2xz5&PjHR#-D>pjU?csM|_fibeVd11AgxaE!{$V~nS=vGq2UgMvXY6%-qBV$Xl zb0*VS6)GO|72>%zBOZ&EG4l+eV8xGxdIj{2IHX94+-OpqLg=IaMnT3KzD5hhlL7>y zDA(TvhC}3eH_jyx3R%OK=*|U*=Q{A`7Uxd_Ru5`*wqhq|vrLdhy2JTwN>+*kWcDqz zCpQaeLS^F}j$Ibtd^^=zd4Z_5>I8XlAA4-1 zmZX(=B0u)tPQB0_2PL{-qQ$_3Rn=1NQGcih53mTaR8q6=OG^(senERkF~yDJcJ;cy zbftU$Pzx@W?!;D-ti zVo-r(^Ft?k)V?`n4&;34C-%3GE+2pWI717 zz`jQEx7P`o!gSbb?;s$co$`x}pz6O#j0Em86a zu{6U=;fVr-fdTLL+tm`9G4o-orIndxFAOCkRW3|5Tn|3~p}`_nKkwYJ^Xy#rYO*-@ zakT|}CFw+n`A_;x(qFC^JGzFBI=Q8A;mj;qdZDm4v8T#L$m3;NRTVd~WG&aJxC6_i zfB<2kBsVng3BO$@+0^$rjwZNaw4 zeylaneV4>(%(O!_5$D3aP|Y6YjiI^P!Wwfl(y?O=a(4R5GG&izCt?!G40M*o5g}Js zflI1GFQ#-Vl{(>sbma&y?+Ehx#CoN=+eLnBWqyt{9Nkov|5_nx(##RYh6Y^2bEq7& zbCCy?mAC=zDY^r+xRit@hm6G*h9k%uSof}yc4(ja1}wm;Cs2d3WzA@G1E0%9s-)@t z^dUz>`s8uf;``~f5A#k>L-wjokkJ4^20h%{22lwBsD0TK&<{eKwh!Eu7)H1W0YYYqZ)$}7ex9iM3 zQ#j=w@DX46Y#Ze^?8ctA2daeiA&1z(8|yWDI@g!Et&0ug_Nk#A+?3&NT>4Q%YY=Q_ zu9&UZvJxRufB-aOn;d7PbbgRJFtEhJ%S4u@rVTkam~^SA)wfa&whtbyjEWiG4ciS0&>z42+nG(CfqjjaN{_j zx*xK7&HPJ&k87dO*U4|EM6Ha~46Nh^0=SDx&oWp|N20|@6tm?sh}B1x z+~!sARv1dnQr@uF02d<(?akZ$BQNm2(fmtbuXZFByPzHvPSEWnri-rY2E6r1@4P=S_;Ro00%)%s~Hl(l-XTpYw1y7Mv`JAjciOz6Q3vqnJYG#Y~Gha$Q3a4j`&-A+xZE zIbpZMpuV|k`#tAE*ZGqjB@DSoHIrc>VN6_n%&3soK1E=@BAGt!NO9k&|42&wRBdJ1 z7xq7Iebrd98^Lp`X%XhKb{n)}g2kW1XPb?9)>aeuZrP{2M_QwZGBl?E>&ip%}qYep*xi0kXk>TgG zES2bSfyp&x#8KHM!Mk~2IMOr)xs4J?Ex%xdfk%!(!p+}s10%TJ;!FbSm&ZSO_p6=H zmw>Ie+A(xAU4D~9wznboW4xgu$b*1M!Jk{nfU_uvWDIs6!2)sHt=;{%$A1FPC!kLw81afg(?cW*oO-ujI`#ed#MxHp7#k5*4(-D98u#3gjulaiTIw$*Q*q~IEO*LoAqh0$2?OkeH+_wJ?RQu0!vmuQt zsxC~R)x*OLMF%>M^@&k)Sq;DRF*`;)*m>IigIrtzkynDK_ zRD1)my*MwXC?>4AC`tfmi&iOHbIWx5Y;23YGNOd#;P@+(F(pzj7r4dFoJYlJK3!dW z`3t_(9)EgoWYl~30{7`mO`ER&5ARXFg&a^w4tISA+HY}ED%DkaY92f`g1>>TMGfVT z0nV4QrwV%8vz-VYb1F~ZZTCvyRnk^wx34~NVM@%!)r$W*KjLn@m50U%H+o_HI!^d0 z22+LlYtQn(IxVsX0V~3Y8w`v@E|h<;Y(@o zzW1J=_^Wt+L2hfBUPlH4#x>=248$|$I4Dz7bFs>Mxt$VW3PRE0=ZRl1 zkx)0Jzwb}U8%UvAi`Mz~&;)SWz^5C3yNgkn_iq#2__8DS+bV=Fg(BU9hph#)nZ(Gc zvTG>o;KYsTFJp8>T3I20$Q!^kRybgay}r+)YH{VsDmGf*;leajZZ+oxUwA|<;tX@O_*Y&jbx>j4dfI5T9$uE{ju6Xh6>9%;lRoA@*6UpJ?B@odr zYm{=uc5M4xagz5tjVg8`;yZasPA0X?+)v+IvXtJdRH1c%D;^j@2*N597U2olr;8@m zAsu>2^DKDX@d@&cbu}6WqwrgN)zkHy;CKlYU~6w+05bYi)!I!;(y&|i72NhZhzc0#LpdSl#%#=@`Ia?7M%szticho43!q*n7lR!lYEtMddQa3u^$ z(oy);Z_xecGW&}r8kB^Z(qzDc&;u+lr7^X@MZq)7;CZ2V=)4EOzW=@eSKY3EK}i5f zy4b3eSXvF7gSwUXwU&Jqg@ zJZ_zqmR4YPuo>20D#;3f1UWE%vS)UZx3wEHPBMMyYzuCr8PMJa2C?|kFP$E$JD9+> z8(%LDU$FlxOZjvan67|l=H=uD3jhN>+j=(|R+?lE7JXk4?0J4?+c33|Q+cuV7!A)U zp$%8k#bkOJQmaj99niz>rKo2PS%@RQLj{d%K~&Y$Uvf0| z%y_H!9@2B-E>iBcvacymG>lYcp1za^-eu%O%9p4|jI{JYP&t<6fOb{@)bbg%(jDlw zVu+FTmkxcl5kI_@k`Mgk8lez%suap8)g{$!7<(_u$+#}z5Ick1IHe(aBLFVc%# zywcLGzkZ@#rk~k4qn?#9k%qvSAmEOoMrJ)+st*gdWoBaX8}aCi(b6v_0e$u8Djo6j zK)E--{*ob4R5>SQkSycTGYjK(cW*e#QGEDb=frrA>F_S&h&<+vJDK8K>6))Vq%)!w8-A3jEy^)neGcb*_pX+uw@+iW>nrz!RVy_u4N zM1Y%k?C5#p%_gXs!lum!liNt((x4?@-k#B;==p_&z5g6n3rTm&vbD;+hr513UiJSa zuXx>$=DEwXH}t4k9GmXPH86VK-1CUAD!A*NTHVjsag3UaNFMwYBj1=?wp<}98xUz9 z67l-JTDhmHTVHB;{ef+j9!7mGIR$Y?PFK;(;G}40`jFzS26x+}K}k8c^&1Z>_kr22 zlxFVmox?Rv@dqj;Bza6?8ip|dmBD*<h_MI2u19e>w69Sx#~5GPV9_9LHE$n;@~J@}X3`EK#o)H*UQ#MUW5fBiqe&IH$3 z4aLw}U;s^73=r)0y0YfgR0NP*90qZ_*4kVLpQNPnezo7DV;?&I)8WIj45^IzKu!@A zWqsa=fa#ijZzbpZtAT2BX1-ERjWs;HQBImKxJ2?V`t+AxbiiSu#Do!i&i*{f_2ci`%f~Sa-4G9P#ttMU5vE@xlB2JD#Q|b4Aq)1Q74j zy*!>HOc3F^Z|z$^Rq$9h$|lQ4ARe_c)PJ|ofqS$-@h_$Vej$>u7vk(?kKYz3#I64l zh`I9xqrg28f6v-vipe}-Gi`~H6n5jCpXhu%Ibw&p$O~QIyeMB0G4j1dmx7e+*sOz& zr%eNIrELBOkcG=tpG@w%_egm}0%dNN5>WX5@=7L^`)rz7wBhxa{s|EUY?dZ2W^`2~wXMRG&2ZtR4R$>Re zD@K89nD4Q0Ph5XOl3e|9`*5$8e^?$+oM9jY~4Y~lij%tLMA~-+^ZD9b;h@fnwkQoGM{J+$UNAl{y{eAW7z_pTE*oexpMZKA> zmwgUTU$hLXXmXo2mc_6eA@06~{QCHTOjgl0R9U=XzAhRAEKx`2VzrBeW3XhZ!aSYb zeX>H_ZkYT2mE-eSY^!iVM^%mAG^ToG_sLlOs!Khlc@*N5WOlvJxmxh+I1unp1h$4U z;J#P%G~iH*s~-eNt$m~XL(((~PNZyUkM@P=rq3|RJ!8%@n1QhZVF!&zcc(ng73&F3 z2F`xB$5Qi~%}$Y%@G)}~(E+h?h~`#Lr}^ClP%mmy)45)6v0KWdBR{0fcV^#O^wg5OwjPQvZ&t1G=%dVIFD`oLNNU8DE zuK+j4#m4^RxBY8s1`gdG<+QLp?KYX4-dk^&DcT&-f4XyAraMbIXE1N4K&1)pgCRfx z4eb7iUa}9nV#sMSOT+hQ>d=TL$_{783d&e~uk+ohwCo6nxtf(E7MXRACzL1sh5m{a zJCdm$gMr5-jb?{`3h97Yxc$lKgU(3W1sZqTSKd-;n$f8yg;@IS zuvkmGplggJ2?>~9@AN7}e!<``MoS#y>RViSVwq3u4ZcOWWvf$MPKf_%Bm-=xK(Gs` z2Q<}9Zf6xj;5>g!a$f?R3DQaQzg=tbuNki?p4vIoa!Mnx#qzv86f%3l#VS1oSkq6Tn5wG$M06nmDt*Q?t30WGjgn-PXB6U*%#`jj z@j0Fy{hSe>z#8)kXB;%OId-=}0$QxUS7MYpPcATM@KL}~7F7;nBP^kfrZfUs;}5)k z7oV49d7vjw^p=u-ee~O-9jLoU1^0-1NU3tR;om5Z6iTx5oHAV0+9E63Z|CGJvTi&I znxsBQciR%k?&WOtPPc~qdOLS$-1%W{FbzuHMNn3B#~`j5Goy&Qk8uP07n;R0qhQRZM7a{P!L*=(-yM8cBjF(jn?2i0$aanb+-H_sghJi{K2V&Mi^Yl-ZAiuU*-fMqeXr83L?2QW*-`9d zzV&a_talot2yc{x?giry=$%bh+0LCv>qb+V^~V~GeiU>X-e}=g0Ro(RjB-U+5izoW z{2>6=bk{if^IX0UDRRAsCda+?JnKIXxnzzz8tib`=o=%u8m2nNp0kc<@3TJq&yfry z^{0R08pUuuD;)e2H*&VzPr#{5{sc0M@*CE@F$KY$rng8C{#Q-4fZE$3LJt`93#RLV zgw1tt+$2#z6vs;?1+G_Gy7Irsf3~siqJHB_EnIY2@nYM+JyACOeT>L0mBt*?AMB?M zDk+-BM5HXf357^$N}GWAVCNHAb!A1`ve83DOx)pmM|`T<2Jc+)-j6fygKZJ9APaZA z!|F28dhE3ujF?$>is#Q?l2~O!xppR|)Vv2~VaG5r_qMM=p90tdFte6&ax8v#k?xf{ z({uE-z0)&Rg2^+=%O2uqe9i~`n^)y0gs~~i$W_>)|mnq!J%-jKY$dMzom zrfBb=E}>-ZAegMcpGPr2va*SHfwcj3a z0SaW%VCZC&g)1T1z-s)+le&Hl>Dy6ScA!)F$ddNVa{mPuVb(pv`Ig<;n+1#Lu*Y6! z)O;5~$LM=U@;c*i^48JO@6SZCmp-Z19Be7W#g9_^e95Nz$Ln8m#P>H?Tlg0ZfGZQT zvIf;|p6iwMcqqj{g>pX=r4Iz%_i#?fc@i&PYbEUH)ArPEu+ILCzBC5ZWcas4y88?I z=XO3j)`k)xEt4Hl{-v#B<1h@~vl4Dm$?N+MpqW-qIk{6YwYGy1UR);@5;X~vVoqlP zaK%O!yOwA>{Par<1}`^5!}S%PlChZFYk~=*;~njGdsuZvCdG#bwiRL0$xR77x7}SLyoTm6WD{}OctZJ+ovj2ocCS}F zx2glTWCp0j8NzsrXu9g&3ouI0p9~@V&*C2Su#%i-Y+B3a*?-FmO%e4CbxJYjoOQVL zTJ5N&U;8fj`^3N2Zdu)f*{E+f?0?0`_~bmPRlm;~{^EhJlJ=8>JPg(=?_@jJ;k+KZ zB@!p*;M0fW4>N9T*sO0P^0dOL5OBr%|9JLrNoe8xn%qf%?XstSV12oJ#_S<-p^9fY zR;0@9#vORVOYh7&fiD=V&z`*Z@ctC^OJ_UhjJ_H9a5GBe;tNgJC&(A=mV$#t98ccw zaoAl~xLL3|jeUCMda_b@BIY_H#Nb~%S#BMppS^mW)7SOb)<*YyPQ14R#O<Kve|N<;m77@*gTe^)YkX%zIra~TJtCUol~(ga7Fw$f|7Q#AB@G^i$qi7t$ES#F%hWZSUymuOPce{0A^A8ni$*!&vztY8U? zXq7`}c{|Bx`gGYC_+Zotzb}ktF%9Rb>j=p@r^wrCY@h93qssW%>Lp$Rm$&8mwjX{e zfc?a6n<{>E%I9iVy`NDpLiW1w4Il6cFLqOPgt6Q|z1!q%xa1E7YarI1-E2P2*}CH}fo!z;o!@btey^HgLNp&7{PQdXXUL z-p&wVH}&JEwL|KpN9RO(F$)yjDdxQI#d^_Pk+Ir3+=ef9f`}Tehm1KqNBQX>bE614u;;gO3Yf2 zK{Rsf2c&*R$&FiWgKqNxk@d7;`_k_=&umtkh$Q!4nfnLtz$tRqP$ZHsni)XCxcENV z&XZ_d19F<*fP>)@aJ>JI7GP~Tk2K8DVuoi2cWIOeC6(^O%0q?Su|7ZMz}2^fKU_m- zP_@QbMzmh07?U$W}xW7?+v99WZ}x}6A3Y!L?*kOxtPAP6u3t^ zp~GxTWL&D@6qgyBZK?aFYBj`k5~&8GJJ~BY`6S^6@S4Ne5dWf+}^Hg4PH zDAgVO`A*g^m_@MGaRtS>k-T2e@|li#i_oft_7*aO^P0{!4DUvi$jrsbIXc!U)|7~> zB>2w+&2^}FJCYZM0~U$l4$=8PCzdwbYzH(JfX>xP4!AZiMV3HDgN zuE>_}#BOUDjU^7fI*h52ckeadc6gf2EsDM-@B5^cLA1uOSi(&|QMFz8*ydf;v}Z8j z3aj07GnL_iQcSCt#lPt$Y;bOuj5Bx=T_k}NpNqA+h@Y1{F`m24nRWICPtIY9CG3Mj zqF$*VWM;m(T2Ht?4Lw})45{zWXUfg0@q8zTUE(<6;0?TLrXM%SFf;?lno##6A3fyZAPh_971VwJv zIUG3mXvB|NB(-{A*Sucg@p`xAla#gAfAtqiW$P*Wu%aAoBspPVLvR&Uz&UGF?f!B3 zojcN>?UbhXN__W>+P+GY=1r*uM@XkkP2D6Nw;bbZ_H3bS*KU!7kUNs5_d(wIFXik@ zzUe5SYn(k?tH3M%Ju7^cw$PH}aMJq7?M1V&g``S_N1t-J(^Y*>d~279+@^f$Y;gEJ zj0!R5#a~+^L8Qt-IfO@ZuYUmSB9V03y3HOc4h|mV zPwts=Co|C`hkO?mr!T!V9)wMh2PIZCNV(eGL}=}zwu~cm!Fu8;ZixLJO-)%Ns{irFk*lkB}sh|Nk5_f|H8s9ZuN2E3VO zh{bn^DE0Hoo@ymGRymc&{&o8Zj^>CrR zvqB$x_Y&Ss9zS92In{GM`38vsv8wc%x9^HSYG$sFlyBR8yC$-y3R^7mUDaBy(VMMCgWw%R>+&PerS4kkJV1d;LA`V<{d49r;B?i$B}#WuG!QT6t~8Q_P3&{*T?aj;QCwY zLgi=FBtF)P@FZ{Zy}=M7g8m8I&X|um1t*R5ZwKk52XsR+zf%=3ve{Wigv!D;a>V_n z7e@@_9J~L{^J7HU(EfP#L-ndQ1#wFdXdKNx;$~I;XFuK9eIh6~=N*zD;y8o9=jG*Y zq*kyc6`iNqYyVNC<&BecMe=9Cs87V>>!s>AW^0$LP0}oOx_`8Pf6<;^g6fwIlQxgh zGM*Kw_Q{|%%+r+%CSgnk-?$3CgPGC> z&&LAuH&|~c@iH`29NE99dEm7mJK>$^A9`);Yq}9$F^4U7J@EXx)rJJ3L*69HTu5aa zGx&OscSFm@+~$qC>XrG^w%yAY8z7DDxe-XK>^n6KqH}mDJR|g%TG;17IlJ~fLPTq< zhTxN!Pk~^QaQiisE17rg=VH{5eY*1Qek!!t54QjQr%t*aZ;ngwkFtm3=ar zR2%l#!mB8qE<_g9>AUJ7#8`t^rcT5_kSRf$dqV7qs?8mQu^n z02Bxq&iVsrhX)d*Bmi)P1?R|9uf55E0HV*7R+S-kq;H@{)cXXH-W;;uM+EMcez(+! zuSmiUaFZV9Cv^qp!0uF5&sr`wxjO`OCrOUBj-_I^Ymrr2D(b}ttWVzyvt6@YecJ~; zX`s~F#ML1&caKoE=64JVL8{Wc@+e}}6lzDir*x^;Do;4)_1zv@;MLNunM6v30H9FK zaqap4Vxn<~D^=Ws$zaqB7%CB8gd^8tNV_RvEp?qx_WJ6w_hv>n{L=+(s!;y^Fug*0 zGF_3;Y|#U^$D-YgDuq7afTY4}5hI5~|K~#pKBR)y8*&Q0mj1XkJW(mZa#wXz&1n^; zCYk6&NJMz32;^2}jDy2OSBBycCLH;%yEn(kJh`iO=3RWB!Uo=s>OU#tCROA@aJY=^ zgt(|nH#5EkV#2@@g3|cQHe_wwN)S>7j>G1$ITQ@bjG5B45*=$0~mox z%t6*a#}Xf8D127^zjoIfcWol%tiS4YGoEFv5dpRP#C#(hC^6>pup_)6eE5>)_1@Ym z-7XCe|L_?R;^(inh7(CThX6Iz=q(zg87v^{l`Q4L6Q=No&(`#3%*ktC=dpzVvHsQ3NHL=R9Jh%qof99G&TjBb{$5(yp zd!zWetJ7-mdx49yUuqlA3k9{Pm4`&Zv8f{zNIkjbK>g*R=JBQCMH{$Dvvk#|*Z6a> z>-NB)vGA&vB+GdSP>R&UA@0MW_Wf{k-GdeA=cA8n6)As%1u(!omckZM=EH1sD1c(& z4_JV4kA68{D8N$&)FU{p%`1S-tTXo;&S2u!#`K~3LgP{@KNrOh8|jV0tx`|+(?7f< zCk%MXnR#^n@HeP`DoC(<|E~KLfC4)f$Y*ECKsi}O z#p`S{`WZ4a;qr4@z4b6!)hh|I+nYDz!50db7%nSc-GpFXoxR+{YJ07U-sLe6(r~(@SNC)KF(j1GTtfk zzdF+{!R1+4q3L4N59bM%k!`;ul)bIHe;WcM)2CMO38FLBhG(F39{l4TEc*Pmwij() zx`Gm_?0r)KoVPM_jffJ#MS?@T{WnLnHG!8K{eh_2UKI3fL5JYg1-s}cw|1Y~;`?8M zhWAf#SIL&ka)NgWC6&>isF0m-N90uYn>$i&BV@zn@Un=I?<)C3x!B|40+||Ta6-_9 z2ft`h*cZfmkx1B4?^-2YU~PYWaS}wSdr)quVwpSXz{}x?VXM2)<>4Tv-g*iiYc7pX zYfSQ!NTt0B?m8I(^pAEC%;4?9WizM2j>7WO)Q2j-t^LpeQ&pQ_1NejJUiL{gh?uaO zHo;@6+P%mvi*x#1&mvL0*8(nGO!n`qf@>`(j{|23W zDf62rr?8%^YxdpKM8FiRMeF9*JP(euWRFlT2qz*$fG1L*_dP1>`paNZ{>xyM%_c1* zL$U^55m4=X=CnOC`ih8hl5w<^K zGiEfT5z7)o5TbAF_e!_fLTJW#m#XTm{C%=Igmf40+=KyiIKMH$UNR1&gy1&uT-`ls zz*@2FxtfY(7oOc&I1U{BKd(kQjK<*~V2hhH61~*A+ohtGGTTXjB8k3cIA6U5D}Mt( zt#4~@9E_DwT+kG6EC4~3H6dP2S-^TC*q zXX#qsDLF}LI&aOg9#I&=^upm^5NV0~_gw!c=19$705QkOsbWo?DjnNyG$(E2WRxkR zo*)7k^ojeGOF?E^xDoTsE$mFW*Nf{yOA3;o0^$vVzPvu<-Ge**S1fd_iV4~Up9amhJ=}&Z^xaXr#AD)pj|Uiez7T*%+UQ8 z^U+rqocs!TedcgF)V7lWYohh7k^YQveGK6HEohidkLaLH+dUW!beui^JRj}%@08gj zn>(r7U>!hIjuGca+#wA=q)6vJfw92Z(0a|0y}SESEtyl@b)svYO?x^VUSoORVzY^=9leL)mt&0pa4x&6 zfg>YsPFD1ZB;n_~y|6rk*mZ-OU7}>o+Z4-mU;ueyBL>L7LPX3&w0;uG>I4fCYm4Do zxuTlEBv!Llth@648E~_@BSItAFJQAeOlp`#0c&&_p506P%SI2ZGkmu8>j^d zJ7%$0-9zcyl%ge&_&Rg+c;afKgp|lAVlz*;cj@lyUF2rcn7y>FS=0OD&no$qu8bX| z!IZ&;-;XGf@2YqZF&vISf6qZN+|-}mVELQoQ)()uB?B(#09-*+vSe#60m zdR}pl$MqNe4;4?6@&GltyYYI^&7HU&QQQk}*mRQ42T*{n<_*>o46#%uZeWtzoFd+r zooYK>jqm>GVW{sC4s=HWNfSo-BCD#A>K6dll?$ESPY6L7d_DJo)>RVmdtM0f3FXUG%nBhS@T)j3AY zFZ0`)G#k45dMjf5FZZ#&K~TNeZ9w`YQ^wIqhs~XRSW>MPCOQx%%3x z{Eo!U@&?%NfX!DrN}jy4Nq6(YzCun*dlsjqogdX(u=P$)<7e7#i}g1e>(LL1Ka0D8 zPfG{HEbH=jIePt_>2fOi=%Lkm#HbWd{^Ko_VBtT1N42Xfo!F_hW~fH4>04G!O{lMK z=I=*6(JS#>(%` zlgoB%cP|3`%cB6mZ4`c94(zOIpRgxRKjBw8YaZWAJmvQaz+LS1{izejIiqgYu~#ud zmZ546aG9Cfjw4oOEc6Xoq40wKZS2+-KXuC4Qx{ZTdk^LSW z>-C*yMOPPjbRV^us;5S)H%U5e0n|p$+#*MzU7;`Rc$( z4vvLz)0M<^#L@@V3^D{tYnKR`_29`@sqlMaqk#gaf9Qpnf0)9*K~&*PF~i4Vu*t|y zfQbm8mn%q)RM7f?wz*}SQ5TG@D|9bHrqryl{N!H93mYet-Zw0ad#Ve+NTem9wzI<= zm=pZ_jVd^a*nr6QM;!fL-)JM8Xb2|Ltol(tq+<+ZmDtVmzwBZUxSWxyLfE0|Q;-`S z1Y*wNoN`_px&~z?wcwL+Qri!{zaU&+N13k=jB3tozyPAp>7+YJ0zX4Nqs1??(Q?>L z-pCnZeey^<+3X0$cfFXjSta}$>HS@=#XX!UL2s-qs z<948yB}wje{|*fdz6$Z$Xy2Yuwxf)ZbN0>-z>GJi#e#C*g-Psa@95iA1uAaKiuM+v zlVzVU?;1|V42Z)mAK)3^CPii&adyih$U5-qo3Am85GO+lI)S;5f>WXezLs}gN+N!s_d=uki@}S|1!jPoQ-GCtAFhVKd8ek+hwCXEeAorOI*LX zOYz5lF4XklZ^6qq2h92n#9&f9#Zj`-kj`cevj(1(+%3gE}xNaY9$8DL4K_Q*F&24(FauQ5Wu#iR^a=5uuF}g5r1$SUR$sw*Y6@ zt?C_81Kbzb^{dB<%8J-TX7+=FGXgwbF4I*u(lcM1CBM7&=k30U&0*9CJ=!XsNQg=-C^WWh5;TTm5 zk87yh-X7!KMv$muZ23)10`HV@G;G|-OL2Lt7xheCd(k&FCUEZ(jr#yDne<ZZgNS zrqYlnGBvM!Uir}&s_`P;7I7Pdo!WW`|gUe zE%Ith5JTvBV>8f~Xt?Y|hZGkvLlw<}gwUMDbOdLGioq7r>-X9&0E#OsCi}~^i`rVx z64m|u%nn#MkW-ljshu2X^uAi3cf$cAEdiJahztw1SjPDIZh3SsoS+iUc|wO1es~x2 zgPSj^EnT?cb%n39MFsE(qlBcd@}tz1ycvo>wY2myJhT1RhnE2g>l{-ajZ4+3iX6*!B*6mHZpKZr5rdPev_YNi!>#(YAJ} z3_%l7i#ZKM1T%3!utRAZGV6gBhOhot#UWR^%R-B6@JvCC|2zV*l9W{iryR#fGTfX5 z1;^w*9&-;y))6ejtOT_xU0u{xUHPSdc(_}}hoidyZ;LDZg<9OoEY*ew|O|_2-7k6PSfJ{wgau1B+$Is&TYP#1BIyzUH$yw9b%$1 zaL=xD14#Q?W<2qe6G}^ID;@2_L##jGT;pG~MBRf0y|ZgPF776J0Vyfud&Syx1-fuB zVF<|H^~}R1FVdsHy6-8{7WzPlKj~viaaZ4tZ*WUdTj3>1{k_*-9b-{MfM#MV$MS1v z`Sn&{4|52yqD)7(z(+1dVzMQ1%ZGY_Ar9dol@36$!2LHOVOYSi^ceSQ)d;F|!f2wJ zR?NGTMT{7lS@;o2l`Z6l_uf&_-<#7P4`S&}P4!-txfR8Ope}H-H*^VExP_NWV)p;W zb^O<+l_hIH?3$o7RvZ0wI?#MFy@WGSVaIIA*TC|josPh6i|Je_>V;r_|)qA8yT(ExJof-&8C^Aa`_k50aW98uB#$?<6 zA)n(gs;HrLF`p|5VY+n{Fwcc~|8UWM%&VCAQoPTl5I2Nc^sSLcVj|T#==$mmnX=Qm zO3`_pe~mzVTr#;=UMG(AYJVu^ne!m0IZoY~i9I;h_}}N@K6YaX0eC7-qs(a27#BpFI@1<^yGB!IpKBto`zc&V_k*?wLx|UqWiZT>iZ4U3f z-@Tg%06As>hJpS^`8%<2Apn{}#)l7h1Xs~rUsHgsDcD?qh9Nvwu-tg|_*L?H_(_a^ zC`FW65i99Faw3zTi+E#`#zhv&^a6(!pjyKv;1lZ+;nIHpMaOq&%8V-&vMr%% z5N{XEqRus*pWQxq*h*MUEgoRKwqkcXoWJDb-QD6z3~)@RQhw9_RVWUp=ywy}j#@a7 zCUEX>ml`>rl)lz|$fvZ9&H5~~4EMcxULAAmDr|1?E}IA!>X|$@1toqERo7!8USAPT zNh-JRLl1COCGn)-OE{o@I(F*q`K21#wS#b%!a@psE@B1;lLt0_SbrE=v zcc1gy--DB$^B^;>k%p`?*icLNYI_0f@$_^vH{C*QAvm}T_wyg^!FGZ)^_t?uLXPCt zMNVK72Bg}@b^@NbJ-hXN{<9!kKgPcQgdC2`O4Sz<;_@jm#_>;G0A*t)8(NGF*B2`C z;JQ10rkvN~U&Xn$2Hs6p(j=GFv-bVWj3yu($gS<=3iLx;de%gw0lU$vs;$Yr?1qHsvff#RH_x2AUN4Z@^sU9tW>mgEFiMZ>%bI2hE+ed#k z$E;ep=t_thel)`#ShO6^%7kifTD*;B8j)B~?yeYYxUrHHqKIY7m@Y_cv5 zo3}~sn0~e!4`=#Xcay#Q>sqgdBk+769k(}L9JRTjeqB1XwkoQ!5w6tzZdpKK= z_@5U5^!e66+mjC%F$WIL;=pE$Tw}%X$y^&WQq3CvYNY>(w>kSq24-lA7$7`m1^&PE zC$IWLby!U%QG%E(&?ALDydjOQ)^b3i&6aeXhR4+hEMW>xi^O?S@RZzfB55fE=Qb+w z`x>+{uDa37+r50Kmc=8UK-2T~yVMa#9X<`7gLy(q*DVoVJ>-#UD(ZOoHY}c+-35Jc zWA1;q(+XI>E;;MR*JK`-FCW{Xdh;K0bxcJnOv^M)wQUZp=;vO2js*zQU%TBHD#kdC z{Hzx8ar^aL?nZAbORu6@&EE6ywk#z^+$nbX;!*Bx>?hW@ehTm(6xU)C?vA)CZfsiA zG0opP>ksE2hdp4^x>&(C-zi$-W#Xhp2mM%&czWZ*{+ht4y1s;81z0K?yB+U}2l8g) z$BbD&g%}6p{q~R^qDUADelwd)l)2YpWcieNauBI-X7)POdBy9icn#mtcE_{Zm%Z1u z>4|XlZa}oh|K;s-W=~v906Qe;^Il3@7VLA!Dt5xq<@YKTed)m7j>^={pk1PaQig;B zk*8DXdXwL5RyW_KFDcMYXV%kK2{<15|5T?-%U6Njn>37Xer$B6uS>n1_>rDnk2*qt zh*;_G*WFGz&#!op)HNWlg3l>#2vt1N;Wl7gAc3zcKs1RJ`2sCV1;%K{PGi+_{hSbc z`&!~Ov4i@d1Uqy;#sgu%6lS*?s=V}dR;P6BW?QI4hS~UltUIn~uU#ocZ}u_^K-`gp zx|8N#y|alOfL|st`Nu-~;0CT^sK;wq%ZIo|IHm0!d=hpTg_!Py$8P8f}Wv1Qd6uy_a)iK@;%ul@` z_d?YW>m4BFzM$mqxu|DD{I)H^z8XZyn{IN>A%H}6coCfa=;Gpn5#D=Y60CuOf}#tC z4sJn`ve3=MEr5J?=)(FbN-WR9i{MZR2o*f-8v|JgG1XFTR`d6`b;leW4BWFXi2fEH964n`qHF)j~=L=B%3h@YZ}uOq>*x_1a^%CjwpyZ=QKA z>pwl{&SFTS$V)EV$3kR1#EW2W?nWaZKnAP1@8H;FODkwmk?WsDRMjhH9%#>hYyXs+ zTwQhbunew*h7H=3x`$R&HosXFtWsR~=yK8`qIvqN*wYIyx{P4Ad2b{q*@2KGYvx&D zcs0Po?sJ~ppa}=kf3uP`Xwvrx$PA=Oz>aCYs!#U`8btvX5bign{E#4)(D&Sox1!1B zBr-ekm5v4QBiM3ebHyCh$%aqwS%F+5@VeK%y|VI0VDh#!o|Sd)2A$%>)P%%H5%DYWTQQaU8|62VH-if=PcFM(9|9J_2 zN#?(41gPHj-Qb)ptD$_h!h#h5Rlpy?x{N1{NUdkBf^v9{oeCB1B&zS6Hvh?a0>KS(6s0l_Xow%NC#X=a8I6 zW^u^AEFUD`;|O}$E%xB!q*q>^f@B<%@bx$;PpQl3>#hBHzVBKPH`!|G0w0F^E5jy269VWv>0t_LHI)3NgHVKhDRwyQ9 zEXs4!6^wb87F6x)dEodE2^uTvwi}D z!K&0xW2K55hO8;B-lT<7aKR59e&;uj-F{HsZVwq`pPfOl+iUp?N4TFpMXO?CFfbwV zuXp%KNQ4EIad)Y1!)6WEn{2aL+`!Z?BS3B8>rWSI5)(V&IyLClh$?^jp4NETa9la@ zxc@VxS}*H?>LWAV&IDRQ6&%y)R@LIf1OK16^t@;_W#yi$w(RU|ol`9f(_qiG$4Aeg zE+r&U`sAmF2bU91?f_8=UoZ_;&E%x(>wR8}B4+QTom1YhgfU2pw#PE6s&Qjq-uLc_ zas`idn>3jBb@%1!G;L}*I4f6PD#O0qVtQC`lW=QgJv|sio6WKJqK~F=n#WNoka0CS zomxp7HvW5r9F(+S|GWbi{&rKj8ajP=XbFOchmV!IFyuKj`f^a;?eNz5b>Gio;V|lF z-7^#_;w=^ftI7GCk?gyWv-bt1QC{q9Y!>xNdc+vvWQ>!2@#=r16!i4g=cEOTX$&d; z@y5j#9mp$=aWf`5-*BTJj@?%9=%|wH8R&X#=rk)~V+^VMObj*-UROq)Hw}l9+7oNi zP@fMfM3dg(BtaH<+YU<~x;Bf+*Eyk6{be4luHW?X8!kOUzRAz2_MUj*2X>fUPSwWm z5*~nO1yYyXaeiL>2;OMj$ZQ=or;fFRzN3TS*Y8b7K>KV+M~sK(%zz(P*d$cv%NhL{ z4B$8j`-9n!I&!ItJW89%M8d03PJVvW<@8`mzq^a6pPNGTz1WpUp~6a9xHw%Q{W_V| z$mT(maHYklVyeVN3; z;C$l<*mYgsycIZ;s7w~t1<*j9D%9h#qHd45o|Kro6CZomlj_vCSxE|!7L2iXj@YOr zb^4durrqXGT;3Xp5jWJot!>fNQs?KeALz@joKMUMuz%z`KlXj3OK^MpM1;IMjlr1T z?57P}ReYg5^T6u}SJ;CGspacb#%YVDv=&ohnb>wBaPfJY>I`N<+kb?})gCBa&NHEA+TJ^PoZ!8qu8_*t_5?-fK*cOonh; z@s2n+7XH6r8gM3eyo)Y={8p)hC=5;(GUu#4%3#c>an~_u5&$U_2Eud^fxl%~v=u{C ziv|E=EW``!89Qcxvm6@MeErLIV+jxuTK!feb6yr?J)C`K?IDBtI?8xYN@fIpP4MYr zCg7!Jvjy{s%yqPQ{51yQiy9|NZnR77)m`vKhgn=7@uNn{5DGu1+1>lIp|4|d^Cy7Cg4u?p_!91yP~<4ZPT*)d zfU7tT(e2DQW!oSSQ?+SjTQsI0M4ZU(NA0^MQk*_yRYR??=}qCY`Ct?qk&`>vq%iW% zCjSwt+k=Gg=oAAGvY39!qW)!KTSkEwenMGW_G$J9jxhlQf9VC(wmGu&ad${_bQKI> zsK{*r_pZ|}-xHR;qBfzAzsYtQIl$NJAGRag)P7(maJ%^osMQ87WODJMF1IzFC46kD zTmJFzvR{v!c;TApU5b z>1bh^ez2ly>hVGqNivuKAKSPnxmJ`~^J32?k`OEwX6S@LZ>l4JgR`bPlvwj~(Em_L zy63nCa^VTx3D3jqpd9N>mxgSz){M z75)*%gnt?fbV#B5tw0p~_fH@qf&H>ME>qRw; zb&`i2R+mbf8_OF%nNapjj7lS~a-RG9(;&W$cB;bI)_+oBKoX95&M0=+nY zU55q~VDsZj-YTYpK8I>%-pR(JB;!cZo%+_w2(;Q|4g=PUvIc?`UuvMLc#=r0Le+Lu zoweVTiEDXW$ThZIXQ!pl&;P0;+pMt^ZKsFg4NhxrXu4OwMniv)8n+upPf-i47pfq#gj`vFNhvUD+L`uQZfD&Mo(3=6pE zxz29kz*YV5-$#S<+oR=3rZNbDE$VP01+kw`ERu+?4t`p>=VKZ1%x@Ipo$ocz5xFwe zch8GuVku~gq1S+6hHqyfetXNG@|oO2RDlC=8p=OL>f%)Z`!=SUuKR7MP=q3@(M!^6 zZNsiI0~3lxUw3iN+Sv_vDT8Of@=XO<1^sK`;n`{%CGd`7Y7ZsPyeYsXAHH*S|LNQV zN##uZ<^;h$u z9ij%7#Il3o=ChZr56~K)qRL2$kb@LdHkDLKNQ1R#L<=?rn(h&ZZZ_ozJG>bKu zz(e4L_)jc9gfyrBp~k_A)RQvl58<#0wbi{usV{KGm9X$C6IN-eeM;c}DO!7O0~Yhc zxTfqR(2|jJYdG@x!+vU z-e}vmWG`s{*>)R4nstzYQG(b+W!uJBKF8sg-7zudHQr=8opU02GiP+1&F=TD-U(2J zTY(N5w-!pixf8Q*1Fnw{f&h8~{oXCQyv4a{C~!QeF~{*R$l((e4gA11YjIs~>SDWo zUdr^cbP`nL7>O@qn|S%j%wWrkFn?@gWqecEe+eXYBd+T~8qBRkkRXNzl9Yoxs2^)- z@nrK7qe_4TnOdzmb%`=ft&szlwD z_L=8MYc}F+T;@yX!g5x==WwCshnjNZTTrO7U^)cx4r>Fkk6?=5Wij;fGfah7ilrY8 zrGt>h_bHzv1}#>4VNnQ!F+=j_|V$!kGUCWL>3NI!0~PT zuR5L?Rph)+1+-%S4wfKhxGJ;|NyzPEj}m7_eB;epS~_x41=b8Bt?`@5MV5xob~bp3 zz;hKYd>TKN!wk*aM}fN8ue-1*eZfTD^01aTYZnVLTOq&b!mCJ_;-&&Q16V=GIqVH3 z0T!vJCaoXqO|x}p?o7SCm7eV^p2zJrp(N=plOCCk;w2f!wb@Aoq+x%SablI1ewb?F zIR*<@U;h(#gw%iyXUCHA=_RizjNA<~*-Gaqmk8mg%-PLX1?1F(KxihoPn~j70gCC}Q&u;KrdhwxMIqw*WNrgwtuVG>~_Je@TwDs%SE(U1##u zWZTnjHc{GT`JS~Nh5U8GT7AtP{Db}`r<-e~g)ptWMqoq>vOxBq^|caNgHRYfT&yRa z)y`OP(_5i!fFksogD>?d+pVLiC4Skw3dw%|IH* z6?dA)6!3rHZxLc*{B&mCg8W71+uhRLW2z->xV{k~M=PCzcVxPvwdlbgu&$g8EMx#r zCo+U(e)_u2Lv$kjo<*mf4)}Y~YJgdN!0PfjeCSe~P;ob4F3_0R*vCiX%_6|RaS>!x zP(wq*!p6!ToM7J;&Ve>^$%md#I0U4r-zstrQq~$)3ECn%+g(RyrLRkStowI?v3xQn zq@Uv=-<)o~32_Y+kdy9sc&@uQGzfl(Mv!xSU9NdXqz1OnjpY9{riPTt6=LuH=Wbj6 zFYH-?GfDj>`$^ZrCFuNcYBcgm>53ifovgEW3@1p+_R>Ayvf$($4he$vY0K!4OoqR? z(`5F!Z(NaE%vkvNJ;txaqTBzz=VM6!NHv78fr-+nHVqE+W3kriP*O%hr9|3;qQSz(%l;k089^Egq8I$E4;o3TSH2g^SA-7j2(V(*Wi^ANl9`7 zutP}e4wjD#yaAV@b!P^=K;S1!(=>)8QBpWJh?-t4^_yu3!Dlx@9BlN%Gty&7fmGc# z>pcmaQxWg%!WOUrHRePywl)k!IHF09px-KQ?ql9PF{%q;Nqxxi>vHu8;xx`8>_H-P zH~$7v#L%s@EI?8^q9wKdZ4yzS1k44#)w_S3%3hye>|5*|4RqX9er1o1nkSDVBB8Ue zP3-EYT@QYX)PKBv-0#SG*rAN^tvM)KD+-|SXP zj*|v!NvwJ4amo?QElgCs?_76->dPP3R#xXpS8us5unU0)>teac?lUxyYMzv>u6;SQ zG#u1dfs_aNzwE8r&}H-Vs~3FrSpT|pSG0=+pv<4|<@VlpfL+~2hFs~w2$lxKWyV(= z4s^^!N`fA25ar=BoqL+x2%^R$Z!jjN&9vUyc>XDK-u<=W+E?Bq%DZ@lbS(w7r+m_K z_T`U(_M`8ZDLAJdthAF_@8Yl_ZLkNJ)!EyD=V^)gAN6S`A`GYz&Vyo5*S)Cxz@@Y zWjM}o++TI!Zzv97Wyec+4LPkJi7X||DbeSIb5o?AH%KmN$R(<#K4xj(P(Jz8vlYzU z$gN-Lto7Ml-i8@VeGMR=YD-^e;kW`m)M=xh%E6E+2cicvPxyI7LUq-Z`N`-@<~ElZ z-X_5x4)qU8KMBp>cIUMqD*X(ytfQ8V#hiyQfw5?+|2S*D?7Hx?1Rbd}`UN>Xy4q-~fsF!UvmdlSq~ff)EnNgs01r zhP~jBtrGlBm|%cCU;Z)*{zqe;+CTv?Ge$L@)l~u*TL<6@Zs`9$!p*e$M>v614IK+g zXlAeAOrWjsyIgehQE4^msRwWCA9uAl#&Qdlx0e@`JVJLn0Eir>*W1!B!ps8Iava(= zazx0dU3pltblk(>UqLqqlq4EkYY_sCzB9)9oo7MR30J9M9 zpL(5KE%X=>srti>>S%5G^HIY!rBj4%JJyBXc*yC#@GIG+gR5Q=3KU7hV2F8Guiec4`l5M0(2w0@<EvB=fZ_AmIu7yFjY4H$`K9JwlmV2HuPSHfWZ+ zM}*LZS6>4aL|GHL@#t}9bYAVhfu)3%6EKr!B2jMC>4x-0+~LWT*Y5?>v~sR}_-0 zg198@Hkv-B^11m|zf7u>7U8;J4uyAjL7%q;Ov~0!b04tNoP)SxOA0#I+uJjuU{5{9 zOiiU=fB)y*?x~F^?1RtE{TP~W9n+N&4CwnIPOWo)Sko{6qK?%JeY1yzzX)^Ks-Neo zh*Xe3BFpQonjwS*2FJt zMVb0C8wF1;h34nauI?Z@a~G@M@SI_69);kSCN($~9f3KMufOayGKxMk#6t;g{en)6 znVu;-D(S`)4l&MK*HBf${DgbF$G6=R6!_9siaWcv2^bTZOM1;viA^N3!}5#K8`r}J z!;N6)Y~RUE?#UxmK)DZAtdQ(vZO`Gjn?>5RegZa#a+Kn-s?g6z#oFVUzZWnfKIKBaV_<|^EqQvxnPVJro(I((EM;kIao zzmYM&3nPB;C4F*#ka~!)zA&{oLz_;XV{LEqe2V)HtQs9`^tcJIq8x8HCC0$op@cqQ zU`<+c+L^h2Gy-vIYrE%D@81|UeuqEo8Q;*4Yp(v&#JMNgZ>MF6A;2yMefwR;6SvEE zeS@!_6=*`66vrYY_{#6G(8e_3XGNt}#gXQ_=y5pJr<~cmQv9>sI1n>N-}c#~l;Tap z#kQ3obUDT6X_g^d6KDGZIkIWbFT`wm?5IE@(*D7eN%&*2II~T04Y+Ywl`j^jR2wsfg{P9{*kB zaxs)YlI$)Tfxc>)-Ra>M;@PWN)>1zn>p7;!q4c7Tx4V!D`b?MY*__LUtnc^5@9*eR z&R1(R??wqZ%uOzHheQYrNk0GR=2($ytMMENU@eotl0Ojdz_)g&jTXi2`s_;$n|kt1 zW9teY{V2=6W>a*2~sL;80z)PBZUlB0eH*YWvG#>r{9>~3C$ z&l4-CxGK)~)+j&oV_kTudg~STprmEq>DYDDajFrmXUrs)Ox{dw5BtcPa*yOsZbSD+ ze>9MUX*|$tbRB7gw}@vmt-)@Nh) zjBY04r4}iThLHb4w)UNXGx03{Ky8G3;mB{@tA5(l(|P*i3#tprDWmXi*>5u(ns$(k zE7?hBKjqp76h)~0HFPRXV+oqjx10#Q0tdzX>pE2DIE~E)D$sNEh)x7Uhdwt%ePFji zXUj6DyxwxW^9;J!omQX`(cA)i`FzGYKV_CzOsIPnD`Qf?`ea z&hxi5ZeiNzHz3Ax%=m3n!T2xC_*G0rqLo{OWbCcX??oY0D-(Boi3@s|Y@#hnhZ!5>E*!H8;VW zk7lvLC1_IQmC$wsa$L~qQ%f))azHw?5V;9?oS^Z@bMds2Z+(Fjpo~cEMu<+LI+&>3 zKquyS&#qqWg*X69*>H3z{eT7P9O>npnoME7FeQOnGH^={1N3yPe<&*+dW*yMi48j^ z=GvScU=SN_$%(N;a4gh7itwFO^YuCm=4u-;vHg)u(XE5uWa`lMiY-_yz-sFC2#}EL09MT)aG+}`%llf%jJHr*IHNQ2Zx~)F?yYe zKC6NmtL7Y8QDXjlJtn^l*_gT2{+iuy0@P%tbf@N4B2!cQti^tPTvV&l%s zIK^`_r$a$oKsM1{{RyYdX<;8*mCPu^K&$_Hn-=IOt?r&x^o<2E#^ik(2h(Cx;|5;0 zb=_m$PCgHfUzH*r?h_L)&ID$%J^93bEQGP#bC+tKjcfm8FPPWW@w*5clcX&S znM@olqy*QD$#}jHZ*p?T&S_9W_&2-Bo%t8s7-R$=@v!QBIKB7PS2SB`Et7VnUt2os zTETqt74Pu-v7=+8|M*;T_Qt+bt-jxuretpIMcQZDTrhW{)j&r=`pN*Mh(P(H@XWu; z`6vs%c_+P%@7P|)zC(>-SzS*9Fb`k?V7 zf-_iiW0JEYs<4BuMY$(SS-;phloW2BzLl3CSN)Dpwe!T=ef-+?GB-#GXPde?!^t3WV>;mF$oOy+eefuNfq*jBoyhJ#D8ag(? z9~R=i65$%YFeQ3jkFk4>g=38IA1ZSjSK|``{9aaH{U`FWpBn_|2tgF6Quk-zg<(@Q z!ffT|T%hewK*3^3-=(zBAClbmnKZ*=B>y@n6=c8~JGMbhxz1~QtlFrwruQ>R#&-Fc z?LrIocR+UjV5Qr*on%CQv9oc#sZ^7HrzTQ3}9){$gA2=%Q&f7eMcpf4- z@lc(cvXt}S+9BQ|qp|9{M!pTZ+!uILLywbmD_3(JS`M^GnxkVmD>(Y^?o~f!2~)(O zXD6Te2RH2R8sO{2g`9S?3%0hy`1uW~UKLMTf|^(P`~WUr$4CkuyW{2^*I&jb2Q~OF zlUUvW zYa5W)D3{j8=>hN7+-X(NuvveN@{PLI#=MN&yWZO2O;KJpgMoN8ICf`WdEu0*afTim zB%B_|kf=d&jVDgfWajMdA2aZ(vNk$46MBHsf5(1|lUrLao7Omgn#$W0^Vd}HZ5k=- z(ea9hrYcdY+;U15Sr+@B9^dZ5VQ9iK{?-%bn?eLafyb!H8N<6YDC7MYBE81RJq9`! znLd@)IchM0hn%64s+owz$dwBa2#XszW0T$n2RfGMqPLJsf)lt_?PYGnA$q&L_Cl{~ zZ8J#{H}|Nu0ejf>)k?>1CLJRh+N&q+m4@x`A{ktA#tMV2|Db@|Mc%h|&*R9aqq0vM z;5%j4FlbW0)MCfuaBp993mj-UA2{SFpSuEI_$0KI8Uu#s z+8$ZWz(!^J_r;h3@4TH`5<` z8u7+HmlwnMTL3e2&ef;x*ogTo*k z41M(o=oZXIq4ZWGL6_Jl2l%nK=CBlud%|ZnrBSgwN_Uy&KwXf@4gVd-Kce-Ir+8H_ zY|x(Im%a6ftB0|~#N|-4JnTnq&rz{}%JB`qVrDOAg$;v(v!&%gWwr;cbW1_r&58T- zFH#`*2;_KlRL~EMrSzWU_N3dRGrEYnySwn=Me23KtUeuHD_b}4 zAZdP*IRy%A*YWn*Hb0-*oBGzhTKOw+=o`cRj8Zy#N(n;rJpsT3ny^CK+K&3-s#7#Y z({`GFXcwUM8Z)`43J6t1;-y;FHV`0S*ikXglKbbeUBJ)hPPJn*IvI&_fZ`eNJBkb1 zGpz9Pf)_vausLu-qLtkLFK+BVSdZ) zcD*hX;SX=r7rJPCM*b;^f{7x{$n*O_xSb3e1(TREYk!i4aTM54T~zQuLfn(9X?uD6 zX&|%J%brLIYwzhI#>dRu?Az7wDeuIU7v$PD1KAHO75kMc!YGf|GLM_FZCkq`Jgd4E zFH=VxZDowFQE-y6$0#}%w?5k665mLcMr6|x1i!MDTzXM*YKpb*$@OH)z?ICx6;|uz zL2jT?B06xCCe0~eKn4gBp{F4`Ok=`B2fmX`^q4BJ&eapp-XM+^N)l%2myCfR9BjVG zU+_mMp+wtRRhZENeJ(H2a6D5RrzcJUr|F3%Po1%Jb^}4lIsib^ZjalYI${Hj=nDP>Nv+x=_06Mg#dcq z9%&j1gEu&V0HMXv4Y8UX;TO^%kakMP#=w8fUjxjmk zC{X_>{j^SV#0r$8$0TfQL9cRZM{WqrI@BKI(Dpr5DM zcc`u1O5^&nE9){1MKmO;Q;+Q&y2xL?8e_uXGWba*_*SGq>_L3Fa<%O>x|Ms)|v%O)2u+3)B#MF-9-&4VgG#k{44i7DCZsgSuQ`tOd2Mr3= z^752gg1dxj=GfEMY2`VJSQn`((kk=ZeH63xP>0M(2x8LN=RELwY85!%HtLZO4GfE= zujjo68Bk$W>9hNMEtZEeMjGBxyl`7M?Pz+w>lyp?o^3!=_XN9!CW~TzsgAd4&ybe`iq{`;?*F6dtAnC^ zyswuAk?!tLx}{UP5s(Jy?vAAdk(BQ4Sfo3oySuwXy5W8Ne1Gp>JHyN_JNw)`=iGZk zedeuSzufup!?#Th7Y@S|k?b!y5Bn#z_1O5&RMfVnWWC(n2%9Iq{x+XH0#9AZCBa4L ztsWnK*P{fVv^iY)7YLdPwTx9Aeqyf2eSzT*H#weA&iWC9&|1o`*iUU%MU;f7j7G%L z*E)!F?ZXMLc*wAUEg;mk4Gr=)iB+MqwZf_tD-KVcTccOt@%(85_!3FT$AX`{xR;)| zW?Ox4$fsM?-^PA~n9c7}>A@SX3b52T*85#W7ckwNe=?j(jd%rwt}u|KM|M+PO}aRkH11YtGDH8D)zI1k|~tWQUb>a0E=eZzZa+)#>}b zGvsqtBc5Gg9c;YOt(cJHC2XJRI#pBEA&tJ2{~Z$%_(4TuAC_Ep~kq=@EOK zA6L3}n_oOFvgcmm`1pEuvy;B|sxZ|FEUG7aj1i5&{`<1%OROLklmWbXsDh(`L2iL; z{fFhI2;OFDkPVus1Anyem}cTj5?D?iDbwN5h$-GlDWP~ruJSdrg1*F_aW2ACXS8DF0MZYi8G5kZO61h~XLmX2L@lIyhhiI3)PFhM7iaLszK z2MXpCrGQ7nR#;uXmzGeGh^Z7dz7 z=2l3@Z~&UIxMV;ru}NCq{G1CuC872_nN^Z!>uA&BbHSASo_Tpw?9X6@p?SI)nnK1P z9l9>Ly%-f!2h@MzX~L*jwKQeBB&elB(Ak1Rk<|OLVFNwNbI_@`-jrKW<0hJ1qyb8= z1r62Y=V;cltLI)?+#1o7@OND#5K>aye7_F^HZq8uhOgpcT3;BK!%8%NP^})<%#1Q% ztNp8@qDsxDLns;At)n`Jx5W1hcUa#Y#D*P9$zQ#~!jyWD{iw+52?aqWE8Hd;6JkQ3 zg(2**L}C;Q(I0~LrZ)=@_NBM?ZEnA!`#0WDPcX)k-SX(zXgBsggHcuEfi>{e0v) z*snoHn(DAOJidLW%P8Bf9qUy`?W%WKWzO;ztD;ZbfC}=saW*Z zw+B9jmvl67w?0;;5XG;tG%=bV;+8lrV6^jwdgZ$MJa%GePgl6Bt$KSwGzflncO69Y zZeR9gmNvuQo4q!99MFPEF-}nRy;!#vS%m6+KmzC%CXnWFrq}_JIk&yMf;EnILWwBg zS#08CgZW8epGQAAZ_UnJri({tssABkbu}7@$u?`1kTP9}JAs6^)*j`3F!*q$Ni!fb zwtm&Nsjkre2RK}Nck6h`{V{KE?|j7=Unhg^QG&5hA()kr&pGp*^UvYq<6DQn0(C$1 zKoDNoMz={~=p3zyiRL;PspI%cA{QV*3L@(Qsaz1CV>JZK>}KW&$6Et#59bY6es@-yQ?~d-$W*1 zv2Syn!Flt}a`eM=9^r!&b;qc77%JSx*x=aGBss)ro0e?gSA@3F$Qk zo&Iqa+j6x1T`lR*&U$go|8ze4Tc^`XyG3~a@iqOJ_K8ZwrCdIVCge#p=vR)Ay?kW3 z{p*1u@l$iMGvj@sb;RSx9R>Sk4k-$$cU#lLD_1Ere&wZYt@eL=jy_XoV}C!cGPdxK z)tRf|PJqQ&!sN@#ODEHc+$AED@t}MRl-I%I=yblwovu>|UmLv!Pnyi-W@jHi;X?=f zy%#esFE#>Cn#7ttDVz70`wAT&*GVm;pOk;OBd}Mnggo$bV{klH`re>b^f|0|Q{Nmd zzV>FSAX7d->x;q@jp4Jky^S=`ChjC8>?I|vF(5>Ezd!bKI(|r2yLd#!>Al<`vebLJ zPi@UD%P@6{GOFJj0=5$15cc-pA;VAZe)UBS3*njHfrajdln?8^RIJqtaR_t;RjizT zzLDJJmft(I*}%>}ADe7O*a*l@`)9d8aNzzCmkE@B?@tEXwZ~Ly(hnE2qCHknvGly1 zGp+&aFkR4X6F6xQ+;8)-rBXvanxDby&InASbdx0)I4kB{BKp7^vB;9G7tdMGigkzl z_W*BWa&-Ez>HMqv@VOKO;^(+UrC7R|D3DJ|F-3d<6X6oU6(%i_4j87K&zEmnP%Yp2 zJgkJv7v3eScSxCygD{f`mlD5NTDQx6@?hm@z7R(zKSgm)?`}qcULnz7CZ5LsntcGoQ)Ij_R%{5WrtQC zPms138OwY0W7ULH>-+p}I?rbrwb+7|Fw2FCdrV)kisAVogrTFt*ns}H`K0Jou5c=W zgQ)#IH+6`Uw}pnpZr_|L-jP4PtZa3}M%z~p@GxGLnPSV4HJ4%PE?UK6$mEH>@86!p z8ZD$I(YCVDimwVn>#tWT<{xpVu^DYfy(8bXISpLD#EL;y?q6>QQeas3-^MyL@q%MA z$|5ruo<>)^<5hM~j-~7$g1s)j)84kNo-S_mnw0`;6tZ(9L9YP{*_vkvTlsXHSf5LW zR<$(klFY*&x(-w|)WxJPgb_23R%+X%#7#zg>3m}tq}IdFVFt=S!~w-U^kx7$kgG(< zL7*Lm;rXIWLwO{nPm2{w6+$p1&CSx~SMjDG@@$W?u02*bUGGJglx;x^=SQtJt-NU; z&!=^+HoV%tDWqVx!23knj>YCE85uC|q3Rb|;~-bgP_ z@xD9M(I(6S+$-qE2P4EKYV^+nL$Zj$s6$PwNy)~+6B{K)jb1d~snud;9Lt7UC{8uk zXQ5SZ*fRnnV=R)*_*7_ZHIcn{aQpociqTM50QdVK@9+Jo6N%-_quNiCSEdJ5I+Ex zo_NdJEP8^)$pUg}5N7uQC2(GvA_w$6$RGXkc(K5aM~cP!4#K=BV(i zpd6i(;h(MB+bDJDhp5=OouFAHsH*E1)W*uBzG_YwmIOFJ;rq2$9Z^peLa)1U3kOWF zsxPI+4?U~8x+Z84s3m$bzTq}-(seVALn&NO0PBRlV5>uea4$h13gVrvp;anTi#94C z+y_j^$E5y?_aM;kqgx@nszFQAml3$Stm*q7cfRLvR2kxw#L(b_wwMn$rRKlJS?xkb z3$}K|(c%Q8$X!^+R%DH%;C$9P4J-FjwF;Co)*LsdUXS@IHrcrN+1%$F5SUNewAEhF zX^gYBzmW9d`|-Mz1dO5h*PR&ry!VS{hhs;`M7FlVJU*8tcj04=WH~XdJA>(dv9YZL zjhcCo*A!_pb?3>J@cD3UPG!jJ+GnVN`~?>^6ZN0+5<}M_=zIpj*K`ydFid()r--1B z4=$xCXXQ8XfYlVY487 zlU`oImKE5#HrUdWKnmB-6JxR&CAgZ2LauXv2;1_l>6&d$y66Q`NVzrtVcF4pN_50% z0$?RDh@8VZHjdQT$EzQ%??kXRRL~qv@{AN?#a#!?^!Fm!<|4k(1R?^fz6<8KSI7;|cL9-Pl+ZHm2M1s#% zAsFLr3{*PyXT>$>(QSc7Prben4=f#+5OIQRxE(9#*pKZqhx}`GK!tt2~7&} z_^PS>gV!J-pZuTm)(TMn7p^xP&PL^W2O|y{tGvd{lLs^PRi6@aY7{atFJRAM6}%oE zB@1`Mm!DcqQAHE&Mq9L@b6)cD4r{8)kDD8^bf(qRuZyiYD?j@rV`iX&fUMHqZgjC5 zGzs>7jBfv#qs)j%w6@AIpg@hGwUBy8g0;7$a}K}gZ>JCR^GI6op37=HYU6!{C`cEc z12~5wNrT!xSXv~JP!}*&V=;5lvnq%#pw^I3bi|BcpmoRHPh{WQDA~;B8_JZ#G~upv z)lk*T%e?BXPtSD%C37&Ij1v5EeH!2zsvDHLGcl-`6`E*2_FgSq2xls_o{@SRouQp4 zhpKpsH->{dTKY8&)8bj-WtaYCLe5ZzS_F>-}?~q4dt}!J+gd+=z7lI;tEPWz&;< z9z34Ii=DMqvCjn*ZObdN`(#5hH@GNPLn9gJVe%{BZ&}mJdC9oeQgYtzEj!1SuKZ7T zrFQ{kcf}KJ!o=L3?~GfGXV)&mrJ(&=GOFU*8z~oBJOe+@&_iFf#xQpqT5mbdYEygH zY0=WTe8JY9jkWGrFB$N3&@{$*wZYth=ya%T?ja;3fB~6cdJ<;0|!yFS%;Y45ili z&KA*TY7fgDx5ED_>sUwuGpe`sHp21^sTW9j-U34W^L79D1?=1Gd|Cz;Sl=GKUKn|# z3;JyQ4WTQCfVuiigkFZb%H^@Gj{5&IHlay$Dalz%^e|F0ZS9kd61rqrw@K)2fYODxW85U>R z4cCBG(po*X^sf3`qFZHgGv-`%s4Q;8OO|xy$lC#g6M05m&iZvAdvGO%>PMGd_|<-4 zJUQKm2URMP*qAXEy>;EchWCHx*)HJ6OjNzN#Zh{B?HhT?61>_}qv{Vu0PKqx1VU;2 z;Y%=%N6Y!rXMbQb*B@l*VbY4gvIz0pDD3=-c9XjBZP`vm=$lvuxA*IQg-R4#V!fU&DUtpan8jtjctRwSA$l->V;E3Rh zeUyZ#nTn_+2I$k`*T3|)XADJ+&ng-=ghI*SL6rJF^$!0%jjK_m0}Bg_ReqiZ?kVwe z`ug{Ex7Ufh9@3|o7X*B(dFw&_0zV@7Nk{p@$IUH?b=rQ?5d#Z zmXnqCl7i2D7$9aLeL%IBG@n~q?zN2PO}K7Qpls`5eN~!AHZxDbK0`;DKiu>+Xujij z>(P_o{(2UF?nP23U*0upkM)c}sfy(q!Hw3+11t#f=v>&xuIKk+(1<^R!+MFDGpzJG z)Buo!mClzv61Qoqfe|P-MS{loZ6+)f%evnDJzS1{!V2bBJHK@hTAL@(1h*xVa6oC4S{wI*UY9h*iIn!f0JAXItgn zFQhQUYUes56KcitM%GgTpe0~Ur**m;?LYKxNHKn=f`JKjpNFSpWjnUb_xA6h^ePARy>2E)){Y*;kJ$7X78jKZfp}nq3wf> zW<@57$v^7#a2=DZN{IwwkLty#oXiUBOXC$%l>V&`>8^=!rjCtrlf5Lc*rW6L zEdJ!P?#=WIJc(XadNsd@$rPgNAAjNKb>D&=C>~dAF(4V#2q3(7f47%s=0|19?sO+c z3Ub-zJA@P6@s%S-N2~z-GvHuFg_P#hvWt$OkNKMC$eH^t+Q?gVH)l$45T- z^YNz1(J9}b-s_Kjn26C_7X)|ukB;^Qxsr@K$PXtdlD`|`+DtiSzQFsF7@Q>}3F)dV z5cL~x=V1mRz|G8Ww$X)3745Kxae1@!M4nC8MR#}`vt%pm<75AABjTX9I#>Va{Aaqd z?am(_?sj9>S?-q>Xby?0j_?DO27C!~;__w>xWSib#+Uc4MZ%d#r3&fH*qEPdm%P}K zha1i0wMn=0-!6b=ht$hQ>8p$Q{Yghlx-H|3ww_FZC0$lcR=ZF4Ff zo=%?pqqL%BbWzA%bxr)5>@X4f4i3hY0GoOSLa^c4-%K)lA<6yl;)#!qWIrEXCw+%~ z`V>yqPbbX_m3PXaTH5)GdDL>JcX1$lnB7|C3N$KNqtKBTO2*xP?04eQhtj#R|e2< zto;*8=VwNjxwJ{}B>p_#&HSo!+=`dlk9a}~7g#@Gr`_|K2Ah#l{WRRoO~ad|W-tQ| z7f3m8Zmzgaz9A=vxWZeHR^tR#WWa{+uK?MR)8|E+CqIF2m)N~|X02?0D$SGR5bUY- z6jU^3<9DjsYlVehpN_G}W_c=t5>9461Zmjum+-KKpz*vk821XuS#Ddm4Opsb}0OhWiEa8*{lJs1GHF zrBBn)Q8Eko75A#4O$z!lUo0&A*8t)frlQI9xk&Z^yPM>*pQUL<34O4BCcB05?}o^x zl)7@2bs~lQaE>Q2q=(jkKl%}2=n;oltc6N#f8>KhJad&gqo0`Lzi4cKgR17i7A>wW zel{6q18Y8k&_bm$Jr~mdL;#{H2x%Fy&|$kM9+aLDwZD=F_0~|$&-0>Tm`-IWZb;YC zfXtf%TOiPWl28I*JDg9Z^h7?1isYH!WBBL9dg8l|m|`PA%GOXOQy>9 ziK~iBu^{o)-gz0T2XuaJfK4d|&mUlggtE{oE{(2Xz7HKu2Mh(|1FT_gHo;WGMbUiu zL5&5K!gKc>F`A7){d?NfxP#7~JHch_+ksbe17g90sXQ($uJUVCuNY~OVH|la7oIp^ zAsM(^^gv;qkt7@GWIkuwSBsUR z&QKcX{4??SxA$ujzwAdGT5rjaN8r{aO(IwdFS>h{$l=DT*@HL{4AWTUX*pDyN@nn? z&^uY|V>hd&Q^ z*!vg5JaIlJfHti9C7W>6zm#xlV^O6+ug}xFW-|AxAE!QG2KT3P$w;{nQ$X051rr~m zt#-Lsfo0PmAN?PplNGds`E=-r!E*usutu@{-Scv}cTw~&YHJu`rHj{}_dHOvm-iQw zo~1etgn#OA9aXmMrygU%iV;_GT$UDDp}I^}%Br$G|GD(W*=V&Bzi^xKv|R66Q54HX>;b~U=|haTj_ zLNc^(x?;m%EUS2p?6 zVOkIeam5oz4$nLc#~AY;%kXEzY>G$a7!VU8Pve6!-{_U7jaF@>n-Pnp@RTW%^Y?$m zkg>d+m!K7bTlXsn>=k_$HdngL@we}bC4fv&v&9Kb<+~n1ey9rBK+&*)*K>n;LCW~a z(E9=JXDt5o{K{d_*))2s8&lrMGVcf*)6UK1i1D}zYNs;?TL{ogAdaH^uQrgaQ>^27 z!Zg-KHdRY&(e@$J$$HQWVLZX)9r_+AtWZAEqAJF%jug$KiCMFCzQ*DCQ%VKH@Hd@ zohxcz6A+T5#t5?kWsyIXuDs?qS8O04CBSqL8%I5ey`H!OcXS;xuHyv}1CJuCKUi6= z9?J}T6yet1I2jv1TzXq%dtn^u8IL@4-zw`(TlaKYocWO>=o1?9*ed9J(5{4g_Hc0M zcgL{Uz>Y2UM_=*mRs-!!)4rXteWlm_frlx=;y#`OhkXu3FGuYi##KeASz-BFhE6JOAzvxO{&A;)(U6g+p=frXu^T?> z2P2J9rj5%7OztcA5C8hdqoDu1n{M~3W2^wneM?X7#gC@gni z``q2<58TMOcOF;IXT$3qzm57!ed{EHqt9`8y~(Yuza7TPd~JW1Bp6i2!!~*)%r0B_ zW;fZq5+A}&m~}Aidf92{rcrU$@c1U*y;LX3*B2Sz(&3tjB8x8fSun)apz5Vg>s%h* zA33z5mhiR(TGJL}k$xf+Fi|xiJJ9AD@{%thkJjK=4PD-2b*mO8@oVDeAuVmjHOOCg zkpv8Dk!bMA_>%tP!m#D*fc`pj=HF9fEOWHe%}o*F9g@T(TD0fu!^zFWay~)KX{YtA z>%grLgsJ4$Pj2Rx8L|8Ga#K;e{i*_1kJj5MeGw8-nqh!Xk4!%6=sFh%sjNif^vj@8 zr(}wFJX*v&U`5GhgV@lG;$7? zTWJGF_Mq3p*&Ii!`jWh4t>Yd3s){?%3}6&(DqU=OalUK(1q#+{RXNNIdzabt}oziNni@D|bf9qFQS78$lK za5W2nU|5y;#n>M~($S;OE`1H5V8K6awO>DKS9@(1J*|n#R|{!O;+%uHPZ~Fm)1)Jv>rr;|mBjSkAQ3po!8r1@g= z1tu2<(3gmW$j}MGVfV|BS)Bp^nq3vRUstONG>cn#Ouiu9{m^zXa*p|j>(0!;c5)a4kqK5|`v!rp!C#VRsZl0qa1q;Qzjctlsu7tSD@FINb%NEsV=wx!k!&PS$CtMFg2#XuA89X8nE z_BkXK4+4kl2=2~S{n*+|eKXwoP=qWxng{{=b|bq%xFb^h7`Csp)^`h1Zoh;4zYZAi zg^f+X1Bpzjki`RMtl|$c!7Iy$(fXqd)d?b>?aK z1=TrsPQo_&0JFOef7&5-U#Ca%|twViP$q|!B@5^G=g6b^XK~eYk ze;r(J*c=!5Uj8xsP5;SaNYj0c6#d<9-MC8Pz*aXcbM%Ch3mNtR@o{Z)@bDmLqJcrj z@4!m6UbVn7OpfY8S_E?r!mA245G&e{UK`C#mF-JETAP=%TBl_gY>n%lA%OKi(_658 z*EG}`@ctiiL(a+4^B;^y^@MB>=%6G<0o4^FvW}+F9p`pgO(X!G9Y{LEY>NwxJYL*YSsiW|FAl?yxo)Kx z0#H9YDZ|HZrh0(FfKWZYk?my!0L{jl{&wr|G(4Z*>YquTQr`i`&IFmZYclUiQt;4r z*6{D1rAf08K!-?}XCq(JX1c%%{SrCqZ3HixG*X+L>s_9Z`3>#Wf82p}5;af~MdK;_ zYU#(fe>Ow_1*38{r>x($ZJ#bI#;;Orc+POdPu#i?E#Nr1*fEo}LVYNG9}B5$o! z6T^NfAjqIgJtLKk!O(WlX5bM{1F#%h;SV~GK6eWpCdoI^i6C^?rW!4*#a+J&EsY2u z8}sr0&kRTo)!`aGYH`(2po%JP%@t$t_hv91eyLC=8IQL89*&7P+FKAjLig5ilUjwj z;cWakd!&(En>s!p_#p;3Z43|gw3B^3riyxb4q$Lj+}aF79yWN4eH&BK+RkG3yLu`o zX|U^f5qA?&>RtFuwYL2D(IETJP}|ok5|<_rC>6i?83m+QQMQqqx6-#E3N`*}&)g8C zSjOM9yW(xEsaU7ecsV!=M<7{hC-5_sGn>eG4?Dp?W~BU_>knT&t>9LQ+m!j*;9|Q~ z<)9iRhswN=%|+8$5DMy3WLbK`>$k)gg__GP^^ zHzVKMu--j1wlKMRUSOTDX>1badaQf$da?-VsB|UA>3YwXR!pbr^7il@dcS9@iT{1a z`)~;kV)96R9`g(UrK**FFbCrf@i+uhpaD zCOiX$ZC?fb)=2uW(l}EAWXAZeabnq zYbWQc-6UVdlFN*kG8o(*GoDO=0Z-5u>m3N5b8>t9jhQ4Ey1PCbDGjh1?hdnjorN#@ zw_~XZI@9KD7~~j|L42u#qKnFj>l_%u72<`Cgl&Fucui%EIt?wf+lS!BUp)W3P+;!BLiVdeGv^LAmnmB&Ua{>oBu;h{||NO zD_i#dk2788yQ82oGe5ko&4JqV=$7S}Ky56M+?>9jccBiWNV+c!7KDuDk_Qfko!~W7 zIj;*0dx*O&PL@VqwS?ajr8g0;Sv&8YO%;3btfs_2a0L&b|A80e?~1h9PX$! z7j_wPX&1qHpJti2hii@*lx4|l$slz5@=qpLuW}7d-nfW=RyGAYv^m=~_IHZkWb#k; zG%a)f{K_uHXya^suF+a=KIBDoM`X4yV{|Df#~v`&xCHz?yZY{dbM|sQeYsCZM>&2;mgPO zA+)K?DNzh-eMiGzaZGFB)tv60z(QX8s!pqJf)p5%(d|Nlts{)@mw8&iRh8wxH`4PR zZ&(^_Fg9>Y5~Rs%QJQtjSN^2N(;!9&VM9}7){+y&^YhwbS3kphL`^i$Of^Tpzkmw< z1&orLGO&~Fpic~yQP%1Nwq<}o=HagUKVxlMdRk#+VTGj*bzrE(syHhV z0owF>B1&E5>ucCx85UwR##i7h38{FJeC{wVjG6n$ualeFk@I$~Jr%t{J}#QBny;>b zr&NT&612f=+NtGYb22O&SNiFnM2;e2B3?2H&X?k%wYi$ICNitoc%18MU_rWn<}<45 z)fXJbDMilA>Nnl|bbW7mrkTw4O)IHk_!0h)Fq$1Y<{xxA7GkMopL4U7xx5t0*6l!Nt~Y8%>Fp5nUpE{0oogho-+ zD@@+Tw7U>@WyIx|&u)|O5S6^%ApgDJDES2lmtCKv$ewH8l;_sn4C6Ay#=`kb7CUKd z(#!SVZa!za@Nf^s!+}Jm_N(^bi|vz%2PjX(Q&iIP5r#7R^eChWwn*3WkN#GZtN^Fr5VcahIkxx{QAvT zUZN)#2_*Hl_BB&-q`$gWbs6ifCAnY6lH(GE*e-e}<(}Hh%1fa@-tNntetvZ!E@Z?m zr@}!q{foX_a^NMK53k7#*O54HAYt^BQQyvCG4fi?-$%rd=an9$Ogi?os;jgt#W!QU zJdtyI!=G9fO+RtSQzkF1IiYys)<(ysh9|u`@%yKHKR@EaTIA}Sb8F$}tYU7Rrt9cJ zfxZUSYBIX{Hj3n^LHQFXnNY8{{2!28Fly5R5hDL(cM zukIJDg)cdO29LN-nHT&BKS!c8N40?9U}_VV6f6z}4TC+m#{t5Bu0c8Bqkf92x$O@> ze!R4H4_yyAUBa*sLuxh(LaC(bT^2R&Whjg+3xpzRbwxG zYxdjrnseo)np8GO^kGoZ9fC`_8 z*SfRxO=tr*f4)1uU>?gE5?sCbb7aq%$Z=QJunPk*xc@y#BG~d0mfgVZ-0rYRF9~X+ z3u8FYMvX{3o&*!ke)48kYEQ6m1>S>Cdzfnoxgbnlj%Aq{2phIl%wkUkEe#ybx~f`j zry}JIJ#3v_v*K1ocM~xF!F0rj#MLuk7d^B#Qs;pCnwS)^pD$Gt^0l(F+Rjhty-$74 zNBTZKp4`T_)rai&qUs+TmEIA|e&!gj)W?5X=5Xv7Z+hO|zi6o`4cD=%0KC4bWH&!a zhHy8q3m^Qp1>Z(rD=mg_`z^?IXc$K5{^{liK}G3^lm#VrwD#po7uu`e>eQHWfKKut z{5M)NJXV#Z(f6_BDuO&!sUEsAJyU!_{a418BSXVV&gaV^;bFpA%6d75BN!ZYD>Xdq zx&@T~6%oO6LI0)|g2z5<l}TKg;r+St?+lV)xs_Z(6|UX>}f0| zfwT5-bKL7{o*xoQ&TelK+xs2e#9-1``_{Zz_vO!yA&P<;_RHc-9Q4l`1F8c>Eg$$m zNOshpqv1|xsaVH4vqqvM*C*)XZX))YjuGv5AYf@W*}30v4={k8ZB1tIOQN{hfc@(7 z&#|~UnglL`e|xBP)arRb0mEA1iIPJQCSW+w5b)K38$uR@iyNIlzWwyTZrG~ZC0eb7;)wv0#)LnXsqh3Pmt z@QGS(6ypUfoORW~>*}Q6 zkMAZv`{zpw5Hh@(zMC`LWk!dc`8h)C!Wy+xm2zB9c_ma|7rt9Zn}k}Oz=QFf{xKAyR9TP))O5-Y z-56C^5HfsUu;-3*qOp>eJxCNCgcjTuWAhsN~N|Fr==m?5-zJJp}WnjgQ z>ur4qe`#6TOJ(JFZ3E}8znWf^&pV0EM8XSxj{6AA`X}|8j)xBIJt&~HXXC+@yCM|u z$F1;ghQhWvmZiEpX^EdWLA6jyu=1S zCT6Q+W4?HUS4TzfFmoxsq)fl9>abx(t@eX0DNvp$#xO)!PYDmmL|ZFqEA~h^~GAu;bwfo?mkzG_4;xx+ro;#l$5~c zN?wHqG^k;0rQJQrmp<+OJ(|BST9(W;Jm^1p9AX91qRKz9vHCFp{j2l9?lN8+ zYow>o{i!ayvqsr&6D(2B(dY;bvJi0egNxh<-mhidLG<}VHUT%7jvy z_Bl4J7{5ePp5|{>AePt1S8=!2rFf3Rh*+33s3=KMojP_7}P<(pM1)*fN2qm z95k?FFk{D`X5Q?kteyxQmxpgdW_W*>%(6;90CIl6tCMlU1b9%pPAz190G-JExL*H@ zHx$gC*`9~YDGU=?`R=sSbHlI50SQFc^Q7Tq(|3cTHgurk^QicL%sdm+|Co7+k45By z-|1O6o`CR&rUK9(4W<$V8Z;eAx+&-On=60mp>uW@TsIYgy7ha?eSZ?|dibcH@#fOh zbG4}2)2NTN>Rv{=f&Tgzugdg8Zj)~WJdye(1PnWMhiNJMii@v7Wpe@CP!br@?8q^Q zpqPhuW86uj{plIk$l}5&QSH4{c-Zo8I%85zdhsBGPSxvz|rdYr&e?K$$%)9g0DZ2iR#;Bt9j zw_|6H6+(#5c)i~nZ0zpHG*Of>+2Zl@#!x}*M3&j#JW>FG0+p!=tex!YBwOe{LNvD5 z&A;7B_Ts63nQh38U)j2Uf160&(6|+(ASIm4|84$ZfdNTNhF9|O_B|yOcx~nE7&(fS zX`iP-t3$?mS)W-9*1r})WJ}nbSoc#tclc3Bff=`uyuNy)xV|SmLAPM3 zF6LK)*+>N|Gyp=pi%;mcp2dqxXaz?f?ZVzU2bkoHPe?{_;Ys@)H+U%B;$SF!nks3j zY*Mqd{Gc$c>b&?TF!|;~KLeKy`uO+U{I}J$CmHEBjL$$1fl>l7+O{JrcI`NB3G$vN zHj@vYXh|!i7+HgWga>+9yNn0qgoRof`_s0T@8XT%J1mVnXW!~GuASIVjT0HZ=qO5B zf2wkC8SfVYW+6Z_^v5?YgDw-R9Uo?KCU(O=q}jTb3e~x*8nbiW4Z~V+HP58EKVj0n z_}V3y?lBDGUm+#c%jZ>otc+UnO=0Wqe|qOxZnn@Q!ID}C@*h|QrjW#}#r{oxBeWZD$gvDy`L}D{P03DoViYgoWn%loIqMa>qR6~?RYw4sFbj+=`OU} zIFl#}UChV8XI&<`uH+zxmIi$ZRrdzP;!{_S03Ee&NL9UWRVahY+e&FsdJFz#D8PYB z3&2l1QJ)yB8xMo$?|P0(<$ct$B}giqO4=p8iY`!iwRUFY}m7K#SOTy#`hiDB#`ib{1s%LHNg3z74VI zDvd3=TrW+@z$lDu+i$kEujgu3+RJl5%1>BIen6Zi$jV$&0a1_UPc?w>0GE1q@W9Y1 z7dr~$rFu*_ZB5+^Wh=DUPq^&98*&pSuPXUk;eXGZZ zrUHx)QV~NZ_1lEAjGHY*@ASfIH;>*DPxNpBJ)@b_*u$Ppr%YB{I@oEssUquqk6Y2x z)uzD@zeZ)qA2xvLx1<=+#_Z^7QK+V>uA728ozL3``?rE+l<2Kow3m~;2kFD}co@^V zOlDuqzb{3Kqa?bo@YSzK**Q{Uw~~ITzdUd>6*7LagffAmL6L5L`7u#uTi3V9A8EYR zIInQzK-`=UF0l=-{{MQ|f2L*ipL7{k^K4D++k6Pa1&8}Bhj~rp-LCGw-CqpE3Yn$0 z>StA6_c(OqbujYIRhc_N2Ydc&J|Ch%BF#D7CTK%9v2cF*^vf zo^&dKS2J}71&OTX>xn}+$UaXuJlD$&(;SXnCiV19maLf&?+=_KJlT!arK_UKObs- z_FgJ!E|L#SkhHvxpGA`_xmtR!d!t5GWMv;;x`@XAeL_C^?_TPnicM4d`PH<*qazVm zTHx{~dFKZauSVBceKb;bh?UuIr}=uF#3y^hs+~QG>ByD))(_v zqQeQ>TTtsAaPzVjdV!BmYO;~^7n3hTPs}~SjcUG#(7$Hybdv-$GJ0@^q2|jFXHf^` zST<50+>wut7Z(TA<4H%2>(0c>1$9SlmK+4%Y)#JJo6b#jk`hE{Rbc8WxWj;QHvYhg z{s(YEY3N5?`vOSxNCNJ^B3DId$mUr7lrhS-iJrsh%}O(=hkH` z!C2v9IPlbJHk^J9XGye$P`l!fR;zSfq3kd4FN{~u;0$Te*WULf@w%NL5LQ+~M(Wr} z$-Gh&4$7Sf@!-PU9(D)d|Ugn@}7sn;)F|S$dHkfN{WU z3W3`P-iO7JPC&|^*MBW-_>WeUFF%w7sm`L-Q?GpRK3K3}f8V$Sxj8qieddmkc|SM0 z`3M3Beiqqa|Ed341tM=v3Ok5huurAZ#=$W=S%W4eWyNW08a@Ak86a1suG1NJ=pOcS zKj*6ufYG_MRIKKn5oU6f)PTj<1jp{uh z9lBk+kqWUqttd;zNm+zHmA?_K6lhOfMzm9pt7$Hs0=iBZrY6`itv<0HXKMYPRK3QV z-#J$AXN0@Bz0V}!8~+i|S6!o)FUBsnjPLJ_8wYY1>2c3G^@Z{R;)>U<+#EEVv-2D6 zy;Ff-C3C~_i@$Pvh#FXW%_rgdh|E;xWRcxNX@y{)zqYrvEz)gs6{uZKEb4Fdl4%^L zl7Y(H9wf>9Tj!P+{quvRI|L}3ggDFS!(9|+-UZ-3PROiU-Tfl=NF7&C-eyp6!?W-i zuIouh5p^pbI`M;JHmEs(o$Mc7krg@zXuk6_WC7FN0i|v$&z8rv4Y#l5#P0P+j*VJ0 z+mUOS**q%@cW;fqYZVxbMQrLI>lX~4M*Jz=*9-;BiW-+ZQeT9RXMx5hE&JJ5$G1Pl ziexyt!zi6~D-vpcW26=)eq%^Q4EHt;g$G_DQhhFW<#{T)SmAUIztr_)^KS9Qt6%Lc z=rfCNSS)-2xnCG%Nq{yew@jl>eQ0iDt>FkpVqJ*j*us*>-ubMW4lU`|58JWL)^(27 zI}WB&FR$3$&UAvwiRu@dC^5@R@r)om{ok~PBCPtsL6~32sP`~>I%Iz%dRJXfOeOy< zoOhl62Rj4?r$u&G9tN@#~1xdV6%luYt1! zuf9tI|&KbNL$}A!Q3gmV-G45)_A$RAJ z|K$Y&MVTzL{%*FEPS2Fip9UP3ykfntT$2d{BAr@;gi0R z0vglF@osqoXgoIqs`)jGcH?GGNBO8AF$lnQr|!7GQ5#!hgLQny#?P=q4+!3)B=}9| zd)y$b3m)_?C5|Ffm7&nXRz*08@#6s}>!QajT% z{r1nUw<3gkw>@*lTf_BNR@>?6y!o#j{4xeU^Vvt`-C8GPzD6EqqF>sT?x#IUUtzkM zBwqJDZDL@*CPE;02Vwh7T+*OVm6}k3`sc!`{9T`BFS&46^v4WG2J5$U&=9&O#-q|= zwn~eWgH~#-9e={+$Yr%W)fL8(cFp8_r}0K$W8{RWbj9s-)|mnDv);G*R?F1Oub)F6Ipj-v8IUv9G$Qd5*?2}aeu}Rd=$UVi9}xo}Yt}yQ z!KjSJT&12R>Q65@(0YuhP3zt}LErR9$Xjh}32bJpl~snN9z;A84Qtdw*ibcM6c!fl zhW<#wei}Yxm;@||?+|0xq5JXML6TDgo~M`n@uQNuyC>B7#22U|D8Chsmr= zI~Gmrob(tY74HDCkfb!Z>wK~`a77$@dI*I|p+|N1z|cYDHJLxJeW)_pmx;38W5II$ zy&nm3aoK0KU&}q=##%B43KzphC&U{wVs34#& z8A-u6ZTtYHacg^gx>Z4!wq@>M;z(KY8E@LgdVBYw_w_+@7`KR9JNoH*FG^C|CI=XDVq$-XV@o;7{tboyC?&aOUt!=FVc>uqq~C~VX4^Mly}q6ZQO4$9IUZ+SL1 zEa*E?-~8WejN~F}ByHt;hq=XjArUX#ySs4h1-uca1I`?oZ5y5|bi?FTs@B`nAuq0F z+b9e?AHP2pO26FxYI-#Mf--wfak2j~Ce0Q~b4&U9DO6soFhOAs1y5-iX_Pnw|AZ1k zF}BoDcW(PfbmCHGw5*_cCj*+RVnbVZUppswdHS5e#8SVJ;ew?}&sP2TA@KKfgICpy zz&zE3U#AfzMyVkcmJu$tZnXGA2^_B*DJR9iMh;r)%Z&LA$n(8$piw$*9soO0{J9XR z{ATc^d3^jkS6~H{Bt`43L2p;(N3Ox2UhW{3iMl`=7_Nft0 zyP_SCj(&G15RVptlzOuSGngB+mwh&kZFC-JW)D--x4< zZ!K#be6TDA3yoQ(<7Pko3Q>llmNPgQBW?L3#Jr7a(Dzr5D&3Qc8q#By2CY|loldQ= z@({aphH+tG7l^#8gLWLCSnlO(Dc)hKmDO>>1dg0hn|3V$3_+`CHs3wHJeDvWIrnQEL)qfBo{ zfzqFwCJZ8nhw4A7kj!?IOH?mz+bN!Rv_hz6+Xy}C`Fxyq&<$rIoF0OlCcw%`VjqSB zJ;zd@f}eN3vr-t1rz1{_@rHdMBUlX_aVd41C(Ae=c|lfarrUkzOZyiLGR-FS)0c%I zf05s;&asCF`3Ch**SHvHu=!*lU4D5hQ)qYctJ(BaSZ%X#V3hL`X&HCyo3IMc6uV3K z?)A-2R0cfVMNOK*2>CR<(w+m@G`=e4OYiY4OsA?GUoUM%xr5{%sOx3w1Gmp;4@*L~w9^I?0x-$u9mS=tr7`^PPb$@M> z0mjVYpTXO5PGzTZonG9RYQ5VoYY#v5cG#EWNUVvH<@N|%{uZIVdSiix=JbTdIE89xa2FF>$T6p2U_obK%+$IpqQk_{#ffXG5h^X4W zp5h}}WjTFI2lm)#Z|4Am_#Wu$gDJT1u1HTYYzJ=7{CO^M$X##$ZPWg$}p}5Qkuh;Uj@(@7DnmBGu&lGSvkmF*J)=2!3Tu6}Mv`}Q+4E{KybP5`|gje#I zLRRDV9@2ql<6T2ZIIG)+kB#9UgyUd<)rqR%7Ut(3^z*MG+E&l%pfB5Mx&M)HG6fV6 ziA2|5b1GXN42NfTJGNOqP-(4V8X`6P{>2X8b14&z$1CPS6j-$jqtixUZE1lMP7AU? z1~G=(zMSE4F8O(QugYb~!-LSibT-G=&vxYuR+Z0=Dt>cEB3-LBl9Q0eJN>$1msmBF zlspE7WDJZB`t2=bWa-I}3$YEjbE+99WvV~j)MY`~`b_O>!eAY9t2Rg~=FA0d7Si$q zGGPNh*iDd04Dz8wBgv$>zVY7~7jP>y(i&Yg2r8v#H*3Q+*}ZjxuxUP>v}NDy+($l9 z2RsZCqY3b=@>hjv&ZF%<>cft65wOSN*eh?ZJ)Z46tS2aE4M75gcE=O$;3Qcq-g&xg z9|uCC2%!Rl4zM^s(sC$vgXD@Gk;a?`OjvX^5A#!gfs6O*5T`y{spdhO%tFQwh|LkI;cYER=o8Tq%+&r`n}a$G z@_#5tzQo@XWcVJU?)-=q%H*Bm;ypYdKTZslshPWy)}Iji@rHV)Nl}&mdH!2}-j;wM z{JcMN&>1dnigwj+zYTXqYzU{xRzF)Z>EVS|SgqT)XW@Y6Dl&>$9}VBHoPWw#D)1#} zl_U<0;0E8ez~suC-wL3b38!sm%ssL;-8k)Ro97>@md_$i>*SfsbJ^ZqWGg6OfX|QkZ zBkMJ;j~9bydpvA!71mW~vUQJ3z_iv)v)e!x;mkIZh`@P;pX_ud*v!y*KhU20@MVVB z#wMoW%GAGS;P2(7gfxb$gJyLAd7y-DD}#2IvVA2E;mr0C`E%rXL3Q}&5zCQYx)OF0 zw($t2k($j6PYu9~6cHW9>nRgDfV< zvVvR~5=9D<_H(4Zd{>?-5AG{p2#q-dZ!8Q)(*0rBUYbh{Ig~5X0?Qdzbtr$A@UMhE z>-ok$gYN(-3aOqK2OWRP&bhoe(O2yZWW8(TeUUQtp1Fd8<)yH>mXba($$4^Cg#0zg#*LV4M+feQwH|HXe@-@p1Zw}J z4Qog-mV-4JEMpnainLj&mgt4yv*vr;L_r#LCad*D&-zq)J)CS#?9!(ICNCnXJ$^h_ zM{ydE!IGN;ID`LI7jHv69_D833RQk~U&k}q=3*IU5Ir4BHg`9gg<;oR1ph2C5*{-x z7?dW#?RWfw_Vu^1XW~oPGl_b&VIe?&O7eKos zl!&k8_-xPv76|2Pc5gKQSPI8tuzR=oonAWfT3Hhmy9vCLE&7n{4IHkC!tZ3r#IZ|s z6XJJ%#UW%Fks?=z!b?y|1*Y4FFBWKb)iPGUdO!G^;%zX__yl2f)H#F z)fzWb!srk(joX-CCReAgYGjojY>~a2*PYEhUvJOsvo$&10l|xUua&nJ*amIHu$-~l|_|kVAdArX+ zdSdkbwQc>U{P_$s`8)EYU*nFQ6?PAeH#bhlQAy1n!r#|FQd(srg#YK8#>G*P061Pj?;kG~FL`A=-_70S2va0^^Nm=6>w_-QoAMH5=>i8opUqd@870#VrL|P89Ne0@SWpW1Cd$jqfmJERr!qR z-452DzN@Ha!5}%LZYZ-63OYM-$2b?MH?@s#!3JH(GvPZ30=^Nye!aSWVEjE(+nB>M zgL|G{f9)oCg+pRcjkl`Qe!kZ?P!G3us(C9j{9@Mh_B1iMTqKXV3|&H&3tyYL`qG?a zjLfqst$a}=gWy(uv-V5cjZbfXAHWYvVk$%k`#UtuNMshBn%#DHaV4o&gKRGD;%V_LaV%8gP8}l?n`^>Xwk; zjk|5_MR$DUJvhanw$2tf^3Q3!50H>C)b~Rl3(A0hB$%4x4WzWZpwshjll)NoEZVC# zi1RyZLF6FekUTZeivHLkdD_bP{!=(Y?%b|l$Ru4iL+^Jwk?`=)w|zGH8Cy1Pr_*&` z(*^Ya<4Lamk9g~`de<}CAHQtO5YSU4ZnT(ke?FG^Q|2Jc)zl`ltkB&;_iRV$^;$yN ztix(!vDI(nme20@;c$K^6M`Wz!XUERK1NLBKF;xRyAXCKdu?rep*!_C4xv%LQmyO_ z?bDG=E8;w<)oDcM!&%}lr(i*Z^i*U&31KRPrltnY7cU(_nO8nd&2gbX^oLwtErU0s zZHy>wnnS6fZ#MtE$Vky>;ST`BTQrf>6IO__a}3T^xKqefJPuj*u8h6=ymQ>?F|CuP z*$|9T5P z$O>ldoqkqKJdB24%?t0wgd0}dy;D}t1KooHf7nh1geT=oc=-QDDLjC5zDD~0;HK?! z+Ev@h>0cY0A5tKCQW&Mzsei>*G6V5_VG+r%vA>?}*0MyOr$e(p^s*fZy$b93)1=bZ ztQnjAB8ZK|9a!$Uad8i7T(mOn*DM&XP?h*fQczqGcv!4eniBOYr#8Gd5O!@{yE*Ii z-S+p=rf9KK{pw!vm7Vq}$`=p_2WDbyErR44?czhnUNS9%PPX&+RnFc{6@C32gt25X zZxnc;s`4xvP|K3;$(?17SHP>+P0Wp2bk+}MxO9a+$6b5$Lv@wBk4F5VNX|Jd5Sn`}@`*ts<;G~?*!9|@YM-9YZbw9) zF`hB!?t3T1y(fB$eY3|^=~C5wn2pTg&a}6oIERcwlD5}nj>kd$8i~n~yd#fpRe@r` zn&$bC+^h5ayj7Ge3kIaZ8D?R!#npY+Qa8Gd97dYFYdw%T5=peXSPL<8j%jF+HCkI5 z5bWwHyqO`L2-loeyqSnguLP9nZFQal;54au9}PqolUDgWj(2hAcpI9L9 z=uf#`&Ft~luQ9c|TReUc$pu{9RClrk;hw2ixGYnO=3L63*dY&vXJJ(XDi1a)>^VLaD5r0KF6Ocu)m<_xah{L={(B_H zvW0q+cT(i*w;?R;AmwxPCBw=idDZwsOi(8X^j}I6F zD3(J+=gTmke6`7xS_X9NfnA6{^myi*^j>{rA7b^uVLBr7S?LQj?W{!88OD;DhgxOJ;c%$ z3-cmQe-NP_N-nBze3j0>d1Y_S+yU=qKA7N0AX#RTO9e$zQb|%q%4uM|{Tp&)H zFpX9>d6Qpk+p~e`=^#8>>A)F}4*K-n)?}M!3Uc^!l6{ppjeg;H^9OJ)xPIQ6TFj;Q znGNAdCqFJuzO$peBJ?bh(JcEbEfawUPP$62)}ay37{>^L2>Jn^xATCh1a29@%KC^F zj7h!yLM!(p>oL^*G|>amjMRdNM3TIHL$*Mgala5-_lkkmi{Dg?iF6r8#%JPJI6cuH zzW4id?Jw{j5o$6l-b7sx>mROxth8u^qg~E%PC1F_%ALGM3;0G4H-?mUUv$sdsy5_} z$l~@KZhU#GbWlFOn{oI;ZIQG|NmZ^Hn$~Zh7NH%d7HWREg_?fMCD;90^?aj0UHZP6 zr^9Ot(-6pQ)6ju(Ur+xy?3{o?4vYJ?600gXKXsIj14b}%Yw1o)i)$@115={PEPjtx zayEtXr6Qf2j5|@ab{Ig`*I;(6zK~5&3D20zq zA`zH5rz^ws@~tsKMUUMb?%Nd{JHUrausGnCIDRV_oBZOCin7$#nGY|R&u@^qGmfQ?fj))l$6p3qVO|9@;@%xN+k9ard3aj+gh1@uTY-tmHRho%noBdv#0R?6Yfqjf5ArBSuaq?4XdHMCAj&3H`oKilsm#?OAo> zRItI~Rdk05XGxG*n};?1bajW@oK;z!lI9W+fl_!R22f)l^2TBEU=j(8Q>xbvnYOVs z60U;Kq->3$q0HZ!m@yinf9HEgziMxBEprWYl1A7@g<~UC6G;p2J7u*{S-m+G#IPGA z{TlsdKi+yJGL3v=+ic5x8~<~=#8Tr9IW#dk8&=8xz1s}(>3a@~_%*W8`B2|vpuQSe z2$fejwyeo^4ZqhE=}PF#zR;M6H82CL%Eij}0r*ZjTk$JjCSGN38ZT;^UMtOZPvzzy zcVV9m@PZKsm$EM6VS_r|#n1BcYW! z^(@;)V3x#La#rRXUUyv|c23O+{F7IGKKxmmT+2EvvB|~+3t7znn?6Y>z5YAMgKnlC zuIZo;zZ&O&9_Kmj@9MR+r(!n_)dbFBZEr>+YU7P}YdvNAyllb6oG+VAlVRluK{OxE z^ynRt2PfOPubW$ZD3Su^A4Ws#=z$9b1gdyzSaKLHRjf{`GeO%X!C3p(Gko=?ahB3L zK46l#eeau1>NWNhDNBTG5wbwXBzSxo-*d#L<~p= zc~_Y3vyG5Nl5@m=BBS5y!Na1?nHk)?|2)=)7R{8rf4tqII^PMSt*zZB{^Y6SsW;OV zL4@S&L4gh@3I5Zh|NL@#^zlqJ2h(vC(w)E~YA=}eqrdMZ%Yhj|`dzhrX_h04-;02! zp*r()X=qA#U~#VILPP>~KR*vFCsD`#X{@r928Tj~P((eoeI~z(&LsnK2tj0d);4D1 zqu20 z^WkgAce3#9C3{dsONbcyr?4P!OLPjray!PmM4II7?nUIaV^JN3Cnn9=OlD-XT3NEb zD#_Z{9`KeTtoRBimX;8n;{0|n>XuNbLFZCw-PK8SSlHC&srzeMv-`6zXH|3U^~uI> ziG}=6Wywc;BQKEeewl894CODe`0zb;@?XF&#|5Jg1T=#pmXvBpL1~`=<~Fcy>WEu# z1li?*v;!HF_@(&Xd5d?Qi}mOwrXf}HQy2zwknL!kdYzX7`NdXDiN-}%@gE*W@-Ur5 zJKAGabJ}ViZ9QGro`ma(h0!mi4&gW+iASpUG98__on(I#wV-PygJb@_n`v8!%Sbrg zocX|eEB$rg?(=+Q+u-|rMb%cWmp^4%ryA>;KjWC&I&)Sze-wHcr)sv_4m){`uU0eg zR8yFKF6R#jSaGq2yf$UBjS-A6T7>HM+pGH&8nn9iOmwap(0s2>*D`g*G>Ld%y98r z?R|Auvg^>5c?{HZ@HEC#&Rdl-X2^k@143D(vjm0N?f#1}1!-tx)E0x8CNL4e!r z%V*1+@*-HMyG{Cz8txwMrSqItmm0I6-tzl+2JGknlS{wGy$7wZ=yiLmPGxo1#{MBG zpEyYiUzJg_k8kMRX}EWXCm!z%Vk8Q+34afCE<$I83>lo06wjxs5&bg-TFjs%=5R~* zq)#-e20e`pJ_*-sg6RK-Tmfr#!v~5#=_mki$6z#K5)K6F6c$3BhBHJe8|=ECv)_tH z(s>&R9}hmj_;KAWngBq+9wcK=S#*A)MR}a7F04C!EIZT&7>#EEem*M#KLe%D0SIzC ze?z8<=C^*D=XdukFR$C}p1v}|kUE$@TY)d)$>Opu7*~lt%$99X!sPsnQ>$usVb)t%Y*Q%-f1el^mgEA8 zy5mem`xwlsa_<2=$5a z!_B%dIb5vgQRc;RDj{emH@|^Nwyp=HG1lk2KaQ&e+_sE}`77jsWII8R9nGe~PNOS& zNLfKLOx`@HF&CLTgwI0qVv(`2=ksY{FrrMo8Jc_`SGgw*3au*D^qMgptVX7~K>1*B zra2Wqx`BxHuJpvVTX9u7JSa{wZ` z|6LGOiho<6Je!|A2OnJ?wWx_LQ>Jdckhg!P+}uomdf;nDtG}7$<}8m0P!>ABD3bJs z^7|ZaoM(hLS!&u1lD&GKef)~#=MorX>n%VYn4o6q=aQ zyX+?OUdK8^H(X+jMToWN%yqb&%oHoHxt`1Lc_VIJgf!2kdp1V3tYgWU8M^jz6?FUr zka1li;x+psGQ=Y7_(Q}>V7OTy-_3F}(oiOQ8D9v zPVsF{Mtb(1P&N~$%z5~V6tXLu!vhmWpsaiTlKa6}I}3C`^-L~IPezV1wk|lr)MvO5 zX7e=D(5d|F2Ln1W_OCgaQOejoj^cNVO+K>3D5!|bt=Y+_icJMKEN9{XrUS%0$qwrsEY}=W$r1X)zz%y8 z+V<_rez@G+4pR#K+3~U(v6`B=Y7?K+5q4XC|H;>vD;>q9)F&XP!|?emBmDiXx*L&G z8@xn*{prQC)!8+ITRaG4I=*71UF1oKpT{e!0wlW|eZ>nzs-|LrAP^l_7bav&{F+K< zC}P9aCPe$dj#DLcX_zCo`a3IOdKgH%csQ;fv(@}+xE#Na<`7eXux-eEUzkVd6Z5y| zgQ^vxmtZ`<+r+SfUy%62^q2EC72YwTwx?n7Eq~s|deOxAm!DX8HmB8#O`91QHuF0v zJc+LncjS{j*ACU=kC1Uad=Y<#jG=XSOsgD|>#z9&+Y0FV>a2z=iqqUI-n4C!@|XR_ zH!o|}AtHPnpS5>#$X*|^^#dczb1oN_MJgR3UE6NUSLlzHgNm#nKSSpWgUIlf(;w?q5{UT>yEGU=p3SIpB%76X;|6!p@ z^Fn-{d`#)9X8pV*hl}SHW4jDufgKL)n_1>KZyY)V?zcfwcsFK_lkL0uaz6-O-gnT`A?_xqtTY-@tanekdWS~D9tu}8L+ zl>b;63!|!J&*0nw34Kdd2K7;+fU6v~LF9XnS6_st%Qt}~iOGHg2iUKgI{u`>3U8=4 zqTxqgW&!vhr?d}RCO-nBjb3jiMxAPFr9^>ug z=TUY7J%hGk(mDO@iml176t3{)d9-HS%pk z%Zx3AA9M9zC^tO1|Dkjv6l{%ws|kpi*qi_4qw& zFYnQD-@e;|6D6CF{gn=RbmJ`lwvvsNQ)eJS5NjrNN)rHLBSvoH`9nQ{=}7~vkIgSl zvKe0GQsUHpHCB_+4Z7<&DaUl8BiQ|s{5RP&N)XMF(_7D2CiX!OWboVXdLfqgdn6<1 z&p_>7qc%RGG?&<=s4;B}81N>N8xi5pO^$KMBO$u+3MV>Z%##=>ER4Y!f80= zi^(PC8KlN{jNbGh;OsJOV&;s*nvwA}_alvM$(vZ7H-Am8N!sNmNAVzi%kFzABca|M zx9=CGc&h){;MWT+VkW#@NKu4!-yAdl$_|{+%Wte!x6Un@xISa`U0_kv@05 zEQBhZ7NMQb@ij&{M@OU-Ud;@AvTA5`xjEX>(+m{=P?xlj1LV1TlgE9O(snnoZ{i^x zM! z3!S-B<1uU{+%(yH1=e8AaQd^|w=mt%*HugWDIt=|U1jeFurWy|UDj~?e97kx>IOYR z5s}oA6&xc?W~I>Nq8>~H=`S~r8!|pkZREmQBD1sg`b+v1e4MW%M(c;0R}Ja&ANOE# zU-^U$)v3XU?~6m=h0hYng-g_Gu0R~`^@<(WA3KEEyRs5y0f2RAZ8dw-ZT;rp3;kj8 zN%kWEwkY2O5zk+M(}FeSCIaRNvY0F)S~GNkw02OJYea|HxQ#J}>?1c0URrpaqQE3P zO}Z%!?7y7}s`^HjxdVutk=0_c$_;p&dUoS)VI^=1D%7-Y$<3TU@S(uxpdV8+l(*lV zY8nPuH-B~`;=GZqXs=$TwYYM^1&Ysm_u&7f1~D*KG!Y&}f<-%yH~*Nx$5l_f6x3D|ODEDKCxL@{Y2?Ho1$O5%+fToLfu zb+Oko!M~K+*xWL=)g6c5zurD!k9H!CTB~*%@a){|xk~&M=FiS~>UN?( z3!CLrKRpJw$97-b_*j-7F;Q|3%!h-^?y2Ha;E&b51*Ww>$lh*Ve)#-9*eQBkp$TYx zvG9RF9H0p@MNOkO`v0yHgZSDapbSyZm!~ntZO&owTAX1pH~J+zb-2ItlI;69a*yeV zYsYDhy1hNJg~@_URBZE-2;R3hA_mE7)QOU2iDY^_0((qDlCQQxBpYK?^)*L&5(1zr z)cKM67pLN}>k&WE2e+~{>)~&8t3l;WTM=`Cjn%flzF?e5fIxA3*XOQ&$e)m|?T99; z9mM=g|GHoGM)hzQ6F>l7mN9{#yfNK65L(lZ2)AwwmDx4+z5+|MR3DzjupB)72=w&l zjU8MOahmdpYqBL9$;V2Di(l(^A#3O}rabtCnZ9%^+*@<%SN z!?Uk{3<8}`G{%&!RW}kLoi7zh$7yuyWZqgwv-BGd>CFKkpmIq! z&<{GM%1^_=EE&jreUb5JWivE=gPm!9u0bBVfm}+Mm0-mnJu^i+8>^1ZD=%aBoSf`9 z3+LQ$hEjiCE8Wx<)oATFTXN>44C%?e>Bq^&g0!4*GZN z@!bxdY-4x{md|KMIYR(>Q+Z#zx%PH!i+vpfaD;FT^h^51(xu60qCD-;(9swzKsH

` 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 Detect outliers in daily reported cases for each geo_value. For a more in-depth guide to outlier detection, see `vignette("outliers")`. -```{r} +```{r message=FALSE} edf %>% group_by(geo_value) %>% mutate(outlier_info = detect_outlr(x = time_value, y = cases_daily)) %>% From b2c10da3d1afe203d16976934b51dda2d2a7607d Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Mon, 21 Oct 2024 13:18:28 -0700 Subject: [PATCH 59/59] doc: fix typo in archive vignette --- vignettes/epi_archive.Rmd | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/vignettes/epi_archive.Rmd b/vignettes/epi_archive.Rmd index 38dc65e6..6fd47bcf 100644 --- a/vignettes/epi_archive.Rmd +++ b/vignettes/epi_archive.Rmd @@ -365,12 +365,10 @@ dv_archive_faux <- edf_latest %>% Now we can slide the forecaster over the faux archive to produce forecasts at a number of dates in the past, spaced a month apart. Note that we will use the -`case_rate_7d_av` signal from the merged archive, which is the smoothed COVID-19 -case rate. This is clearly equivalent, up to a constant, to modeling weekly sums -of COVID-19 cases. We will forecast 7, 14, 21, and 28 days ahead, so to reduce -typing, we create the wrapper function `k_week_ahead()`. We also produce -forecasts in a version-aware way, which simply requires us to use the true -`epi_archive` object instead of the faux one. +`percent_cli` signal from the merged archive, which is the smoothed COVID-19 +case rate. We will forecast 7, 14, 21, and 28 days ahead. We produce forecasts +in a version-aware way, which simply requires us to use the true `epi_archive` +object instead of the faux one. ```{r} # Generate a sequence of forecast dates. Starting 3 months into the data, so we have