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
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.
| Estimate | Std. Error | df | t value | Pr(>|t|) | method | |
|---|---|---|---|---|---|---|
| (Intercept) | 7.6829 | 0.1965 | 12.5650 | 39.1065 | 0.0000 | satterthwaite |
| week | -0.2784 | 0.0260 | 58.9997 | -10.7280 | 0.0000 | satterthwaite |
| treatmentcoached | -0.8995 | 0.2623 | 9.9993 | -3.4298 | 0.0064 | satterthwaite |
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.
spec <- compile_model(score ~ week + treatment + (1 | clinic), clinic_visits)
explain_model(spec)
#> Random effects explanation:
#> formula: score ~ 1 + week + treatment + (1 | clinic)
#>
#> Random effects:
#> r0:
#> wrote: (1 | clinic)
#> canonical: (1 | clinic)
#> named form: re(group = clinic, intercept = TRUE, slopes = NULL, cov = "scalar")
#> scope: `clinic` units may differ in average outcome.
#> covariance: scalar; theta parameters: 1
#> support: sufficient; group levels: 12; min rows/group: 6; median rows/group: 6
#> variation: intercept=not_assessed
#>
#> Design notes:
#> scope_note: `week` varies within `clinic`, so a `clinic`-level slope is structurally possiblevignette("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.
reporting_table(fit, "overview")
#> field value
#> model_class LMM
#> formula score ~ week + treatment + (1 | clinic)
#> effective_formula score ~ 1 + week + treatment + (1 | clinic)
#> fit_method REML
#> mode confirmatory_as_specified
#> nobs 72
#> fit_status converged_interior
#> inference 3/3 available fixed-effect rows
#> artifact_schema mixedmodels.compiled_model_artifact 1
#> crate_version 1.0.0-rc.1
#> package_version 0.2.0
reporting_table(fit, "fixed_effects")
#> term estimate std_error statistic statistic_name
#> (Intercept) 7.6828778 0.19646018 39.106539 z
#> week -0.2783994 0.02595083 -10.727955 z
#> treatment: coached -0.8994747 0.26225014 -3.429835 z
#> p_value method status reliability
#> 0.0000000000 asymptotic_wald_z available low
#> 0.0000000000 asymptotic_wald_z available low
#> 0.0006039485 asymptotic_wald_z available low
reporting_table(fit, "fixed_effects", view = "audit")$table[, c("term", "source", "status")]
#> term source status
#> 1 (Intercept) fixed_effect_inference_table available
#> 2 week fixed_effect_inference_table available
#> 3 treatment: coached fixed_effect_inference_table availableSaving and reloading
The fitted object stores the values needed by the main extractors, so an RDS round trip preserves the pieces you usually report.
path <- tempfile(fileext = ".rds")
saveRDS(fit, path)
restored <- readRDS(path)
fixef(restored)
#> (Intercept) week treatmentcoached
#> 7.6828778 -0.2783994 -0.8994747
reporting_table(restored, "fixed_effects")
#> term estimate std_error statistic statistic_name
#> (Intercept) 7.6828778 0.19646018 39.106539 z
#> week -0.2783994 0.02595083 -10.727955 z
#> treatment: coached -0.8994747 0.26225014 -3.429835 z
#> p_value method status reliability
#> 0.0000000000 asymptotic_wald_z available low
#> 0.0000000000 asymptotic_wald_z available low
#> 0.0006039485 asymptotic_wald_z available lowLower-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()andchanges()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.