|
| 1 | +#' Plot data ellipses. |
| 2 | +#' |
| 3 | +#' @param level The confidence level at which to draw an ellipse (default is 0.95), |
| 4 | +#' or, if \code{type="euclid"}, the radius of the circle to be drawn. |
| 5 | +#' @param type The type of ellipse. |
| 6 | +#' The default \code{"t"} assumes a multivariate t-distribution, and |
| 7 | +#' \code{"norm"} assumes a multivariate normal distribution. |
| 8 | +#' \code{"euclid"} draws a circle with the radius equal to \code{level}, |
| 9 | +#' representing the euclidian distance from the center. |
| 10 | +#' This ellipse probably won't appear circular unless \code{coord_fixed()} is applied. |
| 11 | +#' @param segments The number of segments to be used in drawing the ellipse. |
| 12 | +#' @param na.rm If \code{FALSE} (the default), removes missing values with |
| 13 | +#' a warning. If \code{TRUE} silently removes missing values. |
| 14 | +#' @inheritParams stat_identity |
| 15 | +#' |
| 16 | +#' @details The method for calculating the ellipses has been modified from car::ellipse (Fox and Weisberg, 2011) |
| 17 | +#' |
| 18 | +#' @references |
| 19 | +#' John Fox and Sanford Weisberg (2011). An {R} Companion to Applied Regression, Second Edition. Thousand Oaks CA: Sage. URL: http://socserv.socsci.mcmaster.ca/jfox/Books/Companion |
| 20 | +#' |
| 21 | +#' @export |
| 22 | +#' @importFrom MASS cov.trob |
| 23 | +#' |
| 24 | +#' @examples |
| 25 | +#' ggplot(faithful, aes(waiting, eruptions))+ |
| 26 | +#' geom_point()+ |
| 27 | +#' stat_ellipse() |
| 28 | +#' |
| 29 | +#' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+ |
| 30 | +#' geom_point()+ |
| 31 | +#' stat_ellipse() |
| 32 | +#' |
| 33 | +#' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+ |
| 34 | +#' geom_point()+ |
| 35 | +#' stat_ellipse(type = "norm", linetype = 2)+ |
| 36 | +#' stat_ellipse(type = "t") |
| 37 | +#' |
| 38 | +#' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+ |
| 39 | +#' geom_point()+ |
| 40 | +#' stat_ellipse(type = "norm", linetype = 2)+ |
| 41 | +#' stat_ellipse(type = "euclid", level = 3)+ |
| 42 | +#' coord_fixed() |
| 43 | +#' |
| 44 | +#' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+ |
| 45 | +#' stat_ellipse(geom = "polygon") |
| 46 | + |
| 47 | +stat_ellipse <- function(mapping = NULL, data = NULL, geom = "path", position = "identity", type = "t", level = 0.95, segments = 51, na.rm = FALSE, ...) { |
| 48 | + StatEllipse$new(mapping = mapping, data = data, geom = geom, position = position, type = type, level = level, segments = segments, na.rm = na.rm, ...) |
| 49 | +} |
| 50 | + |
| 51 | +StatEllipse <- proto(Stat, { |
| 52 | + objname <- "ellipse" |
| 53 | + |
| 54 | + required_aes <- c("x", "y") |
| 55 | + default_geom <- function(.) GeomPath |
| 56 | + |
| 57 | + calculate_groups <- function(., data, scales, ...){ |
| 58 | + .super$calculate_groups(., data, scales,...) |
| 59 | + } |
| 60 | + calculate <- function(., data, scales, type = "t", level = 0.95, segments = 51, na.rm = FALSE, ...){ |
| 61 | + data <- remove_missing(data, na.rm, vars = c("x","y"), name = "stat_ellipse", finite = TRUE) |
| 62 | + ellipse <- calculate_ellipse(data=data, vars= c("x","y"), type=type, level=level, segments=segments) |
| 63 | + return(ellipse) |
| 64 | + } |
| 65 | +}) |
| 66 | + |
| 67 | +calculate_ellipse <- function(data, vars, type, level, segments){ |
| 68 | + dfn <- 2 |
| 69 | + dfd <- nrow(data) - 1 |
| 70 | + |
| 71 | + if (!type %in% c("t", "norm", "euclid")){ |
| 72 | + message("Unrecognized ellipse type") |
| 73 | + ellipse <- rbind(as.numeric(c(NA, NA))) |
| 74 | + } else if (dfd < 3){ |
| 75 | + message("Too few points to calculate an ellipse") |
| 76 | + ellipse <- rbind(as.numeric(c(NA, NA))) |
| 77 | + } else { |
| 78 | + if (type == "t"){ |
| 79 | + v <- cov.trob(data[,vars]) |
| 80 | + } else if (type == "norm"){ |
| 81 | + v <- cov.wt(data[,vars]) |
| 82 | + } else if (type == "euclid"){ |
| 83 | + v <- cov.wt(data[,vars]) |
| 84 | + v$cov <- diag(rep(min(diag(v$cov)), 2)) |
| 85 | + } |
| 86 | + shape <- v$cov |
| 87 | + center <- v$center |
| 88 | + chol_decomp <- chol(shape) |
| 89 | + if (type == "euclid"){ |
| 90 | + radius <- level/max(chol_decomp) |
| 91 | + } else { |
| 92 | + radius <- sqrt(dfn * qf(level, dfn, dfd)) |
| 93 | + } |
| 94 | + angles <- (0:segments) * 2 * pi/segments |
| 95 | + unit.circle <- cbind(cos(angles), sin(angles)) |
| 96 | + ellipse <- t(center + radius * t(unit.circle %*% chol_decomp)) |
| 97 | + } |
| 98 | + |
| 99 | + ellipse <- as.data.frame(ellipse) |
| 100 | + colnames(ellipse) <- vars |
| 101 | + return(ellipse) |
| 102 | +} |
0 commit comments