Skip to contents

table_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. yes is 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::coxph refuses AME – the hazard scale has no marginal-probability effect – and provides covariate-adjusted rmst and risk_diff columns instead (also available for survreg; rms::cph and flexsurvreg support 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 = TRUE produces. 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, glmHC0HC5 (sandwich) and CR0CR3 (clubSandwich, bias-reduced with Satterthwaite df), plus "bootstrap" / "jackknife" resampling estimators.
  • Mixed effectslmer and nlme::lme take CR* via clubSandwich; glmer, glmmTMB and gls are model-based only (clubSandwich has no working backend for the first two, and its gls backend is not yet wired and validated in spicy).
  • Ordinal (polr, clm)CR0CR3, no HC*; the cut-point thresholds are reweighted from the same clustered vcov. A clm with a scale or nominal partial-proportional-odds component is model-based only.
  • Categoricalmultinom takes CR* (one cluster value per observation), mlogit takes CR* (one per choice situation). Both refuse HC*: multinom has no working-residual form for a multi-equation model, and for mlogit, 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 robust nid sandwich, iid / ker / rank are 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 rms fits take CR* via rms::robcov() (refit with x = TRUE, y = TRUE); survreg takes CR* via sandwich::vcovCL().
  • gam / bam, betareg, pscl two-partCR* via sandwich::vcovCL(); zero-inflated and hurdle fits cluster both components.
  • Own-estimator classesestimatr fits keep the robust SEs they were computed with; fixest fits keep their estimator (the footer carries fixest’s own label – clustered, Newey-West, Conley, …; fixest’s “IID” is normalised to “Classical”). spicy’s HC* / CR* tokens are refused for both.
  • Robust by constructionsvyglm is design-based (Taylor / replicate): the design variance is the robust variance, and clustering belongs in the design itself (survey::svydesign(ids = )). geeglm displays the sandwich SEs the fit computed over its own id = clusters; change the estimator by refitting with std.err =. Both refuse spicy’s HC* / CR* tokens and cluster.
  • Bayesianvcov is 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) for glm, polr and clm;
  • "boot_percentile" (with vcov = "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 parameters

The 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).