Provides a summary of a MAIHDA model including variance partition coefficients (VPC/ICC) and stratum-specific estimates.
Arguments
- object
A maihda_model object from
fit_maihda().- bootstrap
Logical indicating whether to compute parametric bootstrap confidence intervals for VPC/ICC. Default is FALSE. Supported for lme4 models only;
brmsmodels always return a posterior credible interval (see Details), sobootstrap = TRUEis rejected for them. For a negative-binomial model (glmer.nb) the bootstrap refits vialme4::refit(), which holds the dispersion parameter theta fixed at its original estimate, so the interval is conditional on the estimated theta (theta's own sampling uncertainty is not propagated). Theordinal(clmm) engine has no simulate/refit machinery, sobootstrap = TRUEis rejected there (useengine = "brms"for interval estimates). For a Gaussian model carrying lme4 precisionweights, the simulated responses draw each residual at \(\sigma / \sqrt{w_i}\), so the interval rests on the same \(\sigma^2 / w_i\) semantics as the point estimate above.- n_boot
Number of bootstrap samples if bootstrap = TRUE. Default is 1000.
- conf_level
Confidence level for the VPC/ICC interval – the lme4 bootstrap CI or the brms posterior credible interval. Default is 0.95.
- response_vpc
Logical; for a binomial (lme4) model, also compute the response-scale VPC (
maihda_vpc_response) and attach it as thevpc_responseslot. It is estimated by simulation, so it is opt-in (defaultFALSE) and usesseedfor reproducibility. Ignored for other families/engines.- seed
Optional integer seed for the response-scale VPC simulation when
response_vpc = TRUE.- df_method
Reference distribution for the fixed-effect p-values and intervals of an
lme4fit:"between-within"(default) a \(t\) on containment degrees of freedom for a Gaussian fit and a z elsewhere,"normal"a z,"bootstrap"a null-restricted parametric bootstrap costingn_bootrefits per tested fixed-effect coefficient."bootstrap"is the reference to use for a GLMM term that is constant within a stratum, such as an adjusted model's dimension main effects. Every other engine uses a z regardless.- ...
Additional arguments (not currently used).
Value
A maihda_summary object containing:
- vpc
Variance Partition Coefficient (ICC); for lme4 with
bootstrap = TRUEand for all brms models this includesci_lower/ci_upper/conf_level. For a contextual cross-classified fit this is the between-stratum share of all unexplained variance (net of the context)- variance_components
Data frame of variance components. For a contextual cross-classified fit (
fit_maihda(context = )) each context appears as its ownContext: <name>row- longitudinal
For a longitudinal (growth-curve) fit, the time-varying summary:
vpc_t(the VPC over a reporting grid, with bootstrap or posterior bands), the per-level variances over that grid, the stratum and individual covariance blocks, and the two trajectory VPCsvpc_interceptandvpc_slopedescribed below, each with an interval invpc_intercept_ci/vpc_slope_ci(a posterior credible interval for brms, a bootstrap interval for a bootstrapped lme4 fit,NAotherwise) and the basis intrajectory_vpc_method.NULLfor a cross-sectional fit- context
For a contextual cross-classified fit, the stratum vs. context partition: per-context variances and shares, the contexts' total share (
vpc_context_total, with an interval when bootstrapped or for brms), and the between-stratum share (vpc_stratum);NULLotherwise- discriminatory_accuracy
For a binomial/Bernoulli outcome, the
maihda_daobject (AUC + MOR) frommaihda_discriminatory_accuracy;NULLotherwise. A contextual fit (fit_maihda(context = )) is included – its headline AUC is the intersectional-scope concordance that excludes the context random effect.NULLfor a crossed-dimensions fit (whose headline here is the additive/interaction decomposition) and a longitudinal fit- count_vpc
For a log-link count model, the
approximationthe level-1 variance came from, the marginal countlambda(andtheta/lambda_effectivefor the negative binomial) it was evaluated at, thealternativesall three approximations give at thatlambda(level1_varianceis the one used), andlow_count–TRUEbelow the \(\lambda = 2\) threshold above which Nakagawa et al. (2017) report the three agree. These are plug-in values at a singlelambda: on the likelihood engines that is exactly the number invariance_components, but abrmssummary works draw by draw and reports \(E[\sigma^2_e]\) there, which differs slightly.NULLfor every other family.- vpc_response
The response-scale VPC (
maihda_vpc_response) whenresponse_vpc = TRUEfor a binomial lme4 model, including a contextual fit (the context variance enters the VPC denominator);NULLotherwise (including for crossed-dimensions and longitudinal fits)- stratum_estimates
Data frame of stratum-specific random effects with labels if available
- fixed_effects
Fixed-effect estimates. For the lme4, WeMix and ordinal engines a data frame with
term,estimate,se,statistic,df,p_valueand the Wald intervallower/upperatconf_level;dfisNAwherever the reference is a z. The WeMix standard errors are its sandwich (robust) ones. For brms, thebrms::fixef()matrix (posterior mean,Est.Errorand the credible-interval quantiles atconf_level). Available in a tidy, engine-independent shape fromtidy(x, component = "fixed")- conf_level
The interval level used for the fixed effects (and, when bootstrapped or Bayesian, the VPC)
- df_method
The reference distribution the
fixed_effectstable used,"between-within","normal"or"bootstrap"- thresholds
For a cumulative (ordinal) clmm fit, the threshold (cut point) estimates with standard errors – the cumulative model's "intercepts"; NULL otherwise
- model_summary
Original model summary
- diagnostics
Fit-quality diagnostics (singular fit / convergence) carried over from the fitted model and reported by the print method
- strata_autobin_info
The auto-binning recipe carried over from the fitted model: for each numeric stratum dimension
make_strata()discretised, itsbreaksandlabels. The cut-points are quantiles of the analytic sample, so they define the strata (and hence the estimand); the print method reports them. An empty list when nothing was binned
Note
For lme4 models a VPC/ICC interval is obtained from a parametric
bootstrap (bootstrap = TRUE). For brms models the VPC/ICC is
summarised directly from the posterior draws: the reported estimate is the
posterior median of the per-draw VPC (\(E[\sigma^2]\)-based, not the biased
\(E[\sigma]^2\)) and the interval is a central credible interval at
conf_level (default 95%), so no bootstrap argument is needed.
The variance-components table reports the posterior-mean variance components,
so the stratum proportion shown there may differ slightly from the headline
VPC because the median of a ratio is not the ratio of means. For non-Gaussian
brms families the level-1 (residual) variance uses the usual
latent-scale approximation. For the count families (Poisson, negative
binomial) the marginal expected counts are propagated per draw – from
the fixed-part linear-predictor draws and each draw's total random-intercept
variance – for the intercept-only VPC structures (strata, crossed-dimensions,
contextual), so the credible interval reflects fixed-effect and random-effect
variance uncertainty; the negative-binomial shape draws are always
propagated. A random-slope (longitudinal) structure, whose per-row
random-effect design that fast path does not carry, instead holds the marginal
expected counts at a posterior-mean plug-in (constant across draws) to avoid an
expensive \(ndraws \times nobs\) computation.
Interpreting the VPC/ICC
The VPC is the between-stratum variance
divided by the total unexplained variance. For the canonical
single-stratum model that denominator is between-stratum + residual, but if the
model includes additional random effects (e.g. (1 | site)) their
variance is included in the denominator too (between-stratum + other random
effects + residual), so the VPC is the between-stratum share of all
unexplained variance. It is a conditional/residual ICC that excludes variance
captured by the fixed effects, so for models with covariates it is conditional
on them. It is most commonly read from the null model
outcome ~ 1 + (1 | stratum), where it is the total between-stratum
share. For non-Gaussian families the level-1 (residual) variance uses a
latent/distributional approximation (\(\pi^2/3\) for logistic;
\(\log(1 + 1/\lambda)\) for Poisson per Stryhn et al. 2006 and
\(\log(1 + 1/\lambda + 1/\theta)\) for the negative binomial per Nakagawa,
Johnson & Schielzeth 2017 – their "delta" and "trigamma"
alternatives are available via fit_maihda(count_approximation = ), and
the choice is reported in the printed summary and in count_vpc because
the three diverge materially below a marginal count of 2 – each evaluated at a single marginal
expected count \(\lambda\): the mean over the analytic sample of the
row-level \(\lambda_i = \exp(x_i'\beta + v_i/2)\) – the fixed-part
prediction with the log-normal correction for the row's total random-effect
variance \(v_i\). The counts are averaged before the transform,
which is where the cited \(\lambda\) is defined and which reduces to
Nakagawa et al.'s \(\lambda = \exp(\beta_0 + \sigma^2/2)\) in the null
model; not at the conditional fitted means, whose BLUPs would tie the
level-1 variance to the realized random effects), so the
VPC is on that latent scale; for a weighted Gaussian model the level-1
variance is the mean conditional residual variance,
\(\bar{\sigma^2 / w_i}\), since the per-observation residual variance is
\(\sigma^2 / w_i\). The stratum random effects represent the total
between-stratum deviation; they equal the pure intersectional
(interaction) component only when the additive main effects of the strata
variables are included in the model.
Fixed-effect reference distribution
A Gaussian lme4 fit refers each Wald statistic to a \(t\) on
containment (between-within) degrees of freedom, reported in the
df column: a term absorbed by a random-effect grouping is tested
against that grouping's units minus the terms it absorbs, and a term absorbed
by none against \(n\) minus the random-effect levels. A random slope counts
as absorbing. Set df_method = "normal" for a z instead.
A GLMM, a WeMix pseudo-ML fit and an ordinal::clmm fit have no
finite-sample \(t\) and use the Wald z; a brms summary reports the
posterior. For Kenward-Roger or Satterthwaite, apply pbkrtest or
lmerTest to x$model.
df_method = "bootstrap" replaces that reference for an lme4 fit
with at least one fixed-effect term, Gaussian or not, and is the one to use
for a GLMM – whose z is anticonservative for a term constant within a
stratum, most severely when the strata are few. For
each fixed-effect coefficient the model is refitted with that coefficient
constrained to zero, n_boot responses are simulated from the
restricted fit, the full model is refitted on each, and the observed Wald
statistic is referred to the resulting distribution of \(|t^*|\). The
estimate and standard error are unchanged. The p-value is
\((1 + \#\{|t^*| \ge |t|\}) / (B + 1)\)
over the \(B\) refits that
succeeded, and the interval is the estimate plus or minus a critical value
read from the same draws, times the standard error. The two agree by
construction: zero falls outside the interval exactly when the p-value is at
most 1 - conf_level. That agreement is algebraic, not a coverage
guarantee – the draws are simulated with the coefficient at zero, so how
often the interval covers a coefficient that is not zero rests on the same
approximation as the p-value (below). df is NA, and so are the
intercept's p-value and interval: a MAIHDA intercept is a reference-category
level rather than a term that can be dropped, so it has no null model to
simulate from.
The restriction is on the coefficient, not on its term. A term spanning
several design columns – a factor with three or more levels, a polynomial, an
interaction between factors – carries one row per column, and each is tested
against its own null: that column zeroed, the siblings kept and re-estimated
as the nuisance parameters they are. Zeroing the siblings too would test a
stronger hypothesis than the row states, and would push their effect into the
variance components, so the draws would come from a model the data do not
describe. It would also make the answer depend on spelling, since y ~ f
and y ~ fb + fc fit the identical model. The restriction is the one the
coefficient names under the fitted contrasts: treatment coding merges that
level into the reference, sum coding sets its deviation from the unweighted
mean of the level means to zero.
The constraint is imposed on the fitted design and verified, not assumed from
the formula. Removing a term from a formula does not always remove it from the
model: R's marginality rules recode a surviving higher-order term to absorb a
dropped marginal one, so for y ~ x * f the formula . ~ . - x
still spans the original column space and leaves the coefficient under test
entirely unrestricted. The same holds for either main effect of f * g,
for every main effect and two-way term under a three-way interaction, and for
a nested f / g. Where that happens, and for every coefficient of a
multi-column term, the design columns are constrained directly instead. A
model whose fixed part is additive in one-column terms is unaffected: there,
dropping the term from the formula already is the null.
The bootstrap is an approximation, not an exact test. Its null is the restricted fit, whose other coefficients and variance components were estimated from the same data, so the p-value is only as well calibrated as that fit stands in for the truth, and the usual large-sample argument for it needs many strata and a stratum variance away from zero. It is poorest with few strata, where the stratum variance rests on a handful of units and is often estimated at exactly zero. In simulations of a binomial MAIHDA (120 per stratum, stratum SD 0.5, every dimension effect zero) it rejected at about 14% for a nominal 5% with 4 strata and about 7% with 8, where the Wald z rejected at about 40% and 19%. The excess sits in the fits whose stratum variance is singular – more than half of them at 4 strata, rejecting at about 23% against 3% on the rest. So it removes most of the z's error but not all of it, and a p-value near the threshold from a singular fit on few strata deserves little weight.
n_boot sets the Monte Carlo resolution, not that approximation. The
p-value lies on a grid of step \(1 / (B + 1)\), the smallest attainable
value being \(1 / (B + 1)\) – the added one keeps it off zero – and more
draws shrink the Monte Carlo error of the p-value and of the interval
endpoints, which are order statistics of the draws. Both converge on what an
unlimited bootstrap would give: the interval settles at a fixed, non-zero
width rather than narrowing without end, and at a given seed a larger
n_boot can widen it. No n_boot makes the test exact – on the
4-stratum design above, 19 draws and 99 draws both rejected at about 14%.
It costs n_boot refits per tested coefficient – one block per
row of the table except the intercept, so a \(k\)-level factor costs
\(k - 1\) of them – and is a separate bootstrap from the
bootstrap = TRUE VPC interval, which is not reused.
Budget for it. A Gaussian refit takes milliseconds, but a binomial one takes
about a second at \(n = 1000\) and tens of seconds at \(n = 6000\), so the
default n_boot = 1000 on a three-dimension GLMM is roughly an hour at
the smaller size and impractical at the larger. n_boot = 199 is the
usual compromise for a GLMM; 199 and 999 are conventional because
(n_boot + 1) * 0.05 is then a whole number, which puts the 5% level
itself on the p-value grid. Give it fewer draws than the level needs and
summary() warns: what makes the critical value unsteady is how few of
them lie at or beyond it rather than n_boot on its own, so the check is
level-aware. 99, 199 and 999 are the smallest counts that put ten draws beyond
the cut-off at the 10%, 5% and 1% levels; 199 draws leave only two beyond a
99% one, and fewer than 19 cannot reach the 5% level at all, which leaves the
interval unbounded.
Two VPCs for a longitudinal fit
A longitudinal summary reports two different variance partitions, and they are not interchangeable. They differ in one term of the denominator: the level-1 (occasion) residual variance \(\sigma^2_e\), which in a growth model is within-individual volatility – how far a single measurement falls from that person's own smooth trajectory. It mixes measurement error, genuine short-term fluctuation, and any misfit of the assumed functional form. A cross-sectional MAIHDA cannot separate it from between-individual variance at all; repeated measures are what split the two.
The headline VPC (vpc, and longitudinal$vpc_t over time)
keeps it:
$$VPC_S(t) = \frac{Var_S(t)}{Var_S(t) + Var_I(t) + \sigma^2_e}.$$
This is the discriminatory-accuracy question – how much of an observed
measurement at time \(t\) a stratum accounts for – and it is the quantity
comparable to published cross-sectional MAIHDA VPCs. Report this one unless you
specifically mean the trajectory question.
The trajectory VPCs (longitudinal$vpc_intercept and
longitudinal$vpc_slope) drop it, following Bell et al. (2024), equation
(5):
$$VPC_{intercept} = \frac{Var_S(t_0)}{Var_S(t_0) + Var_I(t_0)}, \qquad
VPC_{slope} = \frac{SlopeVar_S(t_0)}{SlopeVar_S(t_0) + SlopeVar_I(t_0)}.$$
These ask how intersectionally patterned people's trajectories are i.e., what
share of the between-individual variation in where a trajectory starts, and in how
fast it changes, lies between strata. Because \(\sigma^2_e\) is absent neither is
affected by how noisy the outcome measure is, which makes them comparable across
studies using different instruments.
Both are evaluated at the baseline \(t_0\) (ref_time, the earliest
observed time), pairing with PCV_intercept and PCV_slope from
maihda(decomposition = "longitudinal"). The intercept VPC depends on where
time is zeroed and the slope VPC does not, as Bell et al. note; their own examples
centre on mean age rather than the baseline, so an intercept VPC replicated from
the paper will differ from the one reported here unless the reference points are
aligned. vpc_slope is NA when the model was fit with
stratum_slope = FALSE (no between-stratum slope variance exists to take a
share of).
Both come with an interval in vpc_intercept_ci / vpc_slope_ci,
and trajectory_vpc_method records its basis. For a brms fit the
two shares are computed per posterior draw and reported as the posterior
median with a credible interval, matching vpc_t and the headline VPC on
the same fit; for an lme4 fit the point estimates are the plug-in from
the fitted covariance blocks and summary(bootstrap = TRUE) adds a
parametric-bootstrap interval (NA without one). Report the interval:
these shares are poorly determined when the strata are few, and one spanning
half the unit interval is an ordinary result rather than an unusual one –
the twelve strata of maihda_long_data, fitted with brms over 150
individuals, give an intercept VPC of 0.58 running from 0.34 to 0.82.
References
Bell, A., Evans, C., Holman, D., & Leckie, G. (2024). Extending intersectional multilevel analysis of individual heterogeneity and discriminatory accuracy (MAIHDA) to study individual longitudinal trajectories, with application to mental health in the UK. Social Science & Medicine, 351, 116955. doi:10.1016/j.socscimed.2024.116955
Examples
# \donttest{
strata_result <- make_strata(maihda_sim_data, vars = c("gender", "race"))
model <- fit_maihda(health_outcome ~ age + (1 | stratum), data = strata_result$data)
summary_result <- summary(model)
# With bootstrap CI
# summary_boot <- summary(model, bootstrap = TRUE, n_boot = 50)
# }
