Supported models
Source:vignettes/articles/table-regression-supported-models.Rmd
table-regression-supported-models.Rmdtable_regression() takes fitted model objects – never
raw data plus a formula – and renders them as publication-ready
regression tables. “Supported” is a commitment, not a list of classes
that happen to run: every number a supported class produces is validated
against a field reference (the model’s own summary(),
sandwich, clubSandwich,
marginaleffects, effectsize,
performance, Stata or SPSS conventions), and every request
a class cannot honour is refused with a classed error
that names what is available – never rendered as a silently empty or
approximate column.
This article is the map. Each family below links to a dedicated article that walks through its behaviour in depth.
Choosing a model
Most readers arrive with the opposite question: not “what does spicy support?” but “I have this outcome – which model do I fit?”. The table below answers it for the situations applied work meets most often. It recommends and explains; it never chooses for you – model choice depends on your design and your estimand, and spicy will render whichever defensible model you fit.
| Your outcome | The situation | Reach for | Notes |
|---|---|---|---|
| Continuous | roughly symmetric errors, independent observations | lm() |
the default screen; beta for standardized effects |
| Continuous | positive and right-skewed | glm(family = Gamma("log")) |
exponentiate = TRUE gives mean ratios (MR) |
| Continuous | censored at a bound (floor/ceiling) | AER::tobit() |
|
| Continuous | outlying responses distort the fit | MASS::rlm() |
M-estimation: resistant estimates, not just fixed SEs |
| Continuous | the median or another quantile is the estimand | quantreg::rq() |
one model per quantile; effects are quantile-specific |
| Binary | independent observations | glm(family = binomial()) |
OR via exponentiate; add "ame" for
probability effects |
| Binary | overdispersed grouped binomial data | glm(family = quasibinomial()) |
|
| Ordered categories | a Likert scale, severity grades |
MASS::polr() or ordinal::clm()
|
thresholds render as a labelled block; AME per category;
clm() also fits partial proportional odds |
| Unordered categories | 3+ nominal outcomes |
nnet::multinom(); mlogit::mlogit() for
alternative-specific predictors |
outcome categories render as column groups |
| Count | mean roughly equal to variance | glm(family = poisson()) |
IRR via exponentiate; rates via an
offset()
|
| Count | variance well above the mean | MASS::glm.nb() |
models the overdispersion; quasipoisson merely widens
the SEs |
| Count | more zeros than the count part explains |
pscl::zeroinfl() / pscl::hurdle()
|
both components render as labelled blocks |
| Proportion in (0, 1) | rates, indices, shares | betareg::betareg() |
|
| Time-to-event | independent observations | survival::coxph() |
HR – and adjusted RMST / risk-difference columns beyond it |
| Time-to-event | a parametric survival-time model | survival::survreg() |
time ratios (TR) via exponentiate
|
| Any of the above | clustered or repeated measures |
lme4::lmer() / glmer() or
geepack::geeglm()
|
the distinction below matters |
| Any of the above | many absorbed fixed effects (panel) |
fixest::feols() / feglm()
|
the absorbed factors render as a Yes/No block |
| Any of the above | a complex survey design | survey::svyglm() |
design-based SEs are the inference |
| Any of the above | smooth non-linear terms |
mgcv::gam() / bam()
|
|
| Any of the above | a Bayesian analysis |
rstanarm::stan_glm() / brms::brm()
|
posterior medians, MAD SD, credible intervals – no p-values |
| Any of the above | an endogenous predictor, an instrument |
AER::ivreg() / estimatr::iv_robust()
|
Three distinctions the table cannot compress:
Clustered data: marginal or conditional? For
repeated or clustered measurements, glmer() and
geeglm() answer different questions. A mixed model
is subject-specific: its odds ratio compares outcomes within
the same cluster. GEE is population-averaged: its odds ratio
compares whole subpopulations – usually the public-health question.
Under a logit link the two genuinely differ – the population-averaged OR
is attenuated toward the null relative to the subject-specific one – so
the choice is about your estimand, not about taste. spicy renders both
with their own correct inference (boundary-corrected random-effect tests
for the former, the native sandwich SEs and working correlation for the
latter).
Binary outcomes: OR, RR, or a probability effect?
The odds ratio is the default because the logit is; it is also routinely
misread as a risk ratio. When the RR is the estimand, fit the log link
(binomial("log")) and spicy labels the exponentiated
coefficient RR – knowing that log-binomial models can fail to converge
on common data, which is a property of the model, not of the table. When
what you need is “how many percentage points does this predictor change
the probability”, add show_columns = c("b", "ame") – the
average marginal effect is the quantity most readers actually want, it
sidesteps both ratio debates, and for ordinal and multinomial models
spicy gives it per outcome category.
Survival: beyond the hazard ratio. The HR is
non-collapsible – adjusting for a prognostic covariate changes it even
without any confounding – and when proportional hazards fail, the single
reported HR is an average whose weights depend on censoring and
follow-up, not a stable quantity. spicy’s rmst and
risk_diff columns give covariate-adjusted differences in
restricted mean survival time and in cumulative incidence by
g-computation – absolute, time-anchored quantities a reader can act on –
for Cox and parametric AFT fits alike, in single tables and in the
univariable screen.
And when you fit something the guide does not cover: if the class is supported, the registry below renders it; if it is not, spicy refuses with a classed error that names the nearest supported route (see When a class is not supported) – it never renders an approximation of a model it does not understand.
The registry
The table is generated from the same internal registry that the
package itself uses, so it cannot drift from the code. Call
table_regression_models() to get it as a data frame.
| Family | Class | Engine | AME | Exponentiate | Blocks |
|---|---|---|---|---|---|
| Linear and generalized linear | lm |
stats::lm() |
yes | - | - |
glm |
stats::glm() |
yes | OR / IRR / RR / MR / HR (link) | - | |
negbin |
MASS::glm.nb() |
yes | IRR | - | |
rlm |
MASS::rlm() |
yes | - | - | |
nls |
stats::nls() |
no | - | - | |
| Robust, IV, quantile, panel | lm_robust |
estimatr::lm_robust() |
yes | - | - |
iv_robust |
estimatr::iv_robust() |
yes | - | - | |
ivreg |
AER::ivreg() |
yes | - | - | |
tobit |
AER::tobit() |
yes | - | - | |
rq |
quantreg::rq() |
yes | - | - | |
fixest |
fixest::feols(),
fixest::feglm(), fixest::fepois(),
fixest::fenegbin()
|
yes |
feglm: OR / IRR |
- | |
| Mixed effects | lmerMod |
lme4::lmer() |
yes | - | Random effects |
glmerMod |
lme4::glmer() |
yes | OR / IRR (link) | Random effects | |
glmmTMB |
glmmTMB::glmmTMB() |
yes | link-dependent (IRR for count families) | Random effects; Zero-inflation; Dispersion | |
lme |
nlme::lme() |
yes | - | Random effects | |
gls |
nlme::gls() |
yes | - | - | |
| Population-averaged (GEE) | geeglm |
geepack::geeglm() |
yes | OR / IRR / RR / MR / HR (link) | - |
| Ordinal | polr |
MASS::polr() |
per category | OR (logit) | Thresholds |
clm |
ordinal::clm() |
per category | OR (logit) | Thresholds; Non-proportional effects | |
| Categorical | multinom |
nnet::multinom() |
per outcome | OR | per-outcome blocks |
mlogit |
mlogit::mlogit() |
no | OR | per-alternative rows | |
| Counts, two-part | zeroinfl |
pscl::zeroinfl() |
yes (combined response) | IRR (count) + OR (logit zero part) | Zero-inflation |
hurdle |
pscl::hurdle() |
yes (combined response) | IRR (count) + OR (logit zero part) | Zero hurdle | |
| Survival | coxph |
survival::coxph() |
RMST / risk diff | HR | - |
survreg |
survival::survreg() |
yes + RMST / risk diff | TR (log-scale distributions) | - | |
cph |
rms::cph() |
no | HR | - | |
flexsurvreg |
flexsurv::flexsurvreg() |
no | TR / HR (dist) | distribution parameters | |
| Survey-weighted | svyglm |
survey::svyglm() |
yes (design-based) | OR / IRR | - |
svyolr |
survey::svyolr() |
per category (design-based) | OR (logit) | Thresholds | |
svycoxph |
survey::svycoxph() |
no | HR | - | |
| Additive, proportions, selection | gam |
mgcv::gam(), mgcv::bam()
|
yes | OR / IRR (link) | - |
betareg |
betareg::betareg() |
yes | OR (mean link) | - | |
selection |
sampleSelection::selection() |
no | - | selection component | |
| rms | ols |
rms::ols() |
yes | - | - |
lrm |
rms::lrm() |
yes | OR | - | |
Glm |
rms::Glm() |
yes | link-dependent | - | |
| Bayesian | stanreg |
rstanarm::stan_glm(),
rstanarm::stan_glmer()
|
yes (draws) | link-dependent | Random effects (if multilevel) |
brmsfit |
brms::brm() |
yes (draws) | link-dependent | Random effects (if multilevel) |
How to read the columns:
-
AME – what
show_columns = c("b", "ame")adds.yesis an average marginal effect on the response scale (a probability or rate effect for GLM families, the slope itself under identity).per category(ordinal) is the effect on each P(Y = k);per outcome(multinomial) is one effect per non-reference outcome.survival::coxphrefuses AME – the hazard scale has no marginal-probability effect – and provides covariate-adjustedrmstandrisk_diffcolumns instead (also available forsurvreg;rms::cphandflexsurvregsupport neither AME nor the estimand columns).yes (draws)means the effect is computed per posterior draw and summarized as a posterior median with MAD SD and credible interval. -
Exponentiate – the labelled ratio
exponentiate = TRUEproduces. The label follows the link: OR under logit, IRR for count log-links, RR for the binomial log link, MR (mean ratio) for Gamma log links, HR for proportional hazards, TR (time ratio) for accelerated-failure-time models. Identity-link fits warn and stay untouched; links whose exponential is not a ratio (probit, cauchit, inverse) are refused. - Blocks – labelled subordinate row blocks rendered inside the same table (random effects, thresholds, zero components, per-outcome segments), each explained by a footer line.
Cross-cutting arguments
The same arguments work across families, each through the family’s field-standard backend – or a clear refusal.
Robust and cluster-robust standard errors
(vcov, cluster). Family by family:
-
lm,glm–HC0–HC5(sandwich) andCR0–CR3(clubSandwich, bias-reduced with Satterthwaite df), plus"bootstrap"/"jackknife"resampling estimators. -
Mixed effects –
lmerandnlme::lmetakeCR*viaclubSandwich;glmer,glmmTMBandglsare model-based only (clubSandwichhas no working backend for the first two, and itsglsbackend is not yet wired and validated in spicy). -
Ordinal (
polr,clm) –CR0–CR3, noHC*; the cut-point thresholds are reweighted from the same clustered vcov. Aclmwith a scale or nominal partial-proportional-odds component is model-based only. -
Categorical –
multinomtakesCR*(one cluster value per observation),mlogittakesCR*(one per choice situation). Both refuseHC*:multinomhas no working-residual form for a multi-equation model, and formlogit,sandwich::vcovHC()computes a result but silently mis-scales the meat for its per-chooser score structure. -
Quantile (
rq) – its own estimator family:"classical"resolves to the robustnidsandwich,iid/ker/rankare opt-ins, and clustering goes through the native wild gradient bootstrap (vcov = "bootstrap"+cluster).HC*/CR*are refused. -
Survival – Cox models use the Lin-Wei
grouped-dfbeta sandwich; the
rmsfits takeCR*viarms::robcov()(refit withx = TRUE, y = TRUE);survregtakesCR*viasandwich::vcovCL(). -
gam/bam,betareg,pscltwo-part –CR*viasandwich::vcovCL(); zero-inflated and hurdle fits cluster both components. -
Own-estimator classes –
estimatrfits keep the robust SEs they were computed with;fixestfits keep their estimator (the footer carries fixest’s own label – clustered, Newey-West, Conley, …; fixest’s “IID” is normalised to “Classical”). spicy’sHC*/CR*tokens are refused for both. -
Robust by construction –
svyglmis design-based (Taylor / replicate): the design variance is the robust variance, and clustering belongs in the design itself (survey::svydesign(ids = )).geeglmdisplays the sandwich SEs the fit computed over its ownid =clusters; change the estimator by refitting withstd.err =. Both refuse spicy’sHC*/CR*tokens andcluster. -
Bayesian –
vcovis refused: nothing standard plays the sandwich role for a posterior.
Whatever the backend, the footer names the estimator actually applied, and a robust vcov also flows into the AME uncertainty.
Standardized coefficients
(standardized). Available for lm,
glm (including MASS::glm.nb), the mixed
engines (lmer / glmer / glmmTMB /
nlme::lme), and fixed-effects Bayesian fits –
stan_glm-style models and standard-formula
brm() models – where "posthoc",
"basic" and "smart" are exact affine rescales
of the posterior draws. Other classes refuse: the Bayesian refusals
(multilevel fits, brms formulas with distributional or special terms)
hint to standardize predictors before fitting, and the frequentist ones
point to the AME columns instead.
Confidence intervals (ci_method).
- Wald everywhere by default;
-
"profile"(profile likelihood) forglm,polrandclm; -
"boot_percentile"(withvcov = "bootstrap") replaces the bounds with equal-tailed percentile intervals of the bootstrap replicates; -
"hdi"(highest-density interval) for Bayesian fits, which otherwise report equal-tailed credible intervals.
Model comparison and multiplicity.
nested = TRUE compares nested fits by the family’s
change-test convention: Delta R-squared with the partial F test for
lm, the likelihood-ratio test for glm, mixed,
multinom and Cox models, and anova.rq’s
Wald-type F for quantile regressions (all fits at one tau).
p_adjust applies a multiplicity correction across the
displayed p-values – and is refused for Bayesian tables, which carry no
p-values at all.
The families in brief
Linear and generalized linear. The core engines:
lm, glm (with profile CIs on request),
MASS::glm.nb (with opt-in theta /
alpha dispersion statistics), MASS::rlm,
stats::nls. Start with Publication-ready regression
tables and Categorical
predictors.
Robust, IV, quantile, panel.
estimatr::lm_robust() / iv_robust(),
AER::ivreg() and AER::tobit(),
quantreg::rq() (defaulting to the heteroskedasticity-robust
nid sandwich – quantreg’s own large-sample default – with
iid, ker, rank CIs and a native
clustered bootstrap as vcov options), and the
fixest estimators, whose absorbed fixed effects render as a
default-on Fixed effects: block – one Yes / No row per
factor, blank for non-fixest models in a mixed table – with the within
R-squared among the default fit statistics and per-factor
N (<factor>) counts through the opt-in
"n_groups" token.
Mixed effects. lmer (Satterthwaite t
via lmerTest), glmer, glmmTMB
(with zero-inflation and dispersion blocks), nlme::lme and
nlme::gls. Random effects render as rows – SD,
correlations, residual – deliberately without per-row p-values; the
footer carries the boundary-correct chi-bar-squared test, and
re_test = "lrt" / "rlrt" adds per-term tests.
See Mixed-effects regression
tables.
Population-averaged (GEE).
geepack::geeglm(): marginal (population-averaged)
coefficients with the fit’s own sandwich SEs as the default inference,
the working correlation structure disclosed in the footer (with its
estimated alpha), cluster-structure fit statistics, and opt-in QIC /
QICu. See GEE regression
tables for the full workflow; Mixed-effects regression
tables contrasts GEE with subject-specific mixed models in a
single table.
Ordinal. MASS::polr and
ordinal::clm: proportional odds ratios, a
Thresholds block for the cut-points (log-odds scale, never
exponentiated), partial-proportional-odds terms as a
Non-proportional effects block, and per-category AME. See
Ordinal regression
tables.
Categorical. nnet::multinom renders
outcome categories as columns with per-outcome AME;
mlogit::mlogit renders per-alternative rows for
discrete-choice designs. See Multinomial regression
tables.
Counts and two-part. Poisson and negative binomial
through glm / glm.nb / glmmTMB,
plus pscl::zeroinfl() and pscl::hurdle() with
their zero components as labelled blocks and a combined-response AME.
See Count and two-part
regression tables.
Survival. survival::coxph and
rms::cph (hazard ratios, strata() supported),
survival::survreg (time ratios) and
flexsurv::flexsurvreg. Absolute effects come as
covariate-adjusted RMST and risk differences by g-computation for
coxph and survreg fits. The baseline hazard
those estimands standardize follows the tie-handling convention of the
fit, exactly as survfit() and basehaz() do – a
ties = "breslow" fit gives a Breslow baseline, the default
Efron fit an Efron one – so the hazard-ratio column and the dRMST column
always come from the same likelihood. See Survival regression
tables.
Survey-weighted. survey::svyglm():
design-based inference; the unweighted n is a default fit statistic and
the sum of design weights is available as
show_fit_stats = "weighted_nobs".
Additive, proportions, selection.
mgcv::gam() / bam(),
betareg::betareg() (odds ratios on the logit mean link
under exponentiate; the precision is the opt-in
phi fit statistic), and
sampleSelection::selection() with its selection component
as a block.
rms. ols, lrm,
Glm and cph are first-class citizens, so
Harrell-style workflows drop in directly.
Bayesian. rstanarm and
brms fits are summarized from their posterior draws:
posterior median, MAD SD, credible intervals, draws-native
exponentiation and AME, sampler diagnostics checked on every fit (with
opt-in pd, rhat, ess_bulk /
ess_tail and mcse columns). No p-values, by
design. See Bayesian
regression tables.
When a class is not supported
An unsupported class fails fast with a classed error
(spicy_unsupported):
fit <- loess(dist ~ speed, data = cars)
table_regression(fit)
#> Error in `validate_models_input()`:
#> ! Some `models` are not supported by `table_regression()`.
#> Position 1: `loess` – no `as_regression_frame()` method registered. If support would be useful, please open an issue: https://github.com/amaltawfik/spicy/issues
#> ℹ Run `methods('as_regression_frame')` to see all currently supported model classes.The same contract applies inside a family: a request a class cannot
honour – HC* for multinom, AME for
mlogit, exponentiate on a probit link,
p_adjust on a Bayesian table – is refused with the reason
and the supported alternative, never silently degraded.
Programmatic access
table_regression_models() returns the registry as a
plain data frame – convenient to filter, join, or cite:
subset(table_regression_models(), family == "Survival")
#> family class engine ame exponentiate
#> 24 Survival coxph survival::coxph() RMST / risk diff HR
#> 25 Survival survreg survival::survreg() yes + RMST / risk diff TR (log-scale distributions)
#> 26 Survival cph rms::cph() no HR
#> 27 Survival flexsurvreg flexsurv::flexsurvreg() no TR / HR (dist)
#> blocks
#> 24 -
#> 25 -
#> 26 -
#> 27 distribution parametersThe per-family reference sections live on its help page:
?table_regression_models (also reachable as
?table_regression_mixed,
?table_regression_ordinal,
?table_regression_survival, and the other family
aliases).