Skip to content

Commit a3e805d

Browse files
committed
refactor+doc: key_colnames and vignettes
* key_colnames order change * replace kill_time_value with exclude arg in key_colnames * move duplicate time_values check in epi_slide
1 parent dd19428 commit a3e805d

34 files changed

+755
-773
lines changed

.Rbuildignore

+2
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@
1616
^.lintr$
1717
^DEVELOPMENT.md$
1818
man-roxygen
19+
^.venv$
20+
^sandbox.R$

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ docs
1313
renv/
1414
renv.lock
1515
.Rprofile
16+
sandbox.R

DESCRIPTION

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ Imports:
5050
tidyselect (>= 1.2.0),
5151
tsibble,
5252
utils,
53-
vctrs
53+
vctrs,
54+
waldo
5455
Suggests:
5556
covidcast,
5657
devtools,

NAMESPACE

-2
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,8 @@ export("%>%")
5555
export(archive_cases_dv_subset)
5656
export(arrange)
5757
export(arrange_canonical)
58-
export(as_diagonal_slide_computation)
5958
export(as_epi_archive)
6059
export(as_epi_df)
61-
export(as_time_slide_computation)
6260
export(as_tsibble)
6361
export(autoplot)
6462
export(clone)

R/autoplot.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ autoplot.epi_df <- function(
5555

5656
key_cols <- key_colnames(object)
5757
non_key_cols <- setdiff(names(object), key_cols)
58-
geo_and_other_keys <- kill_time_value(key_cols)
58+
geo_and_other_keys <- key_colnames(object, exclude = "time_value")
5959

6060
# --- check for numeric variables
6161
allowed <- purrr::map_lgl(object[non_key_cols], is.numeric)

R/epi_df.R

+4-7
Original file line numberDiff line numberDiff line change
@@ -184,18 +184,14 @@ new_epi_df <- function(x = tibble::tibble(geo_value = character(), time_value =
184184
metadata$other_keys <- other_keys
185185

186186
# Reorder columns (geo_value, time_value, ...)
187-
if (sum(dim(x)) != 0) {
188-
cols_to_put_first <- c("geo_value", "time_value", other_keys)
189-
x <- x[, c(
190-
cols_to_put_first,
191-
# All other columns
192-
names(x)[!(names(x) %in% cols_to_put_first)]
193-
)]
187+
if (nrow(x) > 0) {
188+
x <- x %>% relocate(all_of(c("geo_value", other_keys, "time_value")), .before = 1)
194189
}
195190

196191
# Apply epi_df class, attach metadata, and return
197192
class(x) <- c("epi_df", class(x))
198193
attributes(x)$metadata <- metadata
194+
199195
return(x)
200196
}
201197

@@ -281,6 +277,7 @@ as_epi_df.tbl_df <- function(
281277
if (".time_value_counts" %in% other_keys) {
282278
cli_abort("as_epi_df: `other_keys` can't include \".time_value_counts\"")
283279
}
280+
284281
duplicated_time_values <- x %>%
285282
group_by(across(all_of(c("geo_value", "time_value", other_keys)))) %>%
286283
filter(dplyr::n() > 1) %>%

R/grouped_epi_archive.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,8 @@ epix_slide.grouped_epi_archive <- function(
397397
)),
398398
capture.output(print(waldo::compare(
399399
res[[comp_nms[[comp_i]]]], comp_value[[comp_i]],
400-
x_arg = rlang::expr_deparse(expr(`$`(label, !!sym(comp_nms[[comp_i]])))),
401-
y_arg = rlang::expr_deparse(expr(`$`(comp_value, !!sym(comp_nms[[comp_i]]))))
400+
x_arg = rlang::expr_deparse(dplyr::expr(`$`(label, !!sym(comp_nms[[comp_i]])))), # nolint: object_usage_linter
401+
y_arg = rlang::expr_deparse(dplyr::expr(`$`(comp_value, !!sym(comp_nms[[comp_i]]))))
402402
))),
403403
cli::format_message(c(
404404
"You likely want to rename or remove this column in your output, or debug why it has a different value."

R/key_colnames.R

+20-13
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,46 @@
22
#'
33
#' @param x a data.frame, tibble, or epi_df
44
#' @param ... additional arguments passed on to methods
5-
#'
6-
#' @return If an `epi_df`, this returns all "keys". Otherwise `NULL`
5+
#' @param other_keys an optional character vector of other keys to include
6+
#' @param exclude an optional character vector of keys to exclude
7+
#' @return If an `epi_df`, this returns all "keys". Otherwise `NULL`.
78
#' @keywords internal
89
#' @export
910
key_colnames <- function(x, ...) {
1011
UseMethod("key_colnames")
1112
}
1213

14+
#' @rdname key_colnames
15+
#' @method key_colnames default
1316
#' @export
1417
key_colnames.default <- function(x, ...) {
1518
character(0L)
1619
}
1720

21+
#' @rdname key_colnames
22+
#' @method key_colnames data.frame
1823
#' @export
19-
key_colnames.data.frame <- function(x, other_keys = character(0L), ...) {
24+
key_colnames.data.frame <- function(x, other_keys = character(0L), exclude = character(0L), ...) {
2025
assert_character(other_keys)
21-
nm <- c("geo_value", "time_value", other_keys)
26+
assert_character(exclude)
27+
nm <- setdiff(c("geo_value", other_keys, "time_value"), exclude)
2228
intersect(nm, colnames(x))
2329
}
2430

31+
#' @rdname key_colnames
32+
#' @method key_colnames epi_df
2533
#' @export
26-
key_colnames.epi_df <- function(x, ...) {
34+
key_colnames.epi_df <- function(x, exclude = character(0L), ...) {
35+
assert_character(exclude)
2736
other_keys <- attr(x, "metadata")$other_keys
28-
c("geo_value", "time_value", other_keys)
37+
setdiff(c("geo_value", other_keys, "time_value"), exclude)
2938
}
3039

40+
#' @rdname key_colnames
41+
#' @method key_colnames epi_archive
3142
#' @export
32-
key_colnames.epi_archive <- function(x, ...) {
43+
key_colnames.epi_archive <- function(x, exclude = character(0L), ...) {
44+
assert_character(exclude)
3345
other_keys <- attr(x, "metadata")$other_keys
34-
c("geo_value", "time_value", other_keys)
35-
}
36-
37-
kill_time_value <- function(v) {
38-
assert_character(v)
39-
v[v != "time_value"]
46+
setdiff(c("geo_value", other_keys, "time_value"), exclude)
4047
}

R/methods-epi_archive.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ epix_detailed_restricted_mutate <- function(.data, ...) {
731731
#' library(dplyr)
732732
#'
733733
#' # Reference time points for which we want to compute slide values:
734-
#' versions <- seq(as.Date("2020-06-01"),
734+
#' versions <- seq(as.Date("2020-06-02"),
735735
#' as.Date("2020-06-15"),
736736
#' by = "1 day"
737737
#' )
@@ -780,7 +780,7 @@ epix_detailed_restricted_mutate <- function(.data, ...) {
780780
#' .versions = versions
781781
#' ) %>%
782782
#' ungroup() %>%
783-
#' arrange(geo_value, time_value)
783+
#' arrange(geo_value, version)
784784
#'
785785
#' # --- Advanced: ---
786786
#'

R/methods-epi_df.R

+18-11
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ as_tibble.epi_df <- function(x, ...) {
4141
#' @export
4242
as_tsibble.epi_df <- function(x, key, ...) {
4343
if (missing(key)) key <- c("geo_value", attributes(x)$metadata$other_keys)
44-
return(as_tsibble(tibble::as_tibble(x),
45-
key = tidyselect::all_of(key), index = "time_value",
46-
...
47-
))
44+
return(
45+
as_tsibble(
46+
tibble::as_tibble(x),
47+
key = tidyselect::all_of(key), index = "time_value",
48+
...
49+
)
50+
)
4851
}
4952

5053
#' Base S3 methods for an `epi_df` object
@@ -150,10 +153,10 @@ dplyr_reconstruct.epi_df <- function(data, template) {
150153
# keep any grouping that has been applied:
151154
res <- NextMethod()
152155

153-
cn <- names(res)
156+
col_names <- names(res)
154157

155158
# Duplicate columns, cli_abort
156-
dup_col_names <- cn[duplicated(cn)]
159+
dup_col_names <- col_names[duplicated(col_names)]
157160
if (length(dup_col_names) != 0) {
158161
cli_abort(c(
159162
"Duplicate column names are not allowed",
@@ -163,7 +166,7 @@ dplyr_reconstruct.epi_df <- function(data, template) {
163166
))
164167
}
165168

166-
not_epi_df <- !("time_value" %in% cn) || !("geo_value" %in% cn)
169+
not_epi_df <- !("time_value" %in% col_names) || !("geo_value" %in% col_names)
167170

168171
if (not_epi_df) {
169172
# If we're calling on an `epi_df` from one of our own functions, we need to
@@ -182,7 +185,7 @@ dplyr_reconstruct.epi_df <- function(data, template) {
182185

183186
# Amend additional metadata if some other_keys cols are dropped in the subset
184187
old_other_keys <- attr(template, "metadata")$other_keys
185-
attr(res, "metadata")$other_keys <- old_other_keys[old_other_keys %in% cn]
188+
attr(res, "metadata")$other_keys <- old_other_keys[old_other_keys %in% col_names]
186189

187190
res
188191
}
@@ -424,9 +427,13 @@ arrange_col_canonical.epi_df <- function(x, ...) {
424427
x %>% dplyr::relocate(dplyr::all_of(cols), .before = 1)
425428
}
426429

430+
#' Group an `epi_df` object by default keys
431+
#' @param x an `epi_df`
432+
#' @param exclude character vector of column names to exclude from grouping
433+
#' @return a grouped `epi_df`
427434
#' @export
428-
group_epi_df <- function(x) {
429-
cols <- kill_time_value(key_colnames(x))
435+
group_epi_df <- function(x, exclude = character()) {
436+
cols <- key_colnames(x, exclude = exclude)
430437
x %>% group_by(across(all_of(cols)))
431438
}
432439

@@ -437,7 +444,7 @@ group_epi_df <- function(x) {
437444
#' the resulting `epi_df` will have `geo_value` set to `"total"`.
438445
#'
439446
#' @param .x an `epi_df`
440-
#' @param value_col character vector of the columns to aggregate
447+
#' @param sum_cols character vector of the columns to aggregate
441448
#' @param group_cols character vector of column names to group by. "time_value" is
442449
#' included by default.
443450
#' @return an `epi_df` object

R/outliers.R

+5-7
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,7 @@ detect_outlr <- function(x = seq_along(y), y,
161161
#' group_by(geo_value) %>%
162162
#' mutate(outlier_info = detect_outlr_rm(
163163
#' x = time_value, y = cases
164-
#' )) %>%
165-
#' unnest(outlier_info)
164+
#' ))
166165
detect_outlr_rm <- function(x = seq_along(y), y, n = 21,
167166
log_transform = FALSE,
168167
detect_negatives = FALSE,
@@ -189,7 +188,7 @@ detect_outlr_rm <- function(x = seq_along(y), y, n = 21,
189188

190189
# Calculate lower and upper thresholds and replacement value
191190
z <- z %>%
192-
epi_slide(fitted = median(y), .window_size = n, .align = "center") %>%
191+
epi_slide(fitted = median(y, na.rm = TRUE), .window_size = n, .align = "center") %>%
193192
dplyr::mutate(resid = y - fitted) %>%
194193
roll_iqr(
195194
n = n,
@@ -256,9 +255,8 @@ detect_outlr_rm <- function(x = seq_along(y), y, n = 21,
256255
#' group_by(geo_value) %>%
257256
#' mutate(outlier_info = detect_outlr_stl(
258257
#' x = time_value, y = cases,
259-
#' seasonal_period = 7
260-
#' )) %>% # weekly seasonality for daily data
261-
#' unnest(outlier_info)
258+
#' seasonal_period = 7 # weekly seasonality for daily data
259+
#' ))
262260
detect_outlr_stl <- function(x = seq_along(y), y,
263261
n_trend = 21,
264262
n_seasonal = 21,
@@ -359,7 +357,7 @@ roll_iqr <- function(z, n, detection_multiplier, min_radius,
359357

360358
z %>%
361359
epi_slide(
362-
roll_iqr = stats::IQR(resid),
360+
roll_iqr = stats::IQR(resid, na.rm = TRUE),
363361
.window_size = n, .align = "center"
364362
) %>%
365363
dplyr::mutate(

R/revision_analysis.R

+4-6
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ revision_summary <- function(epi_arch,
8181
should_compactify = TRUE) {
8282
arg <- names(eval_select(rlang::expr(c(...)), allow_rename = FALSE, data = epi_arch$DT))
8383
if (length(arg) == 0) {
84-
first_non_key <- !(names(epi_arch$DT) %in% c(key_colnames(epi_arch), "version"))
85-
arg <- names(epi_arch$DT)[first_non_key][1]
84+
# Choose the first column that's not a key or version
85+
arg <- setdiff(names(epi_arch$DT), c(key_colnames(epi_arch), "version"))[[1]]
8686
} else if (length(arg) > 1) {
8787
cli_abort("Not currently implementing more than one column at a time. Run each separately")
8888
}
@@ -99,11 +99,9 @@ revision_summary <- function(epi_arch,
9999
#
100100
# revision_tibble
101101
keys <- key_colnames(epi_arch)
102-
names(epi_arch$DT)
103102

104-
revision_behavior <-
105-
epi_arch$DT %>%
106-
select(c(geo_value, time_value, all_of(keys), version, !!arg))
103+
revision_behavior <- epi_arch$DT %>%
104+
select(all_of(unique(c("geo_value", "time_value", keys, "version", arg))))
107105
if (!is.null(min_waiting_period)) {
108106
revision_behavior <- revision_behavior %>%
109107
filter(abs(time_value - as.Date(epi_arch$versions_end)) >= min_waiting_period)

R/slide.R

+30-13
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ epi_slide <- function(
122122
assert_class(.x, "epi_df")
123123
if (checkmate::test_class(.x, "grouped_df")) {
124124
expected_group_keys <- .x %>%
125-
key_colnames() %>%
126-
kill_time_value() %>%
125+
key_colnames(exclude = "time_value") %>%
127126
sort()
128127
if (!identical(.x %>% group_vars() %>% sort(), expected_group_keys)) {
129128
cli_abort(
@@ -134,12 +133,11 @@ epi_slide <- function(
134133
)
135134
}
136135
} else {
137-
.x <- group_epi_df(.x)
136+
.x <- group_epi_df(.x, exclude = "time_value")
138137
}
139138
if (nrow(.x) == 0L) {
140139
return(.x)
141140
}
142-
143141
# If `.f` is missing, interpret ... as an expression for tidy evaluation
144142
if (missing(.f)) {
145143
used_data_masking <- TRUE
@@ -191,6 +189,20 @@ epi_slide <- function(
191189

192190
assert_logical(.all_rows, len = 1)
193191

192+
# Check for duplicated time values within groups
193+
duplicated_time_values <- .x %>%
194+
group_epi_df() %>%
195+
filter(dplyr::n() > 1) %>%
196+
ungroup()
197+
if (nrow(duplicated_time_values) > 0) {
198+
bad_data <- capture.output(duplicated_time_values)
199+
cli_abort(
200+
"as_epi_df: some groups in a resulting dplyr computation have duplicated time values.
201+
epi_df requires a unique time_value per group.",
202+
body = c("Sample groups:", bad_data)
203+
)
204+
}
205+
194206
# Begin handling completion. This will create a complete time index between
195207
# the smallest and largest time values in the data. This is used to ensure
196208
# that the slide function is called with a complete window of data. Each slide
@@ -241,7 +253,7 @@ epi_slide <- function(
241253
.keep = TRUE
242254
) %>%
243255
bind_rows() %>%
244-
filter(.data$.real) %>%
256+
filter(.real) %>%
245257
select(-.real) %>%
246258
arrange_col_canonical() %>%
247259
group_by(!!!.x_groups)
@@ -275,11 +287,16 @@ epi_slide_one_group <- function(
275287
missing_times <- all_dates[!(all_dates %in% .data_group$time_value)]
276288
.data_group <- bind_rows(
277289
.data_group,
278-
tibble(time_value = c(
279-
missing_times,
280-
.date_seq_list$pad_early_dates,
281-
.date_seq_list$pad_late_dates
282-
), .real = FALSE)
290+
dplyr::bind_cols(
291+
.group_key,
292+
tibble(
293+
time_value = c(
294+
missing_times,
295+
.date_seq_list$pad_early_dates,
296+
.date_seq_list$pad_late_dates
297+
), .real = FALSE
298+
)
299+
)
283300
) %>%
284301
arrange(.data$time_value)
285302

@@ -405,8 +422,8 @@ epi_slide_one_group <- function(
405422
)),
406423
capture.output(print(waldo::compare(
407424
res[[comp_nms[[comp_i]]]], slide_values[[comp_i]],
408-
x_arg = rlang::expr_deparse(expr(`$`(existing, !!sym(comp_nms[[comp_i]])))),
409-
y_arg = rlang::expr_deparse(expr(`$`(comp_value, !!sym(comp_nms[[comp_i]]))))
425+
x_arg = rlang::expr_deparse(dplyr::expr(`$`(existing, !!sym(comp_nms[[comp_i]])))), # nolint: object_usage_linter
426+
y_arg = rlang::expr_deparse(dplyr::expr(`$`(comp_value, !!sym(comp_nms[[comp_i]])))) # nolint: object_usage_linter
410427
))),
411428
cli::format_message(c(
412429
">" = "You likely want to rename or remove this column from your slide
@@ -711,7 +728,7 @@ epi_slide_opt <- function(
711728
# positions of user-provided `col_names` into string column names. We avoid
712729
# using `names(pos)` directly for robustness and in case we later want to
713730
# allow users to rename fields via tidyselection.
714-
if (class(quo_get_expr(enquo(.col_names))) == "character") {
731+
if (inherits(quo_get_expr(enquo(.col_names)), "character")) {
715732
pos <- eval_select(dplyr::all_of(.col_names), data = .x, allow_rename = FALSE)
716733
} else {
717734
pos <- eval_select(enquo(.col_names), data = .x, allow_rename = FALSE)

0 commit comments

Comments
 (0)