spline.R
###########################################################
# Functions used in fitting additive models in cpglmm #
###########################################################
#######################
# get model frame and factor list
# for cpglmm with smoothing terms
#######################
frFL <- function (formula, data, family, control = list(),
verbose, weights, offset, contrasts, basisGenerators, bySetToZero = T)
{
call <- match.call()
formula <- eval(call$formula)
tf <- terms.formula(formula, specials = eval(call$basisGenerators,
parent.frame(2)))
f.ind <- unlist(attr(tf, "specials"))
n.f <- length(f.ind)
rhs <- safeDeparse(formula[[3]])
fctterm <- fct <- vector(mode = "list", length = n.f)
for (i in 1:n.f)
fctterm[[i]] <- attr(tf, "variables")[[f.ind[i] + 1]]
fct <- lapply(fctterm, eval, envir = data, enclos = parent.frame(2))
for (i in seq_along(fct))
fct[[i]] <- expandBasis(fct[[i]], eval(attr(fct[[i]], "call")$by, data),
eval(attr(fct[[i]], "call")$varying, data), bySetToZero)
names(fct) <- names(fctterm) <- paste("f.", lapply(fct,
function(x) {
paste(as.character(attr(x, "call")$x), ifelse(!is.null(eval(attr(x,
"call")$varying, data)), paste("X", deparse(attr(x,
"call")$varying), sep = ""), ""), ifelse(eval(attr(x,
"call")$allPen), paste(".", deparse(attr(x,
"call")$by), sep = ""), ""), sep = "")
}), sep = "")
rhs <- subFcts(rhs, fctterm, fct, data)
data <- expandMf(data, fct)
call[[1]] <- as.name("lmer")
call$doFit <- FALSE
call$data <- as.name("data")
call$formula <- as.formula(paste(formula[[2]], "~", rhs))
call["basisGenerators"] <- NULL
m <- eval(call, data)
# m$fr$mf <- data
m <- subAZ(m, fct)
fctterm <- lapply(fct, function(x) attr(x, "call"))
return(list(m = m, fct = fct, fctterm = fctterm))
}
### The main event
lmer <-
function(formula, data, family = NULL, REML = TRUE,
control = list(), start = NULL, verbose = FALSE, doFit = TRUE,
subset, weights, na.action, offset, contrasts = NULL,
model = TRUE, x = TRUE, ...)
### Linear Mixed-Effects in R
{
mc <- match.call()
if (!is.null(family)) { # call glmer
mc[[1]] <- as.name("glmer")
return(eval.parent(mc))
}
stopifnot(length(formula <- as.formula(formula)) == 3)
fr <- lmerFrames(mc, formula, contrasts) # model frame, X, etc.
FL <- lmerFactorList(formula, fr, rmInt=FALSE, drop=FALSE) # flist, Zt, dims
largs <- list(...)
if (!is.null(method <- largs$method)) {
warning(paste("Argument", sQuote("method"),
"is deprecated. Use", sQuote("REML"),
"instead"))
REML <- match.arg(method, c("REML", "ML")) == "REML"
largs <- largs[names(largs) != "method"]
}
### FIXME: issue a warning if the control argument has an msVerbose component
FL$dims["LMM"] <- 1L
FL$dims["mxit"] <- 500L
FL$dims["mxfn"] <- 1000L
ans <- list(fr = fr, FL = FL, start = start)
ans
}
############################
# function for 2d splines (from Ngo and Wand)
############################
# compute thin plate spline covariance function
tps.cov <- function(r) {
r <- as.matrix(r)
num.row <- nrow(r)
num.col <- ncol(r)
r <- as.vector(r)
nzi <- (1:length(r))[r!=0]
ans <- rep(0,length(r))
ans[nzi] <- r[nzi]*r[nzi]*log(abs(r[nzi]))
if (num.col>1)
ans <- matrix(ans,num.row,num.col)
return(ans)
}
sp2d <- function(x1, x2, k = max(20,min(length(x1)/4,150)),
by = NULL, allPen = FALSE, varying = NULL,
diag = FALSE, knots1 = quantile(x1, probs = 1:k/(k+1)),
knots2 = quantile(x1, probs = 1:k/(k+1))) {
call <- as.list(expand.call(match.call()))
knots1 <- eval(knots1)
knots2 <- eval(knots2)
k <- eval(k)
# design matrix for fixed effects
X <- cbind(x1,x2)
knots <- cbind(knots1,knots2)
dist.mat <- matrix(0,k,k)
dist.mat[lower.tri(dist.mat)] <- dist(knots)
dist.mat <- dist.mat + t(dist.mat)
Omega <- tps.cov(dist.mat)
diffs.1 <- outer(x1,knots1,"-")
diffs.2 <- outer(x2,knots2,"-")
dists <- sqrt(diffs.1*diffs.1+diffs.2*diffs.2)
svd.Omega <- svd(Omega)
sqrt.Omega <- t(svd.Omega$v %*% (t(svd.Omega$u) * sqrt(svd.Omega$d)))
# design matrix for random effects
Z <- t(solve(sqrt.Omega,t(tps.cov(dists))))
res <- list(X=X, Z=Z, knots=knots)
attr(res, "call") <- as.call(call)
return(res)
}
###########################################################
# These are functions copied from the "amer" package #
###########################################################
getF <- function (object, which, n=100, newdata=NULL, interval = c("NONE", "MCMC", "RW"), addConst = TRUE, varying=1,
level = 0.9, sims = 1000)
{
stopifnot(inherits(object, "cpglmm"), is.null(newdata)||is.data.frame(newdata), n>0, sims>0, level < 1, level > 0)
terms <- object@smooths
interval <- toupper(interval)
interval <- match.arg(interval)
n <- as.integer(n)
sims <- as.integer(sims)
if(missing(which)) which <- seq_along(terms)
if(is.character(which)) {
which <- match(which, names(terms))
if(any(nas <- is.na(which)))
warning("entry ", paste(which[nas], collapse=", "), " in 'which' did not match any function names in ", safeDeparse(object) ,".")
which <- which[!nas]
}
if(length(addConst) != length(which)) addConst <- rep(addConst, length=length(which))
ci.RW <- function(fhat, base, terms, i, j, object, level, addConst){
fctV <-
function(m, indGrp, indPen, indUnpen){
#Cov(hat.beta, hat.b-b) for bias-adjusted empirical Bayes CIs (s. Ruppert/Wand(2003), Semiparametric Regression, p. 138 f.):
#use V = cov(hat.fixef, hat.ranef) = sigma.eps^2 (C'C + sigma.eps^2/sigma.b^2 D)^-1; C=[XZ]*sqrt(W), D = blockdiag(0, I_dim(b))
C <- cBind(m@X[,indUnpen, drop=F], t(as.matrix(m@Zt[indPen,])))
if(length(m@var)) C <- C * sqrt(1/m@var)
V <- crossprod(C)
# FIXME: this works only for scalar ST and length(indGrp=1)- don't use if allPen=T!
if(m@ST[[indGrp]] > 0){
diag(V)[-(1:length(indUnpen))] <- diag(V)[-(1:length(indUnpen))] + m@ST[[indGrp]]^-2
} else {
#FIXME: HACK: V is not invertible for var(ranef)=0, use var(ranef)=10^-9 instead
diag(V)[-(1:length(indUnpen))] <- diag(V)[-(1:length(indUnpen))] + 10^9
}
return(sigma(m)^2 * solve(V))
}
z <- qnorm(1-(1-level)/2)
indUnpen <- if(addConst){
c(attr(terms[[i]],"indConst")[[j]], attr(terms[[i]],"indUnpen")[[j]])
} else attr(terms[[i]],"indUnpen")[[j]]
cV <- as(chol(fctV(object, attr(terms[[i]],"indGrp")[[j]],
attr(terms[[i]],"indPen")[[j]], indUnpen)), "sparseMatrix")
C <- as(cBind(base$X[[j]], base$Z[[j]]), "sparseMatrix")
sd <- apply(C, 1, function(x, cV){
ctc <- cV %*% as(x, "sparseMatrix")
return(sqrt(sum(ctc * ctc)))
#return(sqrt((crossprod(cV%*%as(x, "sparseMatrix")))@x))
}, cV=cV)
ci <- cbind(fhat - z*sd, fhat + z*sd)
colnames(ci) <- c("lo", "hi")
return(ci)
}
ans <- vector(mode="list", length=length(which))
if(interval=="MCMC") attr(ans, "mcmc") <- mcmc
names(ans) <- names(terms)[which]
indWhich <- 1
for(i in which){
#################################
# set up / check newdata
################################
if(!is.null(terms[[i]]$by)){
lvls <- levels(object@frame[, safeDeparse(terms[[i]]$by)])#FIXME: in amerSetup: this will fail if terms[[i]]$x was only in the workspace but not in the supplied data.frame for the original call.
hasBy <- TRUE
} else hasBy <-FALSE
hasVarying <- !is.null(terms[[i]]$varying)
if(is.null(newdata)){
grid <- TRUE
#FIXME: adapt this for 2d/3d-smooths
#get range of covariates + sequence of values
lim <- range(object@frame[, safeDeparse(terms[[i]]$x)], na.rm=T) #FIXME: in amerSetup: this will fail if terms[[i]]$x was only in the workspace but not in the supplied data.frame for the original call.
newX <- seq(lim[1], lim[2], l=n)
if(hasBy){
newBy <- factor(rep(lvls, length=n), labels=lvls)
data <- data.frame(newX, newBy)
colnames(data) <- c(safeDeparse(terms[[i]]$x), safeDeparse(terms[[i]]$by))
} else {
data <- data.frame(newX)
colnames(data) <- safeDeparse(terms[[i]]$x)
}
if(hasVarying){
#varying covariate is set value of varying
data <- cbind(data, rep(varying, nrow(data)))
colnames(data)[NCOL(data)] <- safeDeparse(terms[[i]]$varying)
}
} else {
grid <- FALSE
data <- newdata
n <- nrow(newdata)
vnames <- safeDeparse(terms[[i]]$x)
if(hasBy) vnames <- c(vnames,safeDeparse(terms[[i]]$by))
if(hasVarying) vnames <- c(vnames, safeDeparse(terms[[i]]$varying))
if(any(nas <- is.na(match(vnames, colnames(data)))))
stop("variable ", paste(vnames[nas], collapse=", "), "not found in given data.")
data <- data[, colnames(data) %in% vnames, drop=F]
}
#################################
#create basis:
#################################
base <- eval(terms[[i]], data)
base <- expandBasis(base,
by = eval(attr(base, "call")$by, data),
varying = eval(attr(base, "call")$varying, data),
bySetToZero = !grid)
# need to modify Z, X for allPen-Fits
if(eval(terms[[i]]$allPen)){
nlvl <- length(lvls)
base0 <- base
#where are the random effects for the penalized spline functions:
##use the first because the spline will have more levels (grps*(p-d)) than the grouping factor (grps)
indZ <- reinds(object@Gp)[[ attr(terms[[i]],"indGrp") [[1]] [1] ]]
#how many penalized spline functions per level of by
dimOneZ <- length(indZ)/length(lvls)
useZ <- 1:dimOneZ
if(grid) fullZ <- expandBasis(eval(terms[[i]], data),
by = NULL,
varying = eval(attr(base, "call")$varying, data),
bySetToZero = FALSE)$Z
}
nf <- ifelse(hasBy, length(lvls), 1)
ans[[indWhich]] <- vector(mode="list", length = nf)
names(ans[[indWhich]]) <- if(hasBy) paste(safeDeparse(terms[[i]]$by), lvls, sep="") else names(base$X)
for(j in seq_along(ans[[indWhich]])){
#################################
#calculate fits and cis
#################################
if(eval(terms[[i]]$allPen)){
ansInd <- 1
#need to:
#-append to Z extra columns for the random effects for base$X (+ a random intercept for the by-levels)
#-set columns in base$Z not relevant for the current by-level to 0
#-set base$X to zero
base$Z[[1]] <- base0$Z[[1]]
if(grid){
#fill up rows having artifical zeroes because of structure of newBy with values
base$Z[[1]][,useZ] <- fullZ[[1]]
}
base$Z[[1]][,-useZ] <- 0
#step to next block:
useZ <- useZ + dimOneZ
lvlInd <- rep(0, nlvl)
lvlInd[j] <- 1
X <- cBind("(Intercept)"=1, base0$X[[1]])
if(!grid){
use <- eval(terms[[i]]$by, data)==lvls[j]
X[!use,] <- 0
}
if(eval(terms[[i]]$diag)){
#lmer switches order of terms in X, need to permute X-columns accordingly:
uNames <- unlist(unique(sapply(object@ST[attr(terms[[i]],"indGrp")[[1]][-1]], dimnames)))
X <- X[,uNames]
}
base$Z[[1]] <- as(cBind(base$Z[[1]], kronecker(X, t(lvlInd), FUN = "*")),"sparseMatrix")
base$X[[1]] <- matrix(0, nrow=n, ncol=0)
} else {
ansInd <- j
#if(!grid && hasBy){
# #remove unnecessary rows from design
# use <- eval(terms[[i]]$by, data)==lvls[j]
# base$X[[ansInd]] <- base$X[[ansInd]][use,,drop=F]
# base$Z[[ansInd]] <- base$Z[[ansInd]][use,,drop=F]
#}
}
if(addConst[i]){
#add columns for constant terms to X, append indUnpen:
byColumn <-if(hasBy && paste(safeDeparse(terms[[i]]$by),lvls[j],sep="") %in% names(object@fixef)){
rep(1, nrow(base$X[[ansInd]]))
} else numeric(0)
base$X[[ansInd]] <- cBind(byColumn, base$X[[ansInd]])
if("(Intercept)" %in% names(object@fixef)[attr(terms[[i]],"indConst")[[ansInd]]])
base$X[[ansInd]] <- cBind(1,base$X[[ansInd]])
indUnpen <- c(attr(terms[[i]],"indConst")[[ansInd]], attr(terms[[i]],"indUnpen")[[ansInd]])
} else indUnpen <- attr(terms[[i]],"indUnpen")[[ansInd]]
fhat <- base$X[[ansInd]] %*%
object@fixef[indUnpen, drop = F] +
base$Z[[ansInd]] %*%
object@ranef[attr(terms[[i]],"indPen")[[ansInd]], drop = F]
if(!eval(terms[[i]]$allPen)){
ci <- switch(interval,
"NONE" = matrix(NA, nrow=nrow(base$X[[ansInd]]), ncol=0),
"RW" = ci.RW(fhat, base, terms, i, j, object, level, addConst[i]))
} else {
#TODO: implement CIs for fits with allPen = T
if(interval!="NONE" && j==1) warning("CIs for fits with allPen = T not yet implemented.")
ci <- matrix(NA, nrow=nrow(base$X[[ansInd]]), ncol=0)
}
dataJ <- if(grid){
data[,!(colnames(data)==safeDeparse(terms[[i]]$by)), drop=F]
} else {
## if(!grid && hasBy){
## data[use,]
## } else
data
}
ans[[indWhich]][[j]] <- data.frame(dataJ, fhat= as.matrix(fhat), ci)
}# end for j
indWhich <- indWhich + 1
}#end for i
return(ans)
}
plotF <- function(object, which, n=100, interval = "RW", addConst = TRUE, trans=I,
level = 0.9, sims = 1000, auto.layout = TRUE, rug = TRUE, legendPos="topright", ...)
{
# FIXME: add option for centering function estimates at zero/ anchoring at mean of all other covariates?
# FIXME: valid confints for trans?
terms <- object@smooths
if(missing(which)) which <- seq_along(terms)
if(is.character(which)) {
which <- match(which, names(terms))
if(any(nas <- is.na(which)))
warning("entry ", paste(which[nas], collapse=", "), " in 'which' did not match any function names in ", safeDeparse(object) ,".")
which <- which[!nas]
}
if(length(legendPos) != length(which)) legendPos <- rep(legendPos, length=length(which))
if(length(addConst) != length(which)) addConst <- rep(addConst, length=length(which))
interval <- toupper(interval)
res <- getF(object = object, which = which, n = n, newdata=NULL, interval = interval, addConst = addConst, level = level, sims = sims)
allPen <- sapply(object@smooths[which], function(x) eval(x$allPen))
if(auto.layout){
oldpar <- NULL
on.exit(par(oldpar))
nf <- length(res)
oldpar <- par(mfrow = set.mfrow(Nparms=nf))
}
plot1F <- function(x, interval, legendPos, ...){
dots <- list(...)
fhat <- sapply(x, function(x){trans(x$fhat)})
if(interval) ci <- lapply(x, function(x){trans(cbind(x$lo, x$hi))})
cov <- sapply(x, function(x){x[,1]})
if(is.null(dots$ylim)){
ylim <- range(fhat)
if(interval) ylim <- range(ylim, ci)
} else {
ylim <- dots$ylim
dots <- dots[names(dots)!="ylim"]
}
if(is.null(dots$xlim)){
xlim <- range(cov)
} else {
xlim <- dots$xlim
dots <- dots[names(dots)!="xlim"]
}
do.call(plot, c(list(x=-2*xlim[1]-10, y=0, ylim = ylim, xlim = xlim, ylab="", xlab=colnames(x[[1]])[1]), dots))
do.call(matlines, c(list(x = cov, y = fhat, col=1:length(x), lty=1), dots))
if(interval) for(i in 1:length(ci)) do.call(matlines, c(list(x=cov[,i], y=ci[[i]], col=i, lty=2),dots))
if(legendPos != "none" && length(x)>1){
do.call(legend,c(list(x=legendPos, legend=names(x), lty=1, col=1:length(x)),dots))
}
}
dots <- list(...)
for(i in seq_along(res)){
#FIXME: change as CIs for allPen become available
plot1F(res[[i]], interval = !(interval=="NONE")&&!allPen[i], legendPos = legendPos[i], ...)
if(is.null(dots$ylab)){
ylab <- ifelse(addConst[i], paste(names(res)[i], "+ const"), names(res)[i])
if(any(trans(-2:2)!= (-2:2))) ylab <- paste(safeDeparse(match.call()$trans),"(",ylab,")",sep="")
} else{
ylab <-dots$ylab
dots <- dots[names(dots)!="ylab"]
}
do.call(title, c(list(ylab= ylab), dots))
if(rug){
if(length(res[[i]])==1){
rug(object@frame[,colnames(res[[i]][[1]])[1]], ...)
} else {
nlvls <- length(res[[i]])
lvls <- levels(object@frame[, safeDeparse(object@smooths[[i]]$by)])
for(j in 1:nlvls){
use <- object@frame[,safeDeparse(object@smooths[[i]]$by)] == lvls[j]
rug(object@frame[use, colnames(res[[i]][[1]])[1]], col =j, ...)
}
}
}
}
invisible(res)
}