|
| 1 | + |
| 2 | +support_spans <- function(m, n, k) { |
| 3 | + return(mapply( |
| 4 | + function(lo, hi) { lo:hi }, |
| 5 | + lo = pmax(0L, k - n), hi = pmin(k, m), |
| 6 | + SIMPLIFY = FALSE |
| 7 | + )) |
| 8 | +} |
| 9 | + |
| 10 | +logdc <- function(m, n, k, support) { |
| 11 | + return(mapply( |
| 12 | + function(support, m, n, k) dhyper(support, m, n, k, log = TRUE), |
| 13 | + support = support, m = m, n = n, k = k, SIMPLIFY = FALSE |
| 14 | + )) |
| 15 | +} |
| 16 | + |
| 17 | +dnhyper <- function(ncp, logdc, support) { |
| 18 | + return(mapply( |
| 19 | + function(ncp, logdc, support) { |
| 20 | + d <- logdc + log(ncp) * support |
| 21 | + d <- exp(d - max(d)) |
| 22 | + return(d/sum(d)) |
| 23 | + }, |
| 24 | + ncp = ncp, logdc = logdc, support = support, SIMPLIFY = FALSE |
| 25 | + )) |
| 26 | +} |
| 27 | + |
| 28 | +mnhyper <- function(ncp, lo, hi, logdc, support) { |
| 29 | + lims <- which(ncp == 0 | is.infinite(ncp)) |
| 30 | + if (ncp == 0) { |
| 31 | + return(lo) |
| 32 | + } else if (ncp == Inf) { |
| 33 | + return(hi) |
| 34 | + } else { |
| 35 | + return(sum(support * dnhyper(ncp, logdc, support))) |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +pnhyper <- function( |
| 40 | + q, ncp = 1, upper.tail = FALSE, m, n, k |
| 41 | +) { |
| 42 | + if (ncp == 1) { |
| 43 | + phyper(x - upper.tail, m, n, k, lower.tail = !upper.tail) |
| 44 | + } else if (ncp == 0) { |
| 45 | + return(as.numeric(if (upper.tail) q <= lo else q >= lo)) |
| 46 | + } else if (ncp == Inf) { |
| 47 | + return(as.numeric(if (upper.tail) q <= hi else q >= hi)) |
| 48 | + } else { |
| 49 | + return(sum(dnhyper(ncp)[if (upper.tail) support >= q else support <= q])) |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +#' @title Vectorized fisher.test |
| 54 | +#' |
| 55 | +#' @description |
| 56 | +#' Provides a vectorized version of [stats::fisher.test()] for evaluating a |
| 57 | +#' series of 2x2 contingency tables. |
| 58 | +#' |
| 59 | +#' @inheritParams stats::fisher.test |
| 60 | +#' |
| 61 | +#' @param a `x[1, 1]` in the matrix version of [stats::fisher.test()] |
| 62 | +#' @param b `x[1, 2]` in the matrix version of [stats::fisher.test()] |
| 63 | +#' @param c `x[2, 1]` in the matrix version of [stats::fisher.test()] |
| 64 | +#' @param d `x[2, 2]` in the matrix version of [stats::fisher.test()] |
| 65 | +#' |
| 66 | +#' @details |
| 67 | +#' N.b. `vfisher.test` does less input validation than [stats::fisher.test()]. |
| 68 | +#' |
| 69 | +#' Because `vfisher.test` does not support anything other than 2x2 tests, the |
| 70 | +#' arguments to `fisher.test` associated with other tests (e.g. `hybrid`) do not |
| 71 | +#' appear. |
| 72 | +#' |
| 73 | +#' @return a data.frame, columns `a`, `b`, `c`, `d`, `or`, `estimate`, |
| 74 | +#' `p.value`. If `conf.int == TRUE` (the default), will also include column(s) |
| 75 | +#' for the confidence interval (two if `alternative == "two.sided"` (default) |
| 76 | +#' or one otherwise.). The column names are `ci.lo` and/or `ci.hi`. |
| 77 | +#' |
| 78 | +#' @export |
| 79 | +vfisher.test <- function( |
| 80 | + a, b, c, d, conf.int = TRUE, conf.level = 0.95, or = rep(1, length(a)), |
| 81 | + alternative = c("two.sided", "less", "greater") |
| 82 | +) { |
| 83 | + |
| 84 | + if ( |
| 85 | + length(a) != length(b) || length(a) != length(c) || length(a) != length(d) |
| 86 | + ) stop("all a, b, c, d must be same length.") |
| 87 | + |
| 88 | + storage.mode(a) <- "integer" |
| 89 | + storage.mode(b) <- "integer" |
| 90 | + storage.mode(c) <- "integer" |
| 91 | + storage.mode(d) <- "integer" |
| 92 | + |
| 93 | + if (any(c(a, b, c, d) < 0) || anyNA(c(a, b, c, d))) |
| 94 | + stop("all entries of 'a', 'b', 'c', 'd' must be nonnegative and finite") |
| 95 | + |
| 96 | + con <- list(mult = 30) |
| 97 | + con[names(control)] <- control |
| 98 | + if ((mult <- as.integer(con$mult)) < 2) |
| 99 | + stop("'mult' must be integer >= 2, typically = 30") |
| 100 | + |
| 101 | + alternative <- match.arg(alternative) |
| 102 | + |
| 103 | + if (!((length(conf.level) == 1L) && is.finite(conf.level) && |
| 104 | + (conf.level > 0) && (conf.level < 1))) |
| 105 | + stop("'conf.level' must be a single number between 0 and 1") |
| 106 | + |
| 107 | + if (any(is.na(or) | or < 0)) |
| 108 | + stop("'or' must be a single number between 0 and Inf") |
| 109 | + |
| 110 | + # matrix = |
| 111 | + # a, b |
| 112 | + # c, d |
| 113 | + |
| 114 | + m <- a + c |
| 115 | + n <- b + d |
| 116 | + k <- a + b |
| 117 | + x <- a |
| 118 | + |
| 119 | + lo <- pmax(0L, k - n) |
| 120 | + hi <- pmin(k, m) |
| 121 | + |
| 122 | + core <- function(m, n, k, x, lo, hi) { |
| 123 | + |
| 124 | + } |
| 125 | + |
| 126 | + mapply(function( |
| 127 | + lo, hi |
| 128 | + ) { |
| 129 | + support <- lo:hi |
| 130 | + logdc <- dhyper(support, m, n, k, log = TRUE) |
| 131 | + dnhyper <- function(ncp) { |
| 132 | + d <- logdc + log(ncp) * support |
| 133 | + d <- exp(d - max(d)) |
| 134 | + d/sum(d) |
| 135 | + } |
| 136 | + mnhyper <- function(ncp) { |
| 137 | + if (ncp == 0) return(lo) |
| 138 | + if (ncp == Inf) return(hi) |
| 139 | + sum(support * dnhyper(ncp)) |
| 140 | + } |
| 141 | + pnhyper <- function(q, ncp = 1, upper.tail = FALSE) { |
| 142 | + if (ncp == 1) { |
| 143 | + phyper(x - upper.tail, m, n, k, lower.tail = !upper.tail) |
| 144 | + } |
| 145 | + if (ncp == 0) { |
| 146 | + return(as.numeric(if (upper.tail) q <= lo else q >= lo)) |
| 147 | + } |
| 148 | + if (ncp == Inf) { |
| 149 | + return(as.numeric(if (upper.tail) q <= hi else q >= hi)) |
| 150 | + } |
| 151 | + sum(dnhyper(ncp)[if (upper.tail) support >= q else support <= q]) |
| 152 | + } |
| 153 | + p.value <- switch(alternative, |
| 154 | + less = pnhyper(x, or), greater = pnhyper(x, or, upper.tail = TRUE), |
| 155 | + two.sided = { |
| 156 | + if (or == 0) as.numeric(x == lo) else if (or == Inf) as.numeric(x == hi) else { |
| 157 | + relErr <- 1 + 10^(-7) |
| 158 | + d <- dnhyper(or) |
| 159 | + sum(d[d <= d[x - lo + 1] * relErr]) |
| 160 | + } |
| 161 | + } |
| 162 | + ) |
| 163 | + mle <- if (x == lo) { 0 } else if (x == hi) { Inf } else { |
| 164 | + mu <- mnhyper(1) |
| 165 | + if (mu > x) |
| 166 | + uniroot(function(t) mnhyper(t) - x, c(0, 1))$root |
| 167 | + else if (mu < x) |
| 168 | + 1/uniroot(function(t) mnhyper(1/t) - x, c(.Machine$double.eps, 1))$root |
| 169 | + else 1 |
| 170 | + } |
| 171 | + list(estimate = mle, p.value = p.value) |
| 172 | + }, lo = lo, hi = hi) |
| 173 | + |
| 174 | + |
| 175 | + mapply( |
| 176 | + \(lo, hi) { |
| 177 | + support <- lo:hi |
| 178 | + logdc <- dhyper(support, m, n, k, log = TRUE) |
| 179 | + dnhyper <- function(ncp) { |
| 180 | + d <- logdc + log(ncp) * support |
| 181 | + d <- exp(d - max(d)) |
| 182 | + d/sum(d) |
| 183 | + } |
| 184 | + mnhyper <- function(ncp) { |
| 185 | + if (ncp == 0) |
| 186 | + return(lo) |
| 187 | + if (ncp == Inf) |
| 188 | + return(hi) |
| 189 | + sum(support * dnhyper(ncp)) |
| 190 | + } |
| 191 | + pnhyper <- function(q, ncp = 1, upper.tail = FALSE) { |
| 192 | + if (ncp == 1) { |
| 193 | + return(if (upper.tail) phyper(x - 1, m, n, k, |
| 194 | + lower.tail = FALSE) else phyper(x, m, n, k)) |
| 195 | + } |
| 196 | + if (ncp == 0) { |
| 197 | + return(as.numeric(if (upper.tail) q <= lo else q >= |
| 198 | + lo)) |
| 199 | + } |
| 200 | + if (ncp == Inf) { |
| 201 | + return(as.numeric(if (upper.tail) q <= hi else q >= |
| 202 | + hi)) |
| 203 | + } |
| 204 | + sum(dnhyper(ncp)[if (upper.tail) support >= q else support <= |
| 205 | + q]) |
| 206 | + } |
| 207 | + |
| 208 | + |
| 209 | + list(estimate = mle, p.value = p.value) |
| 210 | + }, lo, hi) |
| 211 | +} |
| 212 | + |
| 213 | + |
| 214 | +function (x, y = NULL, workspace = 2e+05, hybrid = FALSE, hybridPars = c(expect = 5, |
| 215 | + percent = 80, Emin = 1), control = list(), or = 1, alternative = "two.sided", |
| 216 | + conf.int = TRUE, conf.level = 0.95, simulate.p.value = FALSE, |
| 217 | + B = 2000) |
| 218 | +{ |
| 219 | + |
| 220 | + PVAL <- NULL |
| 221 | + else { |
| 222 | + PVAL <- switch(alternative, less = pnhyper(x, or), |
| 223 | + greater = pnhyper(x, or, upper.tail = TRUE), |
| 224 | + two.sided = { |
| 225 | + if (or == 0) as.numeric(x == lo) else if (or == |
| 226 | + Inf) as.numeric(x == hi) else { |
| 227 | + relErr <- 1 + 10^(-7) |
| 228 | + d <- dnhyper(or) |
| 229 | + sum(d[d <= d[x - lo + 1] * relErr]) |
| 230 | + } |
| 231 | + }) |
| 232 | + } |
| 233 | + mle <- function(x) { |
| 234 | + if (x == lo) |
| 235 | + return(0) |
| 236 | + if (x == hi) |
| 237 | + return(Inf) |
| 238 | + mu <- mnhyper(1) |
| 239 | + if (mu > x) |
| 240 | + uniroot(function(t) mnhyper(t) - x, c(0, 1))$root |
| 241 | + else if (mu < x) |
| 242 | + 1/uniroot(function(t) mnhyper(1/t) - x, c(.Machine$double.eps, |
| 243 | + 1))$root |
| 244 | + else 1 |
| 245 | + } |
| 246 | + ESTIMATE <- c(`odds ratio` = mle(x)) |
| 247 | + if (conf.int) { |
| 248 | + ncp.U <- function(x, alpha) { |
| 249 | + if (x == hi) |
| 250 | + return(Inf) |
| 251 | + p <- pnhyper(x, 1) |
| 252 | + if (p < alpha) |
| 253 | + uniroot(function(t) pnhyper(x, t) - alpha, |
| 254 | + c(0, 1))$root |
| 255 | + else if (p > alpha) |
| 256 | + 1/uniroot(function(t) pnhyper(x, 1/t) - alpha, |
| 257 | + c(.Machine$double.eps, 1))$root |
| 258 | + else 1 |
| 259 | + } |
| 260 | + ncp.L <- function(x, alpha) { |
| 261 | + if (x == lo) |
| 262 | + return(0) |
| 263 | + p <- pnhyper(x, 1, upper.tail = TRUE) |
| 264 | + if (p > alpha) |
| 265 | + uniroot(function(t) pnhyper(x, t, upper.tail = TRUE) - |
| 266 | + alpha, c(0, 1))$root |
| 267 | + else if (p < alpha) |
| 268 | + 1/uniroot(function(t) pnhyper(x, 1/t, upper.tail = TRUE) - |
| 269 | + alpha, c(.Machine$double.eps, 1))$root |
| 270 | + else 1 |
| 271 | + } |
| 272 | + CINT <- switch(alternative, less = c(0, ncp.U(x, |
| 273 | + 1 - conf.level)), greater = c(ncp.L(x, 1 - conf.level), |
| 274 | + Inf), two.sided = { |
| 275 | + alpha <- (1 - conf.level)/2 |
| 276 | + c(ncp.L(x, alpha), ncp.U(x, alpha)) |
| 277 | + }) |
| 278 | + attr(CINT, "conf.level") <- conf.level |
| 279 | + } |
| 280 | + RVAL <- c(RVAL, list(conf.int = if (conf.int) CINT, estimate = ESTIMATE, |
| 281 | + null.value = NVAL)) |
| 282 | +} |
| 283 | +structure(c(RVAL, alternative = alternative, method = METHOD, |
| 284 | + data.name = DNAME), class = "htest") |
| 285 | +} |
0 commit comments