Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Djm/autoplot #382

Merged
merged 31 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9ede25a
add S3 method to quickly access the keys in an epi_df or epi_archive
dajmcdon Nov 27, 2023
96c7500
grab autoplot method from ggplot2 and export
dajmcdon Nov 27, 2023
1638430
simplify enlist()
dajmcdon Nov 27, 2023
a3cb78b
add a few argument checking functions, see also #380
dajmcdon Nov 27, 2023
f29936c
add autoplot() for epi_df's
dajmcdon Nov 27, 2023
cc58ddc
document()
dajmcdon Nov 27, 2023
d29b79f
add new functions to pkgdown
dajmcdon Nov 27, 2023
54b9a44
merge dev
dajmcdon Jan 19, 2024
becc554
style: run styler
dajmcdon Jan 19, 2024
ede6c9c
rename epi_keys to key_colnames
dajmcdon Jan 19, 2024
989ee8c
rename based on review
dajmcdon Jan 19, 2024
3c68f1d
pkgdown fix
dajmcdon Jan 19, 2024
f6464c0
bugfix: add missing arg_is_numeric
dajmcdon Jan 25, 2024
b3d876d
bugfix: remove ability to plot non-numeric vars
dajmcdon Jan 25, 2024
b1a2eef
remove unused
dajmcdon Jan 25, 2024
d30fc85
redocument
dajmcdon Jan 25, 2024
f292713
class warnings and errors
nmdefries Jan 25, 2024
42da786
test autoplot warnings and errors
nmdefries Jan 25, 2024
7cd1d25
raise 'plot automatically' error only when dots are empty because use…
nmdefries Jan 25, 2024
9409ae7
wrap cols list in all_of to suppress warning
nmdefries Jan 25, 2024
39f3704
check int positive
nmdefries Jan 26, 2024
2e301ad
test utils-arg
nmdefries Jan 25, 2024
0bb93e2
refactor: use checkmate checks
dshemetov Jan 26, 2024
8671c0c
refactor: move checkmate imports to one place
dshemetov Jan 26, 2024
3040eba
Merge pull request #410 from cmu-delphi/ds/checks
dshemetov Jan 26, 2024
1d45d65
utils-arg removed in #410, so no longer need to test
nmdefries Jan 29, 2024
7c864a1
Merge pull request #409 from cmu-delphi/ndefries/autoplot-tests
nmdefries Jan 29, 2024
35525c0
merge dev
dajmcdon Feb 3, 2024
a53e981
roxygen note
dajmcdon Feb 6, 2024
ab96bad
redocument
dajmcdon Feb 6, 2024
c84d678
fix: follow instructions for @docType deprecated error
dajmcdon Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ Description: This package introduces a common data structure for epidemiological
License: MIT + file LICENSE
Copyright: file inst/COPYRIGHTS
Imports:
checkmate,
cli,
data.table,
dplyr (>= 1.0.0),
fabletools,
feasts,
generics,
genlasso,
ggplot2,
lifecycle (>= 1.0.1),
lubridate,
magrittr,
Expand All @@ -49,7 +51,6 @@ Imports:
Suggests:
covidcast,
epidatr,
ggplot2,
knitr,
outbreaks,
rmarkdown,
Expand All @@ -66,12 +67,13 @@ Config/testthat/edition: 3
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
RoxygenNote: 7.3.1
Depends:
R (>= 2.10)
URL: https://cmu-delphi.github.io/epiprocess/
Collate:
'archive.R'
'autoplot.R'
'correlation.R'
'data.R'
'epi_df.R'
Expand All @@ -80,6 +82,7 @@ Collate:
'methods-epi_archive.R'
'grouped_epi_archive.R'
'growth_rate.R'
'key_colnames.R'
'methods-epi_df.R'
'outliers.R'
'reexports.R'
Expand Down
12 changes: 12 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ S3method(as_epi_df,tbl_df)
S3method(as_epi_df,tbl_ts)
S3method(as_tibble,epi_df)
S3method(as_tsibble,epi_df)
S3method(autoplot,epi_df)
S3method(dplyr_col_modify,col_modify_recorder_df)
S3method(dplyr_col_modify,epi_df)
S3method(dplyr_reconstruct,epi_df)
Expand All @@ -20,6 +21,10 @@ S3method(group_by,grouped_epi_archive)
S3method(group_by_drop_default,grouped_epi_archive)
S3method(group_modify,epi_df)
S3method(groups,grouped_epi_archive)
S3method(key_colnames,data.frame)
S3method(key_colnames,default)
S3method(key_colnames,epi_archive)
S3method(key_colnames,epi_df)
S3method(next_after,Date)
S3method(next_after,integer)
S3method(print,epi_df)
Expand All @@ -34,6 +39,7 @@ export(arrange)
export(as_epi_archive)
export(as_epi_df)
export(as_tsibble)
export(autoplot)
export(detect_outlr)
export(detect_outlr_rm)
export(detect_outlr_stl)
Expand All @@ -51,6 +57,7 @@ export(growth_rate)
export(is_epi_archive)
export(is_epi_df)
export(is_grouped_epi_archive)
export(key_colnames)
export(max_version_with_row_in)
export(mutate)
export(new_epi_df)
Expand All @@ -61,6 +68,10 @@ export(slice)
export(ungroup)
export(unnest)
importFrom(R6,R6Class)
importFrom(checkmate,anyInfinite)
importFrom(checkmate,assert)
importFrom(checkmate,assert_character)
importFrom(checkmate,assert_int)
importFrom(cli,cli_inform)
importFrom(data.table,":=")
importFrom(data.table,address)
Expand Down Expand Up @@ -89,6 +100,7 @@ importFrom(dplyr,rename)
importFrom(dplyr,select)
importFrom(dplyr,slice)
importFrom(dplyr,ungroup)
importFrom(ggplot2,autoplot)
importFrom(lubridate,days)
importFrom(lubridate,weeks)
importFrom(magrittr,"%>%")
Expand Down
166 changes: 166 additions & 0 deletions R/autoplot.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#' Automatically plot an epi_df
#'
#' @param object An `epi_df`
nmdefries marked this conversation as resolved.
Show resolved Hide resolved
#' @param ... <[`tidy-select`][dplyr_tidy_select]> One or more unquoted
#' expressions separated by commas. 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.
#' @param .color_by Which variables should determine the color(s) used to plot
#' lines. Options include:
#' * `all_keys` - the default uses the interaction of any key variables
#' including the `geo_value`
#' * `geo_value` - `geo_value` only
#' * `other_keys` - any available keys that are not `geo_value`
#' * `.response` - the numeric variables (same as the y-axis)
#' * `all` - uses the interaction of all keys and numeric variables
dajmcdon marked this conversation as resolved.
Show resolved Hide resolved
#' * `none` - no coloring aesthetic is applied
#' @param .facet_by Similar to `.color_by` except that the default is to display
#' each numeric variable on a separate facet
#' @param .base_color Lines will be shown with this color. For example, with a
#' single numeric variable and faceting by `geo_value`, all locations would
#' share the same color line.
dajmcdon marked this conversation as resolved.
Show resolved Hide resolved
#' @param .max_facets Cut down of the number of facets displayed. Especially
#' useful for testing when there are many `geo_value`'s or keys.
#'
#' @return A ggplot object
#' @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,
#' .color_by = "none",
#' .facet_by = "geo_value"
#' )
#' autoplot(jhu_csse_daily_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,
#' .base_color = "red", .facet_by = "geo_value")
autoplot.epi_df <- function(
object, ...,
.color_by = c("all_keys", "geo_value", "other_keys", ".response", "all", "none"),
.facet_by = c(".response", "other_keys", "all_keys", "geo_value", "all", "none"),
.base_color = "#3A448F",
.max_facets = Inf) {
.color_by <- match.arg(.color_by)
.facet_by <- match.arg(.facet_by)

assert(anyInfinite(.max_facets), assert_int(.max_facets), combine = "or")
assert_character(.base_color, len = 1)

key_cols <- key_colnames(object)
non_key_cols <- setdiff(names(object), key_cols)
geo_and_other_keys <- kill_time_value(key_cols)

# --- check for numeric variables
allowed <- purrr::map_lgl(object[non_key_cols], is.numeric)
allowed <- allowed[allowed]
if (length(allowed) == 0 && rlang::dots_n(...) == 0L) {
cli::cli_abort("No numeric variables were available to plot automatically.",
class = "epiprocess__no_numeric_vars_available")
}
vars <- tidyselect::eval_select(rlang::expr(c(...)), object)
if (rlang::is_empty(vars)) { # find them automatically if unspecified
vars <- tidyselect::eval_select(names(allowed)[1], object)
cli::cli_warn(
"Plot variable was unspecified. Automatically selecting {.var {names(allowed)[1]}}.",
class = "epiprocess__unspecified_plot_var"
)
} else { # if variables were specified, ensure that they are numeric
ok <- names(vars) %in% names(allowed)
if (!any(ok)) {
cli::cli_abort(
"None of the requested variables {.var {names(vars)}} are numeric.",
class = "epiprocess__all_requested_vars_not_numeric"
)
} else if (!all(ok)) {
cli::cli_warn(c(
"Only the requested variables {.var {names(vars)[ok]}} are numeric.",
i = "`autoplot()` cannot display {.var {names(vars)[!ok]}}."
),
class = "epiprocess__some_requested_vars_not_numeric")
vars <- vars[ok]
}
}

# --- create a viable df to plot
pos <- tidyselect::eval_select(
rlang::expr(c("time_value", tidyselect::all_of(geo_and_other_keys), names(vars))), object
)
if (length(vars) > 1) {
object <- tidyr::pivot_longer(
object[pos], tidyselect::all_of(names(vars)),
values_to = ".response",
names_to = ".response_name"
)
} else {
object <- dplyr::rename(object[pos], .response := !!names(vars))
}
all_keys <- rlang::syms(as.list(geo_and_other_keys))
other_keys <- rlang::syms(as.list(setdiff(geo_and_other_keys, "geo_value")))
all_avail <- rlang::syms(as.list(c(geo_and_other_keys, ".response_name")))

object <- object %>%
dplyr::mutate(
.colours = switch(.color_by,
all_keys = interaction(!!!all_keys, sep = "/"),
geo_value = geo_value,
other_keys = interaction(!!!other_keys, sep = "/"),
all = interaction(!!!all_avail, sep = "/"),
NULL
),
.facets = switch(.facet_by,
all_keys = interaction(!!!all_keys, sep = "/"),
geo_value = as.factor(geo_value),
other_keys = interaction(!!!other_keys, sep = "/"),
all = interaction(!!!all_avail, sep = "/"),
NULL
)
)

if (.max_facets < Inf && ".facets" %in% names(object)) {
n_facets <- nlevels(object$.facets)
if (n_facets > .max_facets) {
top_n <- levels(as.factor(object$.facets))[seq_len(.max_facets)]
object <- dplyr::filter(object, .facets %in% top_n) %>%
dplyr::mutate(.facets = droplevels(.facets))
if (".colours" %in% names(object)) {
object <- dplyr::mutate(object, .colours = droplevels(.colours))
}
}
}

p <- ggplot2::ggplot(object, ggplot2::aes(x = .data$time_value)) +
ggplot2::theme_bw()

if (".colours" %in% names(object)) {
p <- p + ggplot2::geom_line(
ggplot2::aes(y = .data$.response, colour = .data$.colours),
key_glyph = "timeseries"
) +
ggplot2::scale_colour_viridis_d(name = "")
} else if (length(vars) > 1 && .color_by == ".response") {
p <- p +
ggplot2::geom_line(ggplot2::aes(
y = .data$.response, colour = .data$.response_name
)) +
ggplot2::scale_colour_viridis_d(name = "")
} else { # none
p <- p +
ggplot2::geom_line(ggplot2::aes(y = .data$.response), color = .base_color)
}

if (".facets" %in% names(object)) {
p <- p + ggplot2::facet_wrap(~.facets, scales = "free_y") +
ggplot2::ylab(names(vars))
if (.facet_by == "all") p <- p + ggplot2::ylab("")
} else if ((length(vars) > 1 && .facet_by == ".response")) {
p <- p + ggplot2::facet_wrap(~.response_name, scales = "free_y") +
ggplot2::ylab("")
} else {
p <- p + ggplot2::ylab(names(vars))
}
p
}
4 changes: 2 additions & 2 deletions R/epiprocess.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#' measured over space and time, and offers associated utilities to perform
#' basic signal processing tasks.
#'
#' @docType package
#' @importFrom checkmate assert assert_character assert_int anyInfinite
#' @name epiprocess
NULL
"_PACKAGE"
utils::globalVariables(c(".x", ".group_key", ".ref_time_value"))
40 changes: 40 additions & 0 deletions R/key_colnames.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#' Grab any keys associated to an epi_df
#'
#' @param x a data.frame, tibble, or epi_df
#' @param ... additional arguments passed on to methods
#'
#' @return If an `epi_df`, this returns all "keys". Otherwise `NULL`
#' @keywords internal
#' @export
key_colnames <- function(x, ...) {
UseMethod("key_colnames")
}

#' @export
key_colnames.default <- function(x, ...) {
character(0L)
}

#' @export
key_colnames.data.frame <- function(x, other_keys = character(0L), ...) {
assert_character(other_keys)
nm <- c("time_value", "geo_value", other_keys)
intersect(nm, colnames(x))
}

#' @export
key_colnames.epi_df <- function(x, ...) {
other_keys <- attr(x, "metadata")$other_keys
c("time_value", "geo_value", other_keys)
}

#' @export
key_colnames.epi_archive <- function(x, ...) {
other_keys <- attr(x, "metadata")$other_keys
c("time_value", "geo_value", other_keys)
}

kill_time_value <- function(v) {
assert_character(v)
v[v != "time_value"]
}
7 changes: 7 additions & 0 deletions R/reexports.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ dplyr::slice
#' @importFrom tidyr unnest
#' @export
tidyr::unnest


# ggplot2 -----------------------------------------------------------------

#' @importFrom ggplot2 autoplot
#' @export
ggplot2::autoplot
14 changes: 7 additions & 7 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,13 @@ quiet <- function(x) {

# Create an auto-named list
enlist <- function(...) {
x <- list(...)
n <- as.character(sys.call())[-1]
if (!is.null(n0 <- names(x))) {
n[n0 != ""] <- n0[n0 != ""]
}
names(x) <- n
return(x)
# converted to thin wrapper around
rlang::dots_list(
...,
.homonyms = "error",
nmdefries marked this conversation as resolved.
Show resolved Hide resolved
.named = TRUE,
.check_assign = TRUE
)
}

# Variable assignment from a list. NOT USED. Something is broken, this doesn't
Expand Down
6 changes: 5 additions & 1 deletion _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,13 @@ reference:
- archive_cases_dv_subset
- incidence_num_outlier_example
- contains("jhu_csse")
- title: Basic automatic plotting
- contents:
- autoplot.epi_df
- title: internal
contents:
- contents:
- epiprocess
- max_version_with_row_in
- next_after
- guess_period
- key_colnames
Loading
Loading