Mixture Models
Maturity: experimental — see Feature Maturity for what this means.
A mixture model treats each subject as belonging to one of \(K\) unobserved subpopulations (“classes”). Class \(k\) has its own set of class-specific typical values (and, optionally, its own \(\Omega\) / \(\Sigma\)), and a subject is assigned to a class only probabilistically, through covariate-dependent mixing probabilities. This is the ferx analogue of NONMEM’s $MIXTURE.
Model file
Declare the number of classes and the mixing rule in a [mixture] block, and select class-specific typical values with the reserved read-only MIXNUM index (1..=K) inside [individual_parameters]:
[parameters]
theta TVCL1(1.0); theta TVCL2(3.0) # class-specific clearance
theta TVV(10.0)
theta MIXL(0.0) # mixing logit intercept
omega ETA_CL ~ 0.09
sigma EPS ~ 0.04
[mixture]
nsub = 2
logit(1) = MIXL # p(1) = σ(logit); p(2) = 1 − p(1)
# omega(2) ETA_CL ~ 0.15 # optional per-class Ω override
# sigma(2) EPS ~ 0.05 # optional per-class Σ override
[individual_parameters]
CL = if (MIXNUM == 1) TVCL1 * exp(ETA_CL) else TVCL2 * exp(ETA_CL)
V = TVV
[structural_model]
pk one_cpt_iv(cl=CL, v=V)
[error_model]
DV ~ proportional(EPS)The mixing rule may be given as a logit (logit(k) = …) or a probability (p(k) = …) expression over thetas and covariates; the remaining class’s probability is the complement. Mixing expressions must not depend on any \(\eta\).
The two forms differ only in how \(p_{ik}\) is formed: logit(k) is passed through a softmax (reference class \(K\) has logit 0), while p(k) is used as the probability directly and the class weights are renormalised to sum to one. A constant p(1) = 0.3 is therefore identical to logit(1) = \ln(0.3/0.7). The softmax (logit) form has an analytic posterior-weighted outer gradient; the p(k) form does not, so a p(k) fit uses the finite-difference gradient.
Objective
Each subject’s marginal likelihood is the covariate-weighted mixture of its class-conditional likelihoods, and the population objective is the numerically stable log-sum-exp of the per-class FOCE/FOCEI negative log-likelihoods:
\[ p_{ik} = \operatorname{softmax}_k\big(g_k(\theta,\, \text{cov}_i)\big), \qquad L_i = \sum_{k=1}^{K} p_{ik}\, L_{ik}, \]
\[ \text{OFV} = -2 \sum_{i=1}^{N} \log \sum_{k=1}^{K} p_{ik}\, \exp(-\text{nll}_{ik}). \]
nll_ik is the ordinary per-subject FOCE/FOCEI negative log-likelihood evaluated with class-\(k\) parameters and a separate empirical Bayes solve per (subject × class). The mixing-logit coefficients are ordinary thetas and any per-class \(\Omega\)/\(\Sigma\) are estimated jointly. Estimation defaults to the derivative-free BOBYQA outer optimizer; an analytic posterior-weighted outer gradient is available, so a user-selected NLopt gradient optimizer (SLSQP / L-BFGS / MMA) is honoured, with an automatic finite-difference fallback for models outside analytic scope (e.g. MIXNUM-branched typical values).
Output
For a mixture fit, sdtab gains two per-subject diagnostics, matching the NONMEM reserved variables:
MIXEST— the most-probable class \(\operatorname{argmax}_k \text{PMIX}_{ik}\) (1-based).PMIX_1 … PMIX_K— the posterior class-membership probabilities \(\text{PMIX}_{ik} \propto p_{ik}\exp(-\text{nll}_{ik})\), normalised over \(k\).
Every other per-subject diagnostic is evaluated in that subject’s MIXEST class: the reported \(\hat\eta\) are the winning class’s EBEs, and IPRED, PRED, IWRES, CWRES, EBE_OFV, and any [derived] / [output] column that branches on MIXNUM are all computed with MIXNUM set to that class. So a subject the fit assigns to class 2 gets class-2 typical values throughout its sdtab row, not the class-1 default.
Validation against NONMEM
A two-class clearance mixture (tests/nonmem/mixture_iv.ctl and the slow-gated tests/mixture_nonmem.rs) was fit by both engines on the same 30-subject IV dataset — two subpopulations with CL ≈ 1 and CL ≈ 3, a constant mixing fraction, and \(\Omega\)/\(\Sigma\) fixed at the data-generating values (METHOD=1 INTER in NONMEM; FOCEI in ferx). NONMEM’s minimization was successful.
| Quantity | NONMEM 7.5.1 | ferx |
|---|---|---|
| OFV (without constant) | 301.582 | 301.600 |
TVCL1 (class 1) |
0.981 | ≈ 0.98 |
TVCL2 (class 2) |
2.842 | ≈ 2.84 |
V |
9.979 | ≈ 9.98 |
mixing fraction p(1) |
0.472 | ≈ 0.47 |
MIXEST classification |
— | 30 / 30 subjects agree |
The objectives agree to ~0.02 units and the per-subject MIXEST class assignment is identical for every subject.
Standard errors
A mixture fit runs the covariance step like any other fit: the finite-difference Hessian is built on the mixture objective (the K-fold log-sum-exp above), and the standard errors are \(\sqrt{\operatorname{diag}(R^{-1})}\). The mixing-fraction SE is reported on the scale the mixing form is written in — the coefficients of a logit(k) = … form carry a logit-scale SE, a p(k) = … probability an SE directly on the probability scale — because those coefficients are ordinary thetas.
Cross-checked against $COVARIANCE MATRIX=R on the same model (tests/nonmem/mixture_iv_cov.ctl); MATRIX=R is the pure Hessian-inverse estimator ferx computes (NONMEM’s default sandwich \(R^{-1}SR^{-1}\) differs by the usual 10–25 % between the two estimators):
| SE | NONMEM (MATRIX=R) |
ferx |
|---|---|---|
TVCL1 |
0.1056 | 0.1067 |
TVCL2 |
0.2543 | 0.2559 |
V |
0.1861 | 0.1860 |
p(1) (delta-method) |
0.1026 | 0.1030 |
All four agree to < 1 %. The p(1) row compares NONMEM’s direct \(P(1)=\theta_4\) SE to ferx’s logit-scale SE mapped through \(\operatorname{SE}(p) = p(1-p)\operatorname{SE}(\text{logit})\).
Inter-occasion variability under a mixture
A mixture may carry inter-occasion variability (a kappa on a structural parameter) — the two features compose (#985). Each class’s per-subject inner solve estimates the per-occasion κ̂ under that class’s typical values, and the FOCE/FOCEI marginal L_ik is the κ-augmented occasion likelihood, so the same L_i = Σ_k p_ik L_ik mixture is formed over IOV-aware class likelihoods. The IOV Ω is shared across classes (it sits in the base parameter block, ahead of any per-class Ω/Σ override), matching NONMEM’s $OMEGA BLOCK(1) … SAME occasion structure. The analytic outer gradient does not yet emit κ-slot derivatives for a mixture, so an IOV mixture optimises against a central-difference outer gradient; the covariance step runs as usual on the K-fold mixture objective.
NONMEM cross-check (tests/nonmem/mixture_iv_iov.ctl)
Two-class bimodal-CL model with a per-occasion κ on CL (30 subjects × 2 occasions), IIV/IOV Ω and Σ FIXed at the data-generating values in both engines, METHOD=1 INTER:
| Quantity | NONMEM 7.5.1 | ferx |
|---|---|---|
| OFV (without constant) | 470.814 | 470.930 |
TVCL1 (class 1) |
1.057 | 1.069 |
TVCL2 (class 2) |
3.330 | 3.275 |
V |
10.259 | 10.253 |
mixing fraction p(1) |
0.526 | 0.506 |
MIXEST classification |
— | 30 / 30 subjects agree |
The objectives agree to ~0.12 units and every subject’s most-probable class matches. Standard errors are not cross-checked for this model: NONMEM’s covariance step terminates with rounding errors and substitutes the R matrix (R MATRIX SUBSTITUTED: YES, no printable SE block) — a known NONMEM fragility for mixture + IOV covariance — whereas ferx’s covariance step completes. The mixture SE machinery itself is anchored to $COVARIANCE MATRIX=R in the non-IOV standard-errors comparison above.
SAEM under a mixture
Mixtures can also be estimated with SAEM (method = saem, #985). Where FOCE/FOCEI marginalise the latent class analytically (the log-sum-exp above), SAEM samples it: each E-step draws the class indicator \(z_i \sim \operatorname{Categorical}(\mathrm{PMIX}_i)\) from the current per-subject posterior \(\mathrm{PMIX}_{ik} \propto p_{ik}\,e^{-\mathrm{nll}_{ik}}\), then runs the usual η-MCMC within the drawn class (its Ω/Σ and its MIXNUM branch). The M-step partitions the sufficient statistics by sampled class:
- Class-switched typical values (
if MIXNUM == k …) are estimated from each class’s own members — the ordinary θ/σ M-step run under each subject’s class gives \(\partial\text{pred}/\partial\text{TVCL}_2 = 0\) for a class-1 subject, so no special handling is needed. They are not mu-referenced under SAEM; see Mu-referencing under a mixture below. - Class-shared typical values (
V = TVV * exp(ETA_V), the same theta in every class) do take the closed-form mu-referencing shift \(\log\theta \mathrel{+}= \gamma\,\operatorname{mean}_i(\eta_i)\), exactly as in a single-population SAEM fit (#996). - Per-class Ω overrides (
omega(k)) use per-class stochastic-approximation diagonal statistics. - Mixing coefficients are updated from a small optimisation of the SA-averaged expected complete-data mixing log-likelihood \(\sum_i \sum_k \bar r_{ik}\,\ln p_{ik}(\theta_\text{mix})\), where \(\bar r_{ik}\) is the Robbins–Monro average of the sampled class indicators. This is exact for constant mixing (it recovers \(p_k = \operatorname{mean}_i \bar r_{ik}\)) and for covariate-dependent logit mixing (a weighted logistic fit) alike.
The reported OFV, per-subject MIXEST/PMIX, and standard errors are computed from the K-fold mixture marginal at the SAEM optimum, exactly as for FOCEI. Per-class σ overrides (sigma(k)) are the one piece not yet re-estimated under SAEM — they are held at their initial values (with a warning), and every class-k subject is scored under that held σ, so the free base σ is estimated only from the classes that actually share it. Being held, the overrides report a standard error of 0, as FIXed parameters do; route the model to FOCEI if they must be fit. HMC and the conditional-distribution pass are disabled for a mixture (they are class-unaware); the MH E-step is used.
Two further SAEM-specific rules follow from the separated M-step:
- A mixing theta marked
FIXis honoured — it is excluded from the mixing M-step and stays at its declared value, so a known mixture fraction can be pinned. - A theta that drives both the mixing expression and a structural typical value is rejected with an error rather than silently double-owned (SAEM estimates the mixing coefficients and the structural thetas in separate M-steps). Split it into two thetas, or fit with FOCE/FOCEI, whose joint marginal handles the shared parameter.
Mu-referencing under a mixture
A MIXNUM-switched typical value pairs one η with K class thetas, so the classical pooled mu-referencing move \(\log\theta \mathrel{+}= \gamma\,\operatorname{mean}_i(\eta_i)\) (one theta per η) does not apply to it directly. ferx detects the class-aware form at parse time — a chain of MIXNUM == k branches whose arms are all THETA_k * exp(ETA) (or exp(log(THETA_k) + ETA)) on the same ETA — and resolves one anchor theta per class. Anything else (mixed anchors, a different ETA per arm, an additive arm, a non-MIXNUM condition, an out-of-range class) falls back to the numerical M-step, and the parser says so in a warning rather than degrading silently. The warning is only raised for expressions that actually carry an ETA: a class-switched typical value written without IIV (V = if (MIXNUM == 1) TVV1 else TVV2) or an intermediate MIXNUM flag variable is not a missed mu-reference and is left alone.
Each anchor theta belongs to one η. If two η are written against the same typical value, only the first keeps the closed-form shift — applying both would move that θ twice per iteration — and the other η’s θ go to the numerical / weighted M-step.
What each estimator does with that anchor set differs, and the difference is empirical:
| Estimator | Class-shared θ | Class-switched θ |
|---|---|---|
| SAEM | closed-form pooled shift | numerical M-step |
| IMP / IMPMAP (estimating) | closed-form shift | responsibility-weighted closed-form shift |
SAEM deliberately keeps the switched θ numerical. It draws one hard class per subject each E-step, so the per-class mean \(\operatorname{mean}_{i:c_i=k}(\eta_i)\) is a classification-EM statistic: the class boundary is re-drawn from the \(\theta_k\) that the shift just moved, and the two feed back. Measured on the two-class anchor (tests/nonmem/mixture_iv.csv, seed 20250818) the class-aware variant lands at TVCL2 = 3.02 with OFV 305.0, against 2.75 / 302.2 for the numerical M-step — worse on every coordinate (NONMEM SAEM: 2.735; FOCEI optimum: 2.842). A Rao-Blackwellised soft-responsibility variant converges to the same wrong point, so the bias is in the hard-class sufficient statistic rather than the weighting. IMP/IMPMAP importance-samples η within every class and weights by the responsibilities, so it has no hard assignment and no such feedback.
Setting mu_referencing = false disables the class-aware shift under both estimators and restores the pre-#996 behaviour (every θ through the numerical / weighted M-step). Note that for a single-population IMP/IMPMAP fit the closed-form shift is EM-mandatory and stays on regardless of that option; only the mixture path is switchable.
Two further conditions turn an individual θ’s shift off, on either path, each with a warning naming the parameter:
- A θ declared with a negative lower bound is packed on the identity scale, so
log θ += mean(η)would applyθ += mean(η)where the closed form meansθ *= exp(mean(η)). Give the parameter a non-negative lower bound to keep the closed-form update. - A class that receives no responsibility mass in an iteration has no per-class η mean to shift with. Its θ is handed back to the weighted M-step for that iteration rather than held fixed, and the fit warns at the end; a class that collapses this way usually means the mixture is over-specified.
When the class-aware shift is off for a class θ — by any of these routes, or by mu_referencing = false — that θ is estimated by the importance-weighted M-step alone, which is biased for weakly-identified parameters (#406). IMP/IMPMAP fits say so in a warning; cross-check such a fit against FOCEI.
Under IOV, the per-occasion κ is sampled inside each subject’s drawn class — under that class’s MIXNUM branch and its Ω/Σ — so a class-switched typical value is never absorbed into κ.
NONMEM cross-check (tests/nonmem/mixture_iv_saem.ctl)
The two-class constant-mixing model above, estimated with $EST METHOD=SAEM (NBURN=1000 NITER=1000 ISAMPLE=2) followed by a METHOD=IMP EONLY=1 objective-evaluation pass, Ω/Σ FIXed:
| Quantity | NONMEM SAEM | ferx SAEM |
|---|---|---|
| OFV (IMP evaluation) | 300.87 | 302.17 |
TVCL1 (class 1) |
1.002 | 0.982 |
TVCL2 (class 2) |
2.735 | 2.746 |
V |
9.993 | 10.089 |
mixing fraction p(1) |
0.4712 | 0.4670 |
MIXEST classification |
— | 29 / 30 agree (1 borderline) |
Estimates agree to ≤ 3 %. The single MIXEST disagreement is a subject whose posterior sits almost exactly on the class boundary, which NONMEM SAEM and ferx SAEM split marginally differently. Covariate-dependent mixing under SAEM is exercised end-to-end in tests/mixture_nonmem.rs.
Bayesian estimation under a mixture
Mixtures can also be fit with full-MCMC Bayesian estimation (method = bayes, #985). The latent class is handled Rao-Blackwellised — marginalised out rather than sampled — so the sampler still sees a smooth continuous target: each subject’s likelihood contribution is the K-class marginal \(-\log \sum_k p_{ik}\,\exp(-\mathrm{nll}_{ik})\). Because every Gibbs block evaluates the per-subject likelihood through this one marginal, the whole Gibbs-within-MH sampler becomes mixture-aware in one place:
- the η block samples \(\eta_i\) against the class-marginal posterior (a
chol(Ω)-preconditioned random walk; HMC is disabled for mixtures because its analytic gradient is single-class); - the (θ, σ) block — which also carries the mixing thetas — samples them against the marginal, so the mixing coefficients (constant or covariate-dependent logit mixing) are sampled with no extra machinery;
- the Ω block draws the (class-shared) Ω from its conjugate full conditional;
- the κ block (inter-occasion variability, #985) samples the per-occasion \(\kappa_{ik}\) against the same marginal, so κ targets the class-marginal posterior rather than class 1’s.
Ω/Σ are shared across classes (the classes differ only through the MIXNUM-switched typical values); per-class Ω/Σ overrides (omega(k)/sigma(k)) are rejected with a clear error — fit those with FOCE/FOCEI. The reported OFV, per-subject MIXEST/PMIX, and EBEs are the K-fold marginal values at the posterior mean.
Priors, label switching, and the reported OFV
The population priors are weakly informative normals, \(\mathcal{N}(u_0, 10^2)\) on the unconstrained scale (log θ where θ is positive, log σ), centred on the initial estimates \(u_0\) from the model file. Any [parameters] bounds you declare are not enforced as a uniform prior inside the (θ, σ) Metropolis block — only the mu-referenced conjugate θ move clamps to them — so a mixing logit is kept in range by the prior, not by its declared bounds.
Because the classes are identified only through those priors, a chain can swap labels (class 1 ↔︎ class 2) and the pooled posterior mean would then average across two modes and estimate neither class. The signature is a high split-R̂ on the class-switched θ; ferx emits a warning naming label switching when R̂ exceeds its convergence threshold on a mixture. Separating the classes in the initial estimates is the practical fix.
The OFV reported for a Bayes mixture run is the Laplace K-fold marginal at the posterior mean (not the posterior-mean joint NLL ×2 that a non-mixture Bayes run reports), and — like every mixture marginal — it is the FOCE or FOCEI form depending on interaction. In a method chain (method = [saem, bayes]) that flag follows the chain’s last estimator, so compare mixture OFVs only across runs with the same interaction setting.
Under diffuse priors the Bayes posterior mean concentrates at the maximum- likelihood optimum, so it recovers the same estimates the FOCEI/SAEM anchors pin (observed on tests/nonmem/mixture_iv.csv: posterior mean TVCL1 ≈ 0.94, TVCL2 ≈ 2.78, V ≈ 10.0, p(1) ≈ 0.47, marginal OFV ≈ 301.8, vs the FOCEI optimum 0.98 / 2.84 / 9.98 / 0.472 / 301.58; the two class means sit slightly inside the MLE pair, the direction partial label mixing pulls them). That shared optimum — already NONMEM-anchored by the FOCEI run above — is the Bayes cross-check: a direct NONMEM METHOD=BAYES reference is not used because NONMEM’s BAYES sampler aborts in burn-in on this model (it insists on Gibbs-sampling Ω, which is FIXed here), a known NONMEM $MIX+BAYES fragility. The same mixture-marginal machinery is anchored directly against NONMEM SAEM above.
IMP objective evaluation under a mixture
Importance sampling evaluates the class-marginal likelihood for a mixture (method = imp with imp_eval_only, NONMEM METHOD=IMP EONLY=1, #985). For each subject and each class it runs the class-k MAP inner solve (under the MIXNUM guard), importance-samples the class-conditional marginal \(L_{ik}\), and combines them exactly as FOCEI does:
\[ -2\log L = -2\sum_i \log \sum_k p_{ik}\,L_{ik}. \]
This is the Monte-Carlo marginal likelihood — the number to compare against NONMEM’s #OBJV for IMP EONLY — and it is what a chained fit reports on FitResult.importance_sampling. The reported per-subject ESS is the worst ESS among the classes that subject actually loads on (classes with \(\mathrm{PMIX}_{ik}\approx 0\) are excluded, since a poor proposal there barely enters the marginal), so imp_low_ess_threshold flags mixture subjects the same way it does single-population ones. The usual workflow is to estimate with SAEM (or FOCE/FOCEI) and append an evaluation stage:
[fit_options]
method = [saem, imp]
imp_eval_only = trueOn tests/nonmem/mixture_iv.csv this recovers the NONMEM IMP EONLY objective to well under a Monte-Carlo standard error: ferx −2 log L = 300.82 ± 0.06 vs NONMEM 300.87 (mixture_iv_saem.ctl’s second $EST), with ess_min/K = 0.82 and ess_med/K = 0.91.
Estimating IMP / IMPMAP
Importance sampling also estimates a mixture (method = imp / impmap without imp_eval_only) via a class-partitioned MCEM. Each iteration importance- samples every subject’s η within each class (under the MIXNUM guard), forms the deterministic class responsibilities \(\mathrm{PMIX}_{ik} \propto p_{ik}L_{ik}\), then runs responsibility-weighted M-steps: the mixing coefficients from the responsibilities, the class-shared Ω from \(\sum_i\sum_k \mathrm{PMIX}_{ik}\,\widehat{\eta\eta^{\top}}_{ik}/N\), and the class-switched typical values from a responsibility- and importance-weighted observation M-step (each class’s samples scored under its own MIXNUM). Ω/σ are class-shared (per-class overrides are rejected); IOV / FREM / SDE are not supported (as for the single-population MCEM).
The class-switched typical values are mu-referenced per class (#996). Because the MCEM importance-samples η within every class, a per-class posterior mean \(\bar\eta_{ic}\) exists directly, and the EM move is the responsibility-weighted
\[\log\theta_k \mathrel{+}= \frac{\sum_i r_{ic}\,\bar\eta_{ic}}{\sum_i r_{ic}}, \qquad r_{ic} = \mathrm{PMIX}_{ic}\]
summed over the classes \(\theta_k\) serves. Those θ are pinned out of the weighted M-step, which then fits only σ and the remaining θ. A class-shared typical value repeats the same theta in every class slot, so it collapses to the pooled single-population shift. See Mu-referencing under a mixture for the detection rules and for why SAEM does not do the same.
This is what closes the old gap: on the two-class anchor, estimating IMPMAP now recovers TVCL1 = 0.952, TVCL2 = 2.821, V = 10.008, p(1) = 0.472, against 0.949 / 2.819 / 10.010 / 0.473 from NONMEM IMPMAP with MU_1 assigned inside the MIXNUM branch (tests/nonmem/mixture_iv_impmap_mu.ctl, NM 7.6.0) — better than 0.5 % on every coordinate. With mu_referencing = false the same run falls back to the weighted M-step alone and returns TVCL1 = 0.998, TVCL2 = 2.597, which is the pre-#996 behaviour and shows how much of the accuracy the shift buys. Note that the EM estimators settle slightly below the FOCEI optimum (0.98 / 2.84) on TVCL1/TVCL2; NONMEM’s mu-referenced IMPMAP does the same.
A θ whose paired ω is negligible (ω < 1e-3) carries no mean-shift information, so it is routed to the weighted M-step with a warning, as in the single-population MCEM; so is a θ declared with a negative lower bound (identity-packed, so the additive log-scale shift is not its EM optimum). The fit also names any estimated θ that carries no ETA at all — the mixing thetas are excluded, since they are owned by the mixing M-step.
The per-subject proposal machinery is the same as the single-population MCEM: each class’s draws get their own ISCALE pilot search (the guard against importance-weight collapse), and impmap_mceta runs its multi-start MAP within every class. Two IMPMAP options are not wired for mixtures and warn when set: impmap_trace (no iteration trace is produced) and impmap_auto / imp_auto (the sample count stays fixed).
Not yet supported
The following error clearly rather than silently mis-fitting:
- Per-class σ overrides (
sigma(k)) under SAEM (held at their initial values, with a warning), and per-class Ω/σ overrides under Bayes and estimating IMP/IMPMAP (rejected) — use FOCE/FOCEI to fit those. - IOV / FREM / SDE under the mixture IMP paths (objective evaluation and estimation) — use FOCE/FOCEI.
- A theta shared between the
[mixture]mixing expression and a structural typical value, under SAEM and under estimating IMP/IMPMAP. Both run the mixing M-step separately from the θ/σ M-step, so a shared theta would be double-owned; FOCE/FOCEI’s joint marginal handles it.