Distributional Regression Models Using Template Model Builder
Overview
Fast distributional regression models for univariate and bivariate responses using Template Model Builder. The current implementation focuses on Gaussian, Student-t, and skew-normal location-scale models, known sampling covariance, phylogenetic location effects, random-effect scale models, bivariate residual correlation, positive-continuous, 'Tweedie' semi-continuous, strict-proportion, zero-one bounded, and denominator-aware proportion families, fixed-effect Bernoulli/binomial event-probability models, and fixed-effect Poisson, negative-binomial, zero-inflated, zero-truncated, hurdle count, and ordinal cumulative-logit models. Additional response-family models are staged for later phases. Every fitted family also exposes a distributional-output and adequacy layer: randomized quantile-residual worm and QQ plots that detect fixed-effect shape and atom misspecification, and conditional-quantile, exceedance, and centile outputs with plug-in (uncalibrated) intervals.
First CRAN-targeted release candidate. It was first submitted to CRAN on 24
August 2026; this resubmission candidate follows CRAN's example feedback and is
not yet accepted or available on CRAN. drmTMB fits distributional regression
models -- location, scale, shape, zero inflation, and residual correlation --
for one or two responses, using Template Model Builder.
Lognormal ordinary correlated slope
- Complete-data ordinary
lognormal()y ~ x + (1 + x | id)now has apoint_fit_recoveryledger cell (mc-0720) forsd0,sd1, and group-levelrho_reunder the same design-17 map as Poisson (ρ = 0.999999 tanh(η)). This is not the independent-slope cellmc-0380and not Wave 2.5 NB2mc-0719. A slope predictor that is constant within every group now aborts before the fit (unidentifiedsd1/rho_re). Gamma neighbour, REML, missing-response, labelled blocks, mixed(1 | g) + (1 + x | g), intervals, and coverage remain rejected.
Show all changes in 0.7.0Hide the rest of the changes in 0.7.0
- **CRAN / win-builder Julia hang (post-#1061).** The
^juliainvert filter correctly excludedtest-julia-*.R, buttest-binomial-response.Rstill calleddrmTMB(..., engine = "julia")insideexpect_error(). Workflow G admits fixed-effect binomial into the Julia bridge, so that call reachedJuliaCall::julia_setup()and hung Ligges R-release for ~10448s.drm_julia_setup()now hard-blocks the non-interactive CRAN lane unlessDRMTMB_JULIA_TESTS=true,drm_skip_live_julia()matches the same predicate, and the obsolete binomialexpect_error(engine = "julia")is replaced with a pure-R tag check.
Binomial ordinary correlated slope
- Complete-data binomial logit
cbind(success, failure) ~ x + (1 + x | id)now has apoint_fit_recoveryledger cell (mc-0717) forsd0,sd1, and group-levelrho_reunder the log-sech Cholesky. This is not the independent-slope cellmc-0061. A slope predictor that is constant within every group now aborts before the fit (unidentifiedsd1/rho_re). REML, missing-response, labelled blocks, mixed(1 | g) + (1 + x | g), intervals, and coverage remain rejected.
Poisson ordinary correlated slope
- Complete-data Poisson log
count ~ x + (1 + x | id)now has apoint_fit_recoveryledger cell (mc-0718) forsd0,sd1, and group-levelrho_reunder the design-17 mapρ = 0.999999 tanh(η). This is not the independent-slope cellmc-0431and not Wave 1 binomialmc-0717. REML, missing-response, labelled blocks, mixed(1 | g) + (1 + x | g), intervals, and coverage remain rejected.
NB2 ordinary correlated slope
- Complete-data ordinary
nbinom2()count ~ x + (1 + x | id)now has apoint_fit_recoveryledger cell (mc-0719) forsd0,sd1, and group-levelrho_reunder the same design-17 map as Poisson (ρ = 0.999999 tanh(η)). This is not the independent-slope cellmc-0402and not Wave 2 Poissonmc-0718. A slope predictor that is constant within every group now aborts before the fit (unidentifiedsd1/rho_re). Zero-inflated or truncated NB2, REML, missing-response, labelled blocks, mixed(1 | g) + (1 + x | g), intervals, and coverage remain rejected.
Binomial responses accept a phylogenetic random effect
binomial()was the only common response family that could not take a structured random effect --gaussian,poisson,nbinom2,Gammaandbetaall accepted the identicalphylo(1 | id, tree = tree)term while a binomial model aborted at the structured-effect gate (#1048). Phylogenetic logistic regression (a binary trait on a tree) is the canonical comparative-methods use of a binary response, so the hole was conspicuous. The first slice is deliberately narrow, matching howbetaandzero_one_betagrew provider by provider: one unlabelled q1phylo()intercept onmu, in either the Bernoulli or the two-columncbind(successes, failures)form. Phylogenetic slopes, labelled covariance blocks,spatial/animal/relmatproviders, combination with ordinary random effects, and combination with missing-predictormi()all refuse with explicit messages rather than fitting silently narrower models. Supporting recovery evidence (slope andsd_phyloessentially unbiased by 160 tips, Laplace small-sample attenuation ~9% at 40-80 tips) is recorded on #1048.
Fixed: penalized fits reported phylo_penalty and logLik off the optimum
- A penalized (MAP) phylogenetic fit read its penalty from a bare
obj$report(), which TMB evaluates atobj$env$last.par. AfterTMB::sdreport()that is a finite-difference step away from the optimum, sofit$phylo_penalty-- and thereforefit$logLik, which is-opt$objective + phylo_penalty-- were both slightly wrong. The error scaled with the penalty (order 1e-3 atsd_u = 0.5) and, being a diagnostic-time artefact, made both values depend on whether standard errors had been requested at all: the same fit at the same optimum reported different numbers underse = TRUEandse = FALSE. The practical consequence was indrm_phylo_penalty_sweep(), whoselogLikcolumn is the basis for deciding whether a coupling is data-informed or prior-shaped. The error differed per row, so it did not cancel in exactly the comparison the sweep exists to make. Estimates were never affected --opt$parandsdreport()were always at the optimum.check_drm()'slog(sigma)clamp row had the same defect and is fixed alongside. The existing regression test could not catch this because it derived its "expected" penalty from the same barereport()call, so both sides of the assertion moved together; it now derives the expectation fromparList(opt$par), and a new test asserts thatsecannot move a reported estimate.
Experimental MSPL accepts probit and complementary log-log
estimator = "mspl"previously requiredbinomial(link = "logit")exactly. It now also acceptsbinomial(link = "probit")andbinomial(link = "cloglog"). Everything else about the route is unchanged: still one complete Bernoulli or grouped-binomial model, one ordinaryq = 1or correlatedq = 2grouping block,engine = "tmb", no REML, no intervals.- **The basis is measured, not assumed.** Kosmidis and Firth (2021, Theorem 1 with Section 3.1) prove the Jeffreys penalty yields finite estimates for any link whose working weight vanishes in both tails, which logit, probit and cloglog all do. drmTMB's own TMB-Laplace evidence: **no non-finite estimate in 43,972 completed fits**, and Wald standard errors calibrated in the identified regime (probit
mean(SE)/sd(beta)in[0.946, 1.008], cloglog in[0.957, 1.027]). Artifacts underdocs/dev-log/simulation-artifacts/2026-08-11-mspl-nonlogit-links/. **Scope of that measurement:** Bernoulli responses with two fixed-effect columns. Grouped-binomial and wider designs inherit then_effextrapolation noted below without direct measurement -- which was already true of the logit route and is not made worse here, only left unresolved for two more links. - **The soft-penalty scale is unchanged and is a logit constant.**
c_n = 2 * sqrt(p / n_eff)comes from a delta-method argument atbeta = 0for the logit link; the same argument gives about1.25 * sqrt(p / n_eff)for probit and1.31for cloglog. Using the logit constant for all three was measured to move the estimate by roughly **1% of one standard error** (atq = 1with two fixed-effect columns; not measured atq = 2), so it is kept rather than made link-specific -- a per-link constant would define a different estimator. - **Know this before using it on rare events.** Under deep separation with a random slope, the standard error is frequently unavailable:
vcov()andsummary()returnNAwith adrmTMB_mspl_wald_unavailablewarning rather than a fabricated number, and in the most extreme cells measured this affected the large majority of converged fits. The point estimate remains finite -- that is what the penalty is for -- but inference is not available there for any link. - MSPL start values now place the intercept at the link of the observed event rate instead of at zero. At
beta = 0cloglog implies an event rate of1 - exp(-1) = 0.632, so a rare-event design previously began several log units from its own intercept; this removes a class of avoidable optimizer failures. Slopes still start at zero and the start remains deterministic and finite.
Boundary intervals are now flagged on every route
- **
confint(method = "bootstrap")warns at a variance-component or correlation boundary** (classdrmTMB_bootstrap_boundary_warning,conf.status = "bootstrap_at_boundary"). The Wald and profile routes already flagged their own boundary cases; bootstrap did not, which made it the one route that could return a clean-looking interval for a target the other two warn about. **Resampling does not repair a boundary** — a percentile interval whose draws pile up at zero is reporting the constraint, not the sampling distribution. - The flag fires when at least 5% of retained resamples land on the bound, and requires at least 20 retained draws, because a share computed from a handful of resamples is noise rather than evidence. Calibration at
R = 200: a true SD of 0 put **43%** of draws on the bound; a true SD of 0.25 put **5%** there, with a lower endpoint of exactly zero; a true SD of 0.9 put **none** there. - **
check_drm()now states what it does not check.** It reads the fit, so a random-effect SD comfortably clear of zero passes every fit-level check whileconfint()still warns about that same target's interval — measured on a 10-group fit with an SD estimate of 0.1936, every checkok, and 43% of bootstrap draws on the bound. A newinterval_reliability_scopenote records that interval reliability is not assessed there and points atconf.status. Previously that all-clear could reasonably be read as permission to report.
Offsets in mu for every univariate family
offset()in themuformula now works for **all** univariate families, not only Poisson,nbinom2(), and Bernoulli/binomial. Newly accepted: Gaussian,student(),skew_normal(),lognormal(),Gamma(),tweedie(),beta(),beta_binomial(),zero_one_beta(), andcumulative_logit().- **Read the offset on the family's link scale.** For log-link families it is the usual
offset(log(exposure))rate model. For identity-link families it is a known additive shift of the mean. For logit-link families it is a known log-odds shift — a calibration term, **not** an exposure;offset(log(effort))on a logit-link family does not give a rate model. - A
zero_one_beta()offset shifts only the interior beta component and leaveszoi/coiuntouched. Acumulative_logit()offset shifts the latent location against fixed cutpoints rather than an intercept. - **Still rejected, deliberately:**
truncated_nbinom2()and its hurdle path (both renormalise the observed mean over a restricted support, so an exposure term would not scale the reported mean — extending them needs a decision on whether the offset targets the latent untruncated rate or the observed mean), every bivariate family (no per-response offset contract yet), Gaussian sufficient-statistic aggregation, and every distributional parameter other thanmu. - Correctness is pinned by a link-agnostic identity: a constant offset
cmust lower the fitted intercept by exactlycand leave every slope unchanged.tests/testthat/test-offset-families.Rchecks that for each family. - Fixes a documentation defect: the
drmTMB()help previously claimedoffset(log(exposure))support for zero-truncated negative-binomialmuformulas, which the code rejected.
Live Workflow G engine = "julia" FE gate (#499)
- Skip-safe live tests now round-trip DRM.jl's **eleven** admitted Workflow G fixtures through
drmTMB(..., engine = "julia")against committedexpected.tomlnumbers (drmTMB **0.6.0** pin). Measured locally: **11/11** pass when JuliaCall + DRM.jl are available. - The R bridge now admits those Workflow G fixed-effect families (Gaussian, bivariate Gaussian, Student-t, lognormal, Poisson, NB2, Gamma, Beta, Binomial) without requiring a
phylo()term; large-p phylo routes for the count / non-Gaussian set remain. Beta-binomial stays gated. - Marshalling fixes that the live gate surfaced: expand
cbind(successes, failures)into data columns, and rewritemeta_V(V = v)/drmTMB::meta_V(...)to positionalmeta_V(v)for DRM.jl's StatsModels parser. - **Claim fence:** experimental live parity for the eleven cells only — not a CRAN-default Julia engine, not closure of all of #499 (FIML / remaining honest scope stay open).
Binomial models accept probit and cloglog links (new in 0.7.0)
binomial(link = "probit")andbinomial(link = "cloglog")now fit, alongside the existingbinomial(link = "logit").predict(),summary(), andpredict_parameters()back-transform through the fitted link rather than assuming logit. Other binomial links (for examplecauchit) are still rejected.- The link is evaluated on the **log scale** throughout (a tail-safe log-normal CDF for probit; a
log1mexpform for cloglog), not by computing a probability and clamping it. This keeps accuracy in the extreme tails; seedocs/design/252-binomial-link-generalisation.md§3 andinst/COPYRIGHTS. - **This is a fitting capability, not an interval or coverage claim.** The new links inherit binomial's existing capability-ledger evidence; no new recovery or coverage campaign was run for them, and the census is unchanged.
- The
DRM.jlbridge continues to reject probit and cloglog: DRM.jl implements the logit mean only, soengine = "julia"errors rather than silently fitting a different model. - **Superseded later in 0.7.0:** the maximum-softly-penalized-likelihood (MSPL) entry point was initially logit-only. It now also admits probit and complementary log-log links under the explicitly bounded, experimentally supported contract described in “Experimental MSPL accepts probit and complementary log-log” above.
Default uncertainty story
- **Fixed effects / routine Wald targets:** start with
confint(fit)(method = "wald"). It is the fastest fitted-object route whenTMB::sdreport()succeeded. - **Random-effect SDs / variance components:** prefer
confint(fit, parm = ..., method = "profile")afterprofile_targets(fit)lists the row as profile-ready. - **Boundary rows:** read
profile.boundaryandconf.status. A usable profile interval at a variance-component boundary warns with classdrmTMB_profile_boundary_warning. That warning does **not** repair coverage; treat the interval as indicative of scale, not as a calibratedlevelinterval (see?confint.drmTMBBoundary intervals; D-117 evidence). - **No nominal-coverage-everywhere claim.** A computable interval is not coverage certification. Report a route as inference-ready only when the capability guide names that exact cell.
See also the vignette *First-week intervals: fit, profile, and boundary*.
REML capability wording now matches the callable surface
- The capability guide now records the bounded O2 binomial REML route: one ordinary unlabelled location (
mu) random intercept (mc-0060) or independent slope (mc-0062). This route is diagnostic-only despite deterministic agreement withglmmTMB(REML = TRUE); it has no recovery or coverage claim. Fixed-only, multiple-term, correlated, labelled, structured, and missing-response binomial REML remain unavailable. - README and
?drmTMBnow state the same public non-Gaussian REML boundary without requiring the capability guide: binomial O2 only, both diagnostic-only; O3 remains package-private;mc-0227stays public MLpoint_fit_recovery. Other non-Gaussian families rejectREML = TRUE. - The retained cumulative-logit AGHQ plus Cox-Reid campaign is now identified as evidence for a package-private estimator. It does not grant a public
drmTMB()fit or reader-facing reporting permission
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
- OK13 OKNow
- OK7 OK2026-09-16
Documentation
- Help pages
- 86 (8 internal)includes a package overview page
- Examples that run
- 100%67 run in a plain check (3 of them under a condition)
- Exported functions with examples
- 91%
- Documented parameters
- 91%
- Return-value docs
- 100%
- Help pages citing references
- 2%
- Distributional regression with drmTMB HTML
- Adding distribution families HTML
- Animal models and additive relatedness HTML
- Association between mixed outcome pairs HTML
- Bivariate non-Gaussian models: choose a joint or staged association HTML
- Can I fit and report this model? HTML
- Changing residual coupling with rho12 HTML
- Choosing response families HTML
- Comparing drmTMB with other packages HTML
- Coordinate-spatial structured effects HTML
Show 22 moreHide 22 vignettes
- Count abundance and extra zeros HTML
- Errors, warnings, and convergence HTML
- First-week intervals: fit, profile, and boundary HTML
- Formula grammar HTML
- Handling missing data HTML
- Implementation map HTML
- Implemented source map HTML
- Julia engine: future support HTML
- Known-matrix relatedness with relmat HTML
- Model selection with AIC and BIC HTML
- Phylogenetic mixed models HTML
- Proportions and success rates HTML
- Random-effects meta-analysis with known sampling variances HTML
- Robust continuous responses HTML
- Structural dependence overview HTML
- Testing likelihoods HTML
- Two-tree phylogenetic interactions HTML
- What can I fit today? HTML
- When variance carries signal, Part 1: location-scale models HTML
- When variance carries signal, Part 2: location-scale-scale models HTML
- Which scale are you modelling? HTML
- Working with large data HTML
Downloads
Repository
- PRs merged (all time)
- 93%
- Median days to close, 50 most recently updated closed issues
- 24
- Median days to close, 50 most recently updated closed PRs
- 0
Issues over time
PRs over time
Commits over time
Commit identities over time
9 development-tooling and community-health practices detected across 6 families in the upstream repository
Checks run against github.com/itchyshin/drmtmb on 2026-10-11.
The R CMD check workflow runs on Linux, macOS and Windows
GitHub Pages site last deployed 2026-10-11 · The repository has a site deploy workflow
Show all practicesHide 2 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-05-07Seen in the repository: .claude, .claude/agents, .mcp.json, CLAUDE.mdNamed in .Rbuildignore: .claude, .mcp.json, CLAUDE.mdPull request it wrote: opened by a project memberCommits: 14 by its own account · at least 1,112 crediting it in the message
- Codexfirst seen 2026-05-07Seen in the repository: .codexNamed in .Rbuildignore: .codex
- Cursorfirst seen 2026-08-01Seen in the repository: .cursorNamed in .Rbuildignore: .cursorPull request it wrote: opened by a project memberCommits: at least 226 crediting it in the message
- Claude Opus 4.866 commits · Jun to Jul 2026
- Claude Opus 5.520 commits · Oct 2026
- Claude Fable 517 commits · Jul 2026
- Claude Sonnet 5.513 commits · Oct 2026
Some of these counts are a minimum, because the search stopped before the oldest commits.
Dependencies
Installing drmTMB adds 6 packages (5 with compiled code, 5.7 MB of source tarballs). Heaviest: Rcpp 1.8 MB, RcppEigen 1.7 MB, rlang 766 KB.
Code & Tests
Datasets
People & History
- 0.7.0Latest2026-09-15 · current release
- RR 4.6.0 released · 2026-04-24
Package metadata
- First published
- 2026-09-15
- Total releases
- 1 / 1 yrs
- License
- GPL (>= 3) OSI
- Minimum R
- ≥ 4.1.0
- Download size
- 5.5 MB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("drmTMB")The package provides a citation file that builds its entry when R runs it. Run citation() to see it.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-18, which the citation names so these numbers can be found later. More on citing and the projects behind them.