tests/testthat/test-lme4.R

stopifnot(require("testthat"), require("broom.mixed"))
## test tidy, augment, glance methods from lme4-tidiers.R

if (require(lme4, quietly = TRUE)) {
  load(system.file("extdata", "lme4_example.rda",
    package = "broom.mixed",
    mustWork = TRUE
    ))

context("lme4 models")

  d <- as.data.frame(ChickWeight)
  colnames(d) <- c("y", "x", "subj", "tx")
  fit <<- lmer(y ~ tx * x + (x | subj), data = d)

  test_that("tidy works on lme4 fits", {
    td <- tidy(fit)
    ## FIXME: fails if lmerTest has been loaded previously ...
    expect_equal(dim(td), c(12, 6))
    expect_equal(
      names(td),
      c(
        "effect", "group", "term", "estimate",
        "std.error", "statistic"
      )
    )
    expect_equal(
      td$term,
      c(
        "(Intercept)", "tx2", "tx3", "tx4", "x",
        "tx2:x", "tx3:x", "tx4:x",
        "sd__(Intercept)",
        "cor__(Intercept).x",
        "sd__x",
        "sd__Observation"
      )
    )
  })

  test_that("tidy/glance works on glmer fits", {
    gm <- glmer(cbind(incidence, size - incidence) ~ period + (1 | herd),
      cbpp, binomial,
      nAGQ = 0
    )
    ggm <- broom::glance(gm)
    expect_equal(names(ggm), c("nobs", "sigma", "logLik", "AIC", "BIC", "deviance", "df.residual"))
    td <- tidy(gm)
    expect_equal(
      names(td),
      c(
        "effect", "group", "term", "estimate",
        "std.error", "statistic", "p.value"
      )
    )
    td_ran <- tidy(gm, "ran_pars")
    expect_equal(names(td_ran), c("effect", "group", "term", "estimate"))
  })

  test_that("glance includes deviance iff method='ML'", {
    expect(!("deviance" %in% names(glance(lmm0))),"deviance not included")
    expect("REMLcrit" %in% names(glance(lmm0)),"REMLcrit not included")
    expect("deviance" %in% names(glance(lmm0ML)),"deviance not included")
  })


  test_that("tidy works on non-linear fits", {
    startvec <- c(Asym = 200, xmid = 725, scal = 350)
    # use nAGQ = 0 to avoid warnings
    nm <- nlmer(circumference ~ SSlogis(age, Asym, xmid, scal) ~ Asym | Tree,
      Orange,
      start = startvec, nAGQ = 0L
    )
    gnm <- broom::glance(nm)
    expect_equal(names(gnm), c("nobs", "sigma", "logLik", "AIC", "BIC", "deviance", "df.residual"))
    td <- tidy(nm)
    expect_equal(
      names(td),
      c(
        "effect", "group", "term", "estimate",
        "std.error", "statistic"
      )
    )
    td_ran <- tidy(nm, "ran_pars")
    expect_equal(names(td_ran), c("effect", "group", "term", "estimate"))
  })

  test_that("scales works", {
    t1 <- tidy(fit, effects = "ran_pars")
    t2 <- tidy(fit, effects = "ran_pars", scales = "sdcor")
    expect_equal(t1$estimate, t2$estimate)
    expect_error(
      tidy(fit, effects = "ran_pars", scales = "varcov"),
      "unrecognized ran_pars scale"
    )
    t3 <- tidy(fit, effects = "ran_pars", scales = "vcov")

    get_sdvar <- function(x) {
        (x %>% dplyr::filter(grepl("^(sd|var)",term))
            %>% dplyr::select(estimate)
        )}
    ## ?? need to coerce to data frames: https://github.com/tidyverse/dplyr/issues/2751
    expect_equal(
        as.data.frame(get_sdvar(t3)),
        as.data.frame(get_sdvar(t2) %>% mutate_all(~.^2))
    )
    expect_error(
      tidy(fit, scales = "vcov"),
      "must be provided for each effect"
    )
  })

  test_that("tidy works with more than one RE grouping variable", {
    dd <- expand.grid(f = factor(1:10), g = factor(1:5), rep = 1:3)
    dd$y <- suppressMessages(simulate(~(1 | f) + (1 | g),
      newdata = dd,
      newparams = list(beta = 1, theta = c(1, 1)),
      family = poisson, seed = 101
    ))[[1]]
    gfit <- glmer(y ~ (1 | f) + (1 | g), data = dd, family = poisson)
    tnames <- as.character(tidy(gfit, effects = "ran_pars")$term)
    expect_equal(tnames, rep("sd__(Intercept)", 2))
  })

  test_that("augment works on lme4 fits with or without data", {
    au1 <- suppressWarnings(broom::augment(fit))
    au2 <- suppressWarnings(broom::augment(fit, d))
    ## FIXME: columns not ordered the same??
    expect_equal(au1, au2[names(au1)])
  })

  dNAs <<- d
  dNAs$y[c(1, 3, 5)] <- NA

  test_that("augment works on lme4 fits with NAs", {
    fitNAs <- lmer(y ~ tx * x + (x | subj), data = dNAs,
                     control=lmerControl(check.conv.grad=
                     .makeCC("warning", tol = 5e-2, relTol = NULL)))
    au <- suppressWarnings(broom::augment(fitNAs))
    expect_equal(nrow(au), sum(complete.cases(dNAs)))
  })

  test_that("augment works on lme4 fits with na.exclude", {
      fitNAs <- lmer(y ~ tx * x + (x | subj),
                     data = dNAs, na.action = "na.exclude",
                     control=lmerControl(check.conv.grad=
                     .makeCC("warning", tol = 5e-2, relTol = NULL)))

    # expect_error(suppressWarnings(augment(fitNAs)))
    au <- suppressWarnings(broom::augment(fitNAs, dNAs))

    # with na.exclude, should have NAs in the output where there were NAs in input
    expect_equal(nrow(au), nrow(dNAs))
    expect_equal(complete.cases(au), complete.cases(dNAs))
  })

  test_that("glance works on lme4 fits", {
    g <- broom::glance(fit)
    expect_equal(dim(g), c(1, 7))
  })

  test_that("ran_vals works", {
    td0 <- tidy(lmm0, "ran_vals")
    td1 <- tidy(lmm1, "ran_vals")
    expect_equal(dim(td0), c(18, 6))
    expect_equal(dim(td1), c(36, 6))
    if (packageVersion("lme4") >= "1.1.18") {
      td2 <- tidy(lmm2, "ran_vals")
      expect_equal(dim(td2), c(36, 6))
      expect_equal(names(td1), names(td2))
    }
  })
  test_that("confint preserves term names", {
    td3 <- tidy(lmm0, conf.int = TRUE, conf.method = "Wald", effects = "fixed")
    expect_equal(td3$term, c("(Intercept)", "Days"))
  })


test_that("tidy respects conf.level", {
     tmpf <- function(cl=0.95) {
         return(tidy(lmm0,conf.int=TRUE,conf.level=cl)[1,][["conf.low"]])
     }
     expect_equal(tmpf(),232.3019,tolerance=1e-4)
     expect_equal(tmpf(0.5),244.831,tolerance=1e-4)
})

test_that("effects='ran_pars' + conf.int works", {
    tt <- tidy(lmm0, effects="ran_pars", conf.int=TRUE, conf.method="profile",
               quiet=TRUE)[c("conf.low","conf.high")]
    tt0 <- structure(list(conf.low = c(26.007120448854, 27.8138472081303
), conf.high = c(52.9359835296834, 34.591049857869)), row.names = c(NA, 
-2L), class = c("tbl_df", "tbl", "data.frame"))
    tt0 <- structure(list(conf.low = c(26.00712, 27.81384),
                                conf.high = c(52.9359, 34.59104)),
                           row.names = c(NA, -2L),
                     class = c("tbl_df", "tbl", "data.frame"))
    ## ??? why do I need as.data.frame??
    ## otherwise [1] "Rows in x but not y: 2, 1. Rows in y but not x: 2, 1. "
    expect_equal(as.data.frame(tt0), as.data.frame(tt),
                 tolerance=1e-5)

})

test_that("augment returns a tibble", {
    ## GH 51
    expect_is(augment(fit), "tbl")
})

test_that("conf intervals for ranef in correct order", {
    ## GH 65
    t1 <- tidy(lmm1,conf.int=TRUE,effect="ran_pars",conf.method="profile",quiet=TRUE)
    cor_vals <- t1[t1$term=="cor__(Intercept).Days",]
    expect_true(cor_vals$conf.low>(-1) && cor_vals$conf.high<1)
})
}
bbolker/broom.mixed documentation built on Jan. 13, 2024, 10:55 p.m.