R/free1way.R

Defines functions power.free1way.test ppplot free1way.test.factor free1way.test.numeric free1way.test.formula confint.free1way print.summary.free1way summary.free1way print.free1way logLik.free1way vcov.free1way coef.free1way free1way.test.table free1way.test .free1wayML wcrossprod

# R wcrossprod

wcrossprod <- function(x, A, tol = .Machine$double.eps) {
    storage.mode(x) <- "double"
    .Call(R_wcrossprod, a = as.double(A$a), 
                        b = as.double(A$b),
                        X = x,
                        tol = tol)
}

# ML estimation

.free1wayML <- function(x, link, mu = 0, start = NULL, fix = NULL, 
                   residuals = TRUE, score = TRUE, hessian = TRUE, 
                   tol = sqrt(.Machine$double.eps), ...) {

    stopifnot(is.table(x))
    dx <- dim(x)
    dn <- dimnames(x)
    if (length(dx) == 2L) {
        x <- as.table(array(c(x), dim = dx <- c(dx, 1L)))
        dimnames(x) <- dn <- c(dn, list(A = "A"))
    }
    stopifnot(length(dx) == 3L)
    stopifnot(dx[1L] > 1L)
    K <- dx[2L]
    stopifnot(K > 1L)

    F <- function(q) .p(link, q = q)
    Q <- function(p) .q(link, p = p)
    f <- function(q) .d(link, x = q)
    fp <- function(q) .dd(link, x = q)

    # setup
    
    K <- dim(x)[2L]
    B <- dim(x)[3L]
    xlist <- vector(mode = "list", length = B)
    if (NS <- is.null(start))
        start <- rep.int(0, K - 1)
    lwr <- rep(-Inf, times = K - 1)
    for (b in seq_len(B)) {
        xb <- matrix(x[,,b, drop = TRUE], ncol = K)
        xw <- rowSums(abs(xb)) > tol
        xlist[[b]] <- xb[xw,,drop = FALSE]
        attr(xlist[[b]], "idx") <- xw
        lwr <- c(lwr, -Inf, rep.int(tol, times = sum(xw) - 2L))
        if (NS) {
            ecdf0 <- cumsum(rowSums(xlist[[b]]))
            ecdf0 <- ecdf0[-length(ecdf0)] / ecdf0[length(ecdf0)]
            Qecdf <- Q(ecdf0)
            start <- c(start, Qecdf[1], diff(Qecdf))
        }
    }
    
    # cumsumrev
    
    .rcr <- function(z)
        Reduce('+', z, accumulate = TRUE, right = TRUE)
        ### rev(cumsum(rev(z)))
    
    # negative logLik
    
    .nll <- function(parm, x, mu = 0) {
        # parm to prob
        
        bidx <- seq_len(ncol(x) - 1L)
        beta <- c(0, mu + parm[bidx])
        intercepts <- c(-Inf, cumsum(parm[- bidx]), Inf)
        tmb <- intercepts - matrix(beta, nrow = length(intercepts),  
                                         ncol = ncol(x),
                                         byrow = TRUE)
        Ftmb <- F(tmb)
        prb <- pmax(Ftmb[- 1L, , drop = FALSE] - 
                    Ftmb[- nrow(Ftmb), , drop = FALSE], sqrt(.Machine$double.eps))
        
        return(- sum(x * log(prb)))
    }
    
    # negative score
    
    .nsc <- function(parm, x, mu = 0) {
        # parm to prob
        
        bidx <- seq_len(ncol(x) - 1L)
        beta <- c(0, mu + parm[bidx])
        intercepts <- c(-Inf, cumsum(parm[- bidx]), Inf)
        tmb <- intercepts - matrix(beta, nrow = length(intercepts),  
                                         ncol = ncol(x),
                                         byrow = TRUE)
        Ftmb <- F(tmb)
        prb <- pmax(Ftmb[- 1L, , drop = FALSE] - 
                    Ftmb[- nrow(Ftmb), , drop = FALSE], sqrt(.Machine$double.eps))
        
        # d p ratio
        
        ftmb <- f(tmb)
        zu <- x * ftmb[- 1, , drop = FALSE] / prb
        zl <- x * ftmb[- nrow(ftmb), , drop = FALSE] / prb
        

        ret <- numeric(length(parm))
        ret[bidx] <- colSums(zl)[-1L] -
                     colSums(zu[-nrow(zu),,drop = FALSE])[-1L]
        ret[-bidx] <- Reduce("+", 
                             lapply(1:ncol(x), 
                                 function(j) {
                                     .rcr(zu[-nrow(zu),j]) - 
                                     .rcr(zl[-1,j])
                                 })
                             )
        - ret
    }
    
    # negative score residuals
    
    .nsr <- function(parm, x, mu = 0) {
        # parm to prob
        
        bidx <- seq_len(ncol(x) - 1L)
        beta <- c(0, mu + parm[bidx])
        intercepts <- c(-Inf, cumsum(parm[- bidx]), Inf)
        tmb <- intercepts - matrix(beta, nrow = length(intercepts),  
                                         ncol = ncol(x),
                                         byrow = TRUE)
        Ftmb <- F(tmb)
        prb <- pmax(Ftmb[- 1L, , drop = FALSE] - 
                    Ftmb[- nrow(Ftmb), , drop = FALSE], sqrt(.Machine$double.eps))
        
        # d p ratio
        
        ftmb <- f(tmb)
        zu <- x * ftmb[- 1, , drop = FALSE] / prb
        zl <- x * ftmb[- nrow(ftmb), , drop = FALSE] / prb
        

        rowSums(zu - zl) / rowSums(x)
    }
    
    # Hessian
    
    .hes <- function(parm, x, mu = 0) {
        # parm to prob
        
        bidx <- seq_len(ncol(x) - 1L)
        beta <- c(0, mu + parm[bidx])
        intercepts <- c(-Inf, cumsum(parm[- bidx]), Inf)
        tmb <- intercepts - matrix(beta, nrow = length(intercepts),  
                                         ncol = ncol(x),
                                         byrow = TRUE)
        Ftmb <- F(tmb)
        prb <- pmax(Ftmb[- 1L, , drop = FALSE] - 
                    Ftmb[- nrow(Ftmb), , drop = FALSE], sqrt(.Machine$double.eps))
        

        # Hessian prep
        
        ftmb <- f(tmb)
        fptmb <- fp(tmb)

        dl <- ftmb[- nrow(ftmb), , drop = FALSE]
        du <- ftmb[- 1, , drop = FALSE]
        dpl <- fptmb[- nrow(ftmb), , drop = FALSE]
        dpu <- fptmb[- 1, , drop = FALSE]
        dlm1 <- dl[,-1L, drop = FALSE]
        dum1 <- du[,-1L, drop = FALSE]
        dplm1 <- dpl[,-1L, drop = FALSE]
        dpum1 <- dpu[,-1L, drop = FALSE]
        prbm1 <- prb[,-1L, drop = FALSE]

        i1 <- length(intercepts) - 1L
        i2 <- 1L
        

        # Aoffdiag
        
        Aoffdiag <- -rowSums(x * du * dl / prb^2)[-i2]
        Aoffdiag <- Aoffdiag[-length(Aoffdiag)]
        
        # Adiag
        
        Adiag <- -rowSums((x * dpu / prb)[-i1,,drop = FALSE] - 
                          (x * dpl / prb)[-i2,,drop = FALSE] - 
                          ((x * du^2 / prb^2)[-i1,,drop = FALSE] + 
                           (x * dl^2 / prb^2)[-i2,,drop = FALSE]
                          )
                         )
                          
        
        # X and Z
        
        xm1 <- x[,-1L,drop = FALSE] 
        X <- ((xm1 * dpum1 / prbm1)[-i1,,drop = FALSE] - 
              (xm1 * dplm1 / prbm1)[-i2,,drop = FALSE] - 
              ((xm1 * dum1^2 / prbm1^2)[-i1,,drop = FALSE] - 
               (xm1 * dum1 * dlm1 / prbm1^2)[-i2,,drop = FALSE] -
               (xm1 * dum1 * dlm1 / prbm1^2)[-i1,,drop = FALSE] +
               (xm1 * dlm1^2 / prbm1^2)[-i2,,drop = FALSE]
              )
             )

        Z <- -colSums(xm1 * (dpum1 / prbm1 - 
                             dplm1 / prbm1 -
                             (dum1^2 / prbm1^2 - 
                              2 * dum1 * dlm1 / prbm1^2 +
                              dlm1^2 / prbm1^2
                             )
                            )
                     )
        if (length(Z) > 1L) Z <- diag(Z)
        

        return(Z - wcrossprod(x = X, list(a = Adiag, b = Aoffdiag)))
    }
    
    # stratified negative logLik
    
    .snll <- function(parm, x, mu = 0) {
        # stratum prep
        
        if (is.table(x)) {
            C <- dim(x)[1]
            K <- dim(x)[2]
            B <- dim(x)[3]
            sidx <- gl(B, C - 1)
            x <- lapply(seq_len(B), function(b) x[,,b,drop = TRUE])
        } else {
            C <- sapply(x, nrow)
            K <- unique(sapply(x, ncol))
            stopifnot(length(K) == 1L)
            B <- length(x)
            sidx <- factor(rep(seq_len(B), times = C - 1L), levels = seq_len(B))
        }
        bidx <- seq_len(K - 1L)
        beta <- parm[bidx]
        intercepts <- split(parm[-bidx], sidx)
        
        ret <- 0
        for (b in seq_len(B))
            ret <- ret + .nll(c(beta, intercepts[[b]]), x[[b]], mu = mu)
        return(ret)
    }
    
    # stratified negative score
    
    .snsc <- function(parm, x, mu = 0) {
        # stratum prep
        
        if (is.table(x)) {
            C <- dim(x)[1]
            K <- dim(x)[2]
            B <- dim(x)[3]
            sidx <- gl(B, C - 1)
            x <- lapply(seq_len(B), function(b) x[,,b,drop = TRUE])
        } else {
            C <- sapply(x, nrow)
            K <- unique(sapply(x, ncol))
            stopifnot(length(K) == 1L)
            B <- length(x)
            sidx <- factor(rep(seq_len(B), times = C - 1L), levels = seq_len(B))
        }
        bidx <- seq_len(K - 1L)
        beta <- parm[bidx]
        intercepts <- split(parm[-bidx], sidx)
        
        ret <- numeric(length(bidx))
        for (b in seq_len(B)) {
            nsc <- .nsc(c(beta, intercepts[[b]]), x[[b]], mu = mu)
            ret[bidx] <- ret[bidx] + nsc[bidx]
            ret <- c(ret, nsc[-bidx])
        }
        return(ret)
    }
    
    # stratified Hessian
    
    .shes <- function(parm, x, mu = 0) {
        # stratum prep
        
        if (is.table(x)) {
            C <- dim(x)[1]
            K <- dim(x)[2]
            B <- dim(x)[3]
            sidx <- gl(B, C - 1)
            x <- lapply(seq_len(B), function(b) x[,,b,drop = TRUE])
        } else {
            C <- sapply(x, nrow)
            K <- unique(sapply(x, ncol))
            stopifnot(length(K) == 1L)
            B <- length(x)
            sidx <- factor(rep(seq_len(B), times = C - 1L), levels = seq_len(B))
        }
        bidx <- seq_len(K - 1L)
        beta <- parm[bidx]
        intercepts <- split(parm[-bidx], sidx)
        
        ret <- matrix(0, nrow = length(bidx), ncol = length(bidx))
        for (b in seq_len(B))
            ret <- ret + .hes(c(beta, intercepts[[b]]), x[[b]], mu = mu)
        ret
    }
    
    # stratified negative score residual
    
    .snsr <- function(parm, x, mu = 0) {
        # stratum prep
        
        if (is.table(x)) {
            C <- dim(x)[1]
            K <- dim(x)[2]
            B <- dim(x)[3]
            sidx <- gl(B, C - 1)
            x <- lapply(seq_len(B), function(b) x[,,b,drop = TRUE])
        } else {
            C <- sapply(x, nrow)
            K <- unique(sapply(x, ncol))
            stopifnot(length(K) == 1L)
            B <- length(x)
            sidx <- factor(rep(seq_len(B), times = C - 1L), levels = seq_len(B))
        }
        bidx <- seq_len(K - 1L)
        beta <- parm[bidx]
        intercepts <- split(parm[-bidx], sidx)
        
        ret <- c()
        for (b in seq_len(B)) {
            idx <- attr(x[[b]], "idx")
            sr <- numeric(length(idx))
            sr[idx] <- .nsr(c(beta, intercepts[[b]]), x[[b]], mu = mu)
            ret <- c(ret, sr)
        }
        return(ret)
    }
    
    # profile
    
    .profile <- function(start, fix = seq_len(K - 1)) {
        stopifnot(all(fix %in% seq_len(K - 1)))
        beta <- start[fix]
        ret <- optim(par = start[-fix], fn = function(par) {
                         p <- numeric(length(par) + length(fix))
                         p[fix] <- beta
                         p[-fix] <- par
                         .snll(p, x = xlist, mu = mu)
                     },
                     gr = function(par) {
                         p <- numeric(length(par) + length(fix))
                         p[fix] <- beta
                         p[-fix] <- par
                         .snsc(p, x = xlist, mu = mu)[-fix]
                     },
                     lower = lwr[-fix], method = "L-BFGS-B", 
                     hessian = FALSE, ...)
        p <- numeric(length(start))
        p[fix] <- beta
        p[-fix] <- ret$par
        ret$par <- p
        ret
    }
    
    # optim
    
    if (!length(fix)) {
        ret <- optim(par = start, 
                     fn = function(parm)
                         .snll(parm, x = xlist, mu = mu),
                     gr = function(parm)
                         .snsc(parm, x = xlist, mu = mu),
                     lower = lwr, method = "L-BFGS-B", 
                     hessian = FALSE, ...)
    } else if (length(fix) == length(start)) {
        ret <- list(par = start, 
                    value = .snll(start, x = xlist, mu = mu))
    } else {
        ret <- .profile(start, fix = fix)
    }
     
    # post processing
    
    if (is.null(fix) || (length(fix) == length(start)))
        parm <- seq_len(K - 1)
    else 
        parm <- fix
    if (any(parm >= K)) return(ret)

    ret$coefficients <- ret$par[parm]
    dn2 <- dimnames(x)[2L]
    names(ret$coefficients) <- cnames <- dn2[[1L]][1L + parm]

    if (score)
        ret$negscore <- .snsc(ret$par, x = xlist, mu = mu)[parm]
    if (hessian) {
        ret$hessian <- .shes(ret$par, x = xlist, mu = mu)
        if (length(parm) != nrow(ret$hessian))
           ret$hessian <- solve(ret$vcov <- solve(ret$hessian)[parm,parm])
        ret$vcov <- solve(ret$hessian)
        rownames(ret$vcov) <- colnames(ret$vcov) <- rownames(ret$hessian) <-
            colnames(ret$hessian) <-  cnames
    }
    if (residuals)
        ret$residuals <- .snsr(ret$par, x = xlist, mu = mu)

    ret$profile <- function(start, fix)
        .free1wayML(x, link = link, mu = mu, start = start, fix = fix, tol = tol, ...) 

    ret$table <- x
    ret$mu <- mu
    names(ret$mu) <- link$parm
    

    class(ret) <- "free1wayML"
    ret
}

# free1way

free1way.test <- function(y, ...)
    UseMethod("free1way.test")
free1way.test.table <- function(y, link = c("logit", "probit", "cloglog", "loglog"), mu = 0, B = 0, ...)
{

    cl <- match.call()

    d <- dim(y)
    dn <- dimnames(y)
    DNAME <- NULL
    if (!is.null(dn)) {
        DNAME <- paste(names(dn)[1], "by", names(dn)[2], paste0("(", paste0(dn[2], collapse = ", "), ")"))
        if (length(dn) == 3L)
            DNAME <- paste(DNAME, "\n\t stratified by", names(dn)[3])
    }

    if (!inherits(link, "linkfun")) {
        link <- match.arg(link)
        link <- do.call(link, list())
    }

    ret <- .free1wayML(y, link = link, mu = mu, ...)
    ret$link <- link
    ret$data.name <- DNAME
    ret$call <- cl

    alias <- link$alias
    if (length(link$alias) == 2L) alias <- alias[1L + (d[2] > 2L)]
    ret$method <- paste(ifelse(length(d) == 3L, "Stratified", ""), 
                        paste0(d[2], "-sample"), alias, 
                        "test against", link$model, "alternatives")

    cf <- ret$par
    cf[idx <- seq_len(d[2L] - 1L)] <- 0
    pr <- ret$profile(cf, idx)
    if (d[2L] == 2L)
        res <- pr$residuals / sqrt(c(pr$hessian))
    else
        res <- pr$residuals

    # Strasser Weber
    
    .SW <- function(res, xt) {

        if (length(dim(xt)) == 3L) {
            res <- matrix(res, nrow = dim(xt)[1L], ncol = dim(xt)[3])
            STAT <-  Exp <- Cov <- 0
            for (b in seq_len(dim(xt)[3L])) {
                sw <- .SW(res[,b, drop = TRUE], xt[,,b, drop = TRUE])
                STAT <- STAT + sw$Statistic
                Exp <- Exp + sw$Expectation
                Cov <- Cov + sw$Covariance
            }
            return(list(Statistic = STAT, Expectation = as.vector(Exp),
                        Covariance = Cov))
        }

        Y <- matrix(res, ncol = 1, nrow = length(xt))
        weights <- c(xt)
        x <- gl(ncol(xt), nrow(xt))
        X <- model.matrix(~ x, data = data.frame(x = x))[,-1L,drop = FALSE]

        w. <- sum(weights)
        wX <- weights * X
        wY <- weights * Y
        ExpX <- colSums(wX)
        ExpY <- colSums(wY) / w.
        CovX <- crossprod(X, wX)
        Yc <- t(t(Y) - ExpY)
        CovY <- crossprod(Yc, weights * Yc) / w.
        Exp <- kronecker(ExpY, ExpX)
        Cov <- w. / (w. - 1) * kronecker(CovY, CovX) -
               1 / (w. - 1) * kronecker(CovY, tcrossprod(ExpX))
        STAT <- crossprod(X, wY)
        list(Statistic = STAT, Expectation = as.vector(Exp),
             Covariance = Cov)
    }
    
    # resampling
    
    .resample <- function(res, xt, B = 10000) {

        if (length(dim(xt)) == 2L)
            xt <- as.table(array(xt, dim = c(dim(xt), 1)))

        res <- matrix(res, nrow = dim(xt)[1L], ncol = dim(xt)[3L])
        stat <- 0
        ret <- .SW(res, xt)
        if (dim(xt)[2L] == 2L) {
            ret$testStat <- c((ret$Statistic - ret$Expectation) / sqrt(c(ret$Covariance)))
        } else {
            ES <- t(ret$Statistic - ret$Expectation)
            ret$testStat <- sum(ES %*% solve(ret$Covariance) * ES)
        }
        ret$DF <- dim(xt)[2L] - 1L

        if (B) {
            for (j in 1:dim(xt)[3L]) {
               rt <- r2dtable(B, r = rowSums(xt[,,j]), c = colSums(xt[,,j]))
               stat <- stat + sapply(rt, function(x) colSums(x[,-1L, drop = FALSE] * res[,j]))
            }
            if (dim(xt)[2L] == 2L) {
                 ret$permStat <- (stat - ret$Expectation) / sqrt(c(ret$Covariance))
            } else {
                ES <- t(matrix(stat, ncol = B) - ret$Expectation)
                ret$permStat <- rowSums(ES %*% solve(ret$Covariance) * ES)
            }
        }
        ret
    }
    

    ret$perm <- .resample(res, y, B = B)

    class(ret) <- "free1way"
    return(ret)
}

# free1way methods

coef.free1way <- function(object, what = c("shift", "PI", "AUC", "OVL"), ...)
{
    what <- match.arg(what)
    cf <- object$coefficients
    return(switch(what, "shift" = cf,
                        "PI" = object$link$parm2PI(cf),
                        "AUC" = object$link$parm2PI(cf),        ### same as PI
                        "OVL" = object$link$parm2OVL(cf)))
}
vcov.free1way <- function(object, ...)
    object$vcov
logLik.free1way <- function(object, ...)
    -object$value

# free1way summary

print.free1way <- function(x, test = c("Permutation", "Wald", "LRT", "Rao"), 
                           alternative = c("two.sided", "less", "greater"), 
                           tol = .Machine$double.eps, ...)
{

    test <- match.arg(test)
    alternative <- match.arg(alternative)

    ### global
    cf <- coef(x)
    if ((length(cf) > 1L || test == "LRT") && alternative != "two.sided") 
        stop("Cannot compute one-sided p-values")

    DF <- NULL
    parm <- seq_along(cf)
    value <- 0

    # statistics
    
    if (test == "Wald") {
        # Wald statistic
        
        if (alternative == "two.sided") {
            STATISTIC <- c("Wald chi-squared" = c(crossprod(cf, x$hessian %*% cf)))
            DF <- c("df" = length(parm))
            PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
        } else {
            STATISTIC <- c("Wald Z" = c(cf * sqrt(c(x$hessian))))
            PVAL <- pnorm(STATISTIC, lower.tail = alternative == "less")
        }
        
    } else if (test == "LRT") {
        # LRT
        
        par <- x$par
        par[parm] <- value
        unll <- x$value ### neg logLik
        rnll <- x$profile(par, parm)$value ### neg logLik
        STATISTIC <- c("logLR chi-squared" = - 2 * (unll - rnll))
        DF <- c("df" = length(parm))
        PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
        
    } else if (test == "Rao") {
        # Rao
        
        par <- x$par
        par[parm] <- value
        ret <- x$profile(par, parm)
        if (alternative == "two.sided") {
            STATISTIC <- c("Rao chi-squared" = c(crossprod(ret$negscore, ret$vcov %*% ret$negscore)))
            DF <- c("df" = length(parm))
            PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
        } else {
            STATISTIC <- c("Rao Z" = -ret$negscore * sqrt(c(ret$vcov)))
            PVAL <- pnorm(STATISTIC, lower.tail = alternative == "less")
        }
        
    } else if (test == "Permutation") {
        # Permutation
        
        par <- x$par
        par[parm] <- value
        ret <- x$profile(par, parm)
        sc <- -ret$negscore
        if (length(cf) == 1L)
           sc <- sc / sqrt(c(ret$hessian))
        Esc <- sc - x$perm$Expectation
        if (alternative == "two.sided" && length(cf) > 1L) {
            STATISTIC <- c("Perm chi-squared" = sum(Esc %*% solve(x$perm$Covariance) * Esc))
            ps <- x$perm$permStat
            if (!is.null(x$perm$permStat))
                PVAL <- mean(ps > STATISTIC + tol)
            else {
                DF <- c("df" = x$perm$DF)
                PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
            }
        } else {
            STATISTIC <- c("Perm Z" = Esc / sqrt(c(x$perm$Covariance)))
            if (!is.null(x$perm$permStat)) {
                if (alternative == "two.sided")
                    PVAL <- mean(abs(x$perm$permStat) < abs(STATISTIC) - tol)
                else if (alternative == "less")
                    PVAL <- mean(x$perm$permStat < STATISTIC - tol)
                else
                    PVAL <- mean(x$perm$permStat > STATISTIC + tol)
            } else {
                if (alternative == "two.sided")
                    PVAL <- pchisq(STATISTIC^2, df = 1, lower.tail = FALSE)
                else
                    PVAL <- pnorm(STATISTIC, lower.tail = alternative == "less")
            }
        }
        
    }
    

    RVAL <- list(statistic = STATISTIC, parameter = DF, p.value = PVAL, 
        null.value = ret$mu, alternative = alternative, method = x$method, 
        data.name = x$data.name)
    class(RVAL) <- "htest"
    print(RVAL)
    invisible(RVAL)

}
summary.free1way <- function(object, alternative = c("two.sided", "less", "greater"), ...)
{

    alternative <- match.arg(alternative)

    ESTIMATE <- coef(object)
    SE <- sqrt(diag(vcov(object)))
    STATISTIC <- unname(ESTIMATE / SE)
    if (alternative == "less") {
        PVAL <- pnorm(STATISTIC)
    } else if (alternative == "greater") {
        PVAL <- pnorm(STATISTIC, lower.tail = FALSE)
    } else {
        PVAL <- 2 * pnorm(-abs(STATISTIC))
    }
    cfmat <- cbind(ESTIMATE, SE, STATISTIC, PVAL)
    colnames(cfmat) <- c(object$link$parm, "Std. Error", "z value",
                         switch(alternative, "two.sided" = "P(>|z|)",
                                             "less" = "P(<z)",
                                             "greater" = "P(>z)"))
    ret <- list(call = object$call, coefficients = cfmat)
    class(ret) <- "summary.free1way"
    return(ret)
}
print.summary.free1way <- function(x, ...) {
    cat("\nCall:\n", paste(deparse(x$call), sep = "\n", collapse = "\n"), 
        "\n\n", sep = "")
    cat("Coefficients:\n")
    printCoefmat(x$coefficients)
}

# free1way confint

confint.free1way <- function(object, parm,
    level = .95, test = c("Permutation", "Wald", "LRT", "Rao"), 
    what = c("shift", "PI", "AUC", "OVL"), ...)
{

    test <- match.arg(test)
    conf.level <- 1 - (1 - level) / 2

    cf <- coef(object)
    if (missing(parm)) 
        parm <- seq_along(cf)

    ESTIMATE <- cf[parm]
    qSE <- qnorm(conf.level) * sqrt(diag(vcov(object)))[parm]
    CINT <- cbind(ESTIMATE - qSE, ESTIMATE + qSE)
    colnames(CINT) <- paste0(100 * c(1 - conf.level, conf.level), "%")
    if (test == "Wald")
        return(CINT)

    CINT[] <- cbind(ESTIMATE - qSE * 5, ESTIMATE + qSE * 5)

    sfun <- function(value, parm, quantile) {
        x <- object
        alternative <- "two.sided"
        tol <- .Machine$double.eps
        # statistics
        
        if (test == "Wald") {
            # Wald statistic
            
            if (alternative == "two.sided") {
                STATISTIC <- c("Wald chi-squared" = c(crossprod(cf, x$hessian %*% cf)))
                DF <- c("df" = length(parm))
                PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
            } else {
                STATISTIC <- c("Wald Z" = c(cf * sqrt(c(x$hessian))))
                PVAL <- pnorm(STATISTIC, lower.tail = alternative == "less")
            }
            
        } else if (test == "LRT") {
            # LRT
            
            par <- x$par
            par[parm] <- value
            unll <- x$value ### neg logLik
            rnll <- x$profile(par, parm)$value ### neg logLik
            STATISTIC <- c("logLR chi-squared" = - 2 * (unll - rnll))
            DF <- c("df" = length(parm))
            PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
            
        } else if (test == "Rao") {
            # Rao
            
            par <- x$par
            par[parm] <- value
            ret <- x$profile(par, parm)
            if (alternative == "two.sided") {
                STATISTIC <- c("Rao chi-squared" = c(crossprod(ret$negscore, ret$vcov %*% ret$negscore)))
                DF <- c("df" = length(parm))
                PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
            } else {
                STATISTIC <- c("Rao Z" = -ret$negscore * sqrt(c(ret$vcov)))
                PVAL <- pnorm(STATISTIC, lower.tail = alternative == "less")
            }
            
        } else if (test == "Permutation") {
            # Permutation
            
            par <- x$par
            par[parm] <- value
            ret <- x$profile(par, parm)
            sc <- -ret$negscore
            if (length(cf) == 1L)
               sc <- sc / sqrt(c(ret$hessian))
            Esc <- sc - x$perm$Expectation
            if (alternative == "two.sided" && length(cf) > 1L) {
                STATISTIC <- c("Perm chi-squared" = sum(Esc %*% solve(x$perm$Covariance) * Esc))
                ps <- x$perm$permStat
                if (!is.null(x$perm$permStat))
                    PVAL <- mean(ps > STATISTIC + tol)
                else {
                    DF <- c("df" = x$perm$DF)
                    PVAL <- pchisq(STATISTIC, df = DF, lower.tail = FALSE)
                }
            } else {
                STATISTIC <- c("Perm Z" = Esc / sqrt(c(x$perm$Covariance)))
                if (!is.null(x$perm$permStat)) {
                    if (alternative == "two.sided")
                        PVAL <- mean(abs(x$perm$permStat) < abs(STATISTIC) - tol)
                    else if (alternative == "less")
                        PVAL <- mean(x$perm$permStat < STATISTIC - tol)
                    else
                        PVAL <- mean(x$perm$permStat > STATISTIC + tol)
                } else {
                    if (alternative == "two.sided")
                        PVAL <- pchisq(STATISTIC^2, df = 1, lower.tail = FALSE)
                    else
                        PVAL <- pnorm(STATISTIC, lower.tail = alternative == "less")
                }
            }
            
        }
        
        return(STATISTIC - quantile)
    }

    if (test == "Permutation") {
        stopifnot(length(cf) == 1L)
        if (is.null(object$perm$permStat)) {
            qu <- qnorm(conf.level) * c(-1, 1)
        } else {
            qu <- quantile(object$perm$permStat, probs = c(1 - conf.level, conf.level))
            att.level <- mean(object$perm$permStat > qu[1] & object$perm$permStat < qu[2])
            attr(CINT, "Attained level") <- att.level
        }
    } else {
        qu <- rep.int(qchisq(level, df = 1), 2) ### always two.sided
    }

    for (p in parm) {
        CINT[p, 1] <- uniroot(sfun, interval = c(CINT[p,1], cf[p]), parm = p, quantile = qu[2])$root
        CINT[p, 2] <- uniroot(sfun, interval = c(cf[p], CINT[p, 2]), parm = p, quantile = qu[1])$root
    }

    what <- match.arg(what)
    CINT <- switch(what, "shift" = CINT,
                         "PI" = object$link$parm2PI(CINT),
                         "AUC" = object$link$parm2PI(CINT), ### same as PI 
                         "OVL" = object$link$parm2OVL(CINT))
    return(CINT)
}
# power.free1way.test <- function()

# free1way interfaces

free1way.test.formula <- function(formula, data, weights, subset, na.action = na.pass, ...)
{

    cl <- match.call()

    if(missing(formula) || (length(formula) != 3L))
        stop("'formula' missing or incorrect")

    strata <- function(object) object
    formula <- terms(formula, specials = "strata")

    stratum <- attr(formula, "specials")$strata
    if (is.null(stratum)) stratum <- 0L
    
    if (length(attr(formula, "term.labels")) > 1L + stratum)
        stop("'formula' missing or incorrect")
    group <- attr(formula, "term.labels") 
    if (stratum) group <- group[-(stratum - 1L)]

    m <- match.call(expand.dots = FALSE)
    if (is.matrix(eval(m$data, parent.frame())))
        m$data <- as.data.frame(data)
    ## need stats:: for non-standard evaluation
    m[[1L]] <- quote(stats::model.frame)
    m$... <- NULL
    mf <- eval(m, parent.frame())
    response <- attr(attr(mf, "terms"), "response")
    DNAME <- paste(c(names(mf)[response], group), collapse = " by ") # works in all cases
    w <- as.vector(model.weights(mf))
    y <- mf[[response]]
    lev <- sort(unique(mf[[group]]))
    g <- factor(mf[[group]], levels = lev, labels = paste0(group, lev))
    DNAME <- paste(DNAME, paste0("(", paste0(lev, collapse = ", "), ")"))
    if (nlevels(g) < 2L)
        stop("grouping factor must have at least 2 levels")
    if (stratum) {
        st <- factor(mf[[stratum]], levels = )
        if (nlevels(st) < 2L)
            stop("at least two strata must be present")
        RVAL <- free1way.test(y = y, x = g, z = st, weights = w, ...)
        DNAME <- paste(DNAME, paste("\n\t stratified by", names(mf)[stratum]))
    } else {
        ## Call the default method.
        RVAL <- free1way.test(y = y, x = g, weights = w, ...)
    }
    RVAL$data.name <- DNAME
    RVAL$call <- cl
    RVAL
}
 
free1way.test.numeric <- function(y, x, z = NULL, weights = NULL, nbins = 0, ...) {

    cl <- match.call()
    DNAME <- paste(deparse1(substitute(y)), "by",
                   deparse1(substitute(x)))
    DNAME <- paste(DNAME, paste0("(", paste0(levels(x), collapse = ", "), ")"))

    if (!is.null(z))
        DNAME <- paste(DNAME, "\n\t stratified by", deparse1(substitute(z)))

    uy <- unique(y)
    if (nbins && nbins < length(uy)) {
        nbins <- ceiling(nbins)
        breaks <- c(-Inf, quantile(y, prob = seq_len(nbins) / (nbins + 1L)), Inf)
    } else {
        breaks <- c(-Inf, sort(uy), Inf)
    }
    r <- cut(y, breaks = breaks)[, drop = TRUE]
    RVAL <- free1way.test(y = r, x = x, z = z, weights = weights, ...)
    RVAL$data.name <- DNAME
    RVAL$call <- cl
    RVAL
}

free1way.test.factor <- function(y, x, z = NULL, weights = NULL, ...) {

    cl <- match.call()
    DNAME <- paste(deparse1(substitute(y)), "by",
                   deparse1(substitute(x)))
    DNAME <- paste(DNAME, paste0("(", paste0(levels(x), collapse = ", "), ")"))
    if (!is.null(z))
        DNAME <- paste(DNAME, "\n\t stratified by", deparse1(substitute(z)))

    stopifnot(is.factor(x))
    d <- data.frame(y = y, x = x, w = 1)
    if (!is.null(weights)) d$w <- weights
    if (!is.null(z)) {
        d$z <- z
        tab <- xtabs(w ~ y + x + z, data = d)
    } else {
        tab <- xtabs(w ~ y + x, data = d)
    }
    RVAL <- free1way.test(tab, ...)
    RVAL$data.name <- DNAME
    RVAL$call <- cl
    RVAL
}

# ppplot

ppplot <- function(x, y, plot.it = TRUE,
            xlab = deparse1(substitute(x)),
            ylab = deparse1(substitute(y)), 
            interpolate = FALSE, ...,
            conf.level = NULL, conf.args = list(type = "Wald", col = NA, border = NULL)) {

    force(xlab)
    force(ylab)
    if (xlab == ylab) {
        xlab <- paste0("x = ", xlab)
        ylab <- paste0("y = ", ylab)
    }

    ex <- ecdf(x)
    if (interpolate) {
        vals <- sort(unique(x))
        ex <- splinefun(vals, ex(vals), method = "hyman")
    }
    sy <- sort(unique(y))
    py <- ecdf(y)(sy)
    px <- ex(sy)
    ret <- list(x = px, y = py)
    if (!plot.it)
        return(ret)

    plot(px, py, xlim = c(0, 1), ylim = c(0, 1), 
         xlab = xlab, ylab = ylab, type = "n", ...)

    if (!is.null(conf.level)) {
        prb <- seq_len(1000) / 1001
        res <- c(x, y)
        grp <- gl(2, 1, labels = c(xlab, ylab))
        grp <- grp[rep(1:2, c(length(x), length(y)))]
        args <- conf.args
        args$y <- res
        args$x <- grp
        args$border <- args$col <- args$type <- NULL
        f1w <- do.call("free1way.test", args)

        ci <- confint(f1w, level = conf.level, type = args$type)
        lwr <- .p(f1w$link, .q(f1w$link, prb) - ci[1,1])
        upr <- .p(f1w$link, .q(f1w$link, prb) - ci[1,2])
        x <- c(prb, rev(prb))
        y <- c(lwr, rev(upr))
        xn <- c(x[1L], rep(x[-1L], each = 2))
        yn <- c(rep(y[-length(y)], each = 2), y[length(y)])
        polygon(x = xn, y = yn, col = conf.args$col, border = conf.args$border)
        lines(prb, .p(f1w$link, .q(f1w$link, prb) - coef(f1w)))
    }
    points(px, py, ...)
    return(invisible(ret)) 
}

# power

power.free1way.test <- function(n = NULL, prob = NULL, alloc_ratio = 1, strata_ratio = 1, delta = NULL, sig.level = .05, power = NULL,
                                link = c("logit", "probit", "cloglog", "loglog"),
                                alternative = c("two.sided", "less", "greater"), nHess = 100,
norm = TRUE,...) 
{

    if (sum(vapply(list(n, delta, power, sig.level), is.null, 
        NA)) != 1) 
        stop("exactly one of 'n', 'delta', 'power', and 'sig.level' must be NULL")
    stats:::assert_NULL_or_prob(sig.level)
    stats:::assert_NULL_or_prob(power)

    tol <- sqrt(.Machine$double.eps)

    if (is.null(n)) 
        n <- ceiling(uniroot(function(n)
             power.free1way.test(n = n, prob = prob, alloc_ratio = alloc_ratio, strata_ratio = strata_ratio, 
                                 delta = delta, 
                                 sig.level = sig.level, link = link, alternative = alternative, 
                                 nHess = nHess, ...)$power
             - power, interval = c(5, 1e+03), tol = tol, extendInt = "upX")$root)
    else if (is.null(delta)) {
        ### 2-sample only
        stopifnot(length(alloc_ratio) == 1L)
        delta <- uniroot(function(delta) 
            power.free1way.test(n = n, prob = prob, alloc_ratio = alloc_ratio, strata_ratio = strata_ratio,delta = delta, 
                                sig.level = sig.level, link = link, alternative = alternative, 
                                nHess = nHess, ...)$power
            - power, 
    ### <TH> interval depending on alternative, symmetry? </TH>
            interval = c(0, 10), tol = tol, extendInt = "upX")$root
        }
    else if (is.null(sig.level)) 
        sig.level <- uniroot(function(sig.level) 
            power.free1way.test(n = n, prob = prob, alloc_ratio = alloc_ratio, strata_ratio = strata_ratio, delta = delta, 
                                sig.level = sig.level, link = link, alternative = alternative, 
                                ...)$power
            - power, interval = c(1e-10, 1 - 1e-10), tol = tol, extendInt = "yes")$root
    else if (is.null(power)) {

        if (!inherits(link, "linkfun")) {
            link <- match.arg(link)
            link <- do.call(link, list())
        }

        ### if not given, assume continuous distribution
        if (is.null(prob)) prob <- rep(1 / n, n)
        ### matrix means control distributions in different strata
        if (!is.matrix(prob))
            prob <- matrix(prob, nrow = NROW(prob))
        C <- nrow(prob)
        K <- length(delta) + 1L
        B <- ncol(prob)
        if (is.null(colnames(prob))) 
            colnames(prob) <- paste0("stratum", seq_len(B))
        if (is.null(names(delta))) 
            names(delta) <- paste0("group", LETTERS[seq_len(K)[-1]])
        p0 <- apply(prob, 2, cumsum)
        h0 <- .q(link, p0)
        if (length(alloc_ratio) == 1L) 
            alloc_ratio <- rep_len(alloc_ratio, K - 1)
        stopifnot(length(alloc_ratio) == K - 1)
        if (length(strata_ratio) == 1L) 
            strata_ratio <- rep_len(strata_ratio, B - 1)
        stopifnot(length(strata_ratio) == B - 1)
        ### sample size per group (columns) and stratum (rows)
        N <- n * matrix(c(1, alloc_ratio), nrow = B, ncol = K, byrow = TRUE) * 
                 matrix(c(1, strata_ratio), nrow = B, ncol = K)
        rownames(N) <- colnames(prob)
        colnames(N) <- c("Control", names(delta))
        he <- 0

        for (i in 1:nHess) {
            x <- as.table(array(0, dim = c(C, K, B)))
            parm <- delta
            for (b in seq_len(B)) {
                h1 <- h0[,b] - matrix(delta, nrow = C, ncol = K - 1, byrow = TRUE)
                p1 <- .p(link, h1)
                p <- cbind(p0[,b], p1)
                x[,,b] <- sapply(seq_len(K), function(k) 
                                 rmultinom(1, size = N[b, k], prob = c(p[1,k], diff(p[,k]))))
                rs <- rowSums(x[,,b]) > 0
                h <- h0[rs,b]
                theta <- c(h[1], diff(h[-length(h)]))
                parm <- c(parm, theta)
            }
            ### evaluate observed hessian for true parameters parm and x data
            he <- he + .free1wayML(x, link = link, start = parm, fix = seq_along(parm))$hessian
        }
        ### estimate expected Fisher information
        he <- he / nHess

        alternative <- match.arg(alternative)
        if ((length(delta) == 1L) && norm) {
            se <- 1 / sqrt(c(he))
            power  <- switch(alternative, 
                "two.sided" = pnorm(qnorm(sig.level / 2) + delta / se) + 
                              pnorm(qnorm(sig.level / 2) - delta / se),
                "less" = pnorm(qnorm(sig.level) - delta / se),
                "greater" = pnorm(qnorm(sig.level) + delta / se)
            )
        } else {
            stopifnot(alternative == "two.sided")
            ncp <- sum((chol(he) %*% delta)^2)
            qsig <- qchisq(sig.level, df = K - 1L, lower.tail = FALSE)
            power <- pchisq(qsig, df = K - 1L, ncp = ncp, lower.tail = FALSE)
        }
    }
    list(power = power, n = n, delta = delta, sig.level = sig.level, N = N)
}

Try the free1way package in your browser

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

free1way documentation built on June 17, 2025, 3:01 p.m.