Skip to content

Commit dc3c9e6

Browse files
authored
Merge pull request #326 from cmu-delphi/ds/parse-week
hotfix: epiweeks handling and hsa_nci
2 parents bc2c22f + 9b53a5e commit dc3c9e6

File tree

9 files changed

+103
-41
lines changed

9 files changed

+103
-41
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: epidatr
33
Title: Client for Delphi's 'Epidata' API
4-
Version: 1.2.1
4+
Version: 1.2.2
55
Authors@R: c(
66
person("Logan", "Brooks", , "lcbrooks@andrew.cmu.edu", role = "aut"),
77
person("Dmitry", "Shemetov", , "dshemeto@andrew.cmu.edu", role = "aut"),

NEWS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
1+
# epidatr 1.2.2
2+
3+
## Changes
4+
5+
- Add `reference_week_day` argument to `fetch_args_list` and `fetch` functions.
6+
7+
## Patches
8+
9+
- Validate that `time_type` is one of "day" or "week" in `pub_covidcast`.
10+
- Validate that `time_type` is "week" when source is "nssp" in `pub_covidcast`.
11+
- Allow `hsa_nci` as a `geo_type` in `pub_covidcast`.
12+
- Allow `hsa_nci` as a `geo_type` in `pub_covidcast_meta`.
13+
- `pub_covidcast_meta` now returns `min_time`, `max_time`, `max_issue` as
14+
integers rather than Dates. Because these fields can mix YYYYMMDD and YYYYWW
15+
values, we recommend you parse them yourself.
16+
117
# epidatr 1.2.1
18+
219
## Patches
320
- Fix so that `covidcast_epidata()` will still print if fields are missing.
421

R/endpoints.R

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -910,17 +910,17 @@ pub_covidcast_meta <- function(fetch_args = fetch_args_list()) {
910910
create_epidata_field_info(
911911
"geo_type",
912912
"categorical",
913-
categories = c("nation", "msa", "hrr", "hhs", "state", "county", "dma")
913+
categories = c("nation", "msa", "hrr", "hhs", "state", "county", "dma", "hsa_nci")
914914
),
915-
create_epidata_field_info("min_time", "date"),
916-
create_epidata_field_info("max_time", "date"),
915+
create_epidata_field_info("min_time", "int"),
916+
create_epidata_field_info("max_time", "int"),
917917
create_epidata_field_info("num_locations", "int"),
918918
create_epidata_field_info("min_value", "float"),
919919
create_epidata_field_info("max_value", "float"),
920920
create_epidata_field_info("mean_value", "float"),
921921
create_epidata_field_info("stdev_value", "float"),
922922
create_epidata_field_info("last_update", "int"),
923-
create_epidata_field_info("max_issue", "date"),
923+
create_epidata_field_info("max_issue", "int"),
924924
create_epidata_field_info("min_lag", "int"),
925925
create_epidata_field_info("max_lag", "int")
926926
)
@@ -1040,6 +1040,17 @@ pub_covidcast <- function(
10401040
)
10411041
}
10421042

1043+
if (source == "nssp" && time_type != "week") {
1044+
cli::cli_abort(
1045+
"{source} data is only available at the week level",
1046+
class = "epidatr__nchs_week_only"
1047+
)
1048+
}
1049+
1050+
# TODO: This should probably be done in the create_epidata_call function. But
1051+
# this is a quick fix for now.
1052+
checkmate::assert_subset(time_type, c("day", "week"))
1053+
10431054
create_epidata_call(
10441055
"covidcast/",
10451056
list(
@@ -1059,7 +1070,7 @@ pub_covidcast <- function(
10591070
create_epidata_field_info(
10601071
"geo_type",
10611072
"categorical",
1062-
categories = c("nation", "msa", "hrr", "hhs", "state", "county")
1073+
categories = c("nation", "msa", "hrr", "hhs", "state", "county", "dma", "hsa_nci")
10631074
),
10641075
create_epidata_field_info("time_type", "categorical",
10651076
categories =

R/epidatacall.R

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@
4444
#' @importFrom purrr map_chr map_lgl
4545
create_epidata_call <- function(endpoint, params, meta = NULL,
4646
only_supports_classic = FALSE) {
47-
stopifnot(is.character(endpoint), length(endpoint) == 1)
48-
stopifnot(is.list(params))
49-
stopifnot(is.null(meta) || is.list(meta))
50-
stopifnot(all(map_lgl(meta, ~ inherits(.x, "EpidataFieldInfo"))))
51-
stopifnot(is.logical(only_supports_classic), length(only_supports_classic) == 1)
47+
checkmate::assert_character(endpoint, len = 1)
48+
checkmate::assert_list(params)
49+
checkmate::assert_list(meta, null.ok = TRUE)
50+
checkmate::assert_logical(only_supports_classic, len = 1)
51+
checkmate::assert_true(all(map_lgl(meta, ~ inherits(.x, "EpidataFieldInfo"))))
5252

5353
if (length(unique(meta)) != length(meta)) {
5454
cli::cli_abort(
@@ -73,6 +73,10 @@ create_epidata_call <- function(endpoint, params, meta = NULL,
7373
)
7474
}
7575

76+
# TODO: Check the categories in the future? We set up the categories
77+
# but we don't actually validate them yet?
78+
# use checkmate::assert_subset or something like that
79+
7680
if (is.null(meta)) {
7781
meta <- list()
7882
}
@@ -155,7 +159,10 @@ print.epidata_call <- function(x, ...) {
155159
#' @param format_type the format to request from the API, one of classic, json,
156160
#' csv; this is only used by `fetch_debug`, and by default is `"json"`
157161
#' @param refresh_cache if `TRUE`, ignore the cache, fetch the data from the
158-
#' API, and update the cache, if it is enabled
162+
#' API, and update the cache, if it is enabled
163+
#' @param reference_week_day the day of the week to use as the reference day
164+
#' when parsing epiweeks to dates (happens if `disable_date_parsing` is `FALSE`)
165+
#' Defaults to 1 Sunday (the first day of the week).
159166
#' @return A `fetch_args` object containing all the specified options
160167
#' @export
161168
#' @aliases fetch_args
@@ -171,7 +178,8 @@ fetch_args_list <- function(
171178
dry_run = FALSE,
172179
debug = FALSE,
173180
format_type = c("json", "classic", "csv"),
174-
refresh_cache = FALSE
181+
refresh_cache = FALSE,
182+
reference_week_day = 1
175183
) {
176184
rlang::check_dots_empty()
177185

@@ -185,6 +193,7 @@ fetch_args_list <- function(
185193
assert_logical(debug, null.ok = FALSE, len = 1L, any.missing = FALSE)
186194
format_type <- match.arg(format_type)
187195
assert_logical(refresh_cache, null.ok = FALSE, len = 1L, any.missing = FALSE)
196+
assert_numeric(reference_week_day, null.ok = FALSE, len = 1L, any.missing = FALSE)
188197

189198
structure(
190199
list(
@@ -197,7 +206,8 @@ fetch_args_list <- function(
197206
dry_run = dry_run,
198207
debug = debug,
199208
format_type = format_type,
200-
refresh_cache = refresh_cache
209+
refresh_cache = refresh_cache,
210+
reference_week_day = reference_week_day
201211
),
202212
class = "fetch_args"
203213
)
@@ -270,7 +280,12 @@ fetch <- function(epidata_call, fetch_args = fetch_args_list()) {
270280
if (fetch_args$return_empty && length(response_content) == 0) {
271281
fetched <- tibble()
272282
} else {
273-
fetched <- parse_data_frame(epidata_call, response_content, fetch_args$disable_date_parsing) %>% as_tibble()
283+
fetched <- parse_data_frame(
284+
epidata_call,
285+
response_content,
286+
fetch_args$disable_date_parsing,
287+
fetch_args$reference_week_day
288+
) %>% as_tibble()
274289
}
275290
})
276291

R/model.R

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,18 @@ create_epidata_field_info <- function(name,
132132
type,
133133
description = "",
134134
categories = c()) {
135-
stopifnot(is.character(name) && length(name) == 1)
136-
stopifnot(
137-
is.character(type) &&
138-
length(type) == 1 &&
139-
type %in% c(
140-
"text",
141-
"int",
142-
"float",
143-
"date",
144-
"epiweek",
145-
"categorical",
146-
"bool"
147-
)
148-
)
149-
stopifnot(is.character(description) && length(description) == 1)
135+
checkmate::assert_character(name, len = 1)
136+
checkmate::assert_character(type, len = 1)
137+
checkmate::assert_subset(type, c(
138+
"text",
139+
"int",
140+
"float",
141+
"date",
142+
"epiweek",
143+
"categorical",
144+
"bool"
145+
))
146+
checkmate::assert_character(description, len = 1)
150147
structure(
151148
list(
152149
name = name,
@@ -166,15 +163,15 @@ print.EpidataFieldInfo <- function(x, ...) {
166163
}
167164

168165
#' @importFrom stats na.omit
169-
parse_value <- function(info, value, disable_date_parsing = FALSE) {
166+
parse_value <- function(info, value, disable_date_parsing = FALSE, reference_week_day = 1) {
170167
stopifnot(inherits(info, "EpidataFieldInfo"))
171168

172169
if (is.null(value)) {
173170
return(value)
174171
} else if (info$type == "date" && !disable_date_parsing && !inherits(value, "Date")) {
175172
return(parse_api_date(value))
176173
} else if (info$type == "epiweek" && !disable_date_parsing && !inherits(value, "Date")) {
177-
return(parse_api_week(value))
174+
return(parse_api_week(value, reference_week_day = reference_week_day))
178175
} else if (info$type == "bool") {
179176
return(as.logical(value))
180177
} else if (info$type == "int") {
@@ -200,7 +197,7 @@ parse_value <- function(info, value, disable_date_parsing = FALSE) {
200197
}
201198

202199
#' @importFrom purrr map_chr
203-
parse_data_frame <- function(epidata_call, df, disable_date_parsing = FALSE) {
200+
parse_data_frame <- function(epidata_call, df, disable_date_parsing = FALSE, reference_week_day = 1) {
204201
stopifnot(inherits(epidata_call, "epidata_call"))
205202
meta <- epidata_call$meta
206203
df <- as.data.frame(df)
@@ -227,7 +224,12 @@ parse_data_frame <- function(epidata_call, df, disable_date_parsing = FALSE) {
227224
for (i in seq_len(length(meta))) {
228225
info <- meta[[i]]
229226
if (info$name %in% columns) {
230-
df[[info$name]] <- parse_value(info, df[[info$name]], disable_date_parsing = disable_date_parsing)
227+
df[[info$name]] <- parse_value(
228+
info,
229+
df[[info$name]],
230+
disable_date_parsing = disable_date_parsing,
231+
reference_week_day = reference_week_day
232+
)
231233
}
232234
}
233235
df
@@ -254,14 +256,15 @@ parse_api_date <- function(value) {
254256

255257
#' parse_api_week converts an integer to a date
256258
#' @param value value to be converted to an epiweek
259+
#' @param reference_week_day the day of the week to use as the reference day. Defaults to Sunday.
257260
#' @return a date
258261
#' @importFrom MMWRweek MMWRweek2Date
259262
#' @keywords internal
260-
parse_api_week <- function(value) {
263+
parse_api_week <- function(value, reference_week_day = 1) {
261264
v <- as.integer(value)
262265
years <- floor(v / 100)
263266
weeks <- v - (years * 100)
264-
MMWRweek::MMWRweek2Date(years, weeks)
267+
MMWRweek::MMWRweek2Date(years, weeks, MMWRday = reference_week_day)
265268
}
266269

267270
#' @importFrom checkmate test_character test_class test_date test_integerish test_list

man/fetch_args_list.Rd

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/parse_api_week.Rd

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-epidatacall.R

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ test_that("fetch_args", {
4343
dry_run = FALSE,
4444
debug = FALSE,
4545
format_type = "json",
46-
refresh_cache = FALSE
46+
refresh_cache = FALSE,
47+
reference_week_day = 1
4748
),
4849
class = "fetch_args"
4950
)
@@ -59,7 +60,8 @@ test_that("fetch_args", {
5960
dry_run = TRUE,
6061
debug = TRUE,
6162
format_type = "classic",
62-
refresh_cache = TRUE
63+
refresh_cache = TRUE,
64+
reference_week_day = 1
6365
),
6466
structure(
6567
list(
@@ -72,7 +74,8 @@ test_that("fetch_args", {
7274
dry_run = TRUE,
7375
debug = TRUE,
7476
format_type = "classic",
75-
refresh_cache = TRUE
77+
refresh_cache = TRUE,
78+
reference_week_day = 1
7679
),
7780
class = "fetch_args"
7881
)

tests/testthat/test-model.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ test_that("parse_api_date handles missing values appropriately", {
148148
expect_identical(parse_api_date(NA), as.Date(NA))
149149
})
150150

151+
test_that("parse_api_week returns the expected day of the week", {
152+
expect_identical(parse_api_week(202005) %>% weekdays(), "Sunday")
153+
expect_identical(parse_api_week(202005, 4) %>% weekdays(), "Wednesday")
154+
expect_identical(parse_api_week(202005, 7) %>% weekdays(), "Saturday")
155+
})
156+
151157
test_that("date_to_epiweek accepts str and int input", {
152158
expect_identical(date_to_epiweek("20200101"), 202001)
153159
expect_identical(date_to_epiweek(20200101), 202001)

0 commit comments

Comments
 (0)