R/dr.R

Defines functions dr dr.compute dr.x dr.wts dr.qr dr.Q dr.Q.default dr.R dr.R.default dr.z dr.y.name dr.basis dr.basis.default dr.evalues dr.evalues.default dr.fit dr.fit.default dr.M dr.y dr.y.default dr.test dr.test.default dr.coordinate.test dr.coordinate.test.default dr.joint.test dr.joint.test.default dr.M.ols dr.M.sir dr.M.msir dr.test.sir dr.coordinate.test.sir dr.M.save dr.test.save dr.M.phdy dr.M.mphd dr.M.phdres dr.M.mphdres dr.M.mphy dr.M.phd dr.M.mphd dr.y.phdy dr.y.phdres dr.y.phd dr.test.phd dr.test.phdres dr.test.phdy dr.M.phdq fullquad.fit dr.y.phdq dr.test.phdq dr.M.mphdq coord.hyp.basis dr.directions dr.direction dr.direction.default cov.ew.matrix dr.test2.phdres dr.indep.test.phdres dr.pvalue bentlerxie.pvalue dr.permutation.test dr.permutation.test.statistic dr.permutation.test.statistic.default dr.permutation.test.statistic.phdy dr.permutation.test.statistic.phdres dr.permutation.test.statistic.phd dr.slices dr.slices.arc cosangle cosangle1 dr.fit.ire dr.test.ire Gzcomp Gzcomp.ire dr.iteration dr.iteration.ire dr.coordinate.test.ire dr.joint.test.ire print.ire dr.basis.ire dr.direction.ire dr.fit.pire dr.iteration.pire dr.coordinate.test.pire Gzcomp.pire

Documented in bentlerxie.pvalue coord.hyp.basis dr dr.basis dr.basis.default dr.basis.ire dr.compute dr.coordinate.test dr.coordinate.test.default dr.coordinate.test.ire dr.coordinate.test.pire dr.coordinate.test.sir dr.direction dr.direction.default dr.direction.ire dr.directions dr.joint.test dr.joint.test.default dr.joint.test.ire dr.permutation.test dr.permutation.test.statistic dr.pvalue dr.slices dr.slices.arc dr.test dr.x dr.y dr.z

.packageName <- "dr"
#####################################################################
#
#     Dimension reduction methods for R and Splus
#     Revised in July, 2004 by Sandy Weisberg and Jorge de la Vega
#     tests for SAVE by Yongwu Shao added April 2006
#     'ire/pire' methods added by Sanford Weisberg Summer 2006
#     Version 3.0.0 August 2007
#     copyright 2001, 2004, 2006, 2007, Sanford Weisberg
#     sandy@stat.umn.edu
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#####################################################################
#     dr is the primary function
#####################################################################
dr <- 
function(formula, data, subset, group=NULL, na.action=na.fail, weights,...)
{      
    mf <- match.call(expand.dots=FALSE)
    mf$na.action <- na.action
    if(!is.null(mf$group)) {
     mf$group <- eval(parse(text=as.character(group)[2]), 
                     data,environment(group)) }
    mf$... <- NULL # ignore ...
    mf[[1]] <- as.name("model.frame")
    mf <- eval(mf, sys.frame(sys.parent()))
    mt <- attr(mf,"terms")
    Y <- model.extract(mf,"response")
    X <- model.matrix(mt, mf)
    y.name <- if(is.matrix(Y)) colnames(Y) else
        as.character(attr(mt, "variables")[2])
    int <- match("(Intercept)", dimnames(X)[[2]], nomatch=0)
    if (int > 0) X <- X[, -int, drop=FALSE] # drop the intercept from X
    weights <- mf$"(weights)"               
    if(is.null(weights)){                   # create weights if not present
         weights <- rep(1,dim(X)[1])} else
        {
         if(any(weights<0))stop("Negative weights")
         pos.weights <- weights > 1.e-30
         weights <- weights[pos.weights]
         weights <- dim(X)[1]*weights/sum(weights)
         X <- X[pos.weights,]
         Y <- if(is.matrix(Y)) Y[pos.weights,] else Y[pos.weights]}
    ans <- dr.compute(X,Y,weights=weights,group=mf$"(group)",...)
    ans$call <- match.call()
    ans$y.name <- y.name
    ans$terms <- mt
    ans
}

dr.compute <-
function(x,y,weights,group=NULL,method="sir",chi2approx="bx",...)
{ 
    if (NROW(y) != nrow(x))     #check lengths
        stop("The response and predictors have different number of observations")
    if (NROW(y) < ncol(x))
        stop("The methods in dr require more observations than predictors")
    #set the class name
    classname<- if (is.matrix(y)) c(paste("m",method,sep=""),method) else 
        if(!is.null(group)) c(paste("p",method,sep=""),method) else
        method
    genclassname<-"dr"
    sweights <- sqrt(weights)
    qrz <- qr(scale(apply(x,2,function(a, sweights) a  * sweights, sweights), 
                     center=TRUE, scale=FALSE))  # QR decomp of WEIGHTED x matrix
#initialize the object and then call the fit method
    ans <- list(x=x,y=y,weights=weights,method=method,cases=NROW(y),qr=qrz,
                group=group,chi2approx=chi2approx)
    class(ans) <-  c(classname,genclassname)   #set the class
    ans <- dr.fit(object=ans,...)   # ... contains args for the method
    ans$x <- ans$x[,qrz$pivot[1:qrz$rank]] # reorder x and reduce to full rank
    ans #return the object
}
    
#######################################################
#    accessor functions
#######################################################

dr.x <- function(object) {object$x}
dr.wts <- function(object) {object$weights}
dr.qr <- function(object) {object$qr}
dr.Q <- function(object){UseMethod("dr.Q")}
dr.Q.default <- function(object){ qr.Q(dr.qr(object))[,1:object$qr$rank] }
dr.R <- function(object){UseMethod("dr.R")}
dr.R.default <- function(object){ qr.R(dr.qr(object))[1:object$qr$rank,1:object$qr$rank]}
dr.z <- function(object) { sqrt(object$cases) * dr.Q(object) }
dr.y.name <- function(object) {object$y.name}
dr.basis <- function(object,numdir) {UseMethod("dr.basis")}
dr.basis.default <- function(object,numdir=object$numdir){
 object$evectors[,1:numdir]}
dr.evalues <- function(object) {UseMethod("dr.evalues")}
dr.evalues.default <- function(object) object$evalues

#####################################################################
#     Fitting function
#####################################################################
dr.fit <- function(object,numdir=4,...) UseMethod("dr.fit")

dr.fit.default <-function(object,numdir=4,...){ 
    M <- dr.M(object,...)  # Get the kernel matrix M, method specific
    D <- if(dim(M$M)[1] == dim(M$M)[2]) eigen(M$M) else {
          if(ncol(M$M)==1) eigen(M$M %*% t(M$M)) else eigen(t(M$M) %*% M$M)} 
    or <- rev(order(abs(D$values)))
    evalues <- D$values[or]
    raw.evectors <- D$vectors[,or]  
    evectors <- backsolve(sqrt(object$cases)*dr.R(object),raw.evectors)
    evectors <- if (is.matrix(evectors)) evectors else matrix(evectors,ncol=1)
    evectors <- apply(evectors,2,function(x) x/sqrt(sum(x^2)))
    names <- colnames(dr.x(object))[1:object$qr$rank]
    dimnames(evectors)<-
         list(names, paste("Dir", 1:NCOL(evectors), sep=""))
    aa<-c( object, list(evectors=evectors,evalues=evalues, 
                numdir=min(numdir,dim(evectors)[2],dim(M$M)[1]),
                raw.evectors=raw.evectors), M)
    class(aa) <- class(object)
    return(aa)
}


#####################################################################
###
###  dr methods. Each method REQUIRES a dr.M function, and
###  may also have a dr.y function and an function method.
###
#####################################################################

#####################################################################
###  generic functions
#####################################################################

dr.M <- function(object, ...){UseMethod("dr.M")}
dr.y <- function(object) {UseMethod("dr.y")}
dr.y.default <- function(object){object$y}
dr.test <- function(object, numdir,...){  UseMethod("dr.test")}
dr.test.default <-function(object, numdir,...) {NULL}
dr.coordinate.test <- function(object,hypothesis,d,chi2approx,...)
                     {  UseMethod("dr.coordinate.test")  }
dr.coordinate.test.default <-
       function(object, hypothesis,d,chi2approx,...) {NULL}
dr.joint.test <- function(object,...){ UseMethod("dr.joint.test")}
dr.joint.test.default <- function(object,...){NULL}

#####################################################################
#     OLS
#####################################################################

dr.M.ols <- function(object,...) {
 ols <- t(dr.z(object)) %*% (sqrt(dr.wts(object)) * dr.y(object))
 return(list( M= ols %*% t(ols), numdir=1))}
       

#####################################################################
#     Sliced Inverse Regression and multivariate SIR
#####################################################################

dr.M.sir <-function(object,nslices=NULL,slice.function=dr.slices,sel=NULL,...) {
    sel <- if(is.null(sel))1:dim(dr.z(object))[1] else sel
    z <- dr.z(object)[sel,]
    y <- dr.y(object)
    y <- if (is.matrix(y)) y[sel,] else y[sel]
# get slice information    
    h <- if (!is.null(nslices)) nslices else max(8, NCOL(z)+3)
    slices <- slice.function(y,h)
    #slices<- if(is.null(slice.info)) dr.slices(y,h) else slice.info
# initialize slice means matrix
    zmeans <- matrix(0,slices$nslices,NCOL(z))
    slice.weight <- rep(0,slices$nslices)  # NOT rep(0,NCOL(z))
    wts <- dr.wts(object)
# compute weighted means within slice 
    wmean <- function (x, wts) { sum(x * wts) / sum (wts) }
    for (j in 1:slices$nslices){
      sel <- slices$slice.indicator==j
      zmeans[j,]<- apply(z[sel,,drop=FALSE],2,wmean,wts[sel])
      slice.weight[j]<-sum(wts[sel])}
# get M matrix for sir
    M <- t(zmeans) %*% apply(zmeans,2,"*",slice.weight)/ sum(slice.weight)
    return (list (M=M,slice.info=slices))
}

dr.M.msir <-function(...) {dr.M.sir(...)}

dr.test.sir<-function(object,numdir=object$numdir,...) {
#compute the sir test statistic for the first numdir directions
    e<-sort(object$evalues)
    p<-length(object$evalues)
    n<-object$cases
    st<-df<-pv<-0
    nt <- min(p,numdir)
    for (i in 0:(nt-1))
      {st[i+1]<-n*(p-i)*mean(e[seq(1,p-i)])
       df[i+1]<-(p-i)*sum(object$slice.info$nslices-i-1)
       pv[i+1]<-1-pchisq(st[i+1],df[i+1])
      }
    z<-data.frame(cbind(st,df,pv))
    rr<-paste(0:(nt-1),"D vs >= ",1:nt,"D",sep="")
    dimnames(z)<-list(rr,c("Stat","df","p.value")) 
    z
}

#  Written by Yongwu Shao, May 2006
dr.coordinate.test.sir<-function(object,hypothesis,d=NULL,
    chi2approx=object$chi2approx,pval="general",...){
    gamma <- if (class(hypothesis) == "formula")
        coord.hyp.basis(object, hypothesis)
        else as.matrix(hypothesis)
    p<-length(object$evalues)
    n<-object$cases
    z <- dr.z(object)
    ev <- object$evalues
    slices<-object$slice.info
    h<-slices$nslices
    d <- if(is.null(d)) min(h,length(ev)) else d
    M<-object$M
    r<-p-dim(gamma)[2]
    H<- (dr.R(object)) %*% gamma  
    H <- qr.Q(qr(H),complete=TRUE) # a p times p matrix
    QH<- H[,1:(p-r)] # first p-r columns
    H <- H[,(p-r+1):p] # last r columns
    st<-n*sum(ev[1:d])-n*sum(sort(eigen(t(QH)%*%M%*%QH)$values,
               decreasing=TRUE)[1:min(d,p-r)])
    wts <- 1-ev[1:min(d,h-1)]
# each eigenvalue occurs r times.  
    testr <- dr.pvalue(rep(wts,r),st,chi2approx=chi2approx) 
# general test
    epsilon<-array(0,c(n,h))
    zmeans<-array(0,c(p,h)) 
    for (i in 1:h) {
        sel<-(slices$slice.indicator==i)
        f_k<-sum(sel)/n
        zmeans[,i]<-apply(z[sel,],2,mean)
        epsilon[,i]<-(sel-f_k-z%*%zmeans[,i]*f_k)/sqrt(f_k)
        }
    HZ<- z%*%H
    Phi<-svd(zmeans,nv=h)$v[,1:min(d,h)]
    epsilonHZ<-array(0,c(n,r*d))
    for (j in 1:n) epsilonHZ[j,]<-t(Phi)%*%t(t(epsilon[j,]))%*%t(HZ[j,])
    wts <- eigen(((n-1)/n)*cov(epsilonHZ))$values
    testg<-dr.pvalue(wts[wts>0],st,chi2approx=chi2approx)
    pv <- if (pval=="restricted") testg$pval.adj else testr$pval.adj
    z <- data.frame(cbind(st, pv))
    dimnames(z) <- list("Test", c("Statistic", "P.value"))
    z
}

#####################################################################
#     Sliced Average Variance Estimation
#  Original by S. Weisberg; modified by Yongwu Shao 4/27/2006
#  to compute the A array needed for dimension tests
#####################################################################

dr.M.save<-function(object,nslices=NULL,slice.function=dr.slices,sel=NULL,...) {
    sel <- if(is.null(sel)) 1:dim(dr.z(object))[1] else sel
    z <- dr.z(object)[sel,]
    y <- dr.y(object)
    y <- if (is.matrix(y)) y[sel,] else y[sel]
    wts <- dr.wts(object)[sel]
    h <- if (!is.null(nslices)) nslices else max(8, ncol(z) + 3)
    slices <- slice.function(y,h)
    #slices <- if (is.null(slice.info)) dr.slices(y, h) else slice.info
# initialize M
    M <- matrix(0, NCOL(z), NCOL(z))
# A is a new 3D array, needed to compute tests.
    A <- array(0,c(slices$nslices,NCOL(z),NCOL(z)))
    ws <- rep(0, slices$nslices)
# Compute weighted within-slice covariance matrices, skipping any slice with
# total weight smaller than 1
    wvar <- function(x, w) {
        (if (sum(w) > 1) {(length(w) - 1)/(sum(w) - 0)} else {0}) * 
                 var(sweep(x, 1, sqrt(w), "*"))}
    for (j in 1:slices$nslices) {
        ind <- slices$slice.indicator == j
        IminusC <- diag(rep(1, NCOL(z))) - wvar(z[ind, ], wts[ind])
        ws[j] <- sum(wts[ind])
        A[j,,] <- sqrt(ws[j])*IminusC  # new
        M <- M + ws[j] * IminusC %*% IminusC
    }
    M <- M/sum(ws)
    A <- A/sqrt(sum(ws)) # new
    return(list(M = M, A = A, slice.info = slices))
}

# Written by Yongwu Shao, 4/27/2006
dr.test.save<-function(object,numdir=object$numdir,...) {
    p<-length(object$evalues)
    n<-object$cases
    h<-object$slice.info$nslices
    st.normal<-df.normal<-st.general<-df.general<-0
    pv.normal<-pv.general<-0
    nt<-numdir
    A<-object$A
    M<-object$M
    D<-eigen(M)
    or<-rev(order(abs(D$values)))
    evectors<-D$vectors[,or]
    for (i in 1:nt-1) {
        theta<-evectors[,(i+1):p]
        st.normal[i+1]<-0
        for (j in 1:h) {
            st.normal[i+1]<-st.normal[i+1] + 
                            sum((t(theta) %*% A[j,,] %*% theta)^2)*n/2
        }
        df.normal[i+1]<-(h-1)*(p-i)*(p-i+1)/2
        pv.normal[i+1]<-1-pchisq(st.normal[i+1],df.normal[i+1])
# general test
        HZ<- dr.z(object) %*% theta
        ZZ<-array(0,c(n,(p-i)*(p-i)))
        for (j in 1:n) ZZ[j,]<- t(t(HZ[j,])) %*% t(HZ[j,])
        Sigma<-cov(ZZ)/2
        df.general[i+1]<-sum(diag(Sigma))^2/sum(Sigma^2)*(h-1)
        st.general[i+1]<-st.normal[i+1] *sum(diag(Sigma))/sum(Sigma^2)
        pv.general[i+1]<-1-pchisq(st.general[i+1],df.general[i+1])
    }
    z<-data.frame(cbind(st.normal,df.normal,pv.normal,pv.general))
    rr<-paste(0:(nt-1),"D vs >= ",1:nt,"D",sep="")
    dimnames(z)<-list(rr,c("Stat","df(Nor)","p.value(Nor)","p.value(Gen)"))
    z
}

# Written by Yongwu Shao, 4/27/2006
dr.coordinate.test.save <-
function (object, hypothesis, d=NULL, chi2approx=object$chi2approx,...) 
{
    gamma <- if (class(hypothesis) == "formula") 
        coord.hyp.basis(object, hypothesis)
        else as.matrix(hypothesis)
    p <- length(object$evalues)
    n <- object$cases
    h <- object$slice.info$nslices
    st <- df <- pv <- 0
    gamma <- (dr.R(object)) %*% gamma
    r <- p - dim(gamma)[2]
    H <- as.matrix(qr.Q(qr(gamma), complete = TRUE)[, (p - r + 
        1):p])
    A <- object$A
    st <- 0
    for (j in 1:h) {
        st <- st + sum((t(H) %*% A[j, , ] %*% H)^2) * n/2
    }
# Normal predictors
    df.normal <- (h - 1) * r * (r + 1)/2
    pv.normal  <- 1 - pchisq(st, df.normal)
# General predictors
    {
        HZ <- dr.z(object) %*% H
        ZZ <- array(0, c(n, r^2))
        for (j in 1:n) {
            ZZ[j, ] <- t(t(HZ[j, ])) %*% t(HZ[j, ])
        }
        wts <- rep(eigen( ((n-1)/n)*cov(ZZ)/2)$values,h-1)
        testg <- dr.pvalue(wts[wts>0],st,a=chi2approx)
    }  
    z <- data.frame(cbind(st, df.normal, pv.normal, testg$pval.adj))
    dimnames(z) <- list("Test", c("Statistic", "df(Nor)", "p.val(Nor)", 
        "p.val(Gen)"))
    z
}

#####################################################################
#     pHd, pHdy and pHdres
#####################################################################

dr.M.phdy <- function(...) {dr.M.phd(...)}
dr.M.mphd <- function(...) stop("Multivariate pHd not implemented!")

dr.M.phdres <- function(...) {dr.M.phd(...)}
dr.M.mphdres <- function(...) stop("Multivariate pHd not implemented!")
dr.M.mphy <- function(...) stop("Multivariate pHd not implemented!")

dr.M.phd <-function(object,...) {
    wts <- dr.wts(object)
    z <- dr.z(object)
    y <- dr.y(object)
    M<- (t(apply(z,2,"*",wts*y)) %*% z) / sum(wts)
    return(list(M=M))
}

dr.M.mphd <- function(...) stop("Multivariate pHd not implemented!")


dr.y.phdy <- function(object) {y <- object$y ; y - mean(y)}
dr.y.phdres <- function(object) { 
   y <- object$y
   sw <- sqrt(object$weights)
   qr.resid(object$qr,sw*(y-mean(y)))}
dr.y.phd <- function(object) {dr.y.phdres(object)}

dr.test.phd<-function(object,numdir=object$numdir,...) {
# Modified by Jorge de la Vega, February, 2001
#compute the phd asymptotic test statitics under restrictions for the
#first numdir directions, based on response = OLS residuals
# order the absolute eigenvalues
    e<-sort(abs(object$evalues))
    p<-length(object$evalues)
# get the response
    resi<-dr.y(object)
    varres<-2*var(resi)
    n<-object$cases
    st<-df<-pv<-0
    nt<-min(p,numdir)
    for (i in 0:(nt-1))
# the statistic is partial sums of squared absolute eigenvalues
      {st[i+1]<-n*(p-i)*mean(e[seq(1,p-i)]^2)/varres
# compute the degrees of freedom
       df[i+1]<-(p-i)*(p-i+1)/2
# use asymptotic chi-square distrbution for p.values.  Requires normality.
       pv[i+1]<-1-pchisq(st[i+1],df[i+1])
      }
# compute the test for complete independence
    indep <- dr.indep.test.phdres(object,st[1])
# compute tests that do not require normal theory (linear combination of 
# chi-squares):
    lc <- dr.test2.phdres(object,st)
# report results
    z<-data.frame(cbind(st,df,pv,c(indep[[2]],rep(NA,length(st)-1)),lc[,2]))
    rr<-paste(0:(nt-1),"D vs >= ",1:nt,"D",sep="")
    cc<-c("Stat","df","Normal theory","Indep. test","General theory")
    dimnames(z)<-list(rr,cc)
    z
}

dr.test.phdres<-function(object,numdir,...){dr.test.phd(object,numdir)}

dr.test.phdy<-function(object,numdir,...) {
#compute the phd test statitics for the first numdir directions
#based on response = y.  According to Li (1992), this requires normal
#predictors.
# order the absolute eigenvalues
    e<-sort(abs(object$evalues))
    p<-length(object$evalues)
# get the response
    resi<-dr.y(object)
    varres<-2*var(resi)
    n<-object$cases
    st<-df<-pv<-0
    nt<-min(p,numdir)
    for (i in 0:(nt-1))
# the statistic is partial sums of squared absolute eigenvalues
      {st[i+1]<-n*(p-i)*mean(e[seq(1,p-i)]^2)/varres
# compute the degrees of freedom
       df[i+1]<-(p-i)*(p-i+1)/2
# use asymptotic chi-square distrbution for p.values.  Requires normality.
       pv[i+1]<-1-pchisq(st[i+1],df[i+1])
      }
# report results
    z<-data.frame(cbind(st,df,pv))
    rr<-paste(0:(nt-1),"D vs >= ",1:nt,"D",sep="")
    cc<-c("Stat","df","p.value")
    dimnames(z)<-list(rr,cc)
    z}

#####################################################################
# phdq, Reference:  Li (1992, JASA)
# Function to build quadratic form in the full quadratic fit.
# Corrected phdq by Jorge de la Vega 7/10/01
#####################################################################

dr.M.phdq<-function(object,...){
 pars <- fullquad.fit(object)$coef
 k<-length(pars)
 p<-(-3+sqrt(9+8*(k-1)))/2 #always k=1+2p+p(p-1)/2
 mymatrix <- diag(pars[round((p+2):(2*p+1))]) # doesn't work in S without 'round'
 pars <- pars[-(1:(2*p+1))]
 for (i in 1:(p - 1)) {
      mymatrix[i,(i+1):p] <- pars[1:(p - i)]/2
      mymatrix[(i+1):p,i] <- pars[1:(p - i)]/2
      pars <- pars[-(1:(p - i))]
 }
 return(list(M=mymatrix))
 }

fullquad.fit <-function(object) {
 x <- dr.z(object)
 y <- object$y
 w <- dr.wts(object)
 z <- cbind(x,x^2)
 p <- NCOL(x)
 for (j in 1:(p-1)){
   for (k in (j+1):p) { z <- cbind(z,matrix(x[,j]*x[,k],ncol=1))}}
 lm(y~z,weights=w)
 }

 
dr.y.phdq<-function(object){residuals(fullquad.fit(object),type="pearson")}
dr.test.phdq<-function(object,numdir,...){dr.test.phd(object,numdir)}
dr.M.mphdq <- function(...) stop("Multivariate pHd not implemented!")




#####################################################################
#     Helper methods
#####################################################################
#####################################################################
#  Auxillary function to find matrix H used in marginal coordinate tests
#  We test Y indep X2 | X1, and H is a basis in R^p for the column
#  space of X2.
#####################################################################

coord.hyp.basis <- function(object,spec,which=1) {
 mod2 <- update(object$terms,spec)
 Base <- attr(object$terms,"term.labels")
 New  <- attr(terms(mod2), "term.labels")
 cols <-na.omit(match(New,Base))
 if(length(cols) != length(New)) stop("Error---bad value of 'spec'")
 as.matrix(diag(rep(1,length(Base)))[,which*cols])
 }

#####################################################################
#     Recover the direction vectors
#####################################################################

dr.directions <- function(object, which, x) {UseMethod("dr.direction")}
dr.direction  <- function(object, which, x) {UseMethod("dr.direction")}

# standardize so that the first coordinates are always positive.
dr.direction.default <- 
  function(object, which=NULL,x=dr.x(object)) {  
    ans <- (apply(x,2,function(x){x-mean(x)}) %*% object$evectors)
    which <- if (is.null(which)) seq(dim(ans)[2]) else which 
    ans <- apply(ans,2,function(x) if(x[1] <= 0)  -x else x)[,which]
    if (length(which) > 1) dimnames(ans) <-
                       list ( attr(x,"dimnames")[[1]],paste("Dir",which,sep=""))
    ans
  }

  
#####################################################################
#     Plotting methods
#####################################################################

plot.dr <- function
      (x,which=1:x$numdir,mark.by.y=FALSE,plot.method=pairs,...) {
 d <- dr.direction(x,which)
 if (mark.by.y == FALSE) {
    plot.method(cbind(dr.y(x),d),labels=c(dr.y.name(x),colnames(d)),...)
    }
 else {plot.method(d,labels=colnames(d),col=markby(dr.y(x)),...)}
 }


markby <-
function (z, use = "color", values = NULL, color.fn = rainbow,
    na.action = "na.use")
{
    u <- unique(z)
    lu <- length(u)
    ans <- 0
    vals <- if (use == "color") {
        if (!is.null(values) && length(values) == lu)
            values
        else color.fn(lu)
    }
    else {
        if (!is.null(values) && length(values) == lu)
            values
        else 1:lu
    }
    for (j in 1:lu) if (is.na(u[j])) {
        ans[which(is.na(z))] <- if (na.action == "na.use")
            vals[j]
        else NA
    }
    else {
        ans[z == u[j]] <- vals[j]
    }
    ans
}
   

###################################################################
#
#  basic print method for dimension reduction
#
###################################################################
"print.dr" <-
function(x, digits = max(3, getOption("digits") - 3), width=50, 
    numdir=x$numdir, ...)
{   
    fout <- deparse(x$call,width.cutoff=width)
    for (f in fout) cat("\n",f)
    cat("\n")
    cat("Estimated Basis Vectors for Central Subspace:\n")
    evectors<-x$evectors
    print.default(evectors[,1:min(numdir,dim(evectors)[2])])
    cat("Eigenvalues:\n")
    print.default(x$evalues[1:min(numdir,dim(evectors)[2])])
    cat("\n")
    invisible(x)
}

###################################################################
#  basic summary method for dimension reduction
###################################################################
"summary.dr" <- function (object, ...)
{   z <- object
    ans <- z[c("call")]
    nd <- min(z$numdir,length(which(abs(z$evalues)>1.e-15)))
    ans$evectors <- z$evectors[,1:nd]
    ans$method <- z$method
    ans$nslices <- z$slice.info$nslices
    ans$sizes <- z$slice.info$slice.sizes
    ans$weights <- dr.wts(z)
    sw <- sqrt(ans$weights)
    y <- z$y #dr.y(z)
    ans$n <- z$cases #NROW(z$model)
    ols.fit <- qr.fitted(object$qr,sw*(y-mean(y)))
    angles <- cosangle(dr.direction(object),ols.fit)
    angles <- if (is.matrix(angles)) angles[,1:nd] else angles[1:nd]
    if (is.matrix(angles)) dimnames(angles)[[1]] <- z$y.name
    angle.names <- if (!is.matrix(angles)) "R^2(OLS|dr)" else 
          paste("R^2(OLS for ",dimnames(angles)[[1]],"|dr)",sep="")
    ans$evalues <-rbind (z$evalues[1:nd],angles)
    dimnames(ans$evalues)<-
     list(c("Eigenvalues",angle.names),
          paste("Dir", 1:NCOL(ans$evalues), sep=""))
    ans$test <- dr.test(object,nd)
    class(ans) <- "summary.dr"
    ans
}

###################################################################
#
# basic print.summary method for dimension reduction
#
###################################################################
"print.summary.dr" <-
function (x, digits = max(3, getOption("digits") - 3), ...)
{
    cat("\nCall:\n")#S: ' ' instead of '\n'
    cat(paste(deparse(x$call), sep="\n", collapse = "\n"), "\n\n", sep="")
    cat("Method:\n")#S: ' ' instead of '\n'
    if(is.null(x$nslices)){
       cat(paste(x$method, ", n = ", x$n,sep=""))
       if(diff(range(x$weights)) > 0)
                  cat(", using weights.\n") else cat(".\n")}
       else {
         cat(paste(x$method," with ",x$nslices, " slices, n = ",
                   x$n,sep=""))
         if(diff(range(x$weights)) > 0) 
              cat(", using weights.\n") else cat(".\n")
         cat("\nSlice Sizes:\n")#S: ' ' instead of '\n'
         cat(x$sizes,"\n")}
    cat("\nEstimated Basis Vectors for Central Subspace:\n")
    print(x$evectors,digits=digits)
    cat("\n")
    print(x$evalues,digits=digits)
    if (length(x$omitted) > 0){
      cat("\nModel matrix is not full rank.  Deleted columns:\n")
      cat(x$omitted,"\n")}
    if (!is.null(x$test)){
      cat("\nLarge-sample Marginal Dimension Tests:\n")
      #cat("\nAsymp. Chi-square tests for dimension:\n")
      print(as.matrix(x$test),digits=digits)}
    invisible(x)
}



###################################################################3
##
##  Translation of methods in Arc for testing with pHd to R
##  Original lisp functions were mostly written by R. D. Cook
##  Translation to R by S. Weisberg, February, 2001
##
###################################################################3
# this function is a translation from Arc.  It computes the matrices W and
# eW described in Sec. 12.3.1 of Cook (1998), Regression Graphics.
# There are separate versions of this function for R and for Splus because
cov.ew.matrix <- function(object,scaled=FALSE) {
  mat.normalize <- function(a){apply(a,2,function(x){x/(sqrt(sum(x^2)))})}
  n <- dim(dr.x(object))[1]
  TEMPwts <- object$weights
  sTEMPwts <- sqrt(TEMPwts)
  v <- sqrt(n)* mat.normalize(
         apply(scale(dr.x(object),center=TRUE,scale=FALSE),2,"*",sTEMPwts) %*% 
               object$evectors)
  y <- dr.y(object) # get the response
  y <- if (scaled) y-mean(sTEMPwts*y) else 1 # a multiplier in the matrix
  p <- dim(v)[2]
  ew0 <- NULL
  for (i in 1:p){
   for (j in i:p){
    ew0 <- cbind(ew0, if (i ==j) y*(v[,j]^2-1) else y*sqrt(2)*v[,i]*v[,j])}}
  wmean <- function (x,w)  { sum(x * w) / sum (w) }
  tmp <- apply(ew0,2,function(x,w,wmean){sqrt(w)*(x-wmean(x,w))},TEMPwts,wmean)
  ans<-(1/sum(TEMPwts)) * t(tmp) %*% tmp
  ans} 

#translation of :general-pvalues method for phd in Arc
dr.test2.phdres <- function(object,stats){
  covew <- cov.ew.matrix(object,scaled=TRUE)
  C <- .5/var(dr.y(object))
  p <- length(stats)
  pval <- NULL
  d2 <-dim(dr.x(object))[2]
  start <- -d2
  end <- dim(covew)[2]
  for (i in 1:p) {
   start <- start + d2-i+2
   evals <- 
     eigen(as.numeric(C)*covew[start:end,start:end],only.values=TRUE)$values
   pval<-c(pval,
     dr.pvalue(evals,stats[i],chi2approx=object$chi2approx)$pval.adj)
#   pval<-c(pval,wood.pvalue(evals,stats[i]))
   }
# report results
    z<-data.frame(cbind(stats,pval))
    rr<-paste(0:(p-1),"D vs >= ",1:p,"D",sep="")
    dimnames(z)<-list(rr,c("Stat","p.value"))
    z}
   
dr.indep.test.phdres <- function(object,stat) {
  eval <- eigen(cov.ew.matrix(object,scaled=FALSE),only.values=TRUE)
  pval<-dr.pvalue(.5*eval$values,stat,
           chi2approx=object$chi2approx)$pval.adj
# report results
    z<-data.frame(cbind(stat,pval))
    dimnames(z)<-list(c("Test of independence"),c("Stat","p.value"))
    z}

dr.pvalue <- function(coef,f,chi2approx=c("bx","wood"),...){
 method <- match.arg(chi2approx)
 if (method == "bx") {
   bentlerxie.pvalue(coef,f)} else{
   wood.pvalue(coef,f,...)}}


bentlerxie.pvalue <- function(coef,f) {
# Returns the Bentler-Xie approximation to P(coef'X > f), where
# X is a vector of iid Chi-square(1) r.v.'s, coef is a vector of weights,
# and f is the observed value.
  trace1 <- sum(coef)                   
  trace2 <- sum(coef^2)
  df.adj <- trace1^2/trace2   
  stat.adj <- f *  df.adj/trace1
  bxadjusted <- 1 - pchisq(stat.adj, df.adj)  
  ans <- data.frame(test=f,test.adj=stat.adj,
                    df.adj=df.adj,pval.adj=bxadjusted)
  ans
}

wood.pvalue <- function (coef, f, tol=0.0, print=FALSE){
#Returns an approximation to P(coef'X > f) for X=(X1,...,Xk)', a vector of iid
#one df chi-squared rvs.  coef is a list of positive coefficients. tol is used
#to check for near-zero conditions.
#See Wood (1989), Communications in Statistics, Simulation 1439-1456.
#Translated from Arc function wood-pvalue.
#  error checking
  if (min(coef) < 0) stop("negative eigenvalues")
  if (length(coef) == 1)
     {pval <- 1-pchisq(f/coef,1)} else
     {k1 <-     sum(coef)
      k2 <- 2 * sum(coef^2)
      k3 <- 8 * sum(coef^3)
      t1 <- 4*k1*k2^2 + k3*(k2-k1^2)
      t2 <- k1*k3 - 2*k2^2
    if ((t2 <= tol) && (tol < t2) ){
        a1 <- k1^2/k2
    b  <- k1/k2
    pval <- 1 - pgamma(b*f,a1)
        if (print) 
      print(paste("Satterthwaite-Welsh Approximation =", pval))}
      else if( (t1 <= tol) && (tol < t2)){
        a1 <-2 + (k1/k2)^2
    b  <- (k1*(k1^2+k2))/k2
    pval <- if (f < tol) 1.0 else 1 - pgamma(b/f,a1)
        if (print) print(paste("Inverse gamma approximation =",pval))}
      else if ((t1 > tol) && (t2 > tol)) {
        a1 <- (2*k1*(k1*k3 + k2*k1^2 -k2^2))/t1
     b <- t1/t2
    a2 <- 3 + 2*k2*(k2+k1^2)/t2
    pval <- 1-pbeta(f/(f+b),a1,a2)
        if (print) print(paste(
          "Three parameter F(Pearson Type VI) approximation =", pval))}
      else {
        pval <- NULL
        if (print) print("Wood's Approximation failed")}}
   data.frame(test=f,test.adj=NA,df.adj=NA,pval.adj=pval)}


#########################################################################
#
# permutation tests for dimenison reduction
#
#########################################################################

dr.permutation.test <- function(object,npermute=50,numdir=object$numdir) {
 if (inherits(object,"ire")) stop("Permutation tests not implemented for ire")
 else{
   permute.weights <- TRUE
   call <- object$call
   call[[1]] <- as.name("dr.compute")
   call$formula <- call$data <- call$subset <- call$na.action <- NULL
   x <- dr.directions(object) # predictors with a 'better' basis
   call$y <- object$y
   weights <- object$weights
# nd is the number of dimensions to test
   nd <- min(numdir,length(which(abs(object$evalues)>1.e-8))-1)
   nt <- nd + 1
# observed value of the test statistics = obstest
   obstest<-dr.permutation.test.statistic(object,nt)
# count and val keep track of the results and are initialized to zero
   count<-rep(0,nt)
   val<-rep(0,nt)
# main loop
   for (j in 1:npermute){   #repeat npermute times
    perm<-sample(1:object$cases)
    call$weights<- if (permute.weights) weights[perm] else weights    
# inner loop
    for (col in 0:nd){
# z gives a permutation of x.  For a test of dim = col versus dim >= col+1,
# all columns of x are permuted EXCEPT for the first col columns.
        call$x<-if (col==0) x[perm,] else   cbind(x[,(1:col)],x[perm,-(1:col)])
        iperm <- eval(call)
        val[col+1]<- dr.permutation.test.statistic(iperm,col+1)[col+1]
        }  # end of inner loop
# add to counter if the permuted test exceeds the obs. value
     count[val>obstest]<-count[val>obstest]+1
   }
# summarize
   pval<-(count)/(npermute+1)
   ans1 <- data.frame(cbind(obstest,pval))
   dimnames(ans1) <-list(paste(0:(nt-1),"D vs >= ",1:nt,"D",sep=""),
                         c("Stat","p.value"))
   ans<-list(summary=ans1,npermute=npermute)
   class(ans) <- "dr.permutation.test"
   ans
   }}

"print.dr.permutation.test" <-
function(x, digits = max(3, getOption("digits") - 3), ...)
{
   cat("\nPermutation tests\nNumber of permutations:\n")
   print.default(x$npermute)
   cat("\nTest results:\n")
   print(x$summary,digits=digits) 
   invisible(x)
}

"summary.dr.permutation.test" <- function(...)
              {print.dr.permutation.test(...)}


#########################################################################
#
# dr.permutation.test.statistic method
#
#########################################################################

dr.permutation.test.statistic <- function(object,numdir)
  {UseMethod("dr.permutation.test.statistic")}

dr.permutation.test.statistic.default <- function(object,numdir){
   object$cases*rev(cumsum(rev(object$evalues)))[1:numdir]}

dr.permutation.test.statistic.phdy <- function(object,numdir){
       dr.permutation.test.statistic.phd(object,numdir)}
dr.permutation.test.statistic.phdres <- function(object,numdir){
       dr.permutation.test.statistic.phd(object,numdir)}
dr.permutation.test.statistic.phd <- function(object,numdir){
   (.5*object$cases*rev(cumsum(rev(object$evalues^2)))/var(dr.y(object)))[1:numdir]}

#####################################################################
#
#     dr.slices returns non-overlapping slices based on y
#     y is either a list of n numbers or an n by p matrix
#     nslices is either the total number of slices, or else a
#     list of the number of slices in each dimension
#
#####################################################################
dr.slices <- function(y,nslices) {
  dr.slice.1d <- function(y,h) {
      z<-unique(y)
      if (length(z) > h) dr.slice2(y,h) else dr.slice1(y,sort(z))}
  dr.slice1 <- function(y,u){
      z <- sizes <- 0
      for (j in 1:length(u)) {
          temp <- which(y==u[j])
          z[temp] <- j
          sizes[j] <- length(temp) } 
      list(slice.indicator=z, nslices=length(u), slice.sizes=sizes)}
  dr.slice2 <- function(y,h){
       myfind <- function(x,cty){
          ans<-which(x <= cty)
          if (length(ans)==0) length(cty) else ans[1]} 
       or <- order(y)     # y[or] would return ordered y
       cty <- cumsum(table(y))  # cumulative sums of counts of y
       names(cty) <- NULL # drop class names
       n <- length(y)     # length of y
       m<-floor(n/h)      # nominal number of obs per slice
       sp <- end <- 0     # initialize
       j <- 0             # slice counter will end up <= h
       ans <- rep(1,n)    # initialize slice indicator to all slice 1
       while(end < n-2) { # find slice boundaries:  all slices have at least 2 obs
          end <- end+m
          j <- j+1       
          sp[j] <- myfind(end,cty) 
          end <- cty[sp[j]]}
       sp[j] <- length(cty)
       for (j in 2:length(sp)){ # build slice indicator
         firstobs <- cty[sp[j-1]]+1
         lastobs <- cty[sp[j]]
         ans[or[firstobs:lastobs]] <- j}
       list(slice.indicator=ans, nslices=length(sp),
            slice.sizes=c(cty[sp[1]],diff(cty[sp]))) }
  p <- if (is.matrix(y)) dim(y)[2] else 1
  h <- if (length(nslices) == p) nslices else rep(ceiling(nslices^(1/p)),p)
  a <- dr.slice.1d( if(is.matrix(y)) y[,1] else y, h[1])
  if (p > 1){
    for (col in 2:p) {
       ns <- 0
       for (j in unique(a$slice.indicator)) {
         b <- dr.slice.1d(y[a$slice.indicator==j,col],h[col])
         a$slice.indicator[a$slice.indicator==j] <- 
                a$slice.indicator[a$slice.indicator==j] + 10^(col-1)*b$slice.indicator
         ns <- ns + b$nslices}
       a$nslices <- ns }
#recode unique values to 1:nslices and fix up slice sizes
    v <- unique(a$slice.indicator)
    L <- slice.indicator <- NULL
    for (i in 1:length(v)) {
       sel <- a$slice.indicator==v[i]
       slice.indicator[sel] <- i
       L <- c(L,length(a$slice.indicator[sel]))}
    a$slice.indicator <- slice.indicator
    a$slice.sizes <- L }
  a}

dr.slices.arc<-function(y,nslices)  # matches slices produced by Arc
{
  if(is.matrix(y))  stop("dr.slices.arc is used for univariate y only.  Use dr.slices")
  h <- nslices
  or <- order(y)
  n <- length(y)
  m<-floor(n/h)
  r<-n-m*h
  start<-sp<-ans<-0
  j<-1
  while((start+m)<n)
    { if (r==0)
        start<-start
      else 
        {start<-start+1
         r<-r-1
        }
       while (y[or][start+m]==y[or][start+m+1])
          start<-start+1
       sp[j]<-start+m
       start<-sp[j]
       j<-j+1
     }
# next line added 6/17/02 to assure that the last slice has at least 2 obs.
  if (sp[j-1] == n-1) j <- j-1
  sp[j]<-n
  ans[or[1:sp[1]]] <- 1
  for (k in 2:j){ans[ or[(sp[k-1]+1):sp[k] ] ] <- k}
  list(slice.indicator=ans, nslices=j, slice.sizes=c(sp[1],diff(sp)))
}
     
#####################################################################
#
#     Misc. Auxillary functions: cosine of the angle between two 
#     vectors and between a vector and a subspace.
#
#####################################################################

#
# angle between a vector vec and a subspace span(mat)
#
cosangle <- function(mat,vecs){
 ans <-NULL
 if (!is.matrix(vecs)) ans<-cosangle1(mat,vecs) else {
   for (i in 1:dim(vecs)[2]){ans <-rbind(ans,cosangle1(mat,vecs[,i]))}
   dimnames(ans) <- list(colnames(vecs),NULL) }
 ans}

cosangle1 <- function(mat,vec) {
  ans <- 
   cumsum((t(qr.Q(qr(mat))) %*% scale(vec)/sqrt(length(vec)-1))^2) 
# R returns a row vector, but Splus returns a column vector.  The next line
# fixes this difference
  if (version$language == "R") ans else t(ans)
}



#####################################################################
# R Functions for reweighting for elliptical symmetry
# modified from reweight.lsp for Arc
# Sanford Weisberg, sandy@stat.umn.edu
# March, 2001, rev June 2004

# Here is an outline of the function:
#   1.  Estimates of the mean m and covariance matrix S are obtained.  The
#       function cov.rob in the MASS package is used for this purpose.  
#   2.  The matrix X is replaced by Z = (X - 1t(m))S^{-1/2}.  If the columns
#       of X were from a multivariate normal N(m,S), then the rows of Z are
#       multivariate normal N(0, I).
#   3.  Randomly sample from N(0, sigma*I), where sigma is a tuning
#       parameter.  Find the row of Z closest to the random data, and increase
#       its weight by 1
#   4.  Return the weights divided by nsamples and multiplied by n.
#
#     dr.weights
#
#####################################################################
dr.weights <-
function (formula, data = list(), subset, na.action=na.fail, 
   sigma=1,nsamples=NULL,...)
{
# Create design matrix from the formula and call dr.estimate.weights
    mf1 <- match.call(expand.dots=FALSE)
    mf1$... <- NULL # ignore ...
    mf1$covmethod  <- mf1$nsamples <- NULL
    mf1[[1]] <- as.name("model.frame")
    mf <- eval(mf1, sys.frame(sys.parent()))
    mt <- attr(mf,"terms")
    x <- model.matrix(mt, mf)
    int <- match("(Intercept)", dimnames(x)[[2]], nomatch=0)
    if (int > 0) x <- x[, -int, drop=FALSE] # drop the intercept from X
    ans <- cov.rob(x, ...) 
    m <- ans$center
    s<-svd(ans$cov)
    z<-sweep(x,2,m) %*% s$u %*% diag(1/sqrt(s$d))
    n <- dim(z)[1]   # number of obs
    p <- dim(z)[2]   # number of predictors
    ns <- if (is.null(nsamples)) 10*n else nsamples
    dist <- wts <- rep(0,n)  # initialize distances and weights
    for (i in 1:ns) {
       point <- rnorm(p) * sigma      # Random sample from a normal N(0, sigma^2I)
       dist <- apply(z,1,function(x,point){sum((point-x)^2)},point) 
#Dist to each point 
       sel <- dist == min(dist)               # Find closest point(s)
       wts[sel]<-wts[sel]+1/length(wts[sel])} # Increase weights for those points
    w <- n*wts/ns
    if (missing(subset)) return(w)
    if (is.null(subset)) return(w) else {
# find dimension of mf without subset specified
    mf1$subset <- NULL
    w1 <- rep(NA,length=dim(eval(mf1))[1])
    w1[subset] <- w
    return(w1)}}
 
###################################################################
## drop1 methods 
###################################################################
drop1.dr <-
function (object, scope = NULL, update = TRUE, test = "general", 
    trace=1, ...) 
{               
    keep <- if (is.null(scope)) 
        NULL
    else attr(terms(update(object$terms, scope)), "term.labels")
    all <- attr(object$terms, "term.labels")
    candidates <- setdiff(all, keep)
    if (length(candidates) == 0) 
        stop("Error---nothing to drop")
    ans <- NULL
    for (label in candidates) {
        ans <- rbind(ans, dr.coordinate.test(object, as.formula(paste("~.-", 
            label, sep = "")), ...))
    }
    row.names(ans) <- paste("-", candidates)
    ncols <- ncol(ans)
    or <- order(-ans[, if (test == "general") 
        ncols
    else (ncols - 1)])
    form <- formula(object)
    attributes(form) <- NULL
    fout <- deparse(form, width.cutoff = 50)
    if(trace > 0) {
      for (f in fout) cat("\n", f)
      cat("\n")
      print(ans[or, ]) }
    if (is.null(object$stop)) {
        object$stop <- 0
    }
    stopp <- if (ans[or[1], if (test == "general") 
        ncols
    else (ncols - 1)] < object$stop) 
        TRUE
    else FALSE
    if (stopp == TRUE) {
        if(trace > 0) cat("\nStopping Criterion Met\n")
        object$stop <- TRUE
        object
    }
    else if (update == TRUE) {
        update(object, as.formula(paste("~.", row.names(ans)[or][1], 
            sep = "")))
    }
    else invisible(ans)
}
      
dr.step <-
function (object, scope = NULL, d = NULL, minsize = 2, stop = 0, 
     trace=1,...) 
{
    if (is.null(object$stop)) {
        object$stop <- stop
    }
    if (object$stop == TRUE) 
        object
    else {
        minsize <- max(2, minsize)
        keep <- if (is.null(scope)) 
            NULL
        else attr(terms(update(object$terms, scope)), "term.labels")
        all <- attr(object$terms, "term.labels")
        if (length(keep) >= length(all)) {
            if(trace > 0) cat("\nNo more variables to remove\n")
            object
        }
        else if (length(all) <= minsize) {
            if (trace > 0) cat("\nMinimum size reached\n")
            object$numdir <- minsize
            object
        }
        else {
            if (dim(object$x)[2] <= minsize) {
                if (trace > 0) cat("\nMinimum size reached\n")
                object}
            else {   
                obj1 <- drop1(object, scope = scope, d = d, trace=trace, ...)
                dr.step(obj1, scope = scope, d = d, stop = stop,
                       trace=trace, ...)
            }
        }
    }
}

 
#####################################################################
##
##  Add functions to Splus that are built-in to R
##
#####################################################################

if (version$language != "R") {

"is.empty.model" <- function (x)
{
    tt <- terms(x)
    (length(attr(tt, "factors")) == 0) & (attr(tt, "intercept")==0)
}
"NROW" <-
function(x) if(is.array(x)||is.data.frame(x)) nrow(x) else length(x)
"NCOL" <-
function(x) if(is.array(x)||is.data.frame(x)) ncol(x) else as.integer(1)

"colnames" <-
function(x, do.NULL = TRUE, prefix = "col")
{
    dn <- dimnames(x)
    if(!is.null(dn[[2]]))
    dn[[2]]
    else {
    if(do.NULL) NULL else paste(prefix, seq(length=NCOL(x)), sep="")
    }
}

"getOption" <- function(x) options(x)[[1]]
# end of special functions
}

#####################################################################
#     ire:  Cook, RD and Ni, L (2005). Sufficient Dimension reduction via
# inverse regression:  A minimum discrepancy approach.  JASA 410-428
#  This code does all computations by computing the QR factorization of the
#  centered X matrix, and then using sqrt(n) Q in place of X in the
#  computations.  Back transformation to X scale is done only when direction
#  vectors are needed.
#####################################################################
dr.fit.ire <-function(object,numdir=4,nslices=NULL,slice.function=dr.slices,
    tests=TRUE,...){ 
    z <- dr.z(object)    
    h <- if (!is.null(nslices)) nslices else max(8, NCOL(z)+3)
    slices <- slice.function(dr.y(object),h)
    object$slice.info <- slices
    f <- slices$slice.sizes/sum(slices$slice.sizes)
    n <- object$cases
    weights <- object$weights
    p <- dim(z)[2]
    numdir <- min(numdir,p-1)               
    h <- slices$nslices
    xi <- matrix(0,nrow=p,ncol=slices$nslices)
    for (j in 1:h){
     sel <- slices$slice.indicator==j
     xi[,j]<-apply(z[sel,],2,function(a,w) sum(a*w)/sum(w),weights[sel])
    } 
    object$sir.raw.evectors <- eigen(xi %*% diag(f) %*% t(xi))$vectors
    object$slice.means <- xi # matrix of slice means
    An <- qr.Q(qr(contr.helmert(slices$nslices)))
    object$zeta <- xi %*% diag(f) %*% An
    rownames(object$zeta) <- paste("Q",1:dim(z)[2],sep="")
    ans <- NULL

    if (tests == TRUE) {    
      object$indep.test <- dr.test(object,numdir=0,...)
      Gz <- Gzcomp(object,numdir)  # This is the same for all numdir > 0
      for (d in 1:numdir){
         ans[[d]] <- dr.test(object,numdir=d,Gz,...)
         colnames(ans[[d]]$B) <- paste("Dir",1:d,sep="")     
      }
# This is different from Ni's lsp code.  Gamma_zeta is computed from the
# fit of the largest dimension and stored.
    object$Gz <- Gzcomp(object,d,span=ans[[numdir]]$B)
    }
    aa<-c(object, list(result=ans,numdir=numdir,n=n,f=f))
    class(aa) <- class(object)
    return(aa)
}
     
dr.test.ire <- function(object,numdir,Gz=Gzcomp(object,numdir),steps=1,...){       
   ans <- dr.iteration(object,Gz,d=numdir,
             B=object$sir.raw.evectors[,1:numdir,drop=FALSE],...)
   if (steps > 0 & numdir > 0){ # Reestimate if steps > 0.
    for (st in 1:steps){
     Gz <- Gzcomp(object,numdir,ans$B[,1:numdir])
     ans <- dr.iteration(object,Gz,d=numdir,
             B=ans$B[,1:numdir,drop=FALSE],...)}}
# reorder B matrix according to importance (col. 2 of p. 414)
   if (numdir > 1){
     ans0 <- dr.iteration(object,Gz,d=1,T=ans$B)
     sumry <- ans0$summary
     B0 <- matrix(ans0$B/sqrt(sum((ans0$B)^2)),ncol=1)
     C <- ans$B
     for (d in 2:numdir) {
        common <- B0[,d-1]
        for (j in 1:dim(C)[2]){
            C[,j] <- C[,j] - sum(common*(C[,j]))*B0[,d-1]}
        C <- qr.Q(qr(C))[,-dim(C)[2],drop=FALSE]
        ans0 <- dr.iteration(object,Gz,d=1,T=C)
        B0 <- cbind(B0,ans0$B/sqrt(sum((ans0$B)^2)))
        sumry <- rbind(sumry,dr.iteration(object,Gz,d=d,T=B0)$summary)
        }
# scale to length 1 and make first element always positive
    ans$B <- apply(B0,2,function(x){
        b <- x/sqrt(sum(x^2))
        if (b[1] < 0) - b else b})
    ans$sequential <- sumry
    }
   if (numdir > 0) {colnames(ans$B) <- paste("Dir",1:numdir,sep="")}
   if (numdir > 1) rownames(ans$sequential) <- paste(1:numdir)
   ans}
   
Gzcomp <- function(object,numdir,span){UseMethod("Gzcomp")}
Gzcomp.ire <- function(object,numdir,span=NULL){
    slices <- object$slice.info
    An <- qr.Q(qr(contr.helmert(slices$nslices)))
    n <- object$cases
    weights <- object$weights
    z <- dr.z(object)  # z is centered with correct weights.
    p <- dim(object$slice.means)[1]
    h <- slices$nslices
    f <- slices$slice.sizes/sum(slices$slice.sizes)
# page 411, eq (1) except projecting to d dimensions using span.
    span <- if (!is.null(span)) span else diag(rep(1,p))
    xi <- if (numdir > 0){qr.fitted(qr(span),object$slice.means)} else {
          matrix(0,nrow=p,ncol=h)}        
# We next compute the inner product matrix Vn = inverse(Gamma_zeta)
# We compute only the Cholesky decomposition of Gamma_zeta, as that
# is all that is needed.  The name is reused for intermediate quantities
# to save space (it is a p*(h-1) by p*(h-1) matrix).
# First, compute Gamma, defined on line 2 p. 414
# make use of vec(A %*% B) = kronecker(t(B), A)
# and also that the mean of vec, as defined below, is zero.
    Gz <- array(0,c(p*h,p*h)) 
    Gmat <- NULL
    for (i in 1:n){
     epsilon_i <- -f -z[i,,drop=FALSE] %*% xi %*% diag(f)
     epsilon_i[slices$slice.indicator[i]]<-1+epsilon_i[slices$slice.indicator[i]]
     vec <- as.vector( z[i,] %*% epsilon_i)
     Gmat <- rbind(Gmat,vec)}
     Gz <- chol(kronecker(t(An),diag(rep(1,p))) %*% (((n-1)/n)*cov(Gmat)) %*%
                 kronecker(An,diag(rep(1,p))))
     Gz}
   
#####################################################################
##  ire iteration
##  B, if set, is a p by d matrix whose columns are a starting value
##     for a basis for the central subspace.  The default is the first
##     d columns of I_p
##  R, if set, is a restriction matrix, such that the estimated
##     spanning vectors are RB, not B itself.
##  Returned matrix B is really R %*% B
##  --fn is the objective function, eq (5) of Cook & Ni (2004)
##  --updateC is step 2 of the algorithm on p. 414
##  --updateB is step 3 of the algorithm on p. 414
##  --PBk in the paper is the projection on an orthogonal completment
##      of the operator QBk.  It is the QR decomposition of the projection,
##      not its complement (hence the use of qr.resid, not qr.fitted)
######################################################################
dr.iteration <- function(object,Gz,d=2,B,T,eps,itmax,verbose){UseMethod("dr.iteration")}
dr.iteration.ire <- function(object,Gz,d=2,B=NULL,T=NULL,eps=1.e-6,itmax=200,
   verbose=FALSE){
   n <- object$cases
   zeta <- object$zeta
   p <- dim(zeta)[1]
   h1 <- dim(zeta)[2]  # zeta is p by (h-1) for ire and sum(h-1) for pire
   if (d == 0){ 
     err <- n*sum(forwardsolve(t(Gz),as.vector(zeta))^2)
     data.frame(Test=err,df=(p-d)*(h1-d),
               p.value=pchisq(err,(p-d)*(h1-d),lower.tail=FALSE),iter=0)} else {
   T <- if(is.null(T)) diag(rep(1,p)) else T
   B <- if(is.null(B)) diag(rep(1,ncol(T)))[,1:d,drop=FALSE] else B
   fn <- function(B,C){ 
           n * sum( forwardsolve(t(Gz),as.vector(zeta)-as.vector(T%*%B%*%C))^2 ) }
   updateC <- function() {
        matrix( qr.coef(qr(forwardsolve(t(Gz),kronecker(diag(rep(1,h1)),T%*%B))),
                           forwardsolve(t(Gz),as.vector(zeta))), nrow=d)}
   updateB <- function() { 
      for (k in 1:d) { 
         alphak <- as.vector(zeta - T %*% B[,-k,drop=FALSE] %*% C[-k,])
         PBk <- qr(B[,-k])  
         bk <- qr.coef(
                qr(forwardsolve(t(Gz),
                    t(qr.resid(PBk,t(kronecker(C[k,],T)))))),
                forwardsolve(t(Gz),as.vector(alphak)))
         bk[is.na(bk)] <- 0  # can use any OLS estimate; eg, set NA to 0
         bk <- qr.resid(PBk,bk)
         B[,k] <- bk/sqrt(sum(bk^2))}
         B}
     C <- updateC()  # starting values
     err <- fn(B,C)
     iter <- 0
     repeat{
       iter <- iter+1
       B <- updateB()
       C <- updateC()
       errold <- err
       err <- fn(B,C)
       if(verbose==TRUE) print(paste("Iter =",iter,"Fn =",err),quote=FALSE)
       if ( abs(err-errold)/errold < eps || iter > itmax ) break
       }  
     B <- T %*% B
     rownames(B) <- rownames(zeta)
     list(B=B,summary=data.frame(Test=err,df=(p-d)*(h1-d),
               p.value=pchisq(err,(p-d)*(h1-d),lower.tail=FALSE),iter=iter))
     }}

# Equation (12), p. 415 of Cook and Ni (2004) for d=NULL
# Equation (14), p. 415 for d > 0
# Remember...all calculations are in the Q scale, where X=QR...
dr.coordinate.test.ire<-function(object,hypothesis,d=NULL,...){
    gamma <- if (class(hypothesis) == "formula")
        coord.hyp.basis(object, hypothesis)
        else as.matrix(hypothesis)
    gamma <- dr.R(object)%*%gamma  # Rotate to Q-coordinates:
    p <- dim(object$x)[2]
    r <- p-dim(gamma)[2] 
    maxdir <- length(object$result)   
   if(is.null(d)){
    h1 <- dim(object$zeta)[2] # h-1 for ire, sum(h-1) for pire
    H <- qr.Q(qr(gamma),complete=TRUE)[,-(1:(p-r)),drop=FALSE]
    n<-object$cases
    Gz <- object$Gz 
    zeta <- object$zeta
    m1 <- Gz %*% kronecker(diag(rep(1,h1)),H)
    m1 <- chol(t(m1) %*% m1)
    T_H <- n * sum (forwardsolve(t(m1),as.vector(t(H)%*%zeta))^2)
    df <- r*(h1)
    z <- data.frame(Test=T_H,df=df,p.value=pchisq(T_H,df,lower.tail=FALSE))
    z} 
   else {
    F0 <-if(maxdir >= d) object$result[[d]]$summary$Test else
          dr.iteration(object,object$Gz,d=d,...)$summary$Test
    F1 <- dr.joint.test(object,hypothesis,d=d,...)$summary$Test
    data.frame(Test=F1-F0,df=r*d,p.value=pchisq(F1-F0,r*d,lower.tail=FALSE))
    }}
 
# Unnumbered equation middle of second column, p. 415 of Cook and Ni (2004)   
dr.joint.test.ire<-function(object,hypothesis,d=NULL,...){
    if(is.null(d)) {dr.coordinate.test(object,hypothesis,...)} else {
     gamma <- if (class(hypothesis) == "formula")
         coord.hyp.basis(object, hypothesis)
         else as.matrix(hypothesis)
     gamma <- dr.R(object)%*%gamma  # Rotate to Q-coordinates:         
     dr.iteration(object,object$Gz,d=d,T=gamma)}}
    
### print/summary functions
print.ire <- function(x, width=50, ...) { 
    fout <- deparse(x$call,width.cutoff=width)
    for (f in fout) cat("\n",f)
    cat("\n")
    numdir <- length(x$result)
    tests <- x$indep.test
    for (d in 1:numdir) {
     tests <- rbind(tests,x$result[[d]]$summary)}   
    rownames(tests) <- paste(0:numdir,"D vs"," > ",0:numdir,"D",sep="")
    cat("Large-sample Marginal Dimension Tests:\n")
    print(tests)
    cat("\n")
    invisible(x)
}

"summary.ire" <- function (object, ...)
{   ans <- object[c("call")]
    result <- object$result
    numdir <- length(result)
    tests <- object$indep.test
    for (d in 1:numdir) {
       tests <- rbind(tests,result[[d]]$summary)}
    rownames(tests) <- paste(0:numdir,"D vs"," > ",0:numdir,"D",sep="")
    ans$method <- object$method
    ans$nslices <- object$slice.info$nslices
    ans$sizes <- object$slice.info$slice.sizes
    ans$weights <- dr.wts(object)
    ans$result <- object$result
    for (j in 1:length(ans$result)) {ans$result[[j]]$B <- dr.basis(object,j)}
    ans$n <- object$cases #NROW(object$model)
    ans$test <- tests
    class(ans) <- "summary.ire"
    ans
}

"print.summary.ire" <-
function (x, digits = max(3, getOption("digits") - 3), ...)
{
    cat("\nCall:\n")#S: ' ' instead of '\n'
    cat(paste(deparse(x$call), sep="\n", collapse = "\n"), "\n\n", sep="")
    cat("Method:\n")#S: ' ' instead of '\n'
    cat(x$method,"with",x$nslices, " slices, n =",x$n) 
    if(diff(range(x$weights)) > 0)cat(", using weights.\n") else cat(".\n")
    cat("\nSlice Sizes:\n")#S: ' ' instead of '\n'
    cat(x$sizes,"\n")
    cat("\nLarge-sample Marginal Dimension Tests:\n")
      print(as.matrix(x$test),digits=digits)
    cat("\n")       
    cat("\nSolutions for various dimensions:\n")
    for (d in 1:length(x$result)){
     cat("\n",paste("Dimension =",d),"\n")
     print(x$result[[d]]$B,digits=digits)}
     cat("\n")
     invisible(x)
}

dr.basis.ire <- function(object,numdir=length(object$result)) {
    fl <- function(z) apply(z,2,function(x){
        b <- x/sqrt(sum(x^2))
        if (b[1] < 0) -1*b else b}) 
    ans <- fl(backsolve(dr.R(object),object$result[[numdir]]$B))
    dimnames(ans) <- list(colnames(dr.x(object)),
                         paste("Dir",1:dim(ans)[2],sep=""))
    ans}

dr.direction.ire <- 
  function(object, which=1:length(object$result),x=dr.x(object)){
    d <- max(which)
    scale(x,center=TRUE,scale=FALSE) %*% dr.basis(object,d)
    }
   
#############################################################################
# partial ire --- see Wen and Cook (in press), Optimal sufficient dimension
# reduction in regressions with categorical predictors. Journal of Statistical
# planning and inference.
#############################################################################

dr.fit.pire <-function(object,numdir=4,nslices=NULL,slice.function=dr.slices,...){ 
    y <- dr.y(object)
    z <- dr.z(object) 
    p <- dim(z)[2]
    if(is.null(object$group)) object$group <- rep(1,dim(z)[1])
    group.names <- unique(as.factor(object$group))
    nw <- table(object$group)
    if (any(nw < p) ) stop("At least one group has too few cases")
    h <- if (!is.null(nslices)) nslices else NCOL(z)
    group.stats <- NULL
    for (j in 1:length(group.names)){
      name <- group.names[j] 
      group.sel <- object$group==name
      ans <- dr.compute(z[group.sel,],y[group.sel],method="ire",
            nslices=h,slice.function=slice.function,
            tests=FALSE,weights=object$weights[group.sel])
      object$zeta[[j]] <- ans$zeta
      group.stats[[j]] <- ans
      }
    object$group.stats <- group.stats
    numdir <- min(numdir,p-1)
    object$sir.raw.evectors <- dr.compute(z,y,nslices=h,
            slice.function=slice.function,
            weights=object$weights)$raw.evectors
    class(object) <- c("pire","ire","dr")
    object$indep.test <- dr.test(object,numdir=0,...)
    Gz <- Gzcomp(object,numdir)  # This is the same for all numdir > 0
    ans <- NULL
    for (d in 1:numdir){
       ans[[d]] <- dr.test(object,numdir=d,Gz,...)
       colnames(ans[[d]]$B) <- paste("Dir",1:d,sep="")     
    }
    object$Gz <- Gzcomp(object,d,span=ans[[numdir]]$B)
    aa<-c(object, list(result=ans,numdir=numdir))
    class(aa) <- class(object)
    return(aa)
}

dr.iteration.pire <- function(object,Gz,d=2,B=NULL,T=NULL,eps=1.e-6,itmax=200,
   verbose=FALSE){
   gsolve <- function(a1,a2){  #modelled after ginv in MASS
    Asvd <- svd(a1)
    Positive <- Asvd$d > max(sqrt(.Machine$double.eps) * Asvd$d[1], 0)
    if(all(Positive))
     Asvd$v %*% (1/Asvd$d * t(Asvd$u)) %*% a2
    else Asvd$v[, Positive, drop = FALSE] %*% ((1/Asvd$d[Positive]) * 
        t(Asvd$u[, Positive, drop = FALSE])) %*% a2}
   n <- object$cases
   zeta <- object$zeta
   n.groups <- length(zeta)
   p <- dim(zeta[[1]])[1]
   h1 <- 0
   h2 <- NULL
   for (j in 1:n.groups){
      h2[j] <- dim(zeta[[j]])[2]
      h1 <- h1 + h2[j]}
   if (d == 0){ 
     err <- 0
     for (j in 1:n.groups){
      err <- err + n*sum(forwardsolve(t(Gz[[j]]),as.vector(zeta[[j]]))^2)}
     data.frame(Test=err,df=(p-d)*(h1-d),
               p.value=pchisq(err,(p-d)*(h1-d),lower.tail=FALSE),iter=0)} else {
   T <- if(is.null(T)) diag(rep(1,p)) else T
   B <- if(is.null(B)) diag(rep(1,ncol(T)))[,1:d,drop=FALSE] else B
   fn <- function(B,C){ 
           ans <- 0
           for (j in 1:n.groups){ans <- ans +
            n * sum( forwardsolve(t(Gz[[j]]),as.vector(zeta[[j]])-
                as.vector(T%*%B%*%C[[j]]))^2) }
            ans}
   updateC <- function() {
        C <- NULL
        for (j in 1:n.groups){ C[[j]]<-
        matrix( qr.coef(qr(forwardsolve(t(Gz[[j]]),kronecker(diag(rep(1,h2[j])),T%*%B))),
                           forwardsolve(t(Gz[[j]]),as.vector(zeta[[j]]))), nrow=d)}
        C}
   updateB <- function() { 
      for (k in 1:d) { 
         PBk <- qr(B[,-k]) 
         a1 <- a2 <- 0
         for (j in 1:n.groups){
          alphak <- as.vector(zeta[[j]]-T%*%B[,-k,drop=FALSE]%*%C[[j]][-k,])
          m1 <-  forwardsolve(t(Gz[[j]]),
                    t(qr.resid(PBk,t(kronecker(C[[j]][k,],T)))))
          m2 <- forwardsolve(t(Gz[[j]]),alphak)
          a1 <- a1 + t(m1) %*% m1
          a2 <- a2 + t(m1) %*% m2}
         bk <- qr.resid(PBk, gsolve(a1,a2))
         bk[is.na(bk)] <- 0  # can use any OLS estimate; eg, set NA to 0
         bk <- qr.resid(PBk,bk)
         B[,k] <- bk/sqrt(sum(bk^2))}
         B}
     C <- updateC()  # starting values
     err <- fn(B,C)
     iter <- 0
     repeat{
       iter <- iter+1
       B <- updateB()
       C <- updateC()
       errold <- err
       err <- fn(B,C)
       if(verbose==TRUE) print(paste("Iter =",iter,"Fn =",err),quote=FALSE)
       if ( abs(err-errold)/errold < eps || iter > itmax ) break
       }  
     B <- T %*% B
     rownames(B) <- rownames(zeta)
     list(B=B,summary=data.frame(Test=err,df=(p-d)*(h1-d),
               p.value=pchisq(err,(p-d)*(h1-d),lower.tail=FALSE),iter=iter))
     }}
     
dr.coordinate.test.pire<-function(object,hypothesis,d=NULL,...){
    gamma <- if (class(hypothesis) == "formula")
        coord.hyp.basis(object, hypothesis)
        else as.matrix(hypothesis)
    gamma <- dr.R(object)%*%gamma  # Rotate to Q-coordinates:
    p <- object$qr$rank
    r <- p-dim(gamma)[2] 
    maxdir <- length(object$result) 
    n.groups <- length(object$group.stats)
    h1 <- 0
    h2 <- NULL
    zeta <- object$zeta
    for (j in 1:n.groups){
      h2[j] <- dim(zeta[[j]])[2]
      h1 <- h1 + h2[j]}  
   if(is.null(d)){
    H <- qr.Q(qr(gamma),complete=TRUE)[,-(1:(p-r)),drop=FALSE]
    n<-object$cases
    Gz <- object$Gz 
    T_H <- 0
    for (j in 1:n.groups){
      m1 <- Gz[[j]] %*% kronecker(diag(rep(1,h2[j])),H)
      m1 <- chol(t(m1) %*% m1)
     T_H <- T_H + n * sum (forwardsolve(t(m1),as.vector(t(H)%*%zeta[[j]]))^2)}
    df <- r*(h1)
    z <- data.frame(Test=T_H,df=df,p.value=pchisq(T_H,df,lower.tail=FALSE))
    z} 
   else {
    F0 <-if(maxdir >= d) object$result[[d]]$summary$Test else
          dr.iteration(object,object$Gz,d=d,...)$summary$Test
    F1 <- dr.joint.test(object,hypothesis,d=d,...)$summary$Test
    data.frame(Test=F1-F0,df=r*d,p.value=pchisq(F1-F0,r*d,lower.tail=FALSE))
    }}
    
Gzcomp.pire <- function(object,numdir,span=NULL){
    Gz <- NULL
    n.groups <- length(object$group.stats)
    pw <- sum(object$group.stats[[1]]$weights)/sum(object$weights)
    Gz[[1]] <- Gzcomp(object$group.stats[[1]],numdir=numdir,span=span)/sqrt(pw)
    if (n.groups > 1){
      for (j in 2:n.groups){
       pw <- sum(object$group.stats[[j]]$weights)/sum(object$weights)
       Gz[[j]] <- 
         Gzcomp(object$group.stats[[j]],numdir=numdir,span=span)/sqrt(pw)}}
    Gz
    }
    
"summary.pire" <- function (object, ...)
{   ans <- object[c("call")]
    result <- object$result
    numdir <- length(result)
    tests <- object$indep.test
    for (d in 1:numdir) {
       tests <- rbind(tests,result[[d]]$summary)}
    rownames(tests) <- paste(0:numdir,"D vs"," > ",0:numdir,"D",sep="")
    ans$method <- object$method
    ans$nslices <- ans.sizes <- NULL
    ans$n <-NULL
    for (stats in object$group.stats){
     ans$n <- c(ans$n,stats$n)
     ans$nslices <- c(ans$nslices,stats$slice.info$nslices)
     ans$sizes <- c(ans$sizes,stats$slice.info$slice.sizes)
     }
    ans$result <- object$result
    for (j in 1:length(ans$result)) {ans$result[[j]]$B <- dr.basis(object,j)}
    ans$weights <- dr.wts(object)
    ans$test <- tests
    class(ans) <- "summary.ire"
    ans
}
    

Try the dr package in your browser

Any scripts or data that you put into this service are public.

dr documentation built on May 2, 2019, 8:49 a.m.