Fast post hoc computation using R
Asked Answered
T

1

1

I have a large dataset which I would like to perform post hoc computation:

dat = as.data.frame(matrix(runif(10000*300), ncol = 10000, nrow = 300))

dat$group = rep(letters[1:3], 100)

Here is my code:

start <- Sys.time()

vars <- names(dat)[-ncol(dat)] 

aov.out <- lapply(vars, function(x) {
        lm(substitute(i ~ group, list(i = as.name(x))), data = dat)})

TukeyHSD.out <- lapply(aov.out, function(x) TukeyHSD(aov(x)))

Sys.time() - start

Time difference of 4.033335 mins

It takes about 4 min, are there more efficient and elegant ways to perform post hoc using R?

Thanks a lot

Tusche answered 20/8, 2018 at 19:32 Comment(0)
D
2

Your example is too big. For illustration of the idea I use a small one.

set.seed(0)
dat = as.data.frame(matrix(runif(2*300), ncol = 2, nrow = 300))
dat$group = rep(letters[1:3], 100)

Why do you call aov on a fitted "lm" model? That basically refits the same model.

Have a read on Fitting a linear model with multiple LHS first. lm is the workhorse of aov, so you can pass a multiple LHS formula to aov. The model has class c("maov", "aov", "mlm", "lm").

response_names <- names(dat)[-ncol(dat)]
form <- as.formula(sprintf("cbind(%s) ~ group", toString(response_names)))
fit <- do.call("aov", list(formula = form, data = quote(dat)))

Now the issue is: there is no "maov" method for TuckyHSD. So we need a hacking.

TuckyHSD relies on the residuals of the fitted model. In c("aov", "lm") case the residuals is a vector, but in c("maov", "aov", "mlm", "lm") case it is a matrix. The following demonstrates the hacking.

aov_hack <- fit
aov_hack[c("coefficients", "fitted.values")] <- NULL  ## don't need them
aov_hack[c("contrasts", "xlevels")] <- NULL  ## don't need them either
attr(aov_hack$model, "terms") <- NULL  ## don't need it
class(aov_hack) <- c("aov", "lm")  ## drop "maov" and "mlm"
## the following elements are mandatory for `TukeyHSD`
## names(aov_hack)
#[1] "residuals"   "effects"     "rank"        "assign"      "qr"         
#[6] "df.residual" "call"        "terms"       "model" 

N <- length(response_names)  ## number of response variables
result <- vector("list", N)
for (i in 1:N) {
  ## change response variable in the formula
  aov_hack$call[[2]][[2]] <- as.name(response_names[i])
  ## change residuals
  aov_hack$residuals <- fit$residuals[, i]
  ## change effects
  aov_hack$effects <- fit$effects[, i]
  ## change "terms" object and attribute
  old_tm <- terms(fit)  ## old "terms" object in the model
  old_tm[[2]] <- as.name(response_names[i])  ## change response name in terms
  new_tm <- terms.formula(formula(old_tm))  ## new "terms" object
  aov_hack$terms <- new_tm  ## replace `aov_hack$terms`
  ## replace data in the model frame
  aov_hack$model[1] <- data.frame(fit$model[[1]][, i])
  names(aov_hack$model)[1] <- response_names[i]
  ## run `TukeyHSD` on `aov_hack`
  result[[i]] <- TukeyHSD(aov_hack)
  }

result[[1]]  ## for example
#  Tukey multiple comparisons of means
#    95% family-wise confidence level
#
#Fit: aov(formula = V1 ~ group, data = dat)
#
#$group
#            diff        lwr        upr     p adj
#b-a -0.012743870 -0.1043869 0.07889915 0.9425847
#c-a -0.022470482 -0.1141135 0.06917254 0.8322109
#c-b -0.009726611 -0.1013696 0.08191641 0.9661356

I have used a "for" loop. Replace it with a lapply if you want.

Debroahdebs answered 20/8, 2018 at 20:44 Comment(2)
@Dong That bug is now fixed. If you use my method, model estimation can be a few times faster, but post-hoc does not get speedup compared with your original code. So the overall speedup is limited. As I tested, the issue is not the "for" loop, but the slowness of qtukey and ptukey functions in TukeyHSD. These two functions account for 60% ~ 70% execution time of the post-hoc. My hacking is not really a good "maov" method for TukeyHSD as it does not spare repeated computation of qtukey. In fact, this quantile needs only be computed once for all models.Debroahdebs
@Dong Writing up a proper TukeyHSD.maov is more involved, although the code in my answer has provided a decent start. Yes, generally the support for "mlm" and "maov" in R core is poorer. Hopefully this can get better in future.Debroahdebs

© 2022 - 2024 — McMap. All rights reserved.