Skip to content

Commit daeb5df

Browse files
authored
Merge pull request #533 from cmu-delphi/lcb/hint-on-nontidy-f-force-errors
feat(epi[x]_slide): hint on forgotten syntax specifying comp
2 parents 6237036 + f4ce52e commit daeb5df

File tree

8 files changed

+150
-16
lines changed

8 files changed

+150
-16
lines changed

DESCRIPTION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: epiprocess
33
Title: Tools for basic signal processing in epidemiology
4-
Version: 0.9.2
4+
Version: 0.9.3
55
Authors@R: c(
66
person("Jacob", "Bien", role = "ctb"),
77
person("Logan", "Brooks", , "[email protected]", role = c("aut", "cre")),

NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Pre-1.0.0 numbering scheme: 0.x will indicate releases, while 0.x.y will indicat
1212

1313
## Improvements
1414

15+
- `epi_slide` and `epix_slide` now provide some hints if you forget a `~` when
16+
using a formula to specify the slide computation, and other bits of forgotten
17+
syntax.
1518
- Improved validation of `.window_size` arguments.
1619

1720
# epiprocess 0.9

R/grouped_epi_archive.R

+3-2
Original file line numberDiff line numberDiff line change
@@ -312,14 +312,15 @@ epix_slide.grouped_epi_archive <- function(
312312
cli_abort("If `f` is missing then a computation must be specified via `...`.")
313313
}
314314

315-
.slide_comp <- as_diagonal_slide_computation(quosures)
315+
.f_arg <- ".f" # dummy val, shouldn't be used since we're not using `.f`
316+
.slide_comp <- as_diagonal_slide_computation(quosures, .f_arg = .f_arg)
316317
# Magic value that passes zero args as dots in calls below. Equivalent to
317318
# `... <- missing_arg()`, but use `assign` to avoid warning about
318319
# improper use of dots.
319320
assign("...", missing_arg())
320321
} else {
321322
used_data_masking <- FALSE
322-
.slide_comp <- as_diagonal_slide_computation(.f, ...)
323+
.slide_comp <- as_diagonal_slide_computation(.f, ..., .f_arg = caller_arg(.f))
323324
}
324325

325326
# Computation for one group, one time value

R/slide.R

+3-1
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,16 @@ epi_slide <- function(
147147
}
148148

149149
.f <- quosures
150+
.f_arg <- ".f" # dummy val, shouldn't be used since we're not using `.f`
150151
# Magic value that passes zero args as dots in calls below. Equivalent to
151152
# `... <- missing_arg()`, but `assign` avoids warning about improper use of
152153
# dots.
153154
assign("...", missing_arg())
154155
} else {
155156
used_data_masking <- FALSE
157+
.f_arg <- caller_arg(.f)
156158
}
157-
.slide_comp <- as_time_slide_computation(.f, ...)
159+
.slide_comp <- as_time_slide_computation(.f, ..., .f_arg = .f_arg)
158160

159161
.align <- rlang::arg_match(.align)
160162
time_type <- attr(.x, "metadata")$time_type

R/utils.R

+55-10
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,48 @@ assert_sufficient_f_args <- function(.f, ..., .ref_time_value_label) {
358358
#' @importFrom rlang is_function new_function f_env is_environment missing_arg
359359
#' f_rhs is_formula caller_arg caller_env
360360
#' @keywords internal
361-
as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_time_value_label) {
362-
arg <- caller_arg(.f)
363-
call <- caller_env()
361+
as_slide_computation <- function(.f, ...,
362+
.f_arg = caller_arg(.f), .call = caller_env(),
363+
.ref_time_value_long_varnames, .ref_time_value_label) {
364+
if (".col_names" %in% rlang::call_args_names(rlang::call_match())) {
365+
cli_abort(
366+
c("{.code epi_slide} and {.code epix_slide} do not support `.col_names`;
367+
consider:",
368+
"*" = "using {.code epi_slide_mean}, {.code epi_slide_sum}, or
369+
{.code epi_slide_opt}, if applicable",
370+
"*" = "using {.code .f = ~ .x %>%
371+
dplyr::reframe(across(your_col_names, list(your_func_name = your_func)))}"
372+
),
373+
call = .call,
374+
class = "epiprocess__as_slide_computation__given_.col_names"
375+
)
376+
}
377+
378+
f_arg <- .f_arg # for cli interpolation, avoid dot prefix; # nolint: object_usage_linter
379+
withCallingHandlers(
380+
{
381+
force(.f)
382+
},
383+
error = function(e) {
384+
cli_abort(
385+
c("Failed to convert {.code {f_arg}} to a slide computation.",
386+
"*" = "If you were trying to use the formula interface,
387+
maybe you forgot a tilde at the beginning.",
388+
"*" = "If you were trying to use the tidyeval interface,
389+
maybe you forgot to specify the name,
390+
e.g.: `my_output_col_name =`. Note that `.col_names`
391+
is not supported.",
392+
"*" = "If you were trying to use advanced features of the
393+
tidyeval interface such as `!! name_variable :=`,
394+
maybe you forgot the required leading comma.",
395+
"*" = "Something else could have gone wrong; see below."
396+
),
397+
parent = e,
398+
call = .call,
399+
class = "epiprocess__as_slide_computation__error_forcing_.f"
400+
)
401+
}
402+
)
364403

365404
if (rlang::is_quosures(.f)) {
366405
quosures <- rlang::quos_auto_name(.f) # resolves := among other things
@@ -463,10 +502,10 @@ as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_ti
463502
}
464503

465504
if (length(.f) > 2) {
466-
cli_abort("{.code {arg}} must be a one-sided formula",
505+
cli_abort("{.code {f_arg}} must be a one-sided formula",
467506
class = "epiprocess__as_slide_computation__formula_is_twosided",
468507
epiprocess__f = .f,
469-
call = call
508+
.call = .call
470509
)
471510
}
472511
if (rlang::dots_n(...) > 0L) {
@@ -486,7 +525,7 @@ as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_ti
486525
class = "epiprocess__as_slide_computation__formula_has_no_env",
487526
epiprocess__f = .f,
488527
epiprocess__f_env = env,
489-
arg = arg, call = call
528+
.f_arg = .f_arg, .call = .call
490529
)
491530
}
492531

@@ -513,26 +552,32 @@ as_slide_computation <- function(.f, ..., .ref_time_value_long_varnames, .ref_ti
513552
class = "epiprocess__as_slide_computation__cant_convert_catchall",
514553
epiprocess__f = .f,
515554
epiprocess__f_class = class(.f),
516-
arg = arg,
517-
call = call
555+
.f_arg = .f_arg,
556+
.call = .call
518557
)
519558
}
520559

521560
#' @rdname as_slide_computation
561+
#' @importFrom rlang caller_arg caller_env
522562
#' @keywords internal
523-
as_time_slide_computation <- function(.f, ...) {
563+
as_time_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = caller_env()) {
524564
as_slide_computation(
525565
.f, ...,
566+
.f_arg = .f_arg,
567+
.call = .call,
526568
.ref_time_value_long_varnames = ".ref_time_value",
527569
.ref_time_value_label = "reference time value"
528570
)
529571
}
530572

531573
#' @rdname as_slide_computation
574+
#' @importFrom rlang caller_arg caller_env
532575
#' @keywords internal
533-
as_diagonal_slide_computation <- function(.f, ...) {
576+
as_diagonal_slide_computation <- function(.f, ..., .f_arg = caller_arg(.f), .call = caller_env()) {
534577
as_slide_computation(
535578
.f, ...,
579+
.f_arg = .f_arg,
580+
.call = .call,
536581
.ref_time_value_long_varnames = c(".version", ".ref_time_value"),
537582
.ref_time_value_label = "version"
538583
)

man/as_slide_computation.Rd

+14-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/utils.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# as_slide_computation raises errors as expected
2+
3+
Code
4+
toy_edf %>% group_by(geo_value) %>% epi_slide(.window_size = 7, mean,
5+
.col_names = "value")
6+
Condition
7+
Error in `epi_slide()`:
8+
! `epi_slide` and `epix_slide` do not support `.col_names`; consider:
9+
* using `epi_slide_mean`, `epi_slide_sum`, or `epi_slide_opt`, if applicable
10+
* using `.f = ~ .x %>% dplyr::reframe(across(your_col_names, list(your_func_name = your_func)))`
11+
12+
---
13+
14+
Code
15+
toy_edf %>% group_by(geo_value) %>% epi_slide(.window_size = 7, tibble(
16+
slide_value = mean(.x$value)))
17+
Condition
18+
Error in `epi_slide()`:
19+
! Failed to convert `tibble(slide_value = mean(.x$value))` to a slide computation.
20+
* If you were trying to use the formula interface, maybe you forgot a tilde at the beginning.
21+
* 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.
22+
* If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, maybe you forgot the required leading comma.
23+
* Something else could have gone wrong; see below.
24+
Caused by error:
25+
! object '.x' not found
26+
27+
---
28+
29+
Code
30+
toy_archive %>% epix_slide(tibble(slide_value = mean(.x$value)))
31+
Condition
32+
Error in `epix_slide()`:
33+
! Failed to convert `.f` to a slide computation.
34+
* If you were trying to use the formula interface, maybe you forgot a tilde at the beginning.
35+
* 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.
36+
* If you were trying to use advanced features of the tidyeval interface such as `!! name_variable :=`, maybe you forgot the required leading comma.
37+
* Something else could have gone wrong; see below.
38+
Caused by error:
39+
! object '.x' not found
40+

tests/testthat/test-utils.R

+31
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,37 @@ test_that("as_slide_computation raises errors as expected", {
230230
expect_error(as_time_slide_computation(5),
231231
class = "epiprocess__as_slide_computation__cant_convert_catchall"
232232
)
233+
234+
# helper to make initial snapshots less error-prone:
235+
expect_error_snapshot <- function(x, class) {
236+
x_quo <- rlang::enquo(x)
237+
rlang::inject(expect_error(!!x_quo, class = class)) # quick sanity check on class
238+
rlang::inject(expect_snapshot(!!x_quo, error = TRUE)) # don't need cnd_class = TRUE since checked above
239+
}
240+
241+
# If `.f` doesn't look like tidyeval and we fail to force it, then we hint to
242+
# the user some potential problems:
243+
toy_edf <- tibble(geo_value = 1, time_value = c(1, 2), value = 1:2) %>%
244+
as_epi_df(as_of = 1)
245+
toy_archive <- tibble(version = c(1, 2, 2), geo_value = 1, time_value = c(1, 1, 2), value = 1:3) %>%
246+
as_epi_archive()
247+
expect_error_snapshot(
248+
toy_edf %>%
249+
group_by(geo_value) %>%
250+
epi_slide(.window_size = 7, mean, .col_names = "value"),
251+
class = "epiprocess__as_slide_computation__given_.col_names"
252+
)
253+
expect_error_snapshot(
254+
toy_edf %>%
255+
group_by(geo_value) %>%
256+
epi_slide(.window_size = 7, tibble(slide_value = mean(.x$value))),
257+
class = "epiprocess__as_slide_computation__error_forcing_.f"
258+
)
259+
expect_error_snapshot(
260+
toy_archive %>%
261+
epix_slide(tibble(slide_value = mean(.x$value))),
262+
class = "epiprocess__as_slide_computation__error_forcing_.f"
263+
)
233264
})
234265

235266
test_that("as_slide_computation works", {

0 commit comments

Comments
 (0)