|
1 |
| -#' Calculate Data Ellipses |
2 |
| -#' |
| 1 | +#' Plot data ellipses. |
| 2 | +#' |
3 | 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. |
| 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 | 12 | #' @param na.rm If \code{FALSE} (the default), removes missing values with
|
13 |
| -#' a warning. If \code{TRUE} silently removes missing values. |
| 13 | +#' a warning. If \code{TRUE} silently removes missing values. |
14 | 14 | #' @inheritParams stat_identity
|
15 |
| -#' |
16 |
| -#' @details The code for calculating the ellipse is largely borrowed from car::ellipse. |
17 |
| -#' |
| 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 | +#' |
18 | 21 | #' @export
|
19 | 22 | #' @importFrom MASS cov.trob
|
20 |
| -#' |
| 23 | +#' |
21 | 24 | #' @examples
|
22 |
| -#' \donttest{ |
23 | 25 | #' ggplot(faithful, aes(waiting, eruptions))+
|
24 | 26 | #' geom_point()+
|
25 | 27 | #' stat_ellipse()
|
26 |
| -#' |
| 28 | +#' |
27 | 29 | #' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+
|
28 | 30 | #' geom_point()+
|
29 | 31 | #' stat_ellipse()
|
30 |
| -#' |
| 32 | +#' |
31 | 33 | #' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+
|
32 | 34 | #' geom_point()+
|
33 | 35 | #' stat_ellipse(type = "norm", linetype = 2)+
|
34 | 36 | #' stat_ellipse(type = "t")
|
35 |
| -#' |
| 37 | +#' |
36 | 38 | #' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+
|
37 | 39 | #' geom_point()+
|
38 | 40 | #' stat_ellipse(type = "norm", linetype = 2)+
|
39 | 41 | #' stat_ellipse(type = "euclid", level = 3)+
|
40 | 42 | #' coord_fixed()
|
41 |
| -#' |
| 43 | +#' |
42 | 44 | #' ggplot(faithful, aes(waiting, eruptions, color = eruptions > 3))+
|
43 | 45 | #' stat_ellipse(geom = "polygon")
|
44 |
| -#' } |
45 | 46 |
|
46 |
| -stat_ellipse <- function(mapping = NULL, data = NULL, geom = "path", position = "identity", |
47 |
| - type = "t", level = 0.95, segments = 51, na.rm = FALSE, ...) { |
48 |
| - StatEllipse$new(mapping = mapping, data = data, geom = geom, position = position, |
49 |
| - type = type, level = level, segments = segments, na.rm = na.rm, ...) |
| 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, ...) |
50 | 49 | }
|
51 | 50 |
|
52 | 51 |
|
53 |
| -StatEllipse <- proto(Stat, |
54 |
| - { |
55 |
| - objname <- "ellipse" |
56 |
| - |
57 |
| - required_aes <- c("x", "y") |
58 |
| - default_geom <- function(.) GeomPath |
| 52 | +StatEllipse <- proto(Stat, { |
| 53 | + objname <- "ellipse" |
59 | 54 |
|
60 |
| - calculate_groups <- function(., data, scales, ...){ |
61 |
| - .super$calculate_groups(., data, scales,...) |
62 |
| - } |
63 |
| - calculate <- function(., data, scales, type = "t", level = 0.95, segments = 51, na.rm = FALSE, ...){ |
64 |
| - data <- remove_missing(data, na.rm, vars = c("x","y"), |
65 |
| - name = "stat_ellipse", finite = TRUE) |
66 |
| - |
67 |
| - dfn <- 2 |
68 |
| - dfd <- length(data$x) - 1 |
69 |
| - |
70 |
| - if (!type %in% c("t", "norm", "euclid")){ |
71 |
| - message("Unrecognized ellipse type") |
72 |
| - ellipse <- rbind(as.numeric(c(NA, NA))) |
73 |
| - } else if (dfd < 3){ |
74 |
| - message("Too few points to calculate an ellipse") |
75 |
| - ellipse <- rbind(as.numeric(c(NA, NA))) |
| 55 | + required_aes <- c("x", "y") |
| 56 | + default_geom <- function(.) GeomPath |
| 57 | + |
| 58 | + calculate_groups <- function(., data, scales, ...){ |
| 59 | + .super$calculate_groups(., data, scales,...) |
| 60 | + } |
| 61 | + calculate <- function(., data, scales, type = "t", level = 0.95, segments = 51, na.rm = FALSE, ...){ |
| 62 | + data <- remove_missing(data, na.rm, vars = c("x","y"), name = "stat_ellipse", finite = TRUE) |
| 63 | + |
| 64 | + dfn <- 2 |
| 65 | + dfd <- length(data$x) - 1 |
| 66 | + |
| 67 | + if (!type %in% c("t", "norm", "euclid")){ |
| 68 | + message("Unrecognized ellipse type") |
| 69 | + ellipse <- rbind(as.numeric(c(NA, NA))) |
| 70 | + } else if (dfd < 3){ |
| 71 | + message("Too few points to calculate an ellipse") |
| 72 | + ellipse <- rbind(as.numeric(c(NA, NA))) |
| 73 | + } else { |
| 74 | + if (type == "t"){ |
| 75 | + v <- cov.trob(cbind(data$x, data$y)) |
| 76 | + } else if (type == "norm"){ |
| 77 | + v <- cov.wt(cbind(data$x, data$y)) |
| 78 | + } else if (type == "euclid"){ |
| 79 | + v <- cov.wt(cbind(data$x, data$y)) |
| 80 | + v$cov <- diag(rep(min(diag(v$cov)), 2)) |
| 81 | + } |
| 82 | + shape <- v$cov |
| 83 | + center <- v$center |
| 84 | + chol_decomp <- chol(shape) |
| 85 | + if (type == "euclid"){ |
| 86 | + radius <- level/max(chol_decomp) |
76 | 87 | } else {
|
77 |
| - if (type == "t"){ |
78 |
| - v <- cov.trob(cbind(data$x, data$y)) |
79 |
| - } else if (type == "norm"){ |
80 |
| - v <- cov.wt(cbind(data$x, data$y)) |
81 |
| - } else if (type == "euclid"){ |
82 |
| - v <- cov.wt(cbind(data$x, data$y)) |
83 |
| - v$cov <- diag(rep(min(diag(v$cov)), 2)) |
84 |
| - } |
85 |
| - shape <- v$cov |
86 |
| - center <- v$center |
87 |
| - chol_decomp <- chol(shape) |
88 |
| - if (type == "euclid"){ |
89 |
| - radius <- level/max(chol_decomp) |
90 |
| - } else { |
91 |
| - radius <- sqrt(dfn * qf(level, dfn, dfd)) |
92 |
| - } |
93 |
| - angles <- (0:segments) * 2 * pi/segments |
94 |
| - unit.circle <- cbind(cos(angles), sin(angles)) |
95 |
| - ellipse <- t(center + radius * t(unit.circle %*% chol_decomp)) |
| 88 | + radius <- sqrt(dfn * qf(level, dfn, dfd)) |
96 | 89 | }
|
97 |
| - |
98 |
| - ellipse <- as.data.frame(ellipse) |
99 |
| - colnames(ellipse) <- c("x", "y") |
100 |
| - return(ellipse) |
101 |
| - } |
102 |
| - } |
103 |
| -) |
| 90 | + angles <- (0:segments) * 2 * pi/segments |
| 91 | + unit.circle <- cbind(cos(angles), sin(angles)) |
| 92 | + ellipse <- t(center + radius * t(unit.circle %*% chol_decomp)) |
| 93 | + } |
104 | 94 |
|
| 95 | + ellipse <- as.data.frame(ellipse) |
| 96 | + colnames(ellipse) <- c("x", "y") |
| 97 | + return(ellipse) |
| 98 | + } |
| 99 | +}) |
0 commit comments