---
title: Introduction to mixeff
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Introduction to mixeff}
  %\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)
```

If you have ever fit a mixed model in R, you have almost certainly
written `lme4::lmer()`. `mixeff::lmm()` accepts the same formulas
and answers to the same extractors. An `lmer` script becomes an
`lmm` script with two edits.

What `mixeff` adds is mostly *around* the fit. It exposes the
random-effects design before optimization, keeps the method behind
every p-value visible in the output, and produces a fitted object
that survives `saveRDS()` without losing its audit trail. The goal
is not to replace your statistical judgment; it is to make the
fitted object carry enough information that you can inspect, report,
save, reload, and compare models without guessing which details were
used.

This vignette is a guided tour at a deliberate pace. The shorter
elevator pitch is in `vignette("mixeff", package = "mixeff")`.

## What problem does it solve?

In ordinary mixed-model work, three questions come up again and again:

- What does this random-effects formula actually mean?
- Are the p-values or tests available, and by what method?
- Can I reconstruct the model state later, after the fit has been saved?

`mixeff` makes those questions part of the fitted object.

## One small model

```{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 fit-model}
fit <- lmm(
  score ~ week + treatment + (1 | clinic),
  clinic_visits,
  control = mm_control(verbose = -1)
)
```

The same object gives the fitted coefficients and the inferential status of
those coefficients.

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

```{r intro-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]])))
```

## Reading the formula before fitting

If you are unsure what a random-effects expression actually models,
the answer is `compile_model()` followed by `explain_model()`.
`compile_model()` builds the pre-fit specification — the same
specification the optimizer will receive — and `explain_model()`
translates each random term into a named form, a plain-language
scope, and a parameter count.

```{r explain-model}
spec <- compile_model(score ~ week + treatment + (1 | clinic), clinic_visits)
explain_model(spec)
```

`vignette("demystifying-formulas", package = "mixeff")` works
through the random-effects spellings in detail: scalar versus
diagonal versus full covariance, `||` shorthand, nested grouping,
and the difference between "this formula cannot be estimated" and
"this formula can be estimated but the data are sparse".

A lower-level utility — `mm_parse_formula()` — exists for checking
that a formula string parses at all and reducing two equivalent
spellings to the same canonical string. It is the primitive that
equivalence-class testing uses, not a reader-facing explanation, so
it is not what you want when the question is "what does this
formula mean?".

## Reporting tables

Use `reporting_table()` when you want a data-frame result instead of printed
console output. The default view is compact; use `view = "audit"` when you want
the full provenance columns.

```{r reporting-tables}
reporting_table(fit, "overview")
reporting_table(fit, "fixed_effects")
reporting_table(fit, "fixed_effects", view = "audit")$table[, c("term", "source", "status")]
```

## Saving and reloading

The fitted object stores the values needed by the main extractors, so an RDS
round trip preserves the pieces you usually report.

```{r save-reload}
path <- tempfile(fileext = ".rds")
saveRDS(fit, path)
restored <- readRDS(path)

fixef(restored)
reporting_table(restored, "fixed_effects")
```

```{r save-checks, include = FALSE}
stopifnot(identical(fixef(restored), fixef(fit)))
stopifnot(identical(reporting_table(restored, "fixed_effects"),
                    reporting_table(fit, "fixed_effects")))
```

## Lower-level tools

Most users should start with `lmm()`, `summary()`, `contrast()`,
`test_effect()`, `compare()`, and `reporting_table()`. The lower-level
functions are there when you need them:

- `mm_parse_formula()` checks formula syntax.
- `compile_model()` builds a pre-fit model specification.
- `diagnostics()` and `changes()` expose model-state checks.
- `mm_json_known_schemas()` lists the structured artifact schemas understood by
  this version of the package.

The computational backend is intentionally not the opening story for most R
users. It matters because it lets `mixeff` keep a structured audit trail, but
the user-facing reason to use the package is simpler: fit the model, get the
numbers, and keep the status of those numbers attached to the object.

## What's next?

Use `vignette("mixeff", package = "mixeff")` for the fastest end-to-end path.
Use `vignette("lmm-basics", package = "mixeff")` for a slower fitted-model
walkthrough. Use `vignette("inference", package = "mixeff")` for p-values,
contrasts, term tests, and model comparisons. Use
`vignette("demystifying-formulas", package = "mixeff")` for random-effects
syntax.
