---
title: Reporting Linear Mixed Models
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Reporting 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)
```

A mixed-model report needs more than coefficient estimates. The
reader needs to know which observations and grouping units were
used, what random-effects structure was fitted, which inference
method produced each row, whether the optimizer reached an interior
solution, and which quantities were not available. Most of those are
facts about the fit that `lme4` does not store in the fitted object;
the analyst has to remember them, or reconstruct them from console
output weeks after the fact.

`mixeff` stores every one of them. This vignette follows a
structured mixed-model reporting checklist — describe the data and
design, report the model specification, show fixed and random
effects, label inference methods, preserve software provenance, and
make caveats explicit instead of hiding them in prose — drawing each
section from the same fitted object.

## What model will we report?

Use one dataset and one fitted model all the way through. Here, clinics are the
grouping units and each clinic contributes repeated weekly observations.

```{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)
```

The analysis estimates average changes by week and treatment while allowing
clinics to have different baselines.

## What did the formula request?

Before fitting, compile the model and read the design audit. This separates
formula interpretation from optimization and gives you a stable place to check
the requested random-effects structure.

```{r compile-design}
spec <- compile_model(score ~ week + treatment + (1 | clinic), clinic_visits)
audit(spec)
```

```{r compile-checks, include = FALSE}
stopifnot(inherits(spec, "mm_spec"))
```

## What fit was used?

Fit the model once. In reporting work, the fitted object is the source for
summary output, report sections, and provenance.

```{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(identical(fit_status(fit), "converged_interior"))
stopifnot(all(is.finite(fixef(fit))))
```

The overview table is a compact first pass over the fitted model: formula,
fitting mode, number of observations, fit status, inference availability, and
versioned artifact information.

```{r report-overview}
reporting_table(fit, "overview")
```

## Which design facts belong in the report?

A useful mixed-model report names the grouping units and their information
budget. The data-design section gives the number of levels and rows per group.

```{r data-design}
reporting_table(fit, "data_design")
```

The random-term section translates the random-effects formula into auditable
rows. This is where the report records the grouping factor, basis, covariance
family, parameter count, and Rust-authored plain-language description.

```{r random-terms}
reporting_table(fit, "random_terms")
```

```{r design-report-checks, include = FALSE}
design <- reporting_table(fit, "data_design")$table
stopifnot(any(design$group == "clinic"))
stopifnot(all(design$group_levels[design$group == "clinic"] == n_clinic))
terms <- reporting_table(fit, "random_terms")$table
stopifnot(all(c("group", "basis", "covariance", "english") %in% names(terms)))
```

## How are estimates and p-values labelled?

`summary()` gives a familiar coefficient table. Use it for console review.

```{r model-summary}
coef_table <- summary(fit, method = "auto")$coefficients
knitr::kable(coef_table, digits = 4)
```

For report assembly, use `reporting_table()`. The fixed-effect section keeps
the estimate, uncertainty, statistic, p-value, method, row status, and
reliability label together.

```{r fixed-effect-report}
reporting_table(fit, "fixed_effects")
```

When you need to audit where those rows came from, request the audit view.

```{r fixed-effect-audit}
fixed_audit <- reporting_table(fit, "fixed_effects", view = "audit")$table
fixed_audit[, c("term", "method", "status", "reliability", "source")]
```

```{r fixed-effect-checks, include = FALSE}
fixed_report <- reporting_table(fit, "fixed_effects")$table
stopifnot(all(is.finite(fixed_report$estimate)))
stopifnot(any(is.finite(fixed_report$p_value)))
stopifnot(all(c("method", "status", "reliability") %in% names(fixed_report)))
stopifnot(all(fixed_audit$source == "fixed_effect_inference_table"))
```

## How are random effects reported?

The random-effect table reports variance components on the fitted scale. In
the current contract, those rows come from the Rust-authored
`mixedmodels.fit_summary` payload, so the source and availability status travel
with the report.

```{r random-effect-report}
reporting_table(fit, "random_effects")
```

```{r random-effect-checks, include = FALSE}
random_report <- reporting_table(fit, "random_effects")$table
stopifnot(any(random_report$kind == "variance"))
stopifnot(any(random_report$group == "Residual"))
stopifnot(any(is.finite(random_report$variance)))
```

## What is unavailable or caveated?

Build the full model report when you want the section map, software
provenance, and ledger of unavailable or not-applicable fields.

```{r full-report}
report <- model_report(fit)
report
```

The unavailable ledger is part of the report, not an error condition. It is
where `mixeff` records schema gaps, not-applicable sections, and other caveats
with stable reasons and an action taken.

```{r unavailable-ledger}
reporting_table(report, "unavailable")
```

```{r unavailable-checks, include = FALSE}
unavailable <- reporting_table(report, "unavailable")$table
stopifnot(all(c("section", "field", "status", "reason") %in%
                names(unavailable)))
stopifnot(!any(unavailable$field ==
                 "stable_random_effect_variance_covariance_payload"))
```

## What should go into your written report?

Use the report sections as the source material for prose:

- `overview` records the formula, fit method, observations, fit status, and
  versioned artifact information.
- `data_design` records grouping-unit counts and rows per group.
- `random_terms` records the random-effects specification and its design
  support.
- `fixed_effects` records estimates and inference labels.
- `random_effects` records fitted variance components.
- `unavailable` records caveats that should not disappear from the analysis
  record.

The important habit is to report from the fitted object rather than from memory.
Use `vignette("inference", package = "mixeff")` for term tests, contrasts, and
model comparisons. Use `vignette("saving-and-reviving", package = "mixeff")`
when the report needs to survive an RDS round trip.
