Skip to content

Commit d6e685a

Browse files
authored
Merge pull request #198 from cmu-delphi/wide-quantiles
Wide quantiles
2 parents 8108fa9 + e7db92b commit d6e685a

File tree

4 files changed

+128
-3
lines changed

4 files changed

+128
-3
lines changed

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export(layer_threshold)
135135
export(nested_quantiles)
136136
export(new_default_epi_recipe_blueprint)
137137
export(new_epi_recipe_blueprint)
138+
export(pivot_quantiles)
138139
export(prep)
139140
export(quantile_reg)
140141
export(remove_frosting)

R/dist_quantiles.R

+66-3
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ extrapolate_quantiles.dist_quantiles <- function(x, p, ...) {
107107
new_quantiles(q = c(qvals, q), tau = c(tau, p))
108108
}
109109

110-
110+
is_dist_quantiles <- function(x) {
111+
is_distribution(x) && all(stats::family(x) == "quantiles")
112+
}
111113

112114

113115
#' Turn a a vector of quantile distributions into a list-col
@@ -124,8 +126,7 @@ extrapolate_quantiles.dist_quantiles <- function(x, p, ...) {
124126
#' edf_nested <- edf %>% dplyr::mutate(q = nested_quantiles(q))
125127
#' edf_nested %>% tidyr::unnest(q)
126128
nested_quantiles <- function(x) {
127-
stopifnot(is_distribution(x),
128-
all(stats::family(x) == "quantiles"))
129+
stopifnot(is_dist_quantiles(x))
129130
distributional:::dist_apply(x, .f = function(z) {
130131
tibble::as_tibble(vec_data(z)) %>%
131132
dplyr::mutate(dplyr::across(tidyselect::everything(), as.double)) %>%
@@ -134,6 +135,68 @@ nested_quantiles <- function(x) {
134135
}
135136

136137

138+
#' Pivot columns containing `dist_quantile` wider
139+
#'
140+
#' Any selected columns that contain `dist_quantiles` will be "widened" with
141+
#' the "taus" (quantile) serving as names and the values in the data frame.
142+
#' When pivoting multiple columns, the original column name will be used as
143+
#' a prefix.
144+
#'
145+
#' @param .data A data frame, or a data frame extension such as a tibble or
146+
#' epi_df.
147+
#' @param ... <[`tidy-select`][dplyr::dplyr_tidy_select]> One or more unquoted
148+
#' expressions separated by commas. Variable names can be used as if they
149+
#' were positions in the data frame, so expressions like `x:y` can
150+
#' be used to select a range of variables. Any selected columns should
151+
#'
152+
#' @return An object of the same class as `.data`
153+
#' @export
154+
#'
155+
#' @examples
156+
#' d1 <- c(dist_quantiles(1:3, 1:3 / 4), dist_quantiles(2:4, 1:3 / 4))
157+
#' d2 <- c(dist_quantiles(2:4, 2:4 / 5), dist_quantiles(3:5, 2:4 / 5))
158+
#' tib <- tibble::tibble(g = c("a", "b"), d1 = d1, d2 = d2)
159+
#'
160+
#' pivot_quantiles(tib, c("d1", "d2"))
161+
#' pivot_quantiles(tib, tidyselect::starts_with("d"))
162+
#' pivot_quantiles(tib, d2)
163+
pivot_quantiles <- function(.data, ...) {
164+
expr <- rlang::expr(c(...))
165+
cols <- names(tidyselect::eval_select(expr, .data))
166+
dqs <- map_lgl(cols, ~ is_dist_quantiles(.data[[.x]]))
167+
if (!all(dqs)) {
168+
nms <- cols[!dqs]
169+
cli::cli_abort(
170+
"Variables(s) {.var {nms}} are not `dist_quantiles`. Cannot pivot them."
171+
)
172+
}
173+
.data <- .data %>%
174+
dplyr::mutate(dplyr::across(tidyselect::all_of(cols), nested_quantiles))
175+
checks <- map_lgl(cols, ~ diff(range(vctrs::list_sizes(.data[[.x]]))) == 0L)
176+
if (!all(checks)) {
177+
nms <- cols[!checks]
178+
cli::cli_abort(
179+
c("Quantiles must be the same length and have the same set of taus.",
180+
i = "Check failed for variables(s) {.var {nms}}."))
181+
}
182+
if (length(cols) > 1L) {
183+
for (col in cols) {
184+
.data <- .data %>%
185+
tidyr::unnest(tidyselect::all_of(col)) %>%
186+
tidyr::pivot_wider(
187+
names_from = "tau", values_from = "q",
188+
names_prefix = paste0(col, "_")
189+
)
190+
}
191+
} else {
192+
.data <- .data %>%
193+
tidyr::unnest(tidyselect::all_of(cols)) %>%
194+
tidyr::pivot_wider(names_from = "tau", values_from = "q")
195+
}
196+
.data
197+
}
198+
199+
137200

138201

139202
#' @export

man/pivot_quantiles.Rd

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

tests/testthat/test-pivot_quantiles.R

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
test_that("quantile pivotting behaves", {
2+
tib <- tibble::tibble(a = 1:5, b = 6:10)
3+
expect_error(pivot_quantiles(tib, a))
4+
tib$c <- rep(dist_normal(), 5)
5+
expect_error(pivot_quantiles(tib, c))
6+
7+
d1 <- c(dist_quantiles(1:3, 1:3 / 4), dist_quantiles(2:5, 1:4 / 5))
8+
# different quantiles
9+
tib <- tib[1:2,]
10+
tib$d1 <- d1
11+
expect_error(pivot_quantiles(tib, d1))
12+
13+
d1 <- c(dist_quantiles(1:3, 1:3 / 4), dist_quantiles(2:4, 2:4 / 4))
14+
tib$d1 <- d1
15+
# would want to error (mismatched quantiles), but hard to check efficiently
16+
expect_silent(pivot_quantiles(tib, d1))
17+
18+
d1 <- c(dist_quantiles(1:3, 1:3 / 4), dist_quantiles(2:4, 1:3 / 4))
19+
d2 <- c(dist_quantiles(2:4, 2:4 / 5), dist_quantiles(3:5, 2:4 / 5))
20+
tib <- tibble::tibble(g = c("a", "b"), d1 = d1, d2 = d2)
21+
22+
23+
expect_length(pivot_quantiles(tib, c("d1", "d2")), 7L)
24+
expect_length(pivot_quantiles(tib, tidyselect::starts_with("d")), 7L)
25+
expect_length(pivot_quantiles(tib, d2), 5L)
26+
})

0 commit comments

Comments
 (0)