Nothing
cat(crayon::yellow("\ntest singular design matrices:\n"))
# Singular matrix from ?Matrix::qr :
singX <- cbind(int = 1,
b1=rep(1:0, each=3), b2=rep(0:1, each=3),
c1=rep(c(1,0,0), 2), c2=rep(c(0,1,0), 2), c3=rep(c(0,0,1),2))
rownames(singX) <- paste0("r", seq_len(nrow(singX)))
donn <- as.data.frame(singX)
set.seed(123)
donn$y <- runif(6)
fv1 <- fitted(singlm <- lm(y~int+ b1+b2+c1+c2+c3,data=donn)) # (glmmTMB returns a full vector with numeric values instead of NA's)... vcov will be full of NaN's
spaMM.options(rankMethod="qr")
fv2 <- (singfit <- fitme(y~int+ b1+b2+c1+c2+c3,data=donn))$fv
spaMM.options(rankMethod=".rankinfo")
fv3 <- fitme(y~int+ b1+b2+c1+c2+c3,data=donn)$fv ## fixef is not unique, but fitted values must be equivalent
spaMM.options(rankMethod="qr")
testthat::expect_true( max(apply(cbind(fv1,fv2,fv3),1L,var))<1e-16)
crit <- diff(range(anova(singlm) -anova(singfit), na.rm=TRUE))
testthat::test_that("whether anova.lm and spaMM:::.anova.lm give equivalent results for singular X",
testthat::expect_true(crit<1e10))
# the way singular matrices are handled differ for LM vs GLMs: for the latter there are rows with 0 df in the Table
fv1 <- fitted(singglm <- glm(y~int+ b1+b2+c1+c2+c3,data=donn, family=Gamma(log)))
spaMM.options(rankMethod="qr")
fv2 <- (singfit <- fitme(y~int+ b1+b2+c1+c2+c3,data=donn, family=Gamma(log)))$fv
spaMM.options(rankMethod=".rankinfo")
fv3 <- fitme(y~int+ b1+b2+c1+c2+c3,data=donn, family=Gamma(log))$fv ## fixef is not unique, but fitted values must be equivalent
spaMM.options(rankMethod="qr")
testthat::expect_true( max(apply(cbind(fv1,fv2,fv3),1L,var))<1e-9)
# stats::anova(singglm) new default is to return the F test, as by spaMM::anova(singfit, test="F"), but with different result as doc'ed.
crit <- diff(range(anova(singglm, test=FALSE) -anova(singfit), na.rm=TRUE))
testthat::test_that("whether anova.glm and spaMM:::.anova.glm give equivalent results for singular X",
testthat::expect_true(crit<1e10))
donn$dummy<- c(0,0,0,1,1,1) # completely équivalent to b1 or b2!
chk <- try(nlme::lme(y~int+ b1+b2+c1+c2+c3, data = donn, random = ~ 1 | dummy), silent=TRUE)
testthat::test_that("whether nlme::lme does not handle singular X, as stated in the Description of spaMM::rankinfo",
testthat::expect_true(inherits(chk,"try-error"))
)
if (spaMM.getOption("example_maxtime")>0.7) { # a check accumulating possible causes of problems
{ # check on minimal pathological case
## b1 is redundant with | dummy ...
trivml <- fitme(y~b1 + (1 | dummy), data = donn, #verbose=c(TRACE=interactive()),
method="ML") # There is info about lambda, which is estimated as zero
numInfo(trivml) # the nonzero gradient at the boundary is detected. lambda is removed from target params, so that num derivation does not generate negative lambdas
trivreml <- fitme(y~b1 + (1 | dummy), data = donn, #verbose=c(TRACE=interactive()),
method="REML") # There is NO info kept in Re.L about lambda
numInfo(trivreml,which=c("lambda","phi","beta")) # the haphazard 'estimate' of lambda is far from 0, so is included, with vanishing 2nd derivative.
# The phi result is consistent with the next one
# The beta result is different from the next one as expected from the different lambda.
trivreml <- fitme(y~b1 + (1 | dummy), data = donn, control=list(refit=TRUE), method="REML")
numInfo(trivreml, which=c("lambda","phi","beta")) # values at boundary are excluded from computation to avoid negative lambda
}
## Each of b1 and b2 are redundant with | dummy ...
# Here lambda appears unidentifiable by *RE*ML: RE.L is 'completely' flat wrt it (marginal L is not).
(singfitF <- fitme(y~int+ b1+b2+c1+c2+c3 + (1 | dummy), data = donn, method="REML",
# verbose=c(TRACE=TRUE), # quite useful to check what's going on in numInfo computation too
init=list(lambda=0.01),
control=list(refit=FALSE))) ## Final value of lambda dependent on initial value
# The unit SE for the phi coef is also surprizing but may also be a far-fetched consequence of idiosyncrasies of data and model,
# combined with the approxs of the SmythHV method (by contrast, cond.SE is not 1 when formula= y~int + (1 | dummy)).
suppressWarnings(anova(singfitF, type="2")) # lambda not low enough for removal, but numInfo singular since no info on lambda => warning (suppressed here)
ranef(singfitF) # =0, which is consistent with the refit below. The latter de facto replaces an undefined lambda estimate ((v=0)/(1-lev=0)) by v/(tiny value) = 0
(singfitT <- fitme(y~int+ b1+b2+c1+c2+c3 + (1 | dummy), data = donn, method="REML",
# verbose=c(TRACE=TRUE), # quite useful to check what's going on
init=list(lambda=0.01),
control=list(refit=TRUE))) # Note effect on lambda
suppressWarnings(anova(singfitT, type="2")) # Distinct warning for removal of lambda at boundary
suppressWarnings(chk <- lme4::lmer(y~int+ b1+b2+c1+c2+c3 + (1 | dummy), data = donn)) # warnings, but this fits contrary to lme.
suppressWarnings(anova(lmerTest::as_lmerModLmerTest(chk), type="II"))
# Trying to get the same result as lme4... not completely successful, but there are good reasons for that:
(singfit <- fitme(y~int+ b1+b2+c1+c2+c3 + (1 | dummy), data = donn, method="REML",
init=list(lambda=0.0164^2),
lower=list(lambda=0.0163^2),
upper=list(lambda=0.0165^2),
control=list(refit=FALSE))) ## Final value of lambda = initial value
suppressWarnings(anova(singfit, type="2")) # Again, lambda not low enough for removal, but numInfo singular since no info on lambda => warning
suppressWarnings(anova(singfit, type="2",transf=FALSE)) # Note the difference in DenDf hence in p-value
# Since lambda is not quite low, there is no check of the num derivs, despite all the identifiability issues. The difference is on the b1 coeff, redundant with the ranef...
}
if (spaMM.getOption("example_maxtime")>0.7) { # Test of correct handling of singular fits in anova lead to an odd F test| cX2 test comparison on non-singular fits.
# Here for devel purposes a single collinearity is retained ('int' is equiv to the Intercept).
#
## Also b1 was redundant with | dummy and was removed here (this does not prevent lambda -> 0 unless we modify y: see commented-out line.
donn$yy <- donn$y
# donn$yy[donn$dumm==1] <- donn$y[donn$dumm==1]+1 # if we want to avoid the zero lambda estimate
##
okchk <- lme4::lmer(yy~int+c1+c2 + (1 | dummy), data = donn, REML=FALSE)
(okfit <- fitme(yy~c1+c2 + (1 | dummy), data = donn, method="ML"))
(singfit <- fitme(yy~int+ c1+c2 + (1 | dummy), data = donn, method="ML"))
# library("lmerTest")
a1 <- anova(lmerTest::as_lmerModLmerTest(okchk), type="1") # consistent (despite possibly different treatment of lambda) with:
suppressWarnings(a2 <- anova(okfit, type="1"))
suppressWarnings(a3 <- anova(singfit, type="1"))
crit <- max(abs(c(a1[1:2,6]-a2[1:2,6],a1[1:2,6]-a3[1:2,6])))
testthat::test_that("whether LMM type 1 anova for singular X is consistent with trimmed X",
testthat::expect_true(crit<1e8))
#
anova(okfit, type="1", method="t.Chisq")
# Remarkably, the X2 stats are identical to the previous F stats.
# The X2 stat comes from .anova_fallback -> Wald's method, hard to mess with.
# The F stat is the square of the t-stat computed by lmerTest:::anova.lmerModlmerTest() -> single_anova() -> contest1D() -> [locally defined]mk_ttable()
# same compar for type 2, quite different from type 1 ...
a1 <- anova(lmerTest::as_lmerModLmerTest(okchk), type="2")
suppressWarnings(a2 <- anova(okfit, type="2")) #
suppressWarnings(a3 <- anova(singfit, type="2"))
crit <- max(abs(c(a1[1:2,6]-a2[1:2,6],a1[1:2,6]-a3[1:2,6])))
testthat::test_that("whether LMM type 2 anova for singular X is consistent with trimmed X",
testthat::expect_true(crit<1e8))
#
anova(okfit, type="2", method="t.Chisq")
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.