areg.s
# $Id: areg.s 624 2009-03-17 14:48:54Z harrelfe $
areg <- function(x, y, xtype=NULL, ytype=NULL, nk=4,
B=0, na.rm=TRUE,
tolerance=NULL, crossval=NULL)
{
yname <- deparse(substitute(y))
xname <- deparse(substitute(x))
ism <- is.matrix(x)
if(!ism)
{
x <- as.matrix(x)
if(!length(colnames(x))) colnames(x) <- xname
}
if(na.rm)
{
omit <- is.na(x %*% rep(1,ncol(x))) | is.na(y)
nmiss <- sum(omit)
if(nmiss)
{
x <- x[!omit,,drop=FALSE]
y <- y[!omit]
}
} else nmiss <- 0
d <- dim(x)
n <- d[1]; p <- d[2]
xnam <- colnames(x)
if(!length(xnam)) xnam <- paste('x', 1:p, sep='')
nuy <- length(unique(y))
if(!length(ytype)) ytype <-
if(is.factor(y) || is.category(y) || is.character(y)) 'c' else
if(nk==0 || (nuy < 3)) 'l' else 's'
if(nk==0 && ytype=='s') ytype <- 'l'
if(ytype=='s' && nk > 0 && nuy < 3)
{
warning('requested smooth transformation of y but less than 3 unique values; forced linear')
ytype <- 'l'
}
if(!length(xtype)) xtype <- rep(if(nk==0)'l' else 's', p)
xtype[nk==0 & xtype=='s'] <- 'l'
names(xtype) <- xnam
nux <- apply(x, 2, function(z) length(unique(z)))
tooTied <- xtype=='s' & nux < nk
if(any(tooTied))
{
warning(paste('the following x variables have too few unique values for the value of nk;\nlinearity forced:', paste(xnam[tooTied], collapse=',')))
xtype[nux] <- 'l'
}
fcancor <- function(X, Y)
{
## If canonical variate transformation of Y is descending in Y,
## negate all parameters
f <- cancor(X, Y)
f$r2 <- f$cor[1]^2
n <- nrow(Y); if(!length(n)) n <- length(y)
varconst <- sqrt(n-1)
xcoef <- c(intercept = -sum(f$xcoef[, 1] * f$xcenter),
f$xcoef[, 1]) * varconst
ycoef <- c(intercept = -sum(f$ycoef[, 1] * f$ycenter),
f$ycoef[, 1]) * varconst
ty <- matxv(Y, ycoef)
g <- lm.fit.qr.bare(Y,ty)
if(g$coefficients[2] < 0)
{
xcoef <- -xcoef
ycoef <- -ycoef
ty <- -ty
}
f$xcoef <- xcoef
f$ycoef <- ycoef
f$ty <- ty
f
}
need2getinv <- FALSE
Y <- aregTran(y, ytype, nk, functions=TRUE)
at <- attributes(Y)
ytrans <- at$fun
yinv <- at$inversefun ## NULL if type='s'; need coef
yparms <- at$parms
xdf <- ifelse(xtype=='l', 1, nk-1)
j <- xtype=='c'
if(any(j)) xdf[j] <- nux[j] - 1
names(xdf) <- xnam
X <- matrix(NA, nrow=n, ncol=sum(xdf))
xparms <- list()
j <- 0
xn <- character(0)
for(i in 1:p)
{
w <- aregTran(x[,i], xtype[i], nk)
xparms[[xnam[i]]] <- attr(w, 'parms')
m <- ncol(w)
xdf[i] <- m
X[,(j+1):(j+m)] <- w
j <- j + m
xn <- c(xn, paste(xnam[i],1:m,sep=''))
}
## See if rcpsline.eval could not get desired no. of knots due to ties
if(ncol(X) > sum(xdf)) X <- X[,1:sum(xdf),drop=FALSE]
covx <- covy <- r2opt <- r2boot <-
madopt <- madboot <- medopt <- medboot <- NULL
if(B > 0)
{
r <- 1 + sum(xdf)
barx <- rep(0, r)
vname <- c('Intercept',xn)
covx <- matrix(0, nrow=r, ncol=r, dimnames=list(vname,vname))
if(ytype != 'l')
{
r <- ncol(Y)+1
bary <- rep(0, r)
vname <- c('Intercept',paste(yname, 1:(r-1), sep=''))
covy <- matrix(0, nrow=r, ncol=r, dimnames=list(vname,vname))
}
}
if(ytype=='l')
{
f <- lm.fit.qr.bare(X, Y, tolerance=tolerance)
xcof <- f$coefficients
r2 <- f$rsquared
cof <- 1
ty <- y
ydf <- 1
lp <- f$fitted.values
res <- f$residuals
mad <- mean(abs(y-lp))
med <- median(abs(y-lp))
if(B > 0)
{
r2opt <- madopt <- medopt <- 0
for(j in 1:B)
{
s <- sample(1:n, replace=TRUE)
if(ytype=='c' && length(unique(y[s])) < nuy)
stop('a bootstrap resample had too few unique values of y')
xch <- which(xtype=='c')
if(length(xch))
{
xtf <- apply(x[s, xch, drop=FALSE], 2,
function(z)length(unique(z))) < nux[xch]
if(any(xtf))
stop(paste('a bootstrap resample had too few unique values of the following x variables:',paste(xnam[xch[xtf]],collapse=','),sep='\n'))
}
g <- lm.fit.qr.bare(X[s,,drop=FALSE], Y[s])
b <- g$coefficients
r2boot <- g$rsquared
yhat <- matxv(X,b)
r2orig <- cor(yhat, y)^2
r2opt <- r2opt + r2boot - r2orig
er <- abs(Y[s] - g$fitted.values)
madboot <- mean(er)
medboot <- median(er)
er <- abs(y - yhat)
madorig <- mean(er)
medorig <- median(er)
madopt <- madopt + madboot - madorig
barx <- barx + b
b <- as.matrix(b)
covx <- covx + b %*% t(b)
}
r2opt <- r2opt/B
r2boot <- r2 - r2opt
madopt <- madopt/B
madboot <- mad - madopt
medopt <- medopt/B
medboot <- med - medopt
barx <- as.matrix(barx/B)
covx <- (covx - B * barx %*% t(barx))/(B-1)
}
} else
{
f <- fcancor(X, Y)
r2 <- f$r2
xcof <- f$xcoef
cof <- f$ycoef
ty <- f$ty
ydf <- length(cof) - 1
lp <- as.vector(matxv(X, xcof))
res <- as.vector(ty - lp)
if(!length(yinv))
{
## spline transformation, need coef to get inverse y transform
yy <- seq(min(y), max(y), length=1000)
tyy <- ytrans(yy, coef=cof)
yinv <- inverseFunction(yy, tyy)
need2getinv <- TRUE
}
puy <- yinv(lp, what='sample')
if(length(y) != length(puy)) stop('program logic error')
mad <- mean(abs(y-puy))
med <- median(abs(y-puy))
if(B > 0)
{
r2opt <- madopt <- medopt <- 0
for(j in 1:B)
{
s <- sample(1:n, replace=TRUE)
if(ytype=='c' && length(unique(y[s])) < nuy)
stop('a bootstrap resample had too few unique values of y')
xch <- which(xtype=='c')
if(length(xch))
{
xtf <- apply(x[s, xch, drop=FALSE], 2,
function(z)length(unique(z))) < nux[xch]
if(any(xtf))
stop(paste('a bootstrap resample had too few unique values of the following x variables:',paste(xnam[xch[xtf]],collapse=','),sep='\n'))
}
f <- fcancor(X[s,,drop=FALSE],Y[s,,drop=FALSE])
bx <- f$xcoef
by <- f$ycoef
r2boot <- f$r2
xbeta <- matxv(X,bx)
ybeta <- matxv(Y,by)
r2orig <- cor(xbeta, ybeta)^2
r2opt <- r2opt + r2boot - r2orig
puyall <- if(need2getinv)
{
tyyb <- ytrans(yy, coef=by) ## keeping constant knots
yinvb <- inverseFunction(yy, tyyb)
yinvb(xbeta, coef=by, what='sample')
} else yinv(xbeta, coef=by)
er <- abs(y[s] - puyall[s])
madboot <- mean(er)
medboot <- median(er)
er <- abs(y - puyall)
madorig <- mean(er)
medorig <- median(er)
madopt <- madopt + madboot - madorig
medopt <- medopt + medboot - medorig
barx <- barx + bx
bx <- as.matrix(bx)
covx <- covx + bx %*% t(bx)
bary <- bary + by
by <- as.matrix(by)
covy <- covy + by %*% t(by)
}
r2opt <- r2opt/B
r2boot <- r2 - r2opt
madopt <- madopt/B
madboot <- mad - madopt
medopt <- medopt/B
medboot <- med - medopt
barx <- as.matrix(barx/B)
bary <- as.matrix(bary/B)
covx <- (covx - B * barx %*% t(barx))/(B-1)
covy <- (covy - B * bary %*% t(bary))/(B-1)
}
}
j <- 0
beta <- xcof[-1]
tx <- x
xmeans <- list()
for(i in 1:p)
{
m <- xdf[i]
z <- matxv(X[,(j+1):(j+m),drop=FALSE], beta[(j+1):(j+m)])
mz <- mean(z)
xmeans[[xnam[i]]] <- mz
tx[,i] <- z - mz
j <- j + m
}
r2cv <- madcv <- medcv <- NULL
if(length(crossval))
{
s <- sample(1:crossval, n, replace=TRUE)
r2cv <- madcv <- medcv <- 0
for(j in 1:crossval)
{
g <- fcancor(X[s!=j,,drop=FALSE], Y[s!=j,,drop=FALSE])
bx <- g$xcoef
by <- g$ycoef
xbo <- matxv(X[s==j,,drop=FALSE], bx)
ybo <- matxv(Y[s==j,,drop=FALSE], by)
r2cv <- r2cv + cor(xbo, ybo)^2
puy <- if(need2getinv)
{
tyyb <- ytrans(yy, coef=by) ## keeping constant knots
yinvb <- inverseFunction(yy, tyyb)
yinvb(xbo, coef=by, what='sample')
}
else yinv(xbo, coef=by)
er <- abs(y[s==j] - puy)
madcv<- madcv + mean(er)
medcv<- medcv + median(er)
}
r2cv <- r2cv/crossval
madcv <- madcv/crossval
medcv <- medcv/crossval
}
structure(list(y=y, x=x, ty=ty, tx=tx,
rsquared=r2, rsquaredcv=r2cv, nk=nk, xdf=xdf, ydf=ydf,
xcoefficients=xcof, ycoefficients=cof,
xparms=xparms, yparms=yparms, xmeans=xmeans,
ytrans=ytrans, yinv=yinv,
linear.predictors=lp, residuals=res,
xtype=xtype, ytype=ytype, yname=yname,
r2boot=r2boot, r2opt=r2opt,
mad=mad, madboot=madboot, madopt=madopt,
med=med, medboot=medboot, medopt=medopt,
madcv=madcv, medcv=medcv,
xcov=covx, ycov=covy,
n=n, m=nmiss, B=B, crossval=crossval),
class='areg')
}
aregTran <- function(z, type, nk = length(parms), parms = NULL,
functions = FALSE)
{
if(type=='l' || (type=='s' && nk==0))
return(if(functions)
structure(as.matrix(z),
fun =function(x,...)x,
inversefun=function(x,...)x) else as.matrix(z))
if(type=='c')
{
n <- length(z)
lp <- length(parms)
## Assume z is integer code if parms is given
w <- if(lp) z else factor(z)
x <- as.integer(w)
if(!lp) parms <- 1:max(x, na.rm=TRUE)
z <- matrix(0, nrow=n, ncol=length(parms)-1)
z[cbind(1:n, x-1)] <- 1
attr(z, 'parms') <- if(lp)parms else levels(w)
if(functions)
{
attr(z, 'fun') <- function(x, parms, coef) {
if(length(parms) > length(coef)) coef <- c(0,coef)
coef[-1] <- coef[-1] + coef[1]
names(coef) <- parms
coef[x]
}
formals(attr(z, 'fun')) <-
list(x=integer(0), parms=parms, coef=numeric(0))
## what is ignored; for compatibility with inverseFunction in Misc.s
attr(z, 'inversefun') <- function(y, parms, coef, what=character(0)) {
if(length(parms) > length(coef)) coef <- c(0, coef)
isna <- is.na(y)
y[isna] <- 0
x <- parms[whichClosest(c(coef[1], coef[1] + coef[-1]), y)]
x[isna] <- NA
x
}
formals(attr(z, 'inversefun')) <-
list(y=numeric(0), parms=parms,
coef=numeric(0), what=character(0))
}
z
}
else
{
z <- rcspline.eval(z, knots=parms, nk=nk, inclx=TRUE)
knots <- attr(z, 'knots')
attr(z,'parms') <- knots
if(functions) attr(z, 'fun') <- rcsplineFunction(knots)
## inverse function created later when coefficients available
z
}
}
predict.areg <- function(object, x, type=c('lp','fitted'),
what=c('all','sample'), ...)
{
type <- match.arg(type)
what <- match.arg(what)
beta <- object$xcoefficients
xparms <- object$xparms
xtype <- object$xtype
xdf <- object$xdf
ybeta <- object$ycoefficients
yinv <- object$yinv
x <- as.matrix(x)
p <- length(xdf)
X <- matrix(NA, nrow=nrow(x), ncol=sum(xdf))
j <- 0
xnam <- names(xtype)
for(i in 1:p)
{
w <- aregTran(x[,i], xtype[i], parms=xparms[[xnam[i]]])
m <- ncol(w)
X[,(j+1):(j+m)] <- w
j <- j + m
}
xb <- matxv(X, beta)
if(type=='fitted') yinv(xb, what=what, coef=ybeta) else xb
}
print.areg <- function(x, digits=4, ...)
{
xdata <- x[c('n','m','nk','rsquared','xtype','xdf','ytype','ydf')]
xinfo <- data.frame(type=xdata$xtype, d.f.=xdata$xdf,
row.names=names(xdata$xtype))
cat('\nN:',xdata$n,'\t',xdata$m,
' observations with NAs deleted.\n')
cat('R^2: ', round(xdata$rsquared,3),'\tnk: ',xdata$nk,
'\tMean and Median |error|: ',format(x$mad, digits=digits),', ',
format(x$med, digits=digits),'\n\n', sep='')
if(length(x$r2boot)) {
x1 <- format(c(x$r2opt, x$madopt, x$medopt), digits=digits)
x2 <- format(c(x$r2boot, x$madboot, x$medboot), digits=digits)
n <- c('R^2', 'Mean |error|', 'Median |error|')
d <- cbind('Bootstrap Estimates'=n, Optimism=x1, 'Optimism-corrected'=x2)
row.names(d) <- rep('', 3)
if(.R.) print(d, quote=FALSE, right=TRUE) else
print(d, quote=FALSE)
}
if(length(x$crossval))
{
x1 <- format(c(x$rsquaredcv, x$madcv, x$medcv), digits=digits)
n <- c('R^2', 'Mean |error|', 'Median |error|')
d <- cbind(n, x1)
dimnames(d) <- list(rep('',3),
c(paste(x$crossval,'-fold Cross-validation',sep=''),
'Estimate'))
cat('\n')
if(.R.) print(d, quote=FALSE, right=TRUE) else
print(d, quote=FALSE)
}
cat('\n')
print(xinfo)
cat('\ny type:', xdata$ytype,'\td.f.:', xdata$ydf,'\n\n')
invisible()
}
plot.areg <- function(x, whichx=1:ncol(x$x), ...)
{
plot(x$y, x$ty, xlab=x$yname,
ylab=paste('Transformed',x$yname))
r2 <- round(x$rsquared,3)
if(.R.) title(sub=bquote(R^2==.(r2)), adj=0) else
title(sub=paste('R^2=',r2),adj=0)
xdata <- x$x
cn <- colnames(xdata)
for(i in whichx)
plot(xdata[,i], x$tx[,i],
xlab=cn[i], ylab=paste('Transformed', cn[i]), ...)
invisible()
}