Skip to content

Commit d242eed

Browse files
committed
renaming in dist_quantiles
1 parent 015b0ea commit d242eed

File tree

2 files changed

+110
-79
lines changed

2 files changed

+110
-79
lines changed

R/dist_quantiles.R

Lines changed: 108 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,94 @@
1-
new_quantiles <- function(q = double(), tau = double()) {
2-
arg_is_probabilities(tau)
3-
4-
vec_cast(q, double())
5-
vec_cast(tau, double())
6-
stopifnot(length(q) == length(tau))
7-
stopifnot(!vec_duplicate_any(tau))
8-
if (is.unsorted(tau)) {
9-
o <- vec_order(tau)
10-
q <- q[o]
11-
tau <- tau[o]
1+
new_quantiles <- function(values = double(), quantile_values = double()) {
2+
arg_is_probabilities(quantile_values)
3+
4+
vec_cast(values, double())
5+
vec_cast(quantile_values, double())
6+
stopifnot(length(values) == length(quantile_values))
7+
stopifnot(!vec_duplicate_any(quantile_values))
8+
if (is.unsorted(quantile_values)) {
9+
o <- vec_order(quantile_values)
10+
values <- values[o]
11+
quantile_values <- quantile_values[o]
1212
}
13-
if (is.unsorted(q, na.rm = TRUE)) {
14-
rlang::abort("`q[order(tau)]` produces unsorted quantiles.")
13+
if (is.unsorted(values, na.rm = TRUE)) {
14+
cli::cli_abort("`values[order(quantile_values)]` produces unsorted quantiles.")
1515
}
1616

17-
new_rcrd(list(q = q, tau = tau),
18-
class = c("dist_quantiles", "dist_default")
17+
new_rcrd(list(values = values, quantile_values = quantile_values),
18+
class = c("dist_quantiles", "dist_default")
1919
)
2020
}
2121

22+
2223
#' @export
2324
vec_ptype_abbr.dist_quantiles <- function(x, ...) "dist_qntls"
2425
#' @export
2526
vec_ptype_full.dist_quantiles <- function(x, ...) "dist_quantiles"
2627

2728
#' @export
2829
format.dist_quantiles <- function(x, digits = 2, ...) {
29-
q <- field(x, "q")
30-
tau <- field(x, "tau")
31-
rng <- range(tau, na.rm = TRUE)
32-
paste0("[", round(rng[1], digits), ", ", round(rng[2], digits), "]<q-rng>")
30+
q <- field(x, "values")
31+
m <- suppressWarnings(median(x))
32+
paste0("quantiles(", round(m, digits), ")[", vctrs::vec_size(q), "]")
3333
}
3434

3535

36-
37-
3836
#' A distribution parameterized by a set of quantiles
3937
#'
40-
#' @param x A vector of values
41-
#' @param tau A vector of probabilities corresponding to `x`
38+
#' @param values A vector of values
39+
#' @param quantile_values A vector of probabilities corresponding to `values`
4240
#'
4341
#' @export
4442
#'
45-
#' @import vctrs
4643
#' @examples
4744
#' dstn <- dist_quantiles(list(1:4, 8:11), list(c(.2, .4, .6, .8)))
4845
#' quantile(dstn, p = c(.1, .25, .5, .9))
4946
#' median(dstn)
5047
#'
5148
#' # it's a bit annoying to inspect the data
52-
#' vctrs::vec_data(vctrs::vec_data(dstn[1])[[1]])
53-
dist_quantiles <- function(x, tau) {
54-
if (!is.list(x)) x <- list(x)
55-
if (!is.list(tau)) tau <- list(tau)
56-
57-
x <- as_list_of(x, .ptype = double())
58-
tau <- as_list_of(tau, .ptype = double())
59-
args <- vec_recycle_common(x = x, tau = tau)
60-
qntls <- as_list_of(map2(args$x, args$tau, new_quantiles))
49+
#' distributional::parameters(dstn[1])
50+
#' nested_quantiles(dstn[1])[[1]]
51+
#'
52+
#' dist_quantiles(1:4, 1:4 / 5)
53+
#' dist_quantiles(1:4, c(1, 3, 2, 4) / 5)
54+
dist_quantiles <- function(values, quantile_values) {
55+
if (!is.list(values)) values <- list(values)
56+
if (!is.list(quantile_values)) quantile_values <- list(quantile_values)
57+
58+
values <- as_list_of(values, .ptype = double())
59+
quantile_values <- as_list_of(quantile_values, .ptype = double())
60+
args <- vec_recycle_common(values = values, quantile_values = quantile_values)
61+
qntls <- as_list_of(map2(args$values, args$quantile_values, new_quantiles))
6162
new_vctr(qntls, class = "distribution")
6263
}
6364

65+
validate_dist_quantiles <- function(values, quantile_values) {
66+
map(quantile_values, arg_is_probabilities)
67+
common_length <- vctrs::vec_size_common( # aborts internally
68+
values = values,
69+
quantile_values = quantile_values
70+
)
71+
length_diff <- vctrs::list_sizes(values) != vctrs::list_sizes(quantile_values)
72+
if (any(length_diff)) {
73+
cli::cli_abort(c(
74+
"`values` and `quantile_values` must have common length.",
75+
i = "Mismatches found at position(s): {.val {which(length_diff)}}."
76+
))
77+
}
78+
tau_duplication <- map_lgl(quantile_values, vctrs::vec_duplicate_any)
79+
if (any(tau_duplication)) {
80+
cli::cli_abort(c(
81+
"`quantile_values` must not be duplicated.",
82+
i = "Duplicates found at position(s): {.val {which(tau_duplication)}}."
83+
))
84+
}
85+
}
86+
6487

6588
#' Summarize a distribution with a set of quantiles
6689
#'
6790
#' @param x a `distribution` vector
68-
#' @param p a vector of probabilities at which to calculate quantiles
91+
#' @param probs a vector of probabilities at which to calculate quantiles
6992
#' @param ... additional arguments passed on to the `quantile` method
7093
#'
7194
#' @return a `distribution` vector containing `dist_quantiles`
@@ -74,45 +97,45 @@ dist_quantiles <- function(x, tau) {
7497
#' @examples
7598
#' library(distributional)
7699
#' dstn <- dist_normal(c(10, 2), c(5, 10))
77-
#' extrapolate_quantiles(dstn, p = c(.25, 0.5, .75))
100+
#' extrapolate_quantiles(dstn, probs = c(.25, 0.5, .75))
78101
#'
79102
#' dstn <- dist_quantiles(list(1:4, 8:11), list(c(.2, .4, .6, .8)))
80103
#' # because this distribution is already quantiles, any extra quantiles are
81104
#' # appended
82-
#' extrapolate_quantiles(dstn, p = c(.25, 0.5, .75))
105+
#' extrapolate_quantiles(dstn, probs = c(.25, 0.5, .75))
83106
#'
84107
#' dstn <- c(
85108
#' dist_normal(c(10, 2), c(5, 10)),
86109
#' dist_quantiles(list(1:4, 8:11), list(c(.2, .4, .6, .8)))
87110
#' )
88-
#' extrapolate_quantiles(dstn, p = c(.25, 0.5, .75))
89-
extrapolate_quantiles <- function(x, p, ...) {
111+
#' extrapolate_quantiles(dstn, probs = c(.25, 0.5, .75))
112+
extrapolate_quantiles <- function(x, probs, ...) {
90113
UseMethod("extrapolate_quantiles")
91114
}
92115

93116
#' @export
94-
extrapolate_quantiles.distribution <- function(x, p, ...) {
95-
arg_is_probabilities(p)
96-
dstn <- lapply(vec_data(x), extrapolate_quantiles, p = p, ...)
117+
extrapolate_quantiles.distribution <- function(x, probs, ...) {
118+
arg_is_probabilities(probs)
119+
dstn <- lapply(vec_data(x), extrapolate_quantiles, p = probs, ...)
97120
distributional:::wrap_dist(dstn)
98121
}
99122

100123
#' @export
101-
extrapolate_quantiles.dist_default <- function(x, p, ...) {
102-
q <- quantile(x, p, ...)
103-
new_quantiles(q = q, tau = p)
124+
extrapolate_quantiles.dist_default <- function(x, probs, ...) {
125+
q <- quantile(x, probs, ...)
126+
new_quantiles(values = q, quantile_values = probs)
104127
}
105128

106129
#' @export
107-
extrapolate_quantiles.dist_quantiles <- function(x, p, ...) {
108-
q <- quantile(x, p, ...)
109-
tau <- field(x, "tau")
110-
qvals <- field(x, "q")
111-
new_quantiles(q = c(qvals, q), tau = c(tau, p))
130+
extrapolate_quantiles.dist_quantiles <- function(x, probs, ...) {
131+
q <- quantile(x, probs, ...)
132+
tau <- field(x, "quantile_values")
133+
qvals <- field(x, "values")
134+
new_quantiles(values = c(qvals, q), quantile_values = c(tau, probs))
112135
}
113136

114137
is_dist_quantiles <- function(x) {
115-
is_distribution(x) && all(stats::family(x) == "quantiles")
138+
is_distribution(x) & all(stats::family(x) == "quantiles")
116139
}
117140

118141

@@ -131,11 +154,11 @@ is_dist_quantiles <- function(x) {
131154
#' edf_nested %>% tidyr::unnest(q)
132155
nested_quantiles <- function(x) {
133156
stopifnot(is_dist_quantiles(x))
134-
distributional:::dist_apply(x, .f = function(z) {
135-
tibble::as_tibble(vec_data(z)) %>%
136-
dplyr::mutate(dplyr::across(tidyselect::everything(), as.double)) %>%
137-
list_of()
138-
})
157+
map(
158+
x,
159+
~ distributional::parameters(.x) %>%
160+
tidyr::unnest(tidyselect::everything())
161+
)
139162
}
140163

141164

@@ -190,14 +213,14 @@ pivot_quantiles <- function(.data, ...) {
190213
.data <- .data %>%
191214
tidyr::unnest(tidyselect::all_of(col)) %>%
192215
tidyr::pivot_wider(
193-
names_from = "tau", values_from = "q",
216+
names_from = "quantile_values", values_from = "values",
194217
names_prefix = paste0(col, "_")
195218
)
196219
}
197220
} else {
198221
.data <- .data %>%
199222
tidyr::unnest(tidyselect::all_of(cols)) %>%
200-
tidyr::pivot_wider(names_from = "tau", values_from = "q")
223+
tidyr::pivot_wider(names_from = "quantile_values", values_from = "values")
201224
}
202225
.data
203226
}
@@ -207,23 +230,31 @@ pivot_quantiles <- function(.data, ...) {
207230

208231
#' @export
209232
#' @importFrom stats median qnorm family
210-
median.dist_quantiles <- function(x, ..., middle = c("cubic", "linear")) {
233+
median.dist_quantiles <- function(x, na.rm = FALSE, ..., middle = c("cubic", "linear")) {
234+
tau <- field(x, "quantile_values")
235+
qvals <- field(x, "values")
236+
if (0.5 %in% tau) return(qvals[match(0.5, tau)])
237+
if (min(tau) > 0.5 || max(tau) < 0.5 || length(tau) < 2) return(NA)
238+
if (length(tau) < 3 || min(tau) > .25 || max(tau) < .75) {
239+
return(stats::approx(tau, qvals, xout = 0.5)$y)
240+
}
211241
quantile(x, 0.5, ..., middle = middle)
212242
}
213243

214244
# placeholder to avoid errors, but not ideal
215245
#' @export
216-
mean.dist_quantiles <- function(x, ..., middle = c("cubic", "linear")) {
246+
mean.dist_quantiles <- function(x, na.rm = FALSE, ..., middle = c("cubic", "linear")) {
217247
median(x, ..., middle = middle)
218248
}
219249

220250
#' @export
221251
#' @importFrom stats quantile
222252
#' @import distributional
223-
quantile.dist_quantiles <- function(x, probs, ...,
224-
middle = c("cubic", "linear"),
225-
left_tail = c("normal", "exponential"),
226-
right_tail = c("normal", "exponential")) {
253+
quantile.dist_quantiles <- function(
254+
x, probs, ...,
255+
middle = c("cubic", "linear"),
256+
left_tail = c("normal", "exponential"),
257+
right_tail = c("normal", "exponential")) {
227258
arg_is_probabilities(probs)
228259
middle <- match.arg(middle)
229260
left_tail <- match.arg(left_tail)
@@ -233,8 +264,8 @@ quantile.dist_quantiles <- function(x, probs, ...,
233264

234265

235266
quantile_extrapolate <- function(x, tau_out, middle, left_tail, right_tail) {
236-
tau <- field(x, "tau")
237-
qvals <- field(x, "q")
267+
tau <- field(x, "quantile_values")
268+
qvals <- field(x, "values")
238269
r <- range(tau, na.rm = TRUE)
239270
qvals_out <- rep(NA, length(tau_out))
240271

@@ -244,7 +275,7 @@ quantile_extrapolate <- function(x, tau_out, middle, left_tail, right_tail) {
244275
return(qvals[match(tau_out, tau)])
245276
}
246277
if (length(qvals) < 3 || r[1] > .25 || r[2] < .75) {
247-
rlang::warn(c(
278+
cli::cli_warn(c(
248279
"Quantile extrapolation is not possible with fewer than",
249280
"3 quantiles or when the probs don't span [.25, .75]"
250281
))
@@ -345,10 +376,10 @@ norm_tail_q <- function(p, q, target) {
345376
#' @method Math dist_quantiles
346377
#' @export
347378
Math.dist_quantiles <- function(x, ...) {
348-
tau <- field(x, "tau")
349-
q <- field(x, "q")
350-
q <- vctrs::vec_math(.Generic, q, ...)
351-
new_quantiles(q = q, tau = tau)
379+
quantile_values <- field(x, "quantile_values")
380+
values <- field(x, "values")
381+
values <- vctrs::vec_math(.Generic, values, ...)
382+
new_quantiles(values = values, quantile_values = quantile_values)
352383
}
353384

354385
#' @method Ops dist_quantiles
@@ -361,16 +392,16 @@ Ops.dist_quantiles <- function(e1, e2) {
361392
is_dist <- c(inherits(e1, "dist_default"), inherits(e2, "dist_default"))
362393
tau1 <- tau2 <- NULL
363394
if (is_quantiles[1]) {
364-
q1 <- field(e1, "q")
365-
tau1 <- field(e1, "tau")
395+
q1 <- field(e1, "values")
396+
tau1 <- field(e1, "quantile_values")
366397
}
367398
if (is_quantiles[2]) {
368-
q2 <- field(e2, "q")
369-
tau2 <- field(e2, "tau")
399+
q2 <- field(e2, "values")
400+
tau2 <- field(e2, "quantile_values")
370401
}
371402
tau <- union(tau1, tau2)
372403
if (all(is_dist)) {
373-
rlang::abort(
404+
cli::cli_abort(
374405
"You can't perform arithmetic between two distributions like this."
375406
)
376407
} else {
@@ -381,7 +412,7 @@ Ops.dist_quantiles <- function(e1, e2) {
381412
}
382413
}
383414
q <- vctrs::vec_arith(.Generic, q1, q2)
384-
new_quantiles(q = q, tau = tau)
415+
new_quantiles(values = q, quantile_values = tau)
385416
}
386417

387418
#' @method is.na distribution
@@ -393,6 +424,6 @@ is.na.distribution <- function(x) {
393424
#' @method is.na dist_quantiles
394425
#' @export
395426
is.na.dist_quantiles <- function(x) {
396-
q <- field(x, "q")
427+
q <- field(x, "values")
397428
all(is.na(q))
398429
}

tests/testthat/test-dist_quantiles.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ test_that("tail functions give reasonable output", {
2020
})
2121

2222
test_that("single dist_quantiles works, quantiles are accessible", {
23-
z <- new_quantiles(q = 1:5, tau = c(.2, .4, .5, .6, .8))
23+
z <- new_quantiles(values = 1:5, quantile_values = c(.2, .4, .5, .6, .8))
2424
expect_s3_class(z, "dist_quantiles")
2525
expect_equal(median(z), 3)
2626
expect_equal(quantile(z, c(.2, .4, .5, .6, .8)), 1:5)
@@ -30,7 +30,7 @@ test_that("single dist_quantiles works, quantiles are accessible", {
3030
expect_equal(quantile(z, c(.3, .7), middle = "cubic"), Q(c(.3, .7)))
3131
expect_identical(
3232
extrapolate_quantiles(z, c(.3, .7), middle = "linear"),
33-
new_quantiles(q = c(1, 1.5, 2, 3, 4, 4.5, 5), tau = 2:8 / 10)
33+
new_quantiles(values = c(1, 1.5, 2, 3, 4, 4.5, 5), quantile_values = 2:8 / 10)
3434
)
3535
})
3636

0 commit comments

Comments
 (0)