--- title: "Choosing response families" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Choosing response families} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` This article helps users choose response families for distributional regression data: continuous outcomes, counts, proportions, percentages, positive measurements, shape-sensitive outcomes, and ordinal scores. Examples emphasize ecological, evolutionary, and environmental applications. If your open question is about which scale, weights, or known variance you are modelling rather than which family, read [Which scale are you modelling?](which-scale.html) first. For current fit and reporting boundaries, use [Can I fit and report this model?](capability-and-limits.html). Meta-analysis is not listed as a separate family. It uses `family = gaussian()` with `meta_V(V = V)`, while deprecated `meta_known_V(V = V)` remains a compatibility alias. ## At a glance Start from the measurement process, not from the name of the distribution. The table below summarizes the implemented first-pass families and the main quantity each `sigma` formula models. | Response type | First family to try | Distributional parameters | What `sigma` means | Main current limit | | --- | --- | --- | --- | --- | | Continuous, symmetric residuals | `gaussian()` | `mu`, `sigma` | residual standard deviation | most mature path; non-Gaussian extensions are separate | | Continuous with heavy tails | `student()` | `mu`, `sigma`, `nu` | Student-t scale, not exactly residual SD | ordinary `mu` random intercepts/slopes plus an exact recovery-grade q=1 `mu ~ spatial(1 + x | ...)` gate; the intercept-only spatial `mu` and `nu ~ phylo()` gates are diagnostic-only; other `sigma`/`nu` random effects remain planned | | Two heavy-tailed continuous responses | `biv_student()` | `mu1`, `mu2`, `sigma1`, `sigma2`, shared `nu`, `rho12` | two Student-t scales and scatter/residual correlation | exact fixed-effect complete-pair source slice; at finite `nu`, zero `rho12` is uncorrelated but not independent; no random effects, scale/shape/correlation predictors, intervals, smoke, recovery, or capability claim | | Continuous with asymmetric residuals | `skew_normal()` | `mu`, `sigma`, `nu` | response standard deviation; `nu` is residual slant | ordinary `mu` random intercepts and independent slopes are fitted; the exact slope cell is inference-ready with caveats for true SD 0.50 and M>=16; `sigma`/`nu` random effects and structured routes are planned | | Positive multiplicative response | `lognormal()` | `mu`, `sigma` | SD on the log-response scale | `mu` random intercepts/slopes and a `sigma` random intercept are fitted; only the exact ledger domain for the `sigma` intercept is inference-ready with caveats | | Two positive multiplicative responses | `biv_lognormal()` | `mu1`, `mu2`, `sigma1`, `sigma2`, `rho12` | log-response SDs and log-residual correlation | exact fixed-effect complete-pair slice; `rho12` is not raw-scale correlation or latent `eta`; direct profile is primary only within the tested fixed-effect n/rho coverage ladder, while random effects, sigma/rho predictors, and wider claims remain deferred | | Positive response with mean-CV interpretation | `Gamma(link = "log")` | `mu`, `sigma` | coefficient of variation | `mu` random intercepts/slopes and a `sigma` random intercept are fitted; its exact ordinary log-`sigma` intercept domain is inference-ready with caveats at true SD 0.40, `n_each=12`, and `M >= 32` (`M=16` borderline); `sigma` slopes and bivariate models are planned | | Non-negative semicontinuous response with exact zeros | `tweedie()` | `mu`, `sigma`, `nu` | public scale with `phi = sigma^2` | ordinary `mu` random intercepts and independent slopes are fitted; the exact slope cell is inference-ready with caveats for true SD 0.50 and M>=16; `nu ~ 1` remains intercept-only and other distributional random effects are planned | | Continuous proportion in `(0, 1)` | `beta()` | `mu`, `sigma` | public scale mapped to beta precision | ordinary `mu` random intercepts/slopes and exact q=1 `mu`/`sigma ~ animal()` recovery-grade gates are fitted; exact 0/1 boundary values use `zero_one_beta()` | | Continuous proportion in `[0, 1]` with structural exact 0 or 1 values | `zero_one_beta()` | `mu`, `sigma`, `zoi`, `coi` | public interior beta scale mapped to precision | ordinary `mu` random intercepts and independent slopes are fitted; exact ordinary `zoi` q1 intercept/same-raw-symbol slope plus `coi` q1 intercept/same-raw-symbol slope routes are fitted at point-fit recovery only; broader atom random effects are planned | | Event indicator or successes out of known trials | `stats::binomial(link = "logit")` | `mu` | no modelled `sigma`; ordinary binomial sampling variation | ordinary `mu` random intercepts and independent slopes are fitted; use `cbind(successes, failures)` for trial totals | | Successes out of known trials | `beta_binomial()` | `mu`, `sigma` | extra-binomial variation | `mu` random intercepts and independent slopes implemented (recovery grade); `sigma` random effects and bivariate models planned | | Count baseline or rate with exposure | `poisson(link = "log")` | `mu`, optional `zi` | no modelled `sigma` | ordinary non-zero-inflated `mu` random intercepts and independent slopes plus q=1 `phylo()`/`spatial()`/`animal()`/`relmat()` `mu` intercept-plus-one-slope routes are fitted; one diagnostic-only q=1 `zi ~ spatial(1 | id, coords = coords)` intercept is also fitted; pure, multiple, or labelled structured slopes and other `zi` random effects are planned | | Overdispersed count or rate | `nbinom2()` | `mu`, `sigma`, optional `zi` | extra-Poisson dispersion scale | ordinary non-zero-inflated `mu` random intercepts and independent slopes, grouped `sigma` random intercepts, q=1 `phylo()`/`spatial()`/`animal()`/`relmat()` `mu` intercept-plus-one-slope routes, and separate q=1 structured `sigma` intercept-plus-one-slope routes (recovery grade) are fitted; plain `sigma` random slopes, richer or labelled structured routes, and `zi` random effects are planned | | Positive count, zeros absent by design | `truncated_nbinom2()` | `mu`, `sigma` | NB2 dispersion for the untruncated component | `mu` random intercepts and independent slopes implemented (recovery grade; not with `hu`); `sigma` random effects and bivariate models planned | | Count with a separate zero process | `truncated_nbinom2()` plus `hu ~` | `mu`, `sigma`, `hu` | NB2 dispersion for nonzero counts | fixed-effect hurdle models plus one diagnostic-only q=1 `hu ~ relmat(1 | id, K/Q = ...)` intercept route are fitted; other `hu` random effects and structured routes are planned | | Ordered categories | `cumulative_logit()` | `mu`, cutpoints | fixed latent logistic scale | ordinary `mu` random intercepts/slopes plus one diagnostic-only q=1 `mu ~ phylo(1 | id, tree = tree)` intercept are fitted; other structured ordinal effects and scale/discrimination are planned | | Two continuous Gaussian responses | `c(gaussian(), gaussian())` | `mu1`, `mu2`, `sigma1`, `sigma2`, `rho12` | residual SDs for each response | matching labelled `mu1`/`mu2`, `sigma1`/`sigma2`, and same-response `mu`/`sigma` intercept or slope-only covariance pairs implemented; richer covariance planned | The source-level family map in `docs/design/02-family-registry.md` records the links, shape or coscale slots, random-effect allowance, and test evidence behind each row. The supported non-Gaussian evidence goal is intentionally narrower than "everything non-Gaussian." It closes the fixed-effect family evidence and the first count mixed-model slices listed here, while leaving blocked neighbours as future likelihood and simulation work. For non-Gaussian random effects, the current fitted path is deliberately narrow. The eligible ordinary routes accept unlabelled `mu` random intercepts and independent numeric slopes at recovery grade, such as `(1 | site)` and `(0 + effort | site)`, inside their family-specific boundaries. These routes include Student-t, skew-normal, lognormal, Gamma, Tweedie, beta, zero-one beta, beta-binomial, binomial, Poisson, NB2, truncated NB2, and cumulative-logit models. Zero-inflated and hurdle neighbours remain blocked except for the exact structured gates named below; in particular, an active `hu` formula does not admit ordinary count-side `mu` random effects. Ordinary Poisson and ordinary NB2 also have q=1 structured `mu` intercept-plus-one-slope routes using one of `phylo()`, `spatial()`, `animal()`, or `relmat()` on the log-mean scale. Ordinary NB2 separately has the same four q=1 structured `sigma` intercept-plus-one-slope routes at recovery grade. Ordinary NB2, lognormal, and Gamma also have first grouped scale routes, `sigma ~ z + (1 | id)`, on their family-specific log-`sigma` scales. The lognormal and Gamma scale routes must be fitted separately from their `mu` random-effect routes. Tweedie still requires intercept-only `nu ~ 1`, and skew-normal `nu` remains fixed-effect residual slant. Other non-Gaussian random-effect requests are still boundary checks: Tweedie distributional random effects or predictor-dependent power, skew-normal `sigma`/`nu` random effects or structured effects, NB2/lognormal/Gamma plain `sigma` random slopes, combined lognormal/Gamma `mu`+`sigma` random effects, and NB2 `sigma` structured routes beyond the one-slope gate, Student-t `nu` random effects beyond the exact diagnostic-only q=1 `nu ~ phylo(1 | id, tree = tree)` intercept, zero-inflation random effects beyond the exact diagnostic-only q=1 Poisson `zi ~ spatial()` intercept, hurdle random effects beyond the exact diagnostic-only q=1 `hu ~ relmat(1 | id, K/Q = ...)` intercept route, ordinal mixed models beyond ordinary `mu` intercepts/slopes and the exact diagnostic-only q=1 `mu ~ phylo(1 | id, tree = tree)` intercept, other non-count `spatial()`/`animal()`/`relmat()` structured effects beyond the implemented recovery-grade routes (Gamma `relmat()` on `mu`, beta `animal()` on `mu`/`sigma`, Student-t `spatial()` on `mu`), pure, multiple, or labelled structured count slopes, simultaneous structured count effects beyond the exact crossed NB2 `mu ~ spatial(1 | site, coords = coords) + relmat(1 | id, Q = Q)` recovery-only gate, and correlated or labelled ordinary count random-slope blocks need their own likelihood and recovery evidence before they become runnable syntax. Fixed-effect Wald intervals are available where the fixed coefficient covariance is available; profile targets remain limited to direct optimized parameters listed by `profile_targets()`. The current design keeps constructor names close to base R where possible. For example, exposure in count models is written with the standard formula term `offset(log(exposure))`, and Gamma models use `stats::Gamma(link = "log")` because `gamma()` already names the base R gamma function. ## Why `sigma` is not always `phi` `drmTMB` uses `sigma` as the public variability-facing scale slot. This does not mean every likelihood is written internally with a standard deviation. Instead, the package keeps the modelling grammar stable and records the family-specific conversion. The rule is simple: larger fitted `sigma` should mean larger modelled variability. That rule is different from some comparator packages. For example, [glmmTMB's `sigma()` documentation](https://glmmtmb.github.io/glmmTMB/reference/sigma.glmmTMB.html) reports a beta precision `phi`, where larger `phi` decreases the variance, and an NB2 size parameter often written `theta` or `k`, where larger values also lower the variance. In `drmTMB`, these precision-like parameters are internal or comparator quantities: | Family | drmTMB public scale | Internal or comparator parameter | Variability direction | | --- | --- | --- | --- | | `gaussian()` | residual SD `sigma` | residual SD | larger `sigma` means larger residual variance | | `Gamma(link = "log")` | coefficient of variation `sigma` | shape `1 / sigma^2` | larger `sigma` means larger response CV | | `tweedie()` | public scale `sigma` | dispersion `phi = sigma^2` | larger `sigma` means larger semicontinuous-response variance | | `beta()` | public scale `sigma` | precision `phi = 1 / sigma^2` | larger `sigma` means lower precision and larger variance | | `zero_one_beta()` | interior beta scale `sigma` | precision `phi = 1 / sigma^2` for the interior component | larger `sigma` means more variation among non-boundary proportions | | `beta_binomial()` | extra-binomial scale `sigma` | precision `phi = 1 / sigma^2` | larger `sigma` means more among-trial probability variation | | `nbinom2()` | overdispersion scale `sigma` | size `theta = 1 / sigma^2` | larger `sigma` means more extra-Poisson variation | | `student()` | scale `sigma`; shape `nu` | degrees of freedom `nu` | larger `sigma` widens the core scale; larger `nu` lightens tails | | `skew_normal()` | response SD `sigma`; slant `nu` | native skew-normal scale `omega` after moment transform | larger `sigma` widens residuals; positive `nu` gives right skew | So a `sigma` coefficient answers a variability question in the same direction across implemented mean-scale families. If a paper, package, or diagnostic uses `phi`, `theta`, `k`, shape, precision, or variance, convert explicitly before comparing numerical values. ## Reporting variation The formula name `sigma` is stable across families, but the reported variation is family-specific. Start with `predict(fit, dpar = "mu")` and `sigma(fit)`, then transform those fitted quantities for the scale your reader needs. | Family | Variation-facing summary | | --- | --- | | `gaussian()` | residual SD is `sigma`; residual variance is `sigma^2` | | `student()` | model scale is `sigma`; residual variance is `sigma^2 * nu / (nu - 2)` when `nu > 2` | | `skew_normal()` | response residual SD is `sigma`; `nu` controls residual asymmetry | | `lognormal()` | log-scale variance is `sigma^2`; response variance is `(exp(sigma^2) - 1) * exp(2 * mu + sigma^2)` | | `Gamma(link = "log")` | residual SD is `mu * sigma`; variance is `(mu * sigma)^2` | | `tweedie()` | mean is `mu`; variance is `sigma^2 * mu^nu`, with `1 < nu < 2` | | `beta()` | variance is `mu * (1 - mu) * sigma^2 / (1 + sigma^2)` | | `zero_one_beta()` | mean is `(1 - zoi) * mu + zoi * coi`; variance combines the interior beta second moment with exact boundary mass | | `beta_binomial()` | proportion variance is `mu * (1 - mu) * (1 + trials * sigma^2) / (trials * (1 + sigma^2))` | | `poisson(link = "log")` | variance is `mu`; there is no modelled `sigma` | | zero-inflated Poisson | variance is `(1 - zi) * mu * (1 + zi * mu)` | | `nbinom2()` | variance is `mu + sigma^2 * mu^2` | | zero-inflated `nbinom2()` | variance is `(1 - zi) * (mu + sigma^2 * mu^2) + zi * (1 - zi) * mu^2` | | `truncated_nbinom2()` and hurdle models | use the zero-truncated or hurdle mean and variance described in the count-family section | | bivariate Gaussian | marginal variances are `sigma1^2` and `sigma2^2`; residual covariance is `rho12 * sigma1 * sigma2` | For Gaussian location-scale examples, `sigma^2` is a residual variance. Do not carry that shortcut to Gamma, beta, count, zero-inflated, hurdle, or bivariate models without the family-specific transformation. ## Implemented univariate families Gaussian location-scale models use `mu` and `sigma`: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i &\sim \operatorname{Normal}(\mu_i, \sigma_i^2),\\ \mu_i &= \beta_0 + \beta_1 \text{temperature}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i. \end{aligned} \] ```r drmTMB( bf(y ~ temperature, sigma ~ treatment), family = gaussian(), data = dat ) ``` Student-t location-scale-shape models add `nu`, the degrees-of-freedom or tail-shape parameter: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i, \nu_i &\sim \operatorname{Student\text{-}t}(\mu_i, \sigma_i, \nu_i),\\ \mu_i &= \beta_0 + \beta_1 \text{temperature}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \nu_i &= 2 + \exp(\delta_0). \end{aligned} \] ```r drmTMB( bf(y ~ temperature, sigma ~ treatment, nu ~ 1), family = student(), data = dat ) ``` Use this first Student-t path when a continuous response is mostly well described by a location-scale model but has heavier-tailed residuals than a Gaussian model. Here `sigma` is the Student-t scale parameter; when `nu > 2`, the residual standard deviation is `sigma * sqrt(nu / (nu - 2))`. The `mu` predictor accepts random intercepts and independent numeric slopes at recovery grade (trust the point estimate, not the interval). Scale and shape random effects (`sigma`, `nu`), meta-analytic known covariance, and additional phylogenetic terms are planned later. A separate narrow `biv_student()` route fits two complete responses with one shared intercept-only `nu`; it is source-tested only and does not carry the univariate recovery evidence. The tutorial "Robust continuous responses" shows the equation, syntax, diagnostics, and a Gaussian comparison for this family. Skew-normal location-scale-shape models add `nu` for residual asymmetry: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i, \nu_i &\sim \operatorname{SkewNormalMoment}(\mu_i, \sigma_i, \nu_i),\\ \mu_i &= \beta_0 + \beta_1 \text{temperature}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \nu_i &= \delta_0 + \delta_1 \text{habitat}_i. \end{aligned} \] ```r drmTMB( bf(y ~ temperature, sigma ~ treatment, nu ~ habitat), family = skew_normal(), data = dat ) ``` Use this first skew-normal path when the residual distribution is asymmetric after modelling the mean and residual scale. Here `mu` is the arithmetic response mean, `sigma` is the response standard deviation, and `nu` is the residual slant: positive values indicate right-skewed residuals, negative values indicate left-skewed residuals, and `nu = 0` reduces to the Gaussian location-scale likelihood. Random effects, `sd(group)`, known sampling covariance, phylogenetic or spatial terms, bivariate responses, residual `rho12`, and latent `skew(id)` syntax are planned later. Lognormal location-scale models are implemented for positive continuous responses such as biomass, body mass, concentration, time, and area: \[ \begin{aligned} \log(y_i) \mid \mu_i, \sigma_i &\sim \operatorname{Normal}(\mu_i, \sigma_i^2),\\ \mu_i &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ E[y_i] &= \exp(\mu_i + \sigma_i^2 / 2). \end{aligned} \] ```r drmTMB( bf(biomass ~ habitat, sigma ~ treatment), family = lognormal(), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` returns the mean of `log(y)`, `sigma(fit)` returns the standard deviation of `log(y)`, and `fitted(fit)` returns the arithmetic response mean. Use this path when multiplicative variation is scientifically natural and the response values are positive and finite. A `mu` random intercept and independent slopes run at recovery grade. An ordinary log-`sigma` random intercept is also fitted; only the exact tested domain in capability cell `mc-0382` is inference-ready with caveats, with mildly anti-conservative rather than nominal coverage. Fit the `mu` and `sigma` random-effect routes separately; the package rejects their combination. Scale slopes, known sampling covariance, phylogenetic terms, and bivariate lognormal extensions beyond the separate fixed-effect complete-pair slice are planned later. Gamma mean-CV models are implemented for positive continuous responses where relative variability is the scale target: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i &\sim \operatorname{Gamma}(\text{shape}_i, \text{scale}_i),\\ \log(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \text{shape}_i &= 1 / \sigma_i^2,\\ \text{scale}_i &= \mu_i \sigma_i^2,\\ E[y_i] &= \mu_i,\\ \operatorname{Var}(y_i) &= \mu_i^2\sigma_i^2. \end{aligned} \] ```r drmTMB( bf(biomass ~ habitat, sigma ~ treatment), family = Gamma(link = "log"), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` and `fitted(fit)` return the response mean. `sigma(fit)` returns the coefficient of variation, not the residual standard deviation; the residual standard deviation is `mu * sigma`. Use this path for positive responses such as biomass, body mass, metabolic rate, or concentration when predictors may change relative variability. The first implementation requires `stats::Gamma(link = "log")`; `stats::Gamma()` with its default inverse link is rejected, and `drmTMB` does not export `gamma()` because `base::gamma()` already exists. Gamma also fits one ordinary log-`sigma` random intercept. It is `inference_ready_with_caveats` only for the certified ML-Laplace profile-interval domain (true SD 0.40, `n_each=12`, and `M >= 32`; `M=16` is borderline). Fit it separately from the ordinary `mu` random-effect route; combining `mu` and `sigma` random effects is rejected. Gamma `sigma` slopes, labels, REML, and any `supported` claim remain outside that evidence. Tweedie mean-scale-power models are implemented for non-negative semicontinuous responses with exact zeros and positive continuous values: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i, \nu_i &\sim \operatorname{Tweedie}(\mu_i, \phi_i, \nu_i),\\ \log(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \nu_i &= 1 + \operatorname{logit}^{-1}(\delta_0),\\ \phi_i &= \sigma_i^2,\\ E[y_i] &= \mu_i,\\ \operatorname{Var}(y_i) &= \sigma_i^2 \mu_i^{\nu_i}. \end{aligned} \] ```r drmTMB( bf(biomass ~ habitat, sigma ~ treatment, nu ~ 1), family = tweedie(), data = dat ) ``` For this family, `fitted(fit)` returns the unconditional response mean `mu`, including exact-zero mass. `sigma(fit)` returns public `sigma`, the square root of Tweedie dispersion `phi`. Use this path for biomass, cover, CPUE-like indices, or other non-negative field summaries where zeros and positive continuous values are generated by one measurement process. Random effects, predictor-dependent `nu`, structured effects, bivariate or mixed-response Tweedie models, zero-inflation aliases, and hurdle aliases remain planned. Beta mean-scale models are implemented for continuous responses strictly inside `(0, 1)`, such as continuously measured cover proportions or rates that are not generated as successes out of known trials: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i &\sim \operatorname{Beta}(\alpha_i, \beta_i),\\ \operatorname{logit}(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \phi_i &= 1 / \sigma_i^2,\\ \alpha_i &= \mu_i\phi_i,\\ \beta_i &= (1 - \mu_i)\phi_i,\\ E[y_i] &= \mu_i,\\ \operatorname{Var}(y_i) &= \frac{\mu_i(1 - \mu_i)\sigma_i^2}{1 + \sigma_i^2}. \end{aligned} \] ```r drmTMB( bf(cover ~ habitat, sigma ~ treatment), family = beta(), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` and `fitted(fit)` return the mean proportion. `sigma(fit)` returns the public scale parameter, not beta precision; internally `phi = 1 / sigma^2`, so larger `sigma` means more variation around the mean. Responses equal to 0 or 1 use `zero_one_beta()` when those endpoints are structural outcomes. Percentages derived from counts should keep their denominator. Use `stats::binomial(link = "logit")` for ordinary Bernoulli/binomial event probabilities with no extra-binomial variation, or `beta_binomial()` when success probabilities vary beyond binomial sampling. The tutorial [Proportions and success rates](proportion-beta-binomial.html) works through the bounded-response routes. Zero-one beta mean-scale-boundary models are implemented for continuous proportions on `[0, 1]` when exact 0 or 1 values are structural outcomes rather than binomial count outcomes: \[ \begin{aligned} \Pr(y_i = 0) &= zoi_i(1 - coi_i),\\ \Pr(y_i = 1) &= zoi_i coi_i,\\ \Pr(0 < y_i < 1) &= 1 - zoi_i,\\ \operatorname{logit}(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \operatorname{logit}(zoi_i) &= \delta_0 + \delta_1 \text{drought}_i,\\ \operatorname{logit}(coi_i) &= \kappa_0 + \kappa_1 \text{canopy}_i,\\ E[y_i] &= (1 - zoi_i)\mu_i + zoi_i coi_i. \end{aligned} \] ```r drmTMB( bf(cover ~ habitat, sigma ~ treatment, zoi ~ drought, coi ~ canopy), family = zero_one_beta(), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` returns the interior beta mean. `predict(fit, dpar = "zoi")` returns the probability of an exact 0 or exact 1 response, and `predict(fit, dpar = "coi")` returns the probability that a boundary response is exactly 1. `fitted(fit)` returns the unconditional mean including boundary mass. Ordinary unlabelled `mu` random intercepts and independent numeric slopes are fitted at recovery grade. Exact ordinary `zoi ~ 1 + (1 | id)`, same-raw-symbol `zoi ~ x + (0 + x | id)`, `coi ~ 1 + (1 | id)`, and same-raw-symbol `coi ~ x + (0 + x | id)` q1 routes are also fitted at point-fit recovery only. For both `coi` routes, retained population-level recovery used `M = 64` and 50 observations per group; sparse observed atoms or weak boundary-row predictor spread can weaken individual conditional modes, so inspect both before interpreting them. Their limitations are reported as warnings: direct profiles, intervals, coverage, joint atom effects, transformed or mismatched atom slopes, structured effects, known covariance, denominator syntax, and bivariate bounded-response models remain unavailable. Poisson mean models are implemented for count responses: \[ \begin{aligned} y_i \mid \mu_i &\sim \operatorname{Poisson}(\mu_i),\\ \log(\mu_i) &= \log(\text{trap\_nights}_i) + \beta_0 + \beta_1 \text{habitat}_i,\\ E[y_i] &= \operatorname{Var}(y_i) = \mu_i. \end{aligned} \] ```r drmTMB( bf(count ~ habitat + offset(log(trap_nights))), family = poisson(link = "log"), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` and `fitted(fit)` return the count mean. There is no fitted `sigma` distributional parameter; `sigma(fit)` returns a fixed unit dispersion vector only for base-R method compatibility. The `offset(log(trap_nights))` term is the standard R exposure form: the model estimates a rate per trap night while the expected count remains proportional to sampling effort. Use this path as a baseline count model and as a comparator for later overdispersed count families. Biological count data with extra-Poisson variation will usually need `nbinom2()`; COM-Poisson remains a later route for underdispersion or dispersion patterns that NB2 does not describe well. Zero-inflated Poisson models are implemented by adding a `zi` formula: \[ \begin{aligned} y_i \mid \mu_i, zi_i &\sim \operatorname{ZIP}(\mu_i, zi_i),\\ \log(\mu_i) &= \log(\text{trap\_nights}_i) + \beta_0 + \beta_1 \text{habitat}_i,\\ \operatorname{logit}(zi_i) &= \gamma_0 + \gamma_1 \text{survey\_method}_i,\\ E[y_i] &= (1 - zi_i)\mu_i. \end{aligned} \] ```r drmTMB( bf(count ~ habitat + offset(log(trap_nights)), zi ~ survey_method), family = poisson(link = "log"), data = dat ) ``` For this model, `predict(fit, dpar = "mu")` returns the conditional Poisson mean, `predict(fit, dpar = "zi")` returns the structural-zero probability, and `fitted(fit)` returns `(1 - zi) * mu`. There is no separate `zi_poisson()` constructor in the current public API. Negative-binomial 2 mean-dispersion models are implemented for overdispersed count responses: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i &\sim \operatorname{NB2}(\mu_i, \text{size}_i),\\ \log(\mu_i) &= \log(\text{trap\_nights}_i) + \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \text{size}_i &= 1 / \sigma_i^2,\\ E[y_i] &= \mu_i,\\ \operatorname{Var}(y_i) &= \mu_i + \sigma_i^2\mu_i^2. \end{aligned} \] ```r drmTMB( bf(count ~ habitat + offset(log(trap_nights)), sigma ~ treatment), family = nbinom2(), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` and `fitted(fit)` return the count mean. `sigma(fit)` returns the overdispersion scale in the variance equation, not a residual standard deviation. Larger `sigma` means greater extra-Poisson variation. The implementation uses the equivalent `stats::dnbinom(mu = mu, size = 1 / sigma^2)` parameterization internally. The tutorial [Count abundance and extra zeros](count-nbinom2.html) works through this `sigma`-to-size conversion with a soil-invertebrate example. Zero-inflated NB2 models use the same `nbinom2()` family and add a `zi` formula for structural zeros: \[ \begin{aligned} y_i \mid \mu_i, \sigma_i, zi_i &\sim \operatorname{ZINB2}(\mu_i, \sigma_i, zi_i),\\ \log(\mu_i) &= \log(\text{trap\_nights}_i) + \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \operatorname{logit}(zi_i) &= \delta_0 + \delta_1 \text{survey\_method}_i,\\ \text{size}_i &= 1 / \sigma_i^2,\\ E[y_i] &= (1 - zi_i)\mu_i,\\ \operatorname{Var}(y_i \mid \text{count component}) &= \mu_i + \sigma_i^2\mu_i^2. \end{aligned} \] ```r drmTMB( bf(count ~ habitat + offset(log(trap_nights)), sigma ~ treatment, zi ~ survey_method), family = nbinom2(), data = dat ) ``` For this model, `predict(fit, dpar = "mu")` and `sigma(fit)` describe the conditional NB2 count component. `predict(fit, dpar = "zi")` returns the structural-zero probability, and `fitted(fit)` returns the unconditional response mean `(1 - zi) * mu`. There is no separate `zi_nbinom2()` constructor in the current public API. Fit this route only when the data story has a plausible structural-zero process; otherwise start with ordinary NB2 and diagnostics. Zero-truncated NB2 models are implemented for positive counts where zeros are absent by design, such as clutch size among breeding individuals or parasite load among infected hosts: \[ \begin{aligned} y_i \mid y_i > 0, \mu_i, \sigma_i &\sim \operatorname{NB2}_{+}(\mu_i, \sigma_i),\\ \log(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \text{size}_i &= 1 / \sigma_i^2,\\ \Pr_{+}(y_i) &= \Pr_{\operatorname{NB2}}(y_i) / \left(1 - \Pr_{\operatorname{NB2}}(0)\right),\\ E[y_i \mid y_i > 0] &= \mu_i / \left(1 - \Pr_{\operatorname{NB2}}(0)\right),\\ q_i &= 1 - \Pr_{\operatorname{NB2}}(0),\\ \operatorname{Var}(y_i \mid y_i > 0) &= \frac{\mu_i + (1 + \sigma_i^2)\mu_i^2}{q_i} - \left(\frac{\mu_i}{q_i}\right)^2. \end{aligned} \] ```r drmTMB( bf(count ~ habitat, sigma ~ treatment), family = truncated_nbinom2(), data = dat ) ``` For this family, `predict(fit, dpar = "mu")` and `sigma(fit)` describe the untruncated NB2 count component. `fitted(fit)` returns the expected observed positive count, `mu / (1 - Pr_NB2(0))`. Without a `hu` formula, the implementation rejects zeros because the sampling model is conditional on positive counts. Hurdle NB2 models are implemented by adding `hu ~ predictors` to the same family route. Use this when zeros are modelled by a separate process and all nonzero counts come from a zero-truncated count distribution: \[ \begin{aligned} \operatorname{logit}(hu_i) &= \delta_0 + \delta_1 \text{survey\_method}_i,\\ \Pr(y_i = 0) &= hu_i,\\ \Pr(y_i = k > 0) &= (1 - hu_i)\Pr_{+}(k \mid \mu_i, \sigma_i),\\ E[y_i] &= (1 - hu_i)\mu_i / \left(1 - \Pr_{\operatorname{NB2}}(0)\right),\\ \operatorname{Var}(y_i) &= (1 - hu_i)\operatorname{Var}_{+i} + hu_i(1 - hu_i)E_{+i}^2. \end{aligned} \] ```r drmTMB( bf(count ~ habitat, sigma ~ treatment, hu ~ survey_method), family = truncated_nbinom2(), data = dat ) ``` Here `hu` is the hurdle-zero probability. `predict(fit, dpar = "mu")` still returns the untruncated NB2 component mean, while `fitted(fit)` returns the unconditional response mean. The design deliberately uses `hu` as a formula component, parallel to the implemented `zi` component, instead of adding a separate `hurdle_nbinom2()` constructor. One structured hurdle route is also fitted as diagnostic-only: an unlabelled q=1 relatedness intercept in `hu`, supplied as either a covariance matrix `K` or precision matrix `Q`: ```r drmTMB( bf(count ~ habitat, sigma ~ treatment, hu ~ relmat(1 | id, K = K)), family = truncated_nbinom2(), data = dat ) ``` Treat this as a point-estimate route only. Structured `phylo()`, `spatial()`, or `animal()` hurdle effects, structured hurdle slopes, labels, intervals, and coverage remain planned. Use `zi` when the count distribution can still generate sampling zeros and you want an extra structural-zero process. Use `hu` when zeros are modelled separately and all nonzero counts come from a zero-truncated count distribution. ## Bounded and ordered families Percentages derived from counts should keep their denominator. Use `stats::binomial(link = "logit")` when the response is an event indicator or a count of successes out of known trials with ordinary binomial sampling variation. Write the response as `y01` for 0/1 data or `cbind(successes, failures)`, where `trials_i = successes_i + failures_i`; do not use `weights = trials`, `successes / trials`, or `cbind(successes, trials)`. \[ \begin{aligned} y_i \mid n_i, \mu_i &\sim \operatorname{Binomial}(n_i, \mu_i),\\ \operatorname{logit}(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ E[y_i / n_i] &= \mu_i,\\ \operatorname{Var}(y_i / n_i) &= \mu_i(1 - \mu_i) / n_i. \end{aligned} \] ```r drmTMB( bf(cbind(successes, failures) ~ habitat), family = stats::binomial(link = "logit"), data = dat ) ``` For plain binomial models, `mu_i` is the fitted event probability. `fitted(fit)` returns probabilities; multiply by `successes_i + failures_i` when the scientific summary is the expected number of successes. Ordinary `mu` random intercepts and independent numeric slopes are fitted first slices. Only the exact independent-slope domain in capability cell `mc-0061` is inference-ready with caveats; correlated or labelled slopes remain unsupported. Factor responses, proportions with trial weights, structured effects, bivariate responses, and `engine = "julia"` remain unsupported. Use `beta_binomial()` when the response is still counted successes out of known trials but the data show extra-binomial variation. It uses the same `cbind(successes, failures)` denominator syntax and adds a modelled `sigma` parameter for among-row probability variation. \[ \begin{aligned} y_i \mid n_i, p_i &\sim \operatorname{Binomial}(n_i, p_i),\\ p_i \mid \mu_i, \sigma_i &\sim \operatorname{Beta}(\alpha_i, \beta_i),\\ \operatorname{logit}(\mu_i) &= \beta_0 + \beta_1 \text{habitat}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i,\\ \phi_i &= 1 / \sigma_i^2,\\ \alpha_i &= \mu_i\phi_i,\\ \beta_i &= (1 - \mu_i)\phi_i,\\ E[y_i / n_i] &= \mu_i,\\ \operatorname{Var}(y_i / n_i) &= \frac{\mu_i(1 - \mu_i)(1 + n_i\sigma_i^2)} {n_i(1 + \sigma_i^2)}. \end{aligned} \] ```r drmTMB( bf(cbind(successes, failures) ~ habitat, sigma ~ treatment), family = beta_binomial(), data = dat ) ``` For beta-binomial models, `mu_i` is the fitted success probability and `sigma` describes extra-binomial variation, not residual standard deviation. `fitted(fit)` returns probabilities; multiply by `successes_i + failures_i` when the scientific summary is the expected number of successes. The tutorial [Proportions and success rates](proportion-beta-binomial.html) gives a plain event-probability example and a denominator-aware seed-germination example. Ordinal models are implemented first as fixed-effect univariate models with ordered cutpoints and a fixed latent logistic scale: \[ \begin{aligned} \Pr(y_i \le k) &= \operatorname{logit}^{-1}(\theta_k - \mu_i),\\ \mu_i &= \beta_1 \text{habitat}_i,\\ \theta_1 &< \theta_2 < \cdots < \theta_{K-1}. \end{aligned} \] Although the R formula below is `bf(score ~ habitat)`, the cumulative-logit fit drops the location intercept internally because a free location intercept and free cutpoints are not identifiable together. ```r set.seed(1) n <- 120 habitat <- rnorm(n) eta <- 0.8 * habitat p_low <- plogis(-0.9 - eta) p_medium <- plogis(0.7 - eta) - p_low score_id <- vapply(seq_len(n), function(i) { sample.int( 3, size = 1, prob = c(p_low[i], p_medium[i], 1 - p_low[i] - p_medium[i]) ) }, integer(1)) dat <- data.frame( score = ordered(c("low", "medium", "high")[score_id], levels = c("low", "medium", "high") ), habitat = habitat ) fit_ordinal <- drmTMB( bf(score ~ habitat), family = cumulative_logit(), data = dat ) check_drm(fit_ordinal) summary(fit_ordinal) ``` Use an ordinal model for ordered scores such as disease severity, breeding condition, or habitat quality classes where category order matters but distances between categories are not numeric measurements. For an ecology/evolution example, nest success can be recorded as ordered fledging categories. The location equation models expected reproductive success on the latent ordinal scale. `fitted()` returns the expected ordered-category score, `sum_k k * Pr(y_i = k)`, which is useful for plotting the direction of a predictor but should not be treated as a measured continuous outcome. With `Pr(y_i <= k) = logit^{-1}(theta_k - mu_i)`, larger `mu_i` shifts probability toward higher ordered categories. Make that direction visible with an explicit prediction grid. The first table is the fitted latent location shift with its ordinary Wald interval. The second is a table of fitted category probabilities, built with the exported `fitted_distribution()` interface: ```r ordinal_grid <- prediction_grid( fit_ordinal, focal = "habitat", at = list(habitat = c(-1, 0, 1)) ) predict_parameters( fit_ordinal, newdata = ordinal_grid, dpar = "mu", conf.int = TRUE ) ordinal_distribution <- fitted_distribution( fit_ordinal, newdata = ordinal_grid ) ordinal_probability <- sapply(1:3, function(k) { ordinal_distribution$d(rep(k, nrow(ordinal_grid))) }) colnames(ordinal_probability) <- levels(dat$score) cbind(ordinal_grid, ordinal_probability) ``` For this fixed-effect example, the first table is the appropriate uncertainty route for the location coefficient. The category-probability table is a fitted description, not a category-probability interval. `profile_targets()` shows that raw `ordinal:theta_ord:*` entries are internal optimisation coordinates; they are not a public response-scale cutpoint estimand or a reportable cutpoint interval on current `main`. ```r profile_targets(fit_ordinal)[, c( "parm", "estimate", "scale", "profile_ready", "profile_note" )] ``` Ordinal scale or discrimination formulas remain planned. A future extension may add `sigma ~ mismatch` with `Pr(y_i <= k) = logit^{-1}((theta_k - mu_i) / sigma_i)` or expose a direct discrimination parameter. That decision needs a separate formula-grammar and interpretation check before implementation. Bivariate ordinal and mixed-response correlation models remain research features because the latent correlation and residual `rho12` interpretation need dedicated simulation tests. The practical next extensions are ordinal scale or discrimination formulas after the direction of interpretation is documented, plus richer bounded-response random effects only after their fixed-effect likelihoods and diagnostics are stable. COM-Poisson and generalized Poisson remain valuable, but they should wait until the mean-dispersion contract and comparator checks are designed. ## Bivariate Gaussian families For implemented bivariate Gaussian models, the preferred public spelling is to combine two Gaussian response families: ```r family = c(gaussian(), gaussian()) family = list(gaussian(), gaussian()) ``` The all-Gaussian composed case is implemented for both `c()` and `list()` spellings and currently routes to the same likelihood as `biv_gaussian()`. Mixed-response composed families remain future work. For example, `family = c(gaussian(), poisson())` is a planned direction for ecological examples such as body mass with fecundity counts, survival with dispersal counts, or leaf area with seed number. It is not a supported fitting path yet. The package rejects those mixed-family requests before fitting for both `c()` and `list()` spellings until the joint likelihood, residual-association parameter, prediction, simulation, intervals, and examples are designed.