[R] rgl: ellipse3d with axes

Duncan Murdoch murdoch at stats.uwo.ca
Wed Sep 24 18:52:13 CEST 2008


On 24/09/2008 12:32 PM, Michael Friendly wrote:
> Thanks Duncan (& others)
> 
> Here is a function that does what I want in this case, and tries to do 
> it to work generally
> with ellipse3d.  (Note that I reverse the order of centre and scale 
> 'cause I was bitten
> by trying ellipse3d.axes(cov, mu))
> 
> # draw axes in the data ellipse computed by ellipse3d
> ellipse3d.axes <-
> function (x, centre = c(0, 0, 0), scale = c(1, 1, 1), level = 0.95,
>     t = sqrt(qchisq(level, 3)), which = 1:3, ...)
> {
>     stopifnot(is.matrix(x)) # should test for square, symmetric
>     cov <- x[which, which]
>     eigen <- eigen(cov)
>     # coordinate axes, (-1, 1), in pairs
>     axes <- matrix(
>       c(0, 0, -1,   0, 0, 1,
>         0, -1, 0,   0, 1, 0,
>        -1, 0, 0,    1, 0, 0),  6, 3, byrow=TRUE)
> 
>     # transform to PC axes
>     axes <- axes %*% sqrt(diag(eigen$values)) %*% t(eigen$vectors)
>     result <- scale3d(axes, t, t, t)
>     if (!missing(scale))
>         if (length(scale) != 3) scale <- rep(scale, length.out=3)
>         result <- scale3d(result, scale[1], scale[2], scale[3])
>     if (!missing(centre))
>         if (length(centre) != 3) scale <- rep(centre, length.out=3)
>         result <- translate3d(result, centre[1], centre[2], centre[3])
>     segments3d(result, ...)
>     invisible(result)
> }
> 
> Test case:
> 
> library(rgl)
> data(iris)
> iris3 <- iris[,1:3]
> cov <- cov(iris3)
> mu <- mean(iris3)
> col <-c("blue", "green", "red")[iris$Species]
> plot3d(iris3, type="s", size=0.5, col=col, cex=2, box=FALSE)
> plot3d( ellipse3d(cov, centre=mu, level=0.68), col="gray", alpha=0.2,  
> add = TRUE)
> 
> axes <- ellipse3d.axes(cov, centre=mu)
> 
> One thing I can't explain, compared to your example is why the my axes 
> extend outside the ellipse,
> whereas yours didn't.

That's just because you specified level in the ellipse3d call, but not 
in the ellipes3d.axes call.

One thing that looks a little strange is that the PC axes don't appear 
to be orthogonal:  this is because the scaling is not the same on all 
coordinates.  It might look better doing the first plot as

plot3d(iris3, type="s", size=0.5, col=col, cex=2, box=FALSE, aspect="iso")

Duncan Murdoch

> 
> One final remark-  I knew that axes %*% chol(cov) did not give the 
> orthogonal PC axes I wanted,
> but at least it gave me something on the right scale and location. But 
> these axes also turn out to be
> useful for visualizing multivariate scatter and statistical concepts.  
> chol() gives the factorization of
> cov that corresponds to the Gram-Schmidt orthogonalization of a data 
> matrix -- orthogonal axes
> in the order x1,  x2|x1,  x3|x1, x2, ..., and vector length and 
> orientation in this coordinate system
> correspond to Type I SS in linear models.
> Thus, I could see generalizing my ellipse3d.axes function further to 
> allow a type=c("pca", "chol")
> argument.
> 
> -Michael
> 
> 
> 
> Duncan Murdoch wrote:
>> That's easy, but it doesn't give you the principal axes of the 
>> ellipse.  Just use
>>
>> axes %*% chol(cov)
>>
>> If you start with a unit sphere, this will give you points on its 
>> surface, but not the ones you want.  For those you need the SVD or 
>> eigenvectors.  This looks like it does what you want:
>>
>> axes <- matrix(
>>     c(0, 0, 0, # added origin
>>        0, 0, -1,   0, 0, 1,
>>        0, -1, 0,   0, 1,  0,
>>        -1, 0, 0,   1, 0, 0),  7, 3, byrow=TRUE)
>> axes <- axes[c(1,2,1,3,1,4,1,5,1,6,1,7),]  # add the origin before each
>>
>> cov <- cov(trees)
>> eigen <- eigen(cov)
>> shade3d(ellipse3d(cov, t=1, alpha=0.2, col='red'))
>> segments3d(axes %*% sqrt(diag(eigen$values)) %*% t(eigen$vectors))
>>
>> Duncan Murdoch
> 
>



More information about the R-help mailing list