diff --git a/NEWS.md b/NEWS.md index c4da2e4725..d1818f3f0a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # ggplot2 (development version) +* `geom_violin()` gains a `bounds` argument analogous to `geom_density()`s (@eliocamp, #5493). + * Legend titles no longer take up space if they've been removed by setting `legend.title = element_blank()` (@teunbrand, #3587). diff --git a/R/geom-violin.R b/R/geom-violin.R index c9d285f961..c2bfc9f087 100644 --- a/R/geom-violin.R +++ b/R/geom-violin.R @@ -16,6 +16,11 @@ #' to the range of the data. If `FALSE`, don't trim the tails. #' @param geom,stat Use to override the default connection between #' `geom_violin()` and `stat_ydensity()`. +#' @param bounds Known lower and upper bounds for estimated data. Default +#' `c(-Inf, Inf)` means that there are no (finite) bounds. If any bound is +#' finite, boundary effect of default density estimation will be corrected by +#' reflecting tails outside `bounds` around their closest edge. Data points +#' outside of bounds are removed with a warning. #' @export #' @references Hintze, J. L., Nelson, R. D. (1998) Violin Plots: A Box #' Plot-Density Trace Synergism. The American Statistician 52, 181-184. @@ -86,6 +91,7 @@ geom_violin <- function(mapping = NULL, data = NULL, ..., draw_quantiles = NULL, trim = TRUE, + bounds = c(-Inf, Inf), scale = "area", na.rm = FALSE, orientation = NA, @@ -105,6 +111,7 @@ geom_violin <- function(mapping = NULL, data = NULL, draw_quantiles = draw_quantiles, na.rm = na.rm, orientation = orientation, + bounds = bounds, ... ) ) diff --git a/R/stat-ydensity.R b/R/stat-ydensity.R index c7f034bbd5..6156922883 100644 --- a/R/stat-ydensity.R +++ b/R/stat-ydensity.R @@ -35,7 +35,8 @@ stat_ydensity <- function(mapping = NULL, data = NULL, na.rm = FALSE, orientation = NA, show.legend = NA, - inherit.aes = TRUE) { + inherit.aes = TRUE, + bounds = c(-Inf, Inf)) { scale <- arg_match0(scale, c("area", "count", "width")) layer( @@ -54,6 +55,7 @@ stat_ydensity <- function(mapping = NULL, data = NULL, scale = scale, drop = drop, na.rm = na.rm, + bounds = bounds, ... ) ) @@ -78,7 +80,7 @@ StatYdensity <- ggproto("StatYdensity", Stat, compute_group = function(self, data, scales, width = NULL, bw = "nrd0", adjust = 1, kernel = "gaussian", trim = TRUE, na.rm = FALSE, - drop = TRUE, flipped_aes = FALSE) { + drop = TRUE, flipped_aes = FALSE, bounds = c(-Inf, Inf)) { if (nrow(data) < 2) { if (isTRUE(drop)) { cli::cli_warn(c( @@ -98,7 +100,7 @@ StatYdensity <- ggproto("StatYdensity", Stat, dens <- compute_density( data$y, data[["weight"]], from = range[1] - modifier * bw, to = range[2] + modifier * bw, - bw = bw, adjust = adjust, kernel = kernel + bw = bw, adjust = adjust, kernel = kernel, bounds = bounds ) dens$y <- dens$x @@ -118,11 +120,12 @@ StatYdensity <- ggproto("StatYdensity", Stat, compute_panel = function(self, data, scales, width = NULL, bw = "nrd0", adjust = 1, kernel = "gaussian", trim = TRUE, na.rm = FALSE, - scale = "area", flipped_aes = FALSE, drop = TRUE) { + scale = "area", flipped_aes = FALSE, drop = TRUE, + bounds = c(-Inf, Inf)) { data <- flip_data(data, flipped_aes) data <- ggproto_parent(Stat, self)$compute_panel( data, scales, width = width, bw = bw, adjust = adjust, kernel = kernel, - trim = trim, na.rm = na.rm, drop = drop + trim = trim, na.rm = na.rm, drop = drop, bounds = bounds, ) if (!drop && any(data$n < 2)) { cli::cli_warn( diff --git a/man/geom_violin.Rd b/man/geom_violin.Rd index 4a25c6752c..45adedc4f3 100644 --- a/man/geom_violin.Rd +++ b/man/geom_violin.Rd @@ -13,6 +13,7 @@ geom_violin( ..., draw_quantiles = NULL, trim = TRUE, + bounds = c(-Inf, Inf), scale = "area", na.rm = FALSE, orientation = NA, @@ -35,7 +36,8 @@ stat_ydensity( na.rm = FALSE, orientation = NA, show.legend = NA, - inherit.aes = TRUE + inherit.aes = TRUE, + bounds = c(-Inf, Inf) ) } \arguments{ @@ -75,6 +77,12 @@ at the given quantiles of the density estimate.} \item{trim}{If \code{TRUE} (default), trim the tails of the violins to the range of the data. If \code{FALSE}, don't trim the tails.} +\item{bounds}{Known lower and upper bounds for estimated data. Default +\code{c(-Inf, Inf)} means that there are no (finite) bounds. If any bound is +finite, boundary effect of default density estimation will be corrected by +reflecting tails outside \code{bounds} around their closest edge. Data points +outside of bounds are removed with a warning.} + \item{scale}{if "area" (default), all violins have the same area (before trimming the tails). If "count", areas are scaled proportionally to the number of observations. If "width", all violins have the same maximum width.}