aov design questions
Jim Robison-Cox
jimrc@mathfs.math.montana.edu
Fri, 29 May 1998 16:44:12 -0600 (MDT)
R developers,
I have a first attempt to make an aov function. Eventually I want to
build in Error() structure, but first I am trying to get this
presentable for balanced data with only a single stratum, just using
residual error. I am following R. M. Heiberger's Computation for the
Analysis of Designed Experiments, Wiley (1989)
I a using a wrapper (aov.bal) to call the fitting function in the same
way that lm() does a bunch of stuff and then calls lm.fit(), in fact I
took most of the wrapper right out of lm(). I'm trying to make the aov
object look as much as possible like an lm.object
Several questions have come up which need discussion.
Q 1) Is aov supposed to avoid qr decomposition and matrix inversion?
In S it is claimed to be faster than lm() for large datasets. Is
that due to avoidance of qr()?
(My main goal is to get the Error strata working.)
Q 2) If the speed is important, can we give up choice of contrasts used to
set up the dummy factor variables? ( contr.treatment, contr.sum)
Why do I ask?
In pursuit of speed (we're talking nanoseconds for a reasonable size
data set) I have forced in helmert contrasts because their
orthogonality makes it slick to compute. So in this version, whatever
option(contrasts) is set will be ignored.
I don't like making that choice for the user esp. since it gives wrong
answers under the default contrasts, and have thought about
converting back into the original contrasts, but it isn't easy with
higher-way anova models. It looks easy in VR2 p 197-204
(BTW I have kroenecker and ginv functions if anyone wants them)
but I don't get it to work out nicely. V&R show the contrast
transformation as:
alpha_T = ginv(C_T) %*% C_H %*% alpha_H
where C_T is the contrast matrix for treatment contrasts, and C_H for
Helmert contrasts. The alpha's contain only the treatment contrast
effects, not the mean. This would leave the intercept estimate
unchanged (not right). For single factors it works to stick a
col.vector of 1s on the front of C_H and C_T, and include the mu.hat in
the alpha_H or alpha_T. But when I get into interactions, I don't see
what is needed.
Q 3) I am having a problem with keep.order. With balanced data, I know
the effects are orthogonal, so order is not an issue, but as I move
onto the tougher cases, I need model.matrix to preserve order, and
it doesn't.
Q 4) What are the $effects in lm.objects?
For the moment I've stuck in the coefficients.
Q 5) Any objection to passing cov.unscaled from aov to summary.aov?
(Since I'm not using qr and can't pass R)
Feedback needed, comments are welcome.
Jim Robison-Cox ____________
Department of Math Sciences | | phone: (406)994-5340
2-214 Wilson Hall \ BZN, MT | FAX: (406)994-1789
Montana State University | *_______|
Bozeman, MT 59717 \_| e-mail: jimrc@math.montana.edu
#---------------------------------------------------------------------------
## First hack at aov for balanced data
aov.bal <- function(formula, data = list(), subset, weights, na.action,
method = "qr", model = TRUE, keep.order=F, ...)
{
mt <- terms(formula, data = data, keep.order = keep.order)
nt <- length(attr(mt,"order")[attr(mt,"order") ==1])
lt <- rep("contr.helmert",nt) ## Sets helmert contrasts
names(lt) <- attr(mt,"term.labels")[ attr(mt,"order") ==1]
mf <- match.call()
mf$model <- NULL
mf[[1]] <- as.name("model.frame")
mf <- eval(mf, sys.frame(sys.parent()))
if (method == "model.frame")
return(mf)
else if (method != "qr") ## change for aov??
warning(paste("method =", method, "is not supported. Using \"qr\"."))
if (length(list(...)))
warning(paste("Extra arguments", deparse(substitute(...)),
"are just disregarded."))
y <- model.response(mf, "numeric")
w <- model.weights(mf)
if (is.empty.model(mt)) {
z <- list(coefficients = numeric(0), residuals = y,
fitted.values = 0 * y, weights = w, rank = 0,
df.residual = length(y))
class(z) <- if (is.matrix(y))
c("mlm.null", "lm.null", "mlm", "lm")
else c("lm.null", "lm")
}
else {
x <- model.matrix(mt, mf, as.list(lt))
# forces in contr.helmert
z <- if (is.null(w)) {
aov.balanced.fit(x, y, mt)
}
else {
stop("Weighted aov not implemented, use lm")
#aov.balanced.wfit(x, y, mt, w)
}
class(z) <- c(if (is.matrix(y)) c("maov","mlm"), c("aov","lm"))
}
z$call <- match.call()
z$terms <- mt
if (model)
z$model <- mf
z
}
aov.balanced.fit <- function(x,y,mod.terms,wt=NULL){
x <- as.matrix(x); dimx <- dim(x)
xnames <- dimnames(x)[[2]]
y <- as.matrix(y); dimy <- dim(y)
assn <- attr(x,"assign")
df <- c(table(assn), dimy[1]- dimx[2])
D <- diag(1/diag(t(x) %*% x))
dimnames(D) <- list(xnames,xnames)
coef <- as.numeric(D %*% t(x) %*% y)
names(coef) <- xnames
fits <- x %*% coef
resid <- y - fits
eff <- apply((t(x) * rep(coef,dimx[1]))^2,1,sum)
## Take each column of x (row of t(x)) times its
## coefficient, square the result and sum (over columns of x)
eff <- c(sapply( split(eff,assn), sum), sum(resid^2))
## Adds them up over the factors.
if(attr(mod.terms,"intercept") == 1) {
eff <- eff[-1]
df <- df[-1]
}
name <- c(attr(mod.terms,"term.labels"), "Residuals")
names(eff) <- name
names(df) <- name
list(coefficients= coef, residuals = resid, effects = eff,
rank= dimx[2], fitted.values = fits, assign= assn,
cov.unscaled = D, df.residual = dimy[1] - dimx[2],
df = df )
}
print.aov <-
function (x, digits = max(3, .Options$digits - 3), ...)
{
lenx <- length(x$df)
tabl <- cbind(x$df,x$effects, x$effects/x$df)
tabl <- cbind(tabl,c(tabl[1:(lenx-1),3]/tabl[lenx,3],NA))
tabl <- cbind(tabl,1-pf(tabl[,4],tabl[,1],tabl[lenx,1]))
dimnames(tabl) <- list(names(x$effects), c("df","SS","MS","F","P > F"))
cat("\nAnalysis of Variance:\n\n")
print.default(round(tabl, digits), na = "", print.gap = 2)
cat("\n")
invisible(x)
}
summary.aov <- function (z, correlation=F)
{
p <- z$rank
p1 <- 1:p
r <- resid(z)
f <- fitted(z)
n <- length(f)
w <- weights(z)
if (is.null(z$terms)) {
stop("invalid 'aov' object: no terms component")
}
else {
if (is.null(w)) {
mss <- if (attr(z$terms, "intercept"))
sum((f - mean(f))^2)
else sum(f^2)
rss <- sum(r^2)
}
}
resvar <- rss/(n - p)
se <- sqrt(resvar * diag(z$cov.unscaled))
est <- z$coefficients
tval <- est/se
ans <- z[c("call", "terms")]
ans$residuals <- as.numeric(r)
ans$coefficients <- cbind(est, se, tval, 2 * (1 - pt(abs(tval),
n - p)))
dimnames(ans$coefficients) <- list(names(z$coefficients),
c("Estimate", "Std.Error", "t Value", "Pr(>|t|)"))
ans$sigma <- sqrt(resvar)
ans$df <- c(p, n - p)
if (p != attr(z$terms, "intercept")) {
df.int <- if (attr(z$terms, "intercept"))
1
else 0
ans$r.squared <- mss/(mss + rss)
#0.14 : (n/(n-p))
ans$adj.r.squared <- 1 - (1 - ans$r.squared) *
((n - df.int)/(n - p))
ans$fstatistic <- c((mss/(p - df.int))/(rss/(n -
p)), p - df.int, n - p)
#0.14: ans$fstatistic <- c((mss/(p-1))/(rss/(n-p)),p-1,n-p)
names(ans$fstatistic) <- c("value", "numdf",
"dendf")
}
ans$cov.unscaled <- z$cov.unscaled
if (correlation) {
invsqrt.diag <- diag(sqrt(1/diag(z$cov.unscaled)))
ans$correlation <-
invsqrt.diag %*% z$cov.unscaled %*% invsqrt.diag
dimnames(ans$correlation) <- dimnames(z$cov.unscaled)
}
class(ans) <- "summary.aov"
ans
}
print.summary.aov <- function (x, digits = max(3, .Options$digits - 3),
roundfun = round, ...)
{
cat("\nCall:\n")
cat(paste(deparse(x$call), sep = "\n", collapse = "\n"),
"\n\n", sep = "")
resid <- x$residuals
df <- x$df
rdf <- df[2]
if (rdf > 5) {
cat("Residuals:\n")
if (length(dim(resid)) == 2) {
rq <- apply(t(resid), 1, quantile)
dimnames(rq) <- list(c("Min", "1Q", "Median",
"3Q", "Max"), dimnames(resid)[[2]])
}
else {
rq <- quantile(as.numeric(resid))
names(rq) <- c("Min", "1Q", "Median",
"3Q", "Max")
}
print(rq, digits = digits, ...)
}
else if (rdf > 0) {
cat("Residuals:\n")
print(resid, digits = digits, ...)
}
# if (nsingular <- df[3] - df[1])
# cat("\nCoefficients: (", nsingular, " not defined because of singularities)\n",
# sep = "")
# else
cat("\nCoefficients:\n")
print(roundfun(x$coefficients, digits = digits), quote = FALSE,
...)
cat("\nResidual standard error:", format(signif(x$sigma,
digits)), "on", rdf, "degrees of freedom\n")
if (!is.null(x$fstatistic)) {
cat("Multiple R-Squared:", format(signif(x$r.squared,
digits)))
cat(",\tAdjusted R-squared:", format(signif(x$adj.r.squared,
digits)), "\n")
cat("F-statistic:", format(signif(x$fstatistic[1],
digits)), "on", x$fstatistic[2], "and",
x$fstatistic[3], "degrees of freedom")
cat(",\tp-value:", format(signif(1 - pf(x$fstatistic[1],
x$fstatistic[2], x$fstatistic[3]), digits)),
"\n")
}
correl <- x$correlation
if (!is.null(correl)) {
p <- dim(correl)[2]
if (p > 1) {
cat("\nCorrelation of Coefficients:\n")
correl[!lower.tri(correl)] <- NA
print(correl[-1, -NCOL(correl)], digits = digits,
na = "")
}
}
cat("\n")
invisible(x)
}
#-------------------------------------------------------------------------
## Rd file:
\name{aov.bal}
\title{Function to compute analysis of variance for balanced designs}
\usage{
aov.bal(formula, data=list(), subset, weights, na.action, method="qr", model=TRUE, keep.order=F, ...)
}
%- maybe also `usage' for other functions documented here.
\alias{aov.bal}
\alias{summary.aov}
\alias{coefficients.lm}
\alias{df.residual.lm}
\alias{fitted.values.lm}
\alias{residuals.lm}
\alias{aov.balanced.fit}
\alias{print.aov}
\alias{print.summary.aov}
\keyword{regression}
%- Also NEED an `\alias' for EACH other function documented here.
\arguments{
\item{formula}{A detailed description of the model to be fit. See
details under \code{\link{lm}} }
\item{data}{ data frame contining the data to be fit. Default is
working directory.}
\item{subset}{Description of what rows of the data frame to use in the fit. }
\item{na.action}{ Currently not used }
\item{method}{Not currently used, except that method="model.frame" will
extract the model frame only and not fit the anova.}
\item{model}{ If true, model frame is included in the output.}
\item{keep.order}{If true, model should be evaluated in the order
specified by the model statement. (Doesn't work.)}
\item{\dots}{ other arguments (not presently used) }
}
\description{
\code{aov.bal} is used to fit analysis of variance models. Eventually it
will handle multiple strata. The generic accessor functions
\code{coefficients}, \code{fitted.values} and \code{residuals}
can be used to extract various useful features of the
value returned by \code{aov.bal}. Currently \code{aov.bal} uses only the
Helmert contrasts and ignores the contrast functions set by
options()$contrast.
}
\value{
\code{aov.bal} returns an object of class code\aov} which may be printed
with the \code{print.aov} or \code{summary.aov}.
\item{coefficients}{Coefficients under the Helmert contrasts.}
\item{residuals} {Residuals}
\item{effects } {effects ?=? coefficients?}
\item{rank}
\item{fitted.values}
\item{assign}
\item{var }
\item{df.residual}
\item{df}
\item{call}
\item{terms}
}
\references{ Heiberger, R. M., 1989, Computation for the Analysis of
Designed Experiments, Wiley}
\author{ Jim Robison-Cox }
\seealso{ \code{\link{lm}},\code{\link{glm}},\code{\link{coefficients}},\code{\link{residuals}},\code{\link{fitted.values}} }
\examples{
speed <- c(59, 69, 66, 75, 54, 65, 58, 70, 66, 77, 71, 71, 58, 65, 56,
62, 54, 60, 52, 64, 59, 73, 60, 70)
typists <- gl(6,4,24)
brands <- gl(4,1,24)
type.aov <- aov.bal(speed ~ typists + brands)
summary(type.aov)
}
\keyword{ regression }%-- one or more ...
\keyword{models}
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
r-devel mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html
Send "info", "help", or "[un]subscribe"
(in the "body", not the subject !) To: r-devel-request@stat.math.ethz.ch
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._