Skip to content

Commit ca24e27

Browse files
jgjlactions-user
andauthored
Update stat_ecdf to work either on the x or the y aesthetic. (#4005)
* Update stat_ecdf to work either on the x or the y aesthetic, whatever is provided. * Undo breaking change of renaming the stats result and go back to y. * Add bullet to NEWS.md * Fix typo in comments. * Improve comment. * Document Co-authored-by: GitHub Actions <[email protected]>
1 parent f0e561e commit ca24e27

File tree

4 files changed

+52
-10
lines changed

4 files changed

+52
-10
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* Extended `stat_ecdf()` to calculate the cdf from either x or y instead from y only (@jgjl, #4005).
4+
35
* Fixed a bug in `labeller()` so that `.default` is passed to `as_labeller()`
46
when labellers are specified by naming faceting variables. (@waltersom, #4031)
57

R/stat-ecdf.r

+28-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
#' The downside is that it requires more training to accurately interpret,
88
#' and the underlying visual tasks are somewhat more challenging.
99
#'
10+
#' The statistic relies on the aesthetics assignment to guess which variable to
11+
#' use as the input and which to use as the output. Either x or y must be provided
12+
#' and one of them must be unused. The ECDF will be calculated on the given aesthetic
13+
#' and will be output on the unused one.
14+
#'
1015
#' @inheritParams layer
1116
#' @inheritParams geom_point
1217
#' @param na.rm If `FALSE` (the default), removes missing values with
@@ -17,7 +22,6 @@
1722
#' and (Inf, 1)
1823
#' @section Computed variables:
1924
#' \describe{
20-
#' \item{x}{x in data}
2125
#' \item{y}{cumulative density corresponding x}
2226
#' }
2327
#' @export
@@ -67,7 +71,24 @@ stat_ecdf <- function(mapping = NULL, data = NULL,
6771
#' @usage NULL
6872
#' @export
6973
StatEcdf <- ggproto("StatEcdf", Stat,
70-
compute_group = function(data, scales, n = NULL, pad = TRUE) {
74+
required_aes = c("x|y"),
75+
76+
default_aes = aes(y = after_stat(y)),
77+
78+
setup_params = function(data, params) {
79+
params$flipped_aes <- has_flipped_aes(data, params, main_is_orthogonal = FALSE, main_is_continuous = TRUE)
80+
81+
has_x <- !(is.null(data$x) && is.null(params$x))
82+
has_y <- !(is.null(data$y) && is.null(params$y))
83+
if (!has_x && !has_y) {
84+
abort("stat_ecdf() requires an x or y aesthetic.")
85+
}
86+
87+
params
88+
},
89+
90+
compute_group = function(data, scales, n = NULL, pad = TRUE, flipped_aes = FALSE) {
91+
data <- flip_data(data, flipped_aes)
7192
# If n is NULL, use raw values; otherwise interpolate
7293
if (is.null(n)) {
7394
x <- unique(data$x)
@@ -78,13 +99,11 @@ StatEcdf <- ggproto("StatEcdf", Stat,
7899
if (pad) {
79100
x <- c(-Inf, x, Inf)
80101
}
81-
y <- ecdf(data$x)(x)
82-
83-
new_data_frame(list(x = x, y = y), n = length(x))
84-
},
85-
86-
default_aes = aes(y = after_stat(y)),
102+
data_ecdf <- ecdf(data$x)(x)
87103

88-
required_aes = c("x")
104+
df_ecdf <- new_data_frame(list(x = x, y = data_ecdf), n = length(x))
105+
df_ecdf$flipped_aes <- flipped_aes
106+
flip_data(df_ecdf, flipped_aes)
107+
}
89108
)
90109

man/stat_ecdf.Rd

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

tests/testthat/test-stat-ecdf.R

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
context("stat_ecdf")
2+
3+
test_that("stat_ecdf works in both directions", {
4+
p <- ggplot(mpg, aes(hwy)) + stat_ecdf()
5+
x <- layer_data(p)
6+
expect_false(x$flipped_aes[1])
7+
8+
p <- ggplot(mpg, aes(y = hwy)) + stat_ecdf()
9+
y <- layer_data(p)
10+
expect_true(y$flipped_aes[1])
11+
12+
x$flipped_aes <- NULL
13+
y$flipped_aes <- NULL
14+
expect_identical(x, flip_data(y, TRUE)[,names(x)])
15+
})
16+

0 commit comments

Comments
 (0)