[R] Bounding ellipse for any set of points
Andy Lyons
ajlyons at berkeley.edu
Fri Mar 25 01:23:00 CET 2011
After a lot of effort I developed the following function to compute the
bounding ellipse (also known a as minimum volume enclosing ellipsoid) for
any set of points. This script is limited to two dimensions, but I believe
with minor modification the algorithm should work for 3 or more dimensions.
It seems to work well (although I don't know if can be optimized to run
faster) and hope it may be useful to someone else. Andy
######################################################################
## mvee()
## Uses the Khachiyan Algorithm to find the the minimum volume enclosing
## ellipsoid (MVEE) of a set of points. In two dimensions, this is just
## the bounding ellipse (this function is limited to two dimensions).
## Adapted by Andy Lyons from Matlab code by Nima Moshtagh.
## Copyright (c) 2009, Nima Moshtagh
## [1]http://www.mathworks.com/matlabcentral/fileexchange/9542
## [2]http://www.mathworks.com/matlabcentral/fileexchange/13844
## [3]http://stackoverflow.com/questions/1768197/bounding-ellipse
##
## Parameters
## xy a two-column data frame containing x and y coordinates
## if NULL then a random sample set of 10 points will be
generated
## tolerance a tolerance value (default = 0.005)
## plotme FALSE/TRUE. Plots the points and ellipse. Default TRUE.
## max.iter The maximum number of iterations. If the script tries this
## number of iterations but still can't get to the tolerance
## value, it displays an error message and returns NULL
## shiftxy TRUE/FALSE. If True, will apply a shift to the coordinates to
make them
## smaller and speed up the matrix calculations, then reverse
the shift
## to the center point of the resulting ellipoid. Default TRUE
## Output: A list containing the "center form" matrix equation of the
## ellipse. i.e. a 2x2 matrix "A" and a 2x1 vector "C"
representing
## the center of the ellipse such that:
## (x - C)' A (x - C) <= 1
## Also in the list is a 2x1 vector elps.axes.lngth whose
elements
## are one-half the lengths of the major and minor axes
(variables
## a and b
## Also in list is alpha, the angle of rotation
######################################################################
mvee <- function(xy=NULL, tolerance = 0.005, plotme = TRUE, max.iter = 500,
shiftxy = TRUE) {
if (is.null(xy)) {
xy <- data.frame(x=runif(10,100,200), y=runif(10,100,200))
} else if (ncol(xy) != 2) {
warning("xy must be a two-column data frame")
return(NULL)
}
## Number of points
n = nrow(xy)
## Dimension of the points (2)
d = ncol(xy)
if (n <= d) return(NULL)
## Apply a uniform shift to the x&y coordinates to make matrix
calculations computationally
## simpler (if x and y are very large, for example UTM coordinates, this
may be necessary
## to prevent a 'compuationally singular matrix' error
if (shiftxy) {
xy.min <- sapply(xy, FUN = "min")
} else {
xy.min <- c(0,0)
}
xy.use <- xy - rep(xy.min, each = n)
## Add a column of 1s to the (n x 2) matrix xy - so it is now (n x 3)
Q <- t(cbind(xy.use, rep(1,n)))
## Initialize
count <- 1
err <- 1
u <- rep(1/n, n)
## Khachiyan Algorithm
while (err > tolerance)
{
## see
[4]http://stackoverflow.com/questions/1768197/bounding-ellipse
## for commented code
X <- Q %*% diag(u) %*% t(Q)
M <- diag(t(Q) %*% solve(X) %*% Q)
maximum <- max(M)
j <- which(M == maximum)
step_size = (maximum - d -1) / ((d+1)*(maximum-1))
new_u <- (1 - step_size) * u
new_u[j] <- new_u[j] + step_size
err <- sqrt(sum((new_u - u)^2))
count <- count + 1
if (count > max.iter) {
warning(paste("Iterated", max.iter, "times and still can't find
the bounding ellipse. \n", sep=""))
warning("Either increase the tolerance or the maximum number of
iterations")
return(NULL)
}
u <- new_u
}
## Put the elements of the vector u into the diagonal of a matrix
U <- diag(u)
## Take the transpose of xy
P <- t(xy.use)
## Compute the center, adding back the shifted values
c <- as.vector((P %*% u) + xy.min)
## Compute the A-matrix
A <- (1/d) * solve(P %*% U %*% t(P) - (P %*% u) %*% t(P %*% u))
## Find the Eigenvalues of matrix A which will be used to get the major
and minor axes
A.eigen <- eigen(A)
## Calculate the length of the semi-major and semi-minor axes
## from the Eigenvalues of A.
semi.axes <- sort(1 / sqrt(A.eigen$values), decreasing=TRUE)
## Calculate the rotation angle from the first Eigenvector
alpha <- atan2(A.eigen$vectors[2,1], A.eigen$vectors[1,1]) - pi/2
if(plotme) {
## Plotting commands adapted from code by Alberto Monteiro
## [5]https://stat.ethz.ch/pipermail/r-help/2006-October/114652.html
## Create the points for the ellipse
theta <- seq(0, 2 * pi, length = 72)
a <- semi.axes[1]
b <- semi.axes[2]
elp.plot.xs <- c[1] + a * cos(theta) * cos(alpha) - b * sin(theta) *
sin(alpha)
elp.plot.ys <- c[2] + a * cos(theta) * sin(alpha) + b * sin(theta) *
cos(alpha)
## Plot the ellipse with the same scale on each axis
plot(elp.plot.xs, elp.plot.ys, type = "l", lty="dotted", col="blue",
asp=1,
main="minimum volume enclosing ellipsoid", xlab=names(xy)[1],
ylab=names(xy)[2])
## Plot the original points
points(xy[,1], xy[,2], type="p", pch=16)
## add the center of the ellipse using a triangle symbol
points(c[1], c[2], pch=2, col="blue")
}
ellipse.params <- list("A" = A, "c" = c, "ab" = semi.axes, alpha=alpha)
}
References
1. http://www.mathworks.com/matlabcentral/fileexchange/9542
2. http://www.mathworks.com/matlabcentral/fileexchange/13844
3. http://stackoverflow.com/questions/1768197/bounding-ellipse
4. http://stackoverflow.com/questions/1768197/bounding-ellipse
5. https://stat.ethz.ch/pipermail/r-help/2006-October/114652.html
More information about the R-help
mailing list