statspai.multilevel¶
multilevel ¶
Multilevel / mixed-effects models.
Exports
mixed, MixedResult Linear mixed effects (Gaussian response)
meglm, melogit, mepoisson,
menbreg, megamma, meologit,
MEGLMResult Generalised linear mixed models via
Laplace approximation or adaptive
Gauss-Hermite quadrature (nAGQ).
icc Intra-class correlation with 95% CI
lrtest Likelihood-ratio test between two fitted
mixed models (with chi-bar² boundary
correction when variance components are
being tested)
MixedResult
dataclass
¶
Bases: ResultProtocolMixin
Container for sp.mixed() estimation results.
The public attributes mirror Stata mixed and R lme4::lmer
output; the underscored fields carry implementation state used by
methods like predict() and r_squared().
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(42)
>>> g = np.repeat(np.arange(30), 10)
>>> x = rng.normal(size=300)
>>> y = 2.0 + 0.5 * x + rng.normal(0, 1.0, 30)[g] + rng.normal(0, 0.5, 300)
>>> df = pd.DataFrame({"y": y, "x": x, "school": g})
>>> res = sp.mixed(df, y="y", x_fixed=["x"], group="school")
>>> type(res).__name__
'MixedResult'
>>> bool("x" in res.fixed_effects.index)
True
>>> bool(0.0 <= res.icc <= 1.0) # share of variance at the school level
True
conf_int ¶
Wald confidence intervals for the fixed effects.
ranef ¶
Return BLUPs (posterior means of the random effects).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditional_se
|
bool
|
If |
False
|
predict ¶
Fitted / predicted values.
include_random=True returns X β̂ + Z û (conditional on the
realised random effects), which is the Stata predict , fitted
default. include_random=False returns the marginal /
population prediction X β̂.
For unseen groups (i.e. groups absent from training data), the random-effect contribution is treated as zero, which is the best-linear unbiased prediction when nothing is known about the new cluster.
r_squared ¶
Nakagawa & Schielzeth (2013) marginal and conditional R².
Definitions, in our Gaussian LMM:
σ²_f = variance of the fixed-effect predictions Xβ
σ²_r = sum of random-effect variance contributions
(trace of G weighted by Z'Z/n for random slopes)
σ²_ε = residual variance
R²_marginal = σ²_f / (σ²_f + σ²_r + σ²_ε)
R²_conditional = (σ²_f + σ²_r) / (σ²_f + σ²_r + σ²_ε)
wald_test ¶
Wald joint test of linear restrictions on the fixed effects.
restrictions can be:
- A list of parameter names — tests those coefficients are zero.
- A 2-D array / DataFrame R with
R β = 0(rows = restrictions, columns ordered asself.fixed_effects.index).
plot ¶
Quick diagnostic plots.
kind='caterpillar' produces a forest plot of BLUPs with
posterior-SE intervals (the lme4 standard) for a chosen random
effect (default: intercept). kind='residuals' plots the
conditional residuals against fitted values.
MEGLMResult
dataclass
¶
Bases: ResultProtocolMixin
Container for GLMM fits (meglm, melogit, mepoisson,
megamma, menbreg, meologit).
Exposes the fitted fixed_effects / random_effects,
variance_components, BLUPs, and information criteria (aic /
bic). Family-specific helpers include :meth:odds_ratios
(binomial), :meth:incidence_rate_ratios (Poisson / NB), plus
:meth:summary, :meth:predict, :meth:to_latex, and :meth:plot.
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(20), 12)
>>> x = rng.normal(size=240)
>>> u = rng.normal(0, 0.7, 20)[g]
>>> p = 1.0 / (1.0 + np.exp(-(-0.3 + 0.8 * x + u)))
>>> y = (rng.uniform(size=240) < p).astype(float)
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.melogit(df, y="y", x_fixed=["x"], group="gid")
>>> isinstance(res, sp.MEGLMResult)
True
>>> bool(np.isfinite(res.aic))
True
dispersion
property
¶
Estimated dispersion (φ for gamma, α for nbinomial); None otherwise.
odds_ratios ¶
Exponentiated fixed effects with Wald CIs (binomial / ordinal).
incidence_rate_ratios ¶
Exponentiated coefficients for log-link count GLMMs (Poisson / NB).
to_latex ¶
Booktabs LaTeX fragment; mirrors MixedResult.to_latex.
plot ¶
Diagnostic plot for the GLMM fit.
Only kind='caterpillar' is currently supported: a forest plot
of the BLUPs for one random effect (default: the intercept).
Posterior SEs are not returned by meglm at present, so the
error bars are omitted.
predict ¶
predict(data: Optional[DataFrame] = None, include_random: bool = True, type: str = 'response') -> Series
Predict response or linear predictor.
type='response' returns μ = g⁻¹(η); type='linear' returns
η itself. include_random=True adds Z û for groups seen at
fit time.
meglm ¶
meglm(data: DataFrame, y: str, x_fixed: Sequence[str], group: str, family: str = 'gaussian', x_random: Optional[Sequence[str]] = None, cov_type: str = 'unstructured', trials: Optional[str] = None, offset: Optional[str] = None, nAGQ: int = 1, maxiter: int = 300, tol: float = 1e-08, alpha: float = 0.05) -> MEGLMResult
Fit a generalised linear mixed model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Long-format dataframe. |
required |
y
|
str
|
Outcome column. For binomial models this is the number of
successes; pair it with |
required |
x_fixed
|
Sequence[str]
|
Fixed-effect regressors (intercept added automatically). |
required |
group
|
str
|
Grouping variable for random effects. |
required |
family
|
str
|
|
'gaussian'
|
x_random
|
Optional[Sequence[str]]
|
Random-slope variables; defaults to random intercept only. |
None
|
cov_type
|
str
|
Random-effect covariance: |
'unstructured'
|
trials
|
Optional[str]
|
Column of trial counts for binomial responses. Defaults to 1 (Bernoulli). |
None
|
offset
|
Optional[str]
|
Column of fixed offsets added to the linear predictor (e.g.
|
None
|
nAGQ
|
int
|
Number of adaptive Gauss-Hermite quadrature points per scalar
random effect. |
1
|
maxiter
|
int
|
Optimisation controls / CI width. The default |
300
|
tol
|
int
|
Optimisation controls / CI width. The default |
300
|
alpha
|
int
|
Optimisation controls / CI width. The default |
300
|
Returns:
| Type | Description |
|---|---|
MEGLMResult
|
|
References
breslow1993
Examples:
Random-intercept logistic GLMM on simulated grouped binary data:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(20), 12)
>>> x = rng.normal(size=240)
>>> u = rng.normal(0, 0.7, 20)[g]
>>> p = 1.0 / (1.0 + np.exp(-(-0.3 + 0.8 * x + u)))
>>> y = (rng.uniform(size=240) < p).astype(float)
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.meglm(df, y="y", x_fixed=["x"], group="gid", family="binomial")
>>> res.family, res.link
('binomial', 'logit')
>>> bool(res.fixed_effects["x"] > 0) # recovers the positive slope
True
melogit ¶
melogit(data: DataFrame, y: str, x_fixed: Sequence[str], group: str, x_random: Optional[Sequence[str]] = None, trials: Optional[str] = None, nAGQ: int = 1, **kw: Any) -> MEGLMResult
Random-effects logistic regression (Stata melogit).
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(20), 12)
>>> x = rng.normal(size=240)
>>> u = rng.normal(0, 0.7, 20)[g]
>>> p = 1.0 / (1.0 + np.exp(-(-0.3 + 0.8 * x + u)))
>>> y = (rng.uniform(size=240) < p).astype(float)
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.melogit(df, y="y", x_fixed=["x"], group="gid")
>>> res.family
'binomial'
>>> bool(res.fixed_effects["x"] > 0)
True
mepoisson ¶
mepoisson(data: DataFrame, y: str, x_fixed: Sequence[str], group: str, x_random: Optional[Sequence[str]] = None, offset: Optional[str] = None, nAGQ: int = 1, **kw: Any) -> MEGLMResult
Random-effects Poisson regression (Stata mepoisson).
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(20), 12)
>>> x = rng.normal(size=240)
>>> u = rng.normal(0, 0.7, 20)[g]
>>> y = rng.poisson(np.exp(0.2 + 0.5 * x + u)).astype(float)
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.mepoisson(df, y="y", x_fixed=["x"], group="gid")
>>> res.family, res.link
('poisson', 'log')
>>> bool(res.fixed_effects["x"] > 0)
True
menbreg ¶
menbreg(data: DataFrame, y: str, x_fixed: Sequence[str], group: str, x_random: Optional[Sequence[str]] = None, offset: Optional[str] = None, nAGQ: int = 1, **kw: Any) -> MEGLMResult
Random-effects negative-binomial regression (Stata menbreg).
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(20), 12)
>>> x = rng.normal(size=240)
>>> u = rng.normal(0, 0.7, 20)[g]
>>> y = rng.poisson(np.exp(0.2 + 0.5 * x + u)).astype(float)
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.menbreg(df, y="y", x_fixed=["x"], group="gid")
>>> res.family
'nbinomial'
>>> res.dispersion is not None # NB-2 overdispersion alpha
True
megamma ¶
megamma(data: DataFrame, y: str, x_fixed: Sequence[str], group: str, x_random: Optional[Sequence[str]] = None, offset: Optional[str] = None, nAGQ: int = 1, **kw: Any) -> MEGLMResult
Random-effects Gamma GLMM with log link (Stata meglm family(gamma)).
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(20), 12)
>>> x = rng.normal(size=240)
>>> u = rng.normal(0, 0.7, 20)[g]
>>> y = np.exp(0.3 + 0.4 * x + u) * rng.gamma(4.0, 0.25, size=240)
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.megamma(df, y="y", x_fixed=["x"], group="gid")
>>> res.family, res.link
('gamma', 'log')
>>> res.dispersion is not None # phi estimated by ML
True
meologit ¶
meologit(data: DataFrame, y: str, x_fixed: Sequence[str], group: str, x_random: Optional[Sequence[str]] = None, cov_type: str = 'unstructured', offset: Optional[str] = None, nAGQ: int = 1, maxiter: int = 300, tol: float = 1e-06, alpha: float = 0.05) -> MEGLMResult
Random-effects ordinal logit (Stata meologit, R ordinal::clmm).
The outcome y is treated as ordered categorical with K levels.
If y is numeric it is coerced to integer codes 1..K based on
sorted unique values. Other dtypes (str, pandas categorical) are
coerced via pd.Categorical and ordered by appearance.
No intercept enters β — its role is taken by the K-1 thresholds.
Returns an :class:MEGLMResult with family='ordinal' and
thresholds populated.
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(25), 12)
>>> x = rng.normal(size=300)
>>> u = rng.normal(0, 0.6, 25)[g]
>>> latent = 0.9 * x + u + rng.logistic(size=300)
>>> y = np.ones(300, dtype=int)
>>> y[latent > -1.0] = 2
>>> y[latent > 1.0] = 3
>>> df = pd.DataFrame({"y": y, "x": x, "gid": g})
>>> res = sp.meologit(df, y="y", x_fixed=["x"], group="gid")
>>> isinstance(res, sp.MEGLMResult)
True
>>> res.family
'ordinal'
>>> bool(res.fixed_effects["x"] > 0) # recovers the positive slope
True
icc ¶
icc(result: Any, component: str = '_cons', alpha: float = 0.05, n_boot: int = 0, seed: Optional[int] = None) -> ICCResult
Intra-class correlation for a fitted mixed model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
Any
|
A |
required |
component
|
str
|
Name of the random-effect variance to put in the numerator.
Defaults to the random intercept ( |
'_cons'
|
alpha
|
float
|
Significance level for the confidence interval. Default 0.05. |
0.05
|
n_boot
|
int
|
Number of parametric bootstrap replicates used to compute the
CI. |
0
|
seed
|
Optional[int]
|
RNG seed forwarded to :func: |
None
|
Returns:
| Type | Description |
|---|---|
ICCResult
|
|
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(42)
>>> g = np.repeat(np.arange(30), 10)
>>> x = rng.normal(size=300)
>>> y = 2.0 + 0.5 * x + rng.normal(0, 1.0, 30)[g] + rng.normal(0, 0.5, 300)
>>> df = pd.DataFrame({"y": y, "x": x, "school": g})
>>> res = sp.mixed(df, y="y", x_fixed=["x"], group="school")
>>> rho = sp.icc(res)
>>> bool(0.0 <= float(rho) <= 1.0) # share of variance at the school level
True
lrtest ¶
Likelihood-ratio test comparing a restricted and a full model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
restricted
|
Any
|
Two fitted mixed models. |
required |
full
|
Any
|
Two fitted mixed models. |
required |
boundary
|
'bool | None'
|
Whether to apply the χ̄² boundary correction. When |
None
|
Returns:
| Type | Description |
|---|---|
LRTestResult
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the two fits have different response families (e.g. one is binomial, the other Poisson) — their log-likelihoods are not comparable and a naive LR statistic is meaningless. |
ValueError
|
If either fit used REML and the fixed-effect design differs between the two models. REML log-likelihoods are only comparable across fits that share the same fixed-effect design matrix; always use ML for LR tests of fixed effects. |
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(42)
>>> g = np.repeat(np.arange(30), 10)
>>> x = rng.normal(size=300)
>>> y = 2.0 + 0.5 * x + rng.normal(0, 1.0, 30)[g] + rng.normal(0, 0.5, 300)
>>> df = pd.DataFrame({"y": y, "x": x, "school": g})
>>> restricted = sp.mixed(df, y="y", x_fixed=["x"], group="school",
... method="ml")
>>> full = sp.mixed(df, y="y", x_fixed=["x"], group="school",
... x_random=["x"], method="ml")
>>> res = sp.lrtest(restricted, full) # test the random slope on x
>>> bool(0.0 <= res.p_value <= 1.0)
True