Dear list members,
After further testing, I found that the following simplified version of
model.matrix.lme(), which omits passing xlev to the default method, is
more robust. The previous version generated spurious warnings in some
circumstances.
model.matrix.lme <- function(object, ...){
data <- object$data
if (is.null(data)){
NextMethod(formula(object), data=eval(object$call$data),
contrasts.arg=object$contrasts)
} else {
NextMethod(formula(object), data=data,
contrasts.arg=object$contrasts)
}
}
Best,
John
On 2024-09-20 12:49 p.m., John Fox wrote:
Caution: External email.
Dear r-devel list members,
I'm posting this message here because it concerns the nlme package,
which is maintained by R-core. The problem I'm about to describe is
somewhere between a bug and a feature request, and so I thought it a
good idea to ask here rather posting a bug report to the R bugzilla.
I was made aware (by Ben Bolker) that the car::Anova() method for "lme"
models reports unreasonable results and warnings for mixed models in
which non-default contrasts are used for factors. I traced the problem
to a call to model.matrix() on "lme" objects, which was introduced into
car:::Anova.lme() a couple of years ago to check for problems in the
model matrix. That invokes model.matrix.default(), which ignores the
contrasts defined in a call to lme().
Here's a simple direct example:
------- snip -------
> library(nlme)
> m <- lme(distance ~ Sex, random = ~ 1 | Subject,
+ data=Orthodont, contrasts=list(Sex = "contr.sum"))
> m
Linear mixed-effects model fit by REML
Data: Orthodont
Log-restricted-likelihood: -253.629
Fixed: distance ~ Sex
(Intercept) Sex1
23.808239 1.160511
Random effects:
Formula: ~1 | Subject
(Intercept) Residual
StdDev: 1.595838 2.220312
Number of Observations: 108
Number of Groups: 27
> model.matrix(m, data=Orthodont)
(Intercept) SexFemale
1 1 0
2 1 0
3 1 0
. . .
106 1 1
107 1 1
108 1 1
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$Sex
[1] "contr.treatment"
--------- snip ---------
So model.matrix() constructs the model matrix using contr.treatment()
even though contr.sum() was used by lme() to fit the model.
In contrast (pun intended), model.matrix() works as expected with an
"lm" model (via model.matrix.lm()):
--------- snip ---------
> m.lm <- lm(distance ~ Sex, data=Orthodont,
+ contrasts=list(Sex = "contr.sum"))
> m.lm
Call:
lm(formula = distance ~ Sex, data = Orthodont,
contrasts = list(Sex = "contr.sum"))
Coefficients:
(Intercept) Sex1
23.808 1.161
> model.matrix(m.lm)
(Intercept) Sex1
1 1 1
2 1 1
3 1 1
. . .
106 1 -1
107 1 -1
108 1 -1
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$Sex
[1] "contr.sum"
--------- snip ---------
I was able to get around this problem by defining a model.matrix.lme()
method, which is used internally in the car package but not registered:
--------- snip ---------
model.matrix.lme <- function(object, ...){
data <- object$data
contrasts <- object$contrasts
if (length(contrasts) == 0) {
xlev <- NULL
} else {
xlev <- vector(length(contrasts), mode="list")
names(xlev) <- names <- names(contrasts)
for (name in names){
xlev[[name]] <- rownames(contrasts[[name]])
}
}
if (is.null(data)){
NextMethod(formula(object), data=eval(object$call$data),
contrasts.arg=contrasts, xlev=xlev, ...)
} else {
NextMethod(formula(object), data=data,
contrasts.arg=contrasts, xlev=xlev, ...)
}
}
--------- snip ---------
This function is a bit awkward, particularly the part that constructs
the xlev argument, but it does appear to work as intended (note,
however, that the contrast matrix for Sex rather than "contr.sum" is
reported in the "contrasts" attribute):
--------- snip ---------
> model.matrix(m)
(Intercept) Sex1
1 1 1
2 1 1
3 1 1
. . .
106 1 -1
107 1 -1
108 1 -1
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$Sex
[,1]
Male 1
Female -1
> sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS Sonoma 14.6.1
Matrix products: default
BLAS:
/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/
vecLib.framework/Versions/A/libBLAS.dylib
LAPACK:
/Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/
libRlapack.dylib;
LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/Toronto
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] nlme_3.1-165
loaded via a namespace (and not attached):
[1] compiler_4.4.1 tools_4.4.1 grid_4.4.1 lattice_0.22-6
--------- snip ---------
Although this apparently solves the problem for car::Anova(), the
problem is likely more general. For example, insight::get_modelmatrix()
also reports the wrong model matrix for the "lme" model m above.
My suggestion: Include a correct model.matrix.lme() method in the nlme
package. That could be my version, but I expect that R-core could come
up with something better.
Thank you,
John
--
John Fox, Professor Emeritus
McMaster University
Hamilton, Ontario, Canada
web: https://www.john-fox.ca/
--
______________________________________________
R-devel@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
______________________________________________
R-devel@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel