3

Background

I am trying to fit a mixed model in a function based on some parameters. If I want to use contrast from library(contrast) I have to use a workaround, as contrast uses the call slot from the lme object to determine the data, fixed or random argument passed to lme in the function (cf. code). This is by the way not the case with an lm object.

Data

set.seed(1)
dat <- data.frame(x = runif(100), y1 = rnorm(100), y2 = rnorm(100),
                  grp = factor(sample(2, 100, replace = TRUE)))

Code

library(contrast)
library(nlme)
makeMixedModel1 <- function(resp, mdat = dat) {
   mF <- formula(paste(resp, "x", sep = "~"))
   mdat <- mdat[resp > 0, ]
   mod <- lme(mF, random = ~ 1 | grp, data = mdat)
   mC <- mod$call
   mC$fixed <- mF
   mC$data <- mdat
   mod$call <- mC
   mod
}

makeMixedModel2 <- function(resp, mdat = dat) {
   mF <- formula(paste(resp, "x", sep = "~"))
   mdat <- mdat[resp > 0, ]
   lme(mF, random = ~ 1 | grp, data = mdat)
}

mm1 <- makeMixedModel1("y1")
mm2 <- makeMixedModel2("y1")
contrast(mm1, list(x = 1)) ## works as expected
# lme model parameter contrast
# 
#   Contrast      S.E.      Lower     Upper    t df Pr(>|t|)
#  0.1613734 0.2169281 -0.2692255 0.5919722 0.74 96   0.4588

contrast(mm2, list(x = 1)) ## gives an error
# Error in eval(expr, envir, enclos) : object 'mF' not found

Question

I have tracked down the error to the part where contrast evaluates the fixedslot within the call slot of mm2 whis equals to mF which is of course not known at the top level, as it was defined only inside my function makeMixedModel2. The workaround in makeMixedModel1 remedies that by explicitely overwriting the respective slots in call.

Apparently, for lm objects this is solved in a smarter way, as there's no need to do the manual overwriting as contrast seems to evaulate all the parts within the right context though of course mF and mdat are not known either:

makeLinearModel <- function(resp, mdat = dat) {
   mF <- formula(paste(resp, "x", sep = "~"))
   mdat <- mdat[resp > 0, ]
   lm(mF, data = mdat)
}
contrast(makeLinearModel("y1"), list(x = 1))

So, I assume that lm stores the values of the formula and the data somewhere, such that in can be retrieved also at different environments.

I can live with my workaround, though it has some ugly side-effects as a print(mm1) shows all the data instead of a simple names. So my question is, whether there are some other strategies to get what I intend? Or do I have to write to the maintainer of contrast and ask him, whether he can change the code for lme objects such that hes does not rely anymore on the call slot, but tries to solve the issue otherwise (as it is done for the lm)?

thothal
  • 16,690
  • 3
  • 36
  • 71
  • I think this is may be a duplicate of [this question](http://stackoverflow.com/questions/7666807/anova-test-fails-on-lme-fits-created-with-pasted-formula), with an answer showing how to do this via `do.call` (although with the same side effects as your solution, possibly). – aosmith Aug 17 '15 at 18:31
  • Yeah, I've seen that. The problem is not so much that I cannot solve it, the question was how to avoid the side effects. – thothal Aug 18 '15 at 12:31

1 Answers1

1

I think what you're fighting is just a buggy implementation of contrast() for lme objects. I would contact the author to fix it (it may be a result of something recent changing with nlme). But in the mean time you can avoid your side effects by implementing your workaround in the contrast.lme() function rather than in your model construction function:

contrast.lme <- function(fit, ...) {
   mC <- fit$call
   mC$fixed <- formula(fit) 
   mC$data <- fit$data
   fit$call <- mC

   library(nlme)
   contrast:::contrastCalc(fit, ...)
}
assignInNamespace("contrast.lme", contrast.lme, "contrast")

mm2 <- makeMixedModel2("y1")

contrast(mm2, list(x = 1))

Yields:

lme model parameter contrast

  Contrast      S.E.      Lower     Upper    t df Pr(>|t|)
 0.1613734 0.2169281 -0.2692255 0.5919722 0.74 96   0.4588

And:

print(mm2)

Yields:

Linear mixed-effects model fit by REML
  Data: mdat 
  Log-restricted-likelihood: -136.2472
  Fixed: mF 
(Intercept)           x 
 -0.1936347   0.3550081 

Random effects:
 Formula: ~1 | grp
        (Intercept)  Residual
StdDev:    0.131666 0.9365614

Number of Observations: 100
Number of Groups: 2
Forrest R. Stevens
  • 3,435
  • 13
  • 21
  • Thanks, that does indeed the trick and I learned also something new (`assignInNamespace`). More than worth the bounty :) Thx. – thothal Aug 19 '15 at 07:30
  • Heh, happy to help. This isn't likely in your case, but mucking around with functions inside package namespaces can cause some unintended consequences (i.e. if for some reason `contrastCalc()` re-calls `contrast.lme()` for example). But it is very useful occasionally, especially if you're testing minor changes and don't want to deal with the whole package's source. – Forrest R. Stevens Aug 20 '15 at 03:08