Skip to content

Commit 64814b0

Browse files
authored
Add drop argument to stat_ydensity() (#5129)
* Don't drop groups in stat_ydensity * Add `drop` argument as switch * Add test * Add NEWS bullet
1 parent 84cded8 commit 64814b0

File tree

4 files changed

+57
-7
lines changed

4 files changed

+57
-7
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* To apply dodging more consistently in violin plots, `stat_ydensity()` now
4+
has a `drop` argument to keep or discard groups with 1 observation.
35
* Aesthetics listed in `geom_*()` and `stat_*()` layers now point to relevant
46
documentation (@teunbrand, #5123).
57
* `coord_flip()` has been marked as superseded. The recommended alternative is

R/stat-ydensity.R

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#' @param scale if "area" (default), all violins have the same area (before trimming
55
#' the tails). If "count", areas are scaled proportionally to the number of
66
#' observations. If "width", all violins have the same maximum width.
7+
#' @param drop Whether to discard groups with less than 2 observations
8+
#' (`TRUE`, default) or keep such groups for position adjustment purposes
9+
#' (`FALSE`).
710
#'
811
#' @eval rd_computed_vars(
912
#' density = "Density estimate.",
@@ -28,6 +31,7 @@ stat_ydensity <- function(mapping = NULL, data = NULL,
2831
kernel = "gaussian",
2932
trim = TRUE,
3033
scale = "area",
34+
drop = TRUE,
3135
na.rm = FALSE,
3236
orientation = NA,
3337
show.legend = NA,
@@ -48,6 +52,7 @@ stat_ydensity <- function(mapping = NULL, data = NULL,
4852
kernel = kernel,
4953
trim = trim,
5054
scale = scale,
55+
drop = drop,
5156
na.rm = na.rm,
5257
...
5358
)
@@ -72,10 +77,20 @@ StatYdensity <- ggproto("StatYdensity", Stat,
7277
extra_params = c("na.rm", "orientation"),
7378

7479
compute_group = function(self, data, scales, width = NULL, bw = "nrd0", adjust = 1,
75-
kernel = "gaussian", trim = TRUE, na.rm = FALSE, flipped_aes = FALSE) {
80+
kernel = "gaussian", trim = TRUE, na.rm = FALSE,
81+
drop = TRUE, flipped_aes = FALSE) {
7682
if (nrow(data) < 2) {
77-
cli::cli_warn("Groups with fewer than two data points have been dropped.")
78-
return(data_frame0())
83+
if (isTRUE(drop)) {
84+
cli::cli_warn(c(
85+
"Groups with fewer than two datapoints have been dropped.",
86+
i = paste0(
87+
"Set {.code drop = FALSE} to consider such groups for position ",
88+
"adjustment purposes."
89+
)))
90+
return(data_frame0())
91+
}
92+
ans <- data_frame0(x = data$x, n = nrow(data))
93+
return(ans)
7994
}
8095
range <- range(data$y, na.rm = TRUE)
8196
modifier <- if (trim) 0 else 3
@@ -97,21 +112,27 @@ StatYdensity <- ggproto("StatYdensity", Stat,
97112

98113
compute_panel = function(self, data, scales, width = NULL, bw = "nrd0", adjust = 1,
99114
kernel = "gaussian", trim = TRUE, na.rm = FALSE,
100-
scale = "area", flipped_aes = FALSE) {
115+
scale = "area", flipped_aes = FALSE, drop = TRUE) {
101116
data <- flip_data(data, flipped_aes)
102117
data <- ggproto_parent(Stat, self)$compute_panel(
103118
data, scales, width = width, bw = bw, adjust = adjust, kernel = kernel,
104-
trim = trim, na.rm = na.rm
119+
trim = trim, na.rm = na.rm, drop = drop
105120
)
121+
if (!drop && any(data$n < 2)) {
122+
cli::cli_warn(
123+
"Cannot compute density for groups with fewer than two datapoints."
124+
)
125+
}
106126

107127
# choose how violins are scaled relative to each other
108128
data$violinwidth <- switch(scale,
109129
# area : keep the original densities but scale them to a max width of 1
110130
# for plotting purposes only
111-
area = data$density / max(data$density),
131+
area = data$density / max(data$density, na.rm = TRUE),
112132
# count: use the original densities scaled to a maximum of 1 (as above)
113133
# and then scale them according to the number of observations
114-
count = data$density / max(data$density) * data$n / max(data$n),
134+
count = data$density / max(data$density, na.rm = TRUE) *
135+
data$n / max(data$n),
115136
# width: constant width (density scaled to a maximum of 1)
116137
width = data$scaled
117138
)

man/geom_violin.Rd

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-stat-ydensity.R

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,25 @@ test_that("calc_bw() requires at least two values and correct method", {
33
expect_silent(calc_bw(1:5, "nrd0"))
44
expect_snapshot_error(calc_bw(1:5, "test"))
55
})
6+
7+
test_that("`drop = FALSE` preserves groups with 1 observations", {
8+
df <- head(data_frame0(
9+
x = factor(rep(1:2, each = 4)),
10+
y = rep(1:2, 4),
11+
g = rep(c("A", "A", "B", 'B'), 2)
12+
), -1)
13+
14+
p <- ggplot(df, mapping = aes(x, y, fill = g))
15+
16+
expect_warning(
17+
ld <- layer_data(p + geom_violin(drop = TRUE)),
18+
"Groups with fewer than two datapoints have been dropped"
19+
)
20+
expect_equal(length(unique(ld$x)), 3)
21+
22+
expect_warning(
23+
ld <- layer_data(p + geom_violin(drop = FALSE)),
24+
"Cannot compute density for groups with fewer than two datapoints"
25+
)
26+
expect_equal(length(unique(ld$x)), 4)
27+
})

0 commit comments

Comments
 (0)