Rev 8110 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
## (c) Simon N. Wood (2013, 2014) coxph model general family.## Released under GPL2 ...cox.ph <- function (link = "identity") {## Extended family object for Cox PH.linktemp <- substitute(link)if (!is.character(linktemp)) linktemp <- deparse(linktemp)if (linktemp %in% c("identity")) stats <- make.link(linktemp)else if (is.character(link)) {stats <- make.link(link)linktemp <- link} else {if (inherits(link, "link-glm")) {stats <- linkif (!is.null(stats$name))linktemp <- stats$name}else stop(linktemp, " link not available for coxph family; available link is \"identity\" ")}env <- new.env(parent = .GlobalEnv)validmu <- function(mu) all(is.finite(mu))## initialization is tough here... need data frame in reverse time order,## and intercept removed from X...preinitialize <- function(G) {## G is a gam pre-fit object. Pre-initialize can manipulate some of its## elements, returning a named list of the modified ones.## sort y (time) into decending order, and## re-order weights and rows of X accordingly## matrix y has strat as second columnG$family$data <- list()y.order <- if (is.matrix(G$y)) order(G$y[,2],G$y[,1],decreasing=TRUE) elseorder(G$y,decreasing=TRUE)G$family$data$y.order <- y.orderG$y <- if (is.matrix(G$y)) G$y[y.order,] else G$y[y.order]attrX <- attributes(G$X)G$X <- G$X[y.order,,drop=FALSE]attributes(G$X) <- attrXG$w <- G$w[y.order]G$offset <- G$offset[y.order]list(family=G$family,y=G$y,X=G$X,w=G$w,offset=G$offset)} ## preinitializepostproc <- expression({## code to evaluate in estimate.gam, to do with data ordering and## baseline hazard estimation...## first get the estimated hazard and prediction information...G$X <- Sl.initial.repara(G$Sl,G$X,inverse=TRUE,cov=FALSE,both.sides=FALSE)object$family$data <- G$family$hazard(G$y,G$X,object$coefficients,G$w,G$offset)rumblefish <- G$family$hazard(G$y,matrix(0,nrow(G$X),0),object$coefficients,G$w)s0.base <- exp(-rumblefish$h[rumblefish$r]) ## no model baseline survivals0.base[s0.base >= 1] <- 1 - 2*.Machine$double.eps ## avoid NA later## now put the survivor function in object$fittedobject$fitted.values <- exp(-object$family$data$h[object$family$data$r]*exp(object$linear.predictors))## compute the null deviance...s.base <- exp(-object$family$data$h[object$family$data$r]) ## baseline survivals.base[s.base >= 1] <- 1 - 2*.Machine$double.eps ## avoid NA laterobject$null.deviance <- ## sum of squares of null deviance residuals2*sum(abs((object$prior.weights + log(s0.base) + object$prior.weights*(log(-log(s0.base))))))## and undo the re-ordering...y.order <- G$family$data$y.orderobject$linear.predictors[y.order] <- object$linear.predictorsobject$fitted.values[y.order] <- object$fitted.valuesif (is.matrix(object$y)) object$y[y.order,] <- object$y else object$y[y.order] <- object$yobject$prior.weights[y.order] <- object$prior.weightsobject$family$data$Rs[y.order,] <- object$family$data$Rsobject$family$data$Rc[y.order,] <- object$family$data$Rc})initialize <- expression({n <- rep(1, nobs)if (is.null(start)) start <- rep(0,ncol(x))})hazard <- function(y, X,beta,wt,offset=0) {## get the baseline hazard function information, given times in descending order in y## model matrix (same ordering) in X, coefs in beta and censoring in wt (1 = death, 0## = censoring)if (is.matrix(y)) {## first column is time, second is *numeric* code indicating stratastrat <- y[,2] ## stratification variabley <- y[,1] ## event timesstrat.lev <- unique(strat)ns <- length(strat.lev) ## number of stratant <- 0;for (i in 1:ns) nt <- nt + length(unique(y[strat==strat.lev[i]]))tr.strat <- tr <- rep(0,nt)k <- 1for (i in 1:ns) {tr0 <- unique(y[strat==strat.lev[i]])ind <- k:(k+length(tr0)-1);k <- k + length(tr0)tr[ind] <- tr0 ## unique times at this stratification leveltr.strat[ind] <- strat.lev[i] ## stratication index for tr,h,q,km and a}} else {ns <- 1tr <- unique(y)nt <- length(tr)}p <- ncol(X)eta <- as.double(X%*%beta) + offsetgamma <- exp(eta)if (ns==1) {r <- match(y,tr)oo <- .C("coxpp",eta,A=as.double(X),as.integer(r),d=as.integer(wt),h=as.double(rep(0,nt)),q=as.double(rep(0,nt)),km=as.double(rep(0,nt)),n=as.integer(nrow(X)),p=as.integer(ncol(X)),nt=as.integer(nt),PACKAGE="mgcv")## Now compute the Shoenfeld residuals, set to zero for censored...if (p) {X <- (X - apply(gamma*X,2,cumsum)/cumsum(gamma))*wt ## Schoenfeldn <- nrow(X)R <- apply(X[n:1,,drop=FALSE],2,cumsum)[n:1,,drop=FALSE] ## score residuals## now remove the penalization induced drift...x <- (1:n)[wt!=0]; dx <- seq(1,0,length=sum(wt!=0))drift <- approx(x,dx,xout=1:n,method="constant",rule=2)$yR <- R - t(t(matrix(drift,n,ncol(R)))*R[1,])X[wt==0,] <- NA} else R <- Xreturn(list(tr=tr,h=oo$h,q=oo$q,a=matrix(oo$A[1:(p*nt)],p,nt),nt=nt,r=r,km=oo$km,Rs = X,Rc = R))} else {R <- Xr <- y*0;a <- matrix(0,p,nt)h <- q <- km <- rep(0,nt)for (i in 1:ns) { ## loop over strataind <- which(strat==strat.lev[i])trind <- which(tr.strat==strat.lev[i])r0 <- match(y[ind],tr[trind])nti <- length(trind)etai <- if (p>0) eta[ind] else etaoo <- .C("coxpp",etai,A=as.double(X[ind,]),as.integer(r0),d=as.integer(wt[ind]),h=as.double(rep(0,nti)),q=as.double(rep(0,nti)),km=as.double(rep(0,nti)),n=as.integer(length(ind)),p=as.integer(p),nt=as.integer(nti),PACKAGE="mgcv")## now paste all this into return fieldsh[trind] <- oo$hq[trind] <- oo$qkm[trind] <- oo$kmr[ind] <- r0 ## note that indexing is to subsetted tra[,trind] <- matrix(oo$A[1:(p*nti)],p,nti)## compute Schoenfeld resiudals, within stratumif (p) {Xs <- X[ind,] <- (X[ind,,drop=FALSE] - apply(gamma[ind]*X[ind,,drop=FALSE],2,cumsum)/cumsum(gamma[ind]))*wt[ind]n <- nrow(Xs)Rs <- apply(Xs[n:1,,drop=FALSE],2,cumsum)[n:1,,drop=FALSE] ## score residuals## now remove the penalization induced drift...x <- (1:n)[wt[ind]!=0];if (length(x)) {dx <- seq(1,0,length=sum(wt[ind]!=0))drift <- approx(x,dx,xout=1:n,method="constant",rule=2)$yRs <- Rs - t(t(matrix(drift,n,ncol(R)))*Rs[1,])}R[ind,] <- Rs}}X[wt==0,] <- NAreturn(list(tr=tr,h=h,q=q,a=a,nt=nt,r=r,km=km,strat=strat,tr.strat=tr.strat,Rs=X,Rc=R))}} ## hazardresiduals <- function(object,type=c("deviance","martingale","score","schoenfeld")) {type <- match.arg(type)w <- object$prior.weights;log.s <- log(object$fitted.values)res <- w + log.s ## martingale residualsif (type=="deviance") {log.s[log.s>-1e-50] <- -1e-50res <- sign(res)*sqrt(-2*(res + w * log(-log.s)))} else if (type=="score") {res <- object$family$data$Rcterm <- list()if (object$nsdf) {ii <- 1:object$nsdf; sd <- sqrt(diag(object$Vp)[ii])res[,ii] <- t(sd*t(res[,ii]))term$parametric <- ii}m <- length(object$smooth)if (m) for (i in 1:m) {ii <- object$smooth[[i]]$first.para:object$smooth[[i]]$last.paraR <- chol(object$Vp[ii,ii]);## so R^T R = V_b for this term. If b' = R^{-T} b, V_b' = I## and Xb = XR^T b'...res[,ii] <- res[,ii] %*% t(R)term[[object$smooth[[i]]$label]] <- ii}attr(res,"term") <- termcolnames(res) <- names(coef(object))} else if (type=="schoenfeld") {res <- object$family$data$Rscolnames(res) <- names(coef(object))}res} ## residualspredict <- function(family,se=FALSE,eta=NULL,y=NULL,X=NULL,beta=NULL,off=NULL,Vb=NULL) {## prediction function.if (is.matrix(y)) {strat <- y[,2];y <- y[,1]if (is.null(family$data$strat)) stop("something wrong with stratified prediction")ii <- order(strat,y,decreasing=TRUE) ## C code expects non-increasingstrat <- strat[ii]} else {ii <- order(y,decreasing=TRUE) ## C code expects non-increasingstrat <- NULL}if (sum(is.na(y))>0) stop("NA times supplied for cox.ph prediction")X <- X[ii,,drop=FALSE];y <- y[ii];n <- nrow(X)if (is.null(off)) off <- rep(0,n)if (is.null(strat)) {oo <- .C("coxpred",as.double(X),t=as.double(y),as.double(beta),as.double(off),as.double(Vb),a=as.double(family$data$a),h=as.double(family$data$h),q=as.double(family$data$q),tr = as.double(family$data$tr),n=as.integer(n),p=as.integer(ncol(X)),nt = as.integer(family$data$nt),s=as.double(rep(0,n)),se=as.double(rep(0,n)),PACKAGE="mgcv")s <- sef <- oo$ss[ii] <- oo$ssef[ii] <- oo$se} else { ## stratified fit, need to unravel everything by stratapstrata <- unique(strat)ns <- length(pstrata)p <- ncol(X)s <- sef <- rep(0,length(y))for (i in 1:ns) {ind <- which(strat==pstrata[i]) ## prediction data indextrind <- which(family$data$tr.strat == pstrata[i])n <- length(ind)oo <- .C("coxpred",as.double(X[ind,]),t=as.double(y[ind]),as.double(beta),as.double(off),as.double(Vb),a=as.double(family$data$a[,trind]),h=as.double(family$data$h[trind]),q=as.double(family$data$q[trind]),tr = as.double(family$data$tr[trind]),n=as.integer(n),p=as.integer(p),nt = as.integer(length(trind)),s=as.double(rep(0,n)),se=as.double(rep(0,n)),PACKAGE="mgcv")s[ind] <- oo$ssef[ind] <- oo$se} ## strata loops[ii] <- ssef[ii] <- sef}if (se) return(list(fit=s,se.fit=sef)) else return(list(fit=s))} ## predictrd <- qf <- NULL ## these functions currently undefined for Cox PHll <- function(y,X,coef,wt,family,offset=NULL,deriv=0,d1b=0,d2b=0,Hp=NULL,rank=0,fh=NULL,D=NULL) {## function defining the Cox model log lik.## Calls C code "coxlpl"## deriv codes: 0 - evaluate the log likelihood## 1 - evaluate the grad and Hessian, H, of log lik w.r.t. coefs (beta)## 2 - evaluate tr(Hp^{-1}dH/drho) - Hp^{-1} must be supplied in fh## and db/drho in d1b. Not clear if a more efficient algorithm exists for this.## 3 - evaluate d1H =dH/drho given db/drho in d1b## (2 is for evaluation of diagonal only)## 4 - given d1b and d2b evaluate trHid2H= tr(Hp^{-1}d2H/drhodrho')## Hp is the preconditioned penalized Hessian of the log lik## which is of rank 'rank'.## fh is a factorization of Hp - either its eigen decomposition## or its Choleski factor## D is the diagonal pre-conditioning matrix used to obtain Hp## if Hr is the raw Hp then Hp = D*t(D*Hr)if (is.matrix(y)) {## first column is time, second is *numeric* code indicating stratastrat <- y[,2] ## stratification variabley <- y[,1] ## event timesstrat.lev <- unique(strat)ns <- length(strat.lev) ## number of strata} else ns <- 1p <- ncol(X)deriv <- deriv - 1mu <- X%*%coef + offsetg <- rep(0,p);H <- rep(0,p*p)if (deriv > 0) {M <- ncol(d1b)d1H <- #if (deriv==1) rep(0,p*M) elserep(0,p*p*M)} else M <- d1Ho <- d1H <- 0if (deriv > 2) {d2H <- rep(0,p*M*(M+1)/2)if (is.list(fh)) {ev <- fh} else { ## need to compute eigen hereev <- eigen(Hp,symmetric=TRUE)if (rank < p) ev$values[(rank+1):p] <- 0}X <- X%*%(ev$vectors*D)d1b <- t(ev$vectors)%*%(d1b/D); d2b <- t(ev$vectors)%*%(d2b/D)} else d2Ho <- trHid2H <- d2H <- 0for (j in 1:ns) { ## loop over strataind <- if (ns==1) 1:length(y) else which(strat==strat.lev[j]) ## index for points in this stratatr <- unique(y[ind])r <- match(y[ind],tr)## note that the following call can not use .C(C_coxlpl,...) since the ll## function is not in the mgcv namespace.cderiv <- if (deriv==1) 2 else deriv ## reset 1 to 2 to get whole d1H returnedoo <- .C("coxlpl",as.double(mu[ind]),as.double(X[ind,]),as.integer(r),as.integer(wt[ind]),as.double(tr),n=as.integer(length(y[ind])),p=as.integer(p),nt=as.integer(length(tr)),lp=as.double(0),g=as.double(g),H=as.double(H),d1b=as.double(d1b),d1H=as.double(d1H),d2b=as.double(d2b),d2H=as.double(d2H),n.sp=as.integer(M),deriv=as.integer(cderiv),PACKAGE="mgcv");if (j==1) {lp <- oo$lplb <- oo$glbb <- matrix(oo$H,p,p)} else { ## accumulating over stratalp <- oo$lp + lplb <- oo$g + lblbb <- matrix(oo$H,p,p) + lbb}if (deriv==1) {#d1Ho <- matrix(oo$d1H,p,M) + if (j==1) 0 else d1Hoind <- 1:(p^2)if (j==1) d1Ho <- rep(0,M)for (i in 1:M) {d1Ho[i] <- d1Ho[i] + sum(fh*matrix(oo$d1H[ind],p,p)) ## tr(Hp^{-1}dH/drho_i)ind <- ind + p^2}} else if (deriv>1) {ind <- 1:(p^2)if (j==1) d1Ho <- list()for (i in 1:M) {d1Ho[[i]] <- if (j==1) matrix(oo$d1H[ind],p,p) else matrix(oo$d1H[ind],p,p) + d1Ho[[i]]ind <- ind + p^2}}if (deriv>2) {d2Ho <- if (j==1) matrix(oo$d2H,p,M*(M+1)/2) else d2Ho + matrix(oo$d2H,p,M*(M+1)/2)}} ## strata loopif (deriv>2) {d <- ev$valuesd[d>0] <- 1/d[d>0];d[d<=0] <- 0trHid2H <- colSums(d2Ho*d)}assign(".log.partial.likelihood", oo$lp, envir=environment(sys.function()))list(l=lp,lb=lb,lbb=lbb,d1H=d1Ho,d2H=d2Ho,trHid2H=trHid2H)} ## ll# environment(dev.resids) <- environment(aic) <- environment(getTheta) <-# environment(rd)<- environment(qf)<- environment(variance) <- environment(putTheta)#environment(aic) <-environment(ll) <- envstructure(list(family = "Cox PH", link = linktemp, linkfun = stats$linkfun,linkinv = stats$linkinv, ll=ll, ## aic = aic,mu.eta = stats$mu.eta,initialize = initialize,preinitialize=preinitialize,postproc=postproc,hazard=hazard,predict=predict,residuals=residuals,validmu = validmu, valideta = stats$valideta,rd=rd,qf=qf,drop.intercept = TRUE,ls=1, ## signal ls not neededavailable.derivs = 2 ## can use full Newton here),class = c("general.family","extended.family","family"))} ## cox.ph