--- title: Fitting Linear Mixed Models output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Fitting Linear Mixed Models} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE ) ``` ```{r load-package} library(mixeff) ``` You use a linear mixed model when observations are not independent: visits within clinics, trials within people, students within classrooms. The standard R answer is `lme4::lmer()`. `mixeff::lmm()` accepts the same formula language — `(1 | group)`, `(x | group)`, `(1 + x || group)`, `(1 | a/b)` — and the fitted object answers to `fixef()`, `ranef()`, `VarCorr()`, `predict()`, `summary()`, and the rest of the generics you already use. What it adds, and the reason to use it for a walkthrough like this one, is that the fitted object also carries the design, the convergence status, and the inference labels with it. You can audit, summarize, save, and reload without recomputing. This vignette fits one clinic-visit model and reads the pieces an analyst usually reaches for first: fixed effects, p-values, variance components, fitted values, residuals, and a compact design report. ## What data are we fitting? ```{r clinic-data, include = FALSE} set.seed(42) n_clinic <- 12L n_per_clinic <- 6L clinic <- factor(rep(seq_len(n_clinic), each = n_per_clinic)) week <- rep(0:(n_per_clinic - 1L), n_clinic) treatment <- factor( rep(rep(c("usual", "coached"), each = n_per_clinic), length.out = length(week)), levels = c("usual", "coached") ) clinic_shift <- rnorm(n_clinic, sd = 0.45) score <- 7.2 - 0.28 * week - 0.55 * (treatment == "coached") + clinic_shift[as.integer(clinic)] + rnorm(length(week), sd = 0.35) clinic_visits <- data.frame(score, week, treatment, clinic) ``` ```{r show-data} head(clinic_visits) ``` `score` is the response, `week` is a numeric predictor, `treatment` is a fixed-effect factor, and `clinic` identifies repeated visits from the same clinic. ## What happens when you call `lmm()`? The model estimates average week and treatment effects while allowing clinics to have different baselines. ```{r fit-model} fit <- lmm( score ~ week + treatment + (1 | clinic), clinic_visits, control = mm_control(verbose = -1) ) ``` ```{r fit-checks, include = FALSE} stopifnot(inherits(fit, "mm_lmm")) stopifnot(all(is.finite(fixef(fit)))) stopifnot(length(predict(fit)) == nrow(clinic_visits)) ``` Printing the fit gives the formula, convergence status, likelihood summary, residual scale, and fixed effects. ```{r print-fit} fit ``` ## How do you read the coefficient table? `summary()` gives estimates, standard errors, degrees of freedom, test statistics, p-values, and method labels when those inference rows are available. ```{r summarize-fit} coef_table <- summary(fit, method = "auto")$coefficients knitr::kable(coef_table, digits = 4) ``` ```{r summary-checks, include = FALSE} p_col <- grep("^Pr\\(|^p\\.value$", names(coef_table), value = TRUE) stopifnot(length(p_col) == 1L) stopifnot(any(is.finite(coef_table[[p_col]]))) ``` For a focused term-level test, use `test_effect()`. ```{r term-test} test_effect(fit, "treatment", method = "kenward_roger") ``` ## Which familiar extractors work? The usual fixed-effect and fit-statistic extractors are available. ```{r fixed-effects} fixef(fit) sigma(fit) logLik(fit) ``` `VarCorr()` reports fitted variance components, and `ranef()` returns conditional random effects by grouping factor. ```{r random-effects} VarCorr(fit) head(ranef(fit)$clinic) ``` ```{r extractor-checks, include = FALSE} stopifnot(nrow(ranef(fit)$clinic) == length(unique(clinic_visits$clinic))) stopifnot(is.finite(as.numeric(logLik(fit)))) stopifnot(is.finite(deviance(fit))) ``` ## How do prediction and residuals line up? For fitted data, `predict()` returns in-sample fitted values. Use `re.form = NA` for the fixed-effect part only. ```{r predictions} prediction_check <- data.frame( score = clinic_visits$score, fitted = predict(fit), fixed_only = predict(fit, re.form = NA), residual = residuals(fit) ) head(prediction_check) ``` ```{r prediction-checks, include = FALSE} stopifnot(all(is.finite(prediction_check$fitted))) stopifnot(all(is.finite(prediction_check$residual))) stopifnot(max(abs(prediction_check$score - prediction_check$fitted - prediction_check$residual)) < 1e-8) ``` ## Where is the design audit? Use reporting tables when you want a compact, data-frame result for a report or review. The data-design table is often the first one to inspect. ```{r data-design} reporting_table(fit, "data_design") ``` The random-term table translates the random-effects part of the formula into rows. ```{r random-term-report} reporting_table(fit, "random_terms") ``` For lower-level checks, use `diagnostics()`, `fit_status()`, and `parameterization()`. ```{r diagnostics} fit_status(fit) diagnostics(fit) parameterization(fit) ``` ## What should you read next? Use `vignette("inference", package = "mixeff")` for p-values, contrasts, term tests, and model comparisons. Use `vignette("demystifying-formulas", package = "mixeff")` when you want to understand how `(1 | clinic)`, `(week | clinic)`, split blocks, and `||` change the random-effects structure.