From 2f278f118c4256d9ebd81b415ab8f112cad5e052 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 15 Nov 2024 10:24:30 +0100 Subject: [PATCH 1/3] add `weight` aesthetic --- R/stat-ellipse.R | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/R/stat-ellipse.R b/R/stat-ellipse.R index 5d1f41dd55..934e11c082 100644 --- a/R/stat-ellipse.R +++ b/R/stat-ellipse.R @@ -76,6 +76,8 @@ stat_ellipse <- function(mapping = NULL, data = NULL, #' @export StatEllipse <- ggproto("StatEllipse", Stat, required_aes = c("x", "y"), + optional_aes = "weight", + dropped_aes = "weight", setup_params = function(data, params) { params$type <- params$type %||% "t" @@ -96,6 +98,9 @@ calculate_ellipse <- function(data, vars, type, level, segments){ dfn <- 2 dfd <- nrow(data) - 1 + weight <- data$weight %||% rep(1, nrow(data)) + weight <- weight / sum(weight) + if (!type %in% c("t", "norm", "euclid")) { cli::cli_inform("Unrecognized ellipse type") ellipse <- matrix(NA_real_, ncol = 2) @@ -104,11 +109,12 @@ calculate_ellipse <- function(data, vars, type, level, segments){ ellipse <- matrix(NA_real_, ncol = 2) } else { if (type == "t") { - v <- MASS::cov.trob(data[,vars]) + # Prone to convergence problems when `sum(weight) != nrow(data)` + v <- MASS::cov.trob(data[,vars], wt = weight * nrow(data)) } else if (type == "norm") { - v <- stats::cov.wt(data[,vars]) + v <- stats::cov.wt(data[,vars], wt = weight) } else if (type == "euclid") { - v <- stats::cov.wt(data[,vars]) + v <- stats::cov.wt(data[,vars], wt = weight) v$cov <- diag(rep(min(diag(v$cov)), 2)) } shape <- v$cov From 43611385ab4ec938e24d8a3a28568583eba83957 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 15 Nov 2024 10:26:36 +0100 Subject: [PATCH 2/3] add news bullet --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 0c493a8f58..33833f488d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # ggplot2 (development version) +* Added `weight` aesthetic for `stat_ellipse()` (@teunbrand, #5272) * Custom and raster annotation now respond to scale transformations, and can use AsIs variables for relative placement (@teunbrand based on @yutannihilation's prior work, #3120) From fa0b587ec5650d8e5c9c1807b1d25e0baccfa3db Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 15 Nov 2024 10:28:50 +0100 Subject: [PATCH 3/3] document aesthetics --- R/stat-ellipse.R | 1 + man/stat_ellipse.Rd | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/R/stat-ellipse.R b/R/stat-ellipse.R index 934e11c082..d462bf3575 100644 --- a/R/stat-ellipse.R +++ b/R/stat-ellipse.R @@ -20,6 +20,7 @@ #' @param segments The number of segments to be used in drawing the ellipse. #' @inheritParams layer #' @inheritParams geom_point +#' @eval rd_aesthetics("stat", "ellipse") #' @export #' @examples #' ggplot(faithful, aes(waiting, eruptions)) + diff --git a/man/stat_ellipse.Rd b/man/stat_ellipse.Rd index 4bc30ef863..8ef16d92cc 100644 --- a/man/stat_ellipse.Rd +++ b/man/stat_ellipse.Rd @@ -125,6 +125,18 @@ the default plot specification, e.g. \code{\link[=borders]{borders()}}.} The method for calculating the ellipses has been modified from \code{car::dataEllipse} (Fox and Weisberg 2011, Friendly and Monette 2013) } +\section{Aesthetics}{ + +\code{stat_ellipse()} understands the following aesthetics (required aesthetics are in bold): +\itemize{ +\item \strong{\code{\link[=aes_position]{x}}} +\item \strong{\code{\link[=aes_position]{y}}} +\item \code{\link[=aes_group_order]{group}} +\item \code{weight} +} +Learn more about setting these aesthetics in \code{vignette("ggplot2-specs")}. +} + \examples{ ggplot(faithful, aes(waiting, eruptions)) + geom_point() +