--- title: "Publication-ready regression tables" description: > Coefficient tables for `lm` and `glm` fits with APA-aligned formatting: classical, heteroskedasticity-consistent and cluster-robust variance, four standardisation methods, partial effect sizes with noncentral-F CIs, average marginal effects, multiple-comparison adjustment, side-by-side and hierarchical layouts, and output to console, gt, tinytable, flextable, Excel, Word, or clipboard. output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Publication-ready regression tables} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) build_rich_tables <- identical(Sys.getenv("IN_PKGDOWN"), "true") source("_pkgdown-helpers.R") ``` ```{r setup} library(spicy) ``` `table_regression()` produces a coefficient summary table from one or several fitted `lm()` or `glm()` models. The output is publication-ready by default and follows APA Manual 7 (American Psychological Association 2020, Tables 7.13–7.15) formatting conventions: paired estimate-and-CI columns, APA p-values without leading zero, factor levels grouped under their parent variable, fit statistics at the foot of the table, and a self-documenting note line that names the variance estimator and any methodological choice that affected the rendered values. The function is **fit-first**: you pass already-fitted models, not raw data and a formula. The same object exports cleanly to a long broom-canonical frame (`broom::tidy()`) and to a one-row-per-model glance summary (`broom::glance()`). `lm` and `glm` are both supported. The *Generalised linear models* section covers the glm-specific argument semantics; everything else applies to both. Mixed-effects models are on the roadmap. ## Basic usage Pass a fitted `lm()` object. The default rendering returns a single-model table with `B`, `SE`, `95% CI`, and `p` columns and a fit-statistics footer (`n`, `R²`, `Adj.R²`): ```{r basic} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) table_regression(fit) ``` Reading the table: * Each predictor occupies one row; factor predictors are grouped under their parent variable name with one row per level. The reference level carries the `(ref.)` annotation and an em-dash across the statistic columns, so the substantive comparison is visible in a single glance (NEJM / BMJ clinical convention). * The `95% CI` column reports a symmetric Wald interval at the level set by `ci_level` (default 0.95). The CI header tracks the chosen level — switching to `ci_level = 0.99` re-labels the column accordingly. * The footer line names the variance estimator in plain English so the reader can find the inferential regime without leaving the table. The estimator switches with `vcov` (covered in *Robust variance* below). ## Standardised coefficients Standardised coefficients (`β`) make predictors with different natural scales comparable: a one-standard-deviation increase in `X` predicts a `β`-standard-deviation change in `Y`. APA Manual 7 §7.13 recommends reporting both `B` and `β` so the unstandardised effect (natural units, interpretable) stays alongside the standardised effect (comparable across predictors). `standardized` selects the method. Four are available; the choice is consequential and well-documented (Cohen, Cohen, West, and Aiken 2003 §3.4; Gelman 2008): * `"refit"` — refit the model on z-scored outcome and predictors. Gold-standard convention, used by SPSS `REGRESSION` and Stata `regress, beta`. Both numeric and dummy-coded predictors enter the refit on the same scale. * `"posthoc"` — algebraic rescaling `β = B × SD(X) / SD(Y)`, applied to the original fit. Numerically identical to `"refit"` for purely linear-additive Gaussian models; preferred when refitting is expensive or when `lm()` was wrapped in a pipeline that resists re-execution. * `"basic"` — algebraic, but factor dummies keep their 0/1 scale rather than being z-scored. Useful when factor levels carry meaningful base rates that scale-free standardisation would obscure. * `"smart"` — Gelman's (2008) recommendation: numeric predictors divided by `2 × SD(X)`; binary predictors centred only. The resulting `β` is the predicted change in `Y` for one within- sample standard deviation of `X`, on the original `Y` scale, which keeps factor and numeric coefficients on roughly comparable footing. When `standardized != "none"`, the `"beta"` token is auto-injected into `show_columns` immediately after `"b"`: ```{r beta} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) table_regression(fit, standardized = "refit") ``` **Caveat: standardised coefficients with interactions or transformed terms.** When the model contains a product term, an `I()`, `poly()`, `log()`, or `splines::ns()` wrapper, the standardised coefficient of the non-additive term has no closed-form "one-SD change in X" reading (Aiken and West 1991; Cohen et al. 2003 §7.7). The function emits a classed `spicy_caveat` warning at runtime AND prints a method-specific caveat line in the table footer, so the limitation is exposed at the point of use without blocking the table. ## Per-coefficient effect sizes For each predictor, you can request a partial effect-size column. Three measures are available — Cohen's `f²` (`partial_f2`), Pearson's partial `η²` (`partial_eta2`), and the Olejnik–Algina bias-corrected partial `ω²` (`partial_omega2`). Each carries a confidence interval derived from noncentral-`F` inversion (Smithson 2003; Steiger 2004), exposed as a separate `_ci` column. The shortcuts `"all_f2"`, `"all_eta2"`, `"all_omega2"` expand to the point estimate and its CI pair in one go: ```{r partial} fit <- lm(wellbeing_score ~ age + sex + smoking + education, data = sochealth) table_regression( fit, show_columns = c("b", "p", "all_eta2", "all_omega2") ) ``` Methodology notes: * The partial F-test is computed on a Type-II ANOVA reference (`car::Anova`), which respects the principle of marginality and is the SAS / SPSS default for unbalanced designs. * The `ω²` point estimate is bias-corrected via the Olejnik and Algina (2003) formula `((F − 1) × df1) / (F × df1 + N − df1)`, yielding a less-biased small-sample estimator than partial `η²`. * The CI bounds come from inverting the noncentrality parameter of the F-distribution at the lower and upper confidence levels (Steiger 2004 §4). The Steiger inversion always brackets the bias-corrected point estimate, even when the lower bound clips at zero (common for near-null terms). * For factor predictors with `k` levels, the partial F-test is the joint `(k − 1)` df Wald test, so the same effect-size value is broadcast across all non-reference dummy rows; the reference row shows an em-dash. ## Average marginal effects (AME) The `B` coefficient is the model's *structural* effect — the change in the linear predictor `Xβ` for a one-unit change in `x`, holding the other predictors constant. The **average marginal effect (AME)** is the *observable* effect on the response: the average partial derivative `dE[Y|X] / dx` over the sample. For a purely linear-additive `lm()` the two coincide. For models with interactions or transformed terms, and for any `glm()` with a non-identity link, the AME is the quantity the substantive reader actually needs. Add `"ame"` to `show_columns` for the point estimate. Companion tokens display the SE, CI, and p-value: `"ame_se"`, `"ame_ci"`, `"ame_p"`. The shortcut `"all_ame"` expands to the full set. ```{r ame-lm} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) table_regression( fit, show_columns = c("b", "ame", "ame_ci", "ame_p") ) ``` Reading conventions: * `"p"` always refers to the `B` (or `β`) coefficient, never to the AME. Use `"ame_p"` for the AME-specific p-value. * Placing `"ame"` after `"p"` keeps the "which p belongs to what" reading unambiguous — the B-block (`B / SE / p`) is closed before the AME-block opens. * For non-linear models the AME is reported on the **response scale**, not the link scale. The *Generalised linear models* section below works out a logistic-regression example. Inference is delegated to [`marginaleffects::avg_slopes()`][marginaleffects::avg_slopes] and respects the chosen variance estimator. Under cluster-robust variance (covered in *Robust variance*), the AME inference shares the coefficient's t-distribution and Satterthwaite-corrected degrees of freedom via `clubSandwich::linear_contrast()`, so `B` and AME are reported on the same inferential footing in the same table. ## Multiple models side by side Pass a list of `lm()` fits. The default column layout places each model in its own panel under a centered **spanner label** showing the model name; sub-columns (`B / SE / p`) are repeated under the spanner. When dependent variables differ across models and the user did not supply labels (`model_labels = ` or named list), the bare DV name is lifted into the spanner automatically: ```{r multi} m_wellbeing <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) m_bmi <- lm(bmi ~ age + sex + smoking, data = sochealth) table_regression(list(m_wellbeing, m_bmi)) ``` When all models share the same DV, the DV appears in the title. Use a named list — e.g. `list(Crude = m1, Adjusted = m2)` — to set the spanner labels explicitly; pass `model_labels = c(...)` to override the names from the list. ## Hierarchical / nested regression Set `nested = TRUE` to add **in-table change-statistic rows** (APA Table 7.13 / Stata `esttab` / SPSS Model Summary convention). Each adjacent pair (M2 vs M1, M3 vs M2, …) contributes one column of change stats below `R² / Adj.R²`; the FIRST model column gets em-dashes (no previous model to compare to). Hierarchical regression requires every model in the sequence to share the same analytic sample: identical `nobs` AND identical response variable. R's listwise deletion otherwise produces different `n` per model as soon as a predictor introduced by M2 or M3 has missing values that M1 did not see — a silent bias on the change statistics. We restrict to complete cases on the union of predictors up front: ```{r sochealth-cc} sochealth_cc <- sochealth |> dplyr::select(wellbeing_score, age, sex, smoking, bmi, region) |> na.omit() ``` ```{r nested} m1 <- lm(wellbeing_score ~ age + sex, data = sochealth_cc) m2 <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth_cc) m3 <- lm(wellbeing_score ~ age + sex + smoking + bmi, data = sochealth_cc) table_regression(list(m1, m2, m3), nested = TRUE) ``` Default change tokens auto-injected: `c("r2_change", "f_change", "p_change")` for `lm` (APA hierarchical-regression standard), `c("lrt_change", "p_change")` for `glm` (Hosmer & Lemeshow §3.5). Customise via `show_fit_stats`; the order of tokens controls the order of the rows. Other change tokens are available: `"adj_r2_change"`, `"f2_change"`, `"deviance_change"`, `"aic_change"` / `"aicc_change"` / `"bic_change"`. Validation is strict: identical `nobs` AND identical response across all models, otherwise a `spicy_invalid_input` error explains the listwise-deletion trap and suggests refitting on the common subset. ## Robust variance The default `vcov = "classical"` reports the OLS standard error, valid under homoskedastic, independent errors. Two robust alternatives are first-class arguments; bootstrap and jackknife variants are also available via `vcov = "bootstrap"` / `"jackknife"`. ### Heteroskedasticity-consistent (HC) When error variance plausibly depends on the predictors (a ubiquitous concern in cross-sectional social-science data), set `vcov = "HC*"` for sandwich-style standard errors via [`sandwich::vcovHC()`][sandwich::vcovHC]. The valid types are `HC0` through `HC5`; `HC3` is the small-sample-friendly default (Long and Ervin 2000): ```{r hc} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) table_regression(fit, vcov = "HC3") ``` The footer reads "*Std. errors: heteroskedasticity-robust (HC3)*"; the column header for the confidence interval automatically tracks `ci_level`. ### Cluster-robust (CR) For clustered observations (repeated measures on the same person, students within schools, observations within regions), `vcov = "CR*"` requests cluster-robust variance via [`clubSandwich::vcovCR()`][clubSandwich::vcovCR], with the cluster identifier supplied through `cluster`. Three forms are accepted: * **Formula** — `cluster = ~region`. The variables are looked up in `model.frame(fit)` first, then in the model's original `data` argument. Recommended: independent of the dataset's name, composable for multi-way clustering (`cluster = ~region:year`). * **String** — `cluster = "region"`. Single column name resolved the same way. Convenient but cannot express interactions. * **Vector** — `cluster = df$region`. Atomic vector of length `nobs(fit)`. Use this when the cluster key is derived on the fly (`cluster = interaction(df$region, df$year)`) or pulled from a different dataset with matching row order. Bare unquoted names (`cluster = region`) are **not** accepted — they would require non-standard evaluation that breaks under programmatic use (function wrapping, loops, dynamic column choice). ```{r cr} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth_cc) table_regression( fit, vcov = "CR2", cluster = ~region, show_columns = c("b", "se", "ci", "p", "ame", "ame_p") ) ``` `CR2` (the Bell-McCaffrey adjustment) is the recommended default under few clusters (Pustejovsky and Tipton 2018; Imbens and Kolesár 2016). Coefficient inference uses [`clubSandwich::coef_test()`][clubSandwich::coef_test] with Satterthwaite-corrected degrees of freedom. When AME columns are requested, the same Satterthwaite framework is applied to the AME contrast via `clubSandwich::linear_contrast()`, so `B` and AME share the same t-distribution with the same df — visible in the footer. ## Multiple-comparison adjustment `p_adjust` applies a family-wise or false-discovery-rate adjustment to the p-values via [`stats::p.adjust()`][stats::p.adjust]. The family is the model's full coefficient set (intercept and reference rows excluded); `B` and `AME` are adjusted as independent families within the same call. Available methods are the same as in `stats::p.adjust()`: `"none"` (default), `"holm"`, `"hochberg"`, `"hommel"`, `"bonferroni"`, `"BH"` / `"fdr"`, `"BY"`. ```{r p-adjust} fit <- lm(wellbeing_score ~ age + sex + smoking + education, data = sochealth) table_regression(fit, p_adjust = "bonferroni") ``` The footer documents the chosen method and the family size; the SE column is unchanged. `p_adjust` is orthogonal to `vcov`: combining a robust SE with a family-wise correction is fully supported, e.g. `vcov = "HC3", p_adjust = "holm"`. The adjustment is applied **before** `keep` / `drop` filtering, so the family stays the model's full coefficient set and the displayed adjusted p-values reflect the right denominator regardless of which subset is shown. A methodological note. Adjusting the p-values of every coefficient of a single regression model is *not* the standard convention in social-science or clinical reporting (Rothman 1990; Gelman, Hill, and Yajima 2012; Greenland 2017; Harrell 2015 §5.4; APA Manual 7 §6.46). Each coefficient tests a scientifically distinct hypothesis on a distinct predictor, which is not the situation that family-wise procedures were designed for. The default `"none"` reflects this consensus. Adjustment is nonetheless legitimate in three contexts: 1. **Mass screening** with many candidate predictors and no prior hypothesis — typically `"BH"` (Benjamini–Hochberg, false discovery rate). 2. **Pre-registered multi-endpoint confirmatory designs** — typically `"holm"` (Holm's step-down, strong family-wise error rate control). 3. **When a reviewer, an editor, or a statistical analysis plan explicitly requests it** — apply the requested method and document it in the footer. The argument is exposed under the same *transparency* rule used for `standardized`: the tool is here, the methodological choice is yours, and the footer makes the choice visible to the reader. ## Filtering displayed coefficients `keep` and `drop` accept regular expressions matched against coefficient names (as returned by [stats::coef()]). They are mutually exclusive — pick `keep` to whitelist focal predictors, or `drop` to hide a few control variables. Multiple patterns combine with logical OR. ```{r keep} fit <- lm(wellbeing_score ~ age + sex + smoking + bmi + education, data = sochealth) table_regression(fit, keep = c("^smoking", "^bmi$")) ``` ```{r drop} table_regression(fit, drop = "^education") ``` Together with `p_adjust`, this is the article-ready workflow: adjust on the full model, display only the rows the reader cares about — `table_regression(fit, p_adjust = "BH", keep = "^treatment")`. ## Generalised linear models (glm) `table_regression()` accepts any `glm()` fit. Inference defaults to the `z`-asymptotic Wald regime — the convention used by `summary.glm()`, Stata's `logit, or`, and SPSS `LOGISTIC REGRESSION`. The table title becomes family-aware ("Logistic regression", "Poisson regression", "Probit regression", …) and the default fit-statistics block swaps in `nobs`, `pseudo_r2_mcfadden`, `pseudo_r2_nagelkerke`, and `AIC` instead of `R²` and `Adj.R²`. We illustrate with a logistic regression of `smoking` on `sex`, `age`, and `education` from `sochealth`. The `education` variable is an ordered factor in the source data; we convert it to a plain factor so contrasts are dummy-coded (one row per level) rather than the polynomial trends (`.L`, `.Q`, …) R applies by default to ordered factors. The model mixes a binary factor (`sex`), a numeric predictor (`age`), and a 3-level factor (`education`) — enough to exercise every glm-specific feature below. ```{r glm-basic} sh <- sochealth |> dplyr::mutate(education = factor(education, ordered = FALSE)) fit <- glm(smoking ~ sex + age + education, data = sh, family = binomial) table_regression(fit) ``` ### Response-scale display: `exponentiate = TRUE` Set `exponentiate = TRUE` to switch the `B` column to the response scale and rebrand its header per family and link: `OR` for `binomial(logit)`, `IRR` for `poisson(log)`, `HR` for `binomial(cloglog)`, `RR` for `binomial(log)`, `MR` for `Gamma(log)`, and the generic `exp(B)` otherwise. The standard error follows the delta-method approximation `SE_OR = OR × SE_log-odds` (the Stata `logit, or` convention). The test statistic and the p-value are invariant under any monotone transformation, so they remain on the link scale and match the unexponentiated table verbatim: ```{r glm-or} table_regression(fit, exponentiate = TRUE) ``` ### Term-level partial chi-square `partial_chi2` is the glm analog of `partial_f2`: for each model term, the partial likelihood-ratio chi-square via `drop1(test = "LRT")` (SAS PROC LOGISTIC `TYPE3`; Long & Freese 2014 §3.5). Rendered as `value (df)` so factor terms (`k − 1` df) and numeric terms (1 df) read at a glance: ```{r glm-partial-chi2} fit2 <- glm(smoking ~ age + education, data = sh, family = binomial) table_regression(fit2, show_columns = c("b", "partial_chi2", "p")) ``` ### Standardised coefficients: `refit` and the new `pseudo` For `glm`, `standardized = "refit"` z-scores numeric *predictors* only and refits the model — the response stays on its observed scale because the link function is fixed. This is the "x-standardization" convention (Long and Freese 2014 §4.3.4). The other algebraic methods (`"posthoc"`, `"basic"`, `"smart"`) apply X-only scaling using the same algebra as in the `lm` case. `standardized = "pseudo"` (`glm` only) is the Menard (2004, 2011) *fully* standardised coefficient, scaling by `SD(X) / SD(Y*)` where `Y*` is the latent variable on the link scale and `SD(Y*) = sqrt(var(linear-predictor) + var_link)` with `var_link` = π²/3 for logit, 1 for probit, π²/6 for cloglog. Defined for binomial families; non-binomial returns NA with a `spicy_caveat`: ```{r glm-pseudo} table_regression(fit, standardized = "pseudo") ``` ### Average marginal effects: probability units, not log-odds For `glm`, AME is computed as the average of `dE[Y|X] / dx` over the observed sample — **on the response scale, not the link scale**. For logistic regression this means the displayed AME is in **probability units, not log-odds**: an AME of `-0.15` for `education = Tertiary` reads "on average, holding sex and age constant, a respondent with tertiary education is 15 percentage points less likely to be a current smoker than one with lower secondary education". This is almost always the quantity a substantive reader wants from a logistic regression, and it is the reason AME is increasingly recommended over odds ratios for communicating logistic effects (Mood 2010; Long and Freese 2014 §5.3). ```{r glm-ame} table_regression(fit, show_columns = c("b", "p", "ame", "ame_ci", "ame_p")) ``` Point estimate and inference are delegated to [`marginaleffects::avg_slopes()`][marginaleffects::avg_slopes]. Under cluster-robust variance, the Satterthwaite-df handling described in the *Robust variance* section applies to `glm` AME as well. ### Profile-likelihood CIs: `ci_method = "profile"` The default is `ci_method = "wald"` (symmetric `estimate ± z × SE`, matching `summary.glm`). For `glm` you can also request `ci_method = "profile"`, which calls `MASS::confint.glm()` to compute the profile-likelihood CI — asymmetric, exact for likelihood-based inference, and the gold standard under sparse data or near-boundary estimates (Venables & Ripley *MASS* §7.2). The estimate, SE, statistic, and p-value all remain Wald; `"profile"` only refines the CI bounds: ```{r glm-profile} table_regression(fit, ci_method = "profile") ``` ### Hierarchical glm (LRT) `nested = TRUE` on a list of nested glm fits adds a "Model comparison" block whose default tokens are `c("LRT", "p")` — the APA-conventional layout for hierarchical logistic regression (Hosmer & Lemeshow §3.5; Long & Freese 2014 §3.6). The chi-square statistic comes from `anova(test = "LRT")`. `r2_change` and `F` are not defined for glm and return em-dashes if explicitly requested: ```{r glm-nested} m1 <- glm(smoking ~ sex, data = sh, family = binomial) m2 <- glm(smoking ~ sex + age, data = sh, family = binomial) m3 <- glm(smoking ~ sex + age + education, data = sh, family = binomial) table_regression(list(m1, m2, m3), nested = TRUE) ``` ### Gaussian glm caveat A `glm()` with `family = gaussian` and `link = "identity"` is mathematically equivalent to `lm()` but lacks the variance- explained effect-size family (`partial_f2 / η² / ω²`) and the Satterthwaite-corrected AME path. Following the *transparency over rejection* rule, spicy accepts the fit and emits a `spicy_caveat` suggesting a refit with `lm()`. ## Significance stars Stars are off by default. APA 7 §6.46 explicitly discourages asterisks-only reporting in favour of exact p-values, and ASA's post-2019 guidance (Wasserstein, Schirm, and Lazar 2019) reinforces the same point. Set `stars = TRUE` for the APA preset (`*** p < .001, ** p < .01, * p < .05`) or pass a named numeric vector for custom thresholds: ```{r stars} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) table_regression(fit, stars = TRUE) ``` Stars suffix the `B` column (or `β` when standardisation is requested); the threshold mapping is auto-documented in the footer. The `p` column itself remains unstarred so the numeric value stays readable. ## Display knobs `labels` accepts a named character vector keyed by either formula term labels (`"sex"`) or coefficient names (`"sexMale"`), so individual contrast rows can be relabelled. `intercept_position` switches the intercept to the bottom (Stata convention). `reference_style = "annotation"` lifts the reference level into the factor header (`"sex: [ref: Female]"`) and drops the explicit reference row, for compact tables. `decimal_mark = ","` switches to European decimal notation and automatically changes the CI separator to `";"` to avoid ambiguity: ```{r polish} fit <- lm(wellbeing_score ~ age + sex + education, data = sochealth) table_regression( fit, labels = c( age = "Age (years)", sex = "Sex", education = "Education" ), reference_style = "annotation", intercept_position = "last", decimal_mark = "," ) ``` ## Output formats The default print method draws an ASCII table to the console. The same content is available as a plain `data.frame` (`output = "data.frame"`), a long broom-style data.frame (`output = "long"`), or as a rich-format table for `gt`, `tinytable`, `flextable`, `excel`, `word`, or `clipboard`: ```{r data-frame} out <- table_regression( lm(wellbeing_score ~ age + sex + smoking, data = sochealth), output = "data.frame" ) str(out) ``` ```{r long} out <- table_regression( lm(wellbeing_score ~ age + sex + smoking, data = sochealth), output = "long" ) out[, c("model_id", "term", "estimate_type", "estimate", "std.error", "p.value")] ``` ```{r gt-output, eval = build_rich_tables} pkgdown_dark_gt( table_regression( lm(wellbeing_score ~ age + sex + smoking, data = sochealth), output = "gt" ) ) ``` ## broom integration `broom::tidy()` returns a long tibble with one row per `(model_id, term, estimate_type)` and broom-canonical column names (`estimate`, `std.error`, `conf.low`, `conf.high`, `statistic`, `p.value`). `broom::glance()` returns one row per `(model_id, outcome)` with model-level statistics; `df.residual` is preserved as a numeric so cluster-robust Satterthwaite df flows through verbatim: ```{r broom} fit <- lm(wellbeing_score ~ age + sex + smoking, data = sochealth) out <- table_regression(fit, standardized = "refit") broom::tidy(out) broom::glance(out) ``` The long format is the right entry point when the table is one step in a larger pipeline — saving to disk for the manuscript appendix, faceting by subgroup, or feeding a downstream post-estimation analysis. The `tidy()` output keeps the `estimate_type` column so the same data frame can hold rows for B, β, AME, and per-coefficient effect-size estimates without ambiguity. ## See also - `vignette("table-continuous-lm", package = "spicy")` for the one-predictor-by-many-outcomes counterpart (estimated marginal means, contrast or slope, four effect-size families). - `vignette("summary-tables-reporting", package = "spicy")` for an end-to-end reporting workflow combining the spicy summary- table helpers. ## References Aiken, L. S., and West, S. G. (1991). *Multiple Regression: Testing and Interpreting Interactions*. Sage. American Psychological Association (2020). *Publication Manual of the American Psychological Association* (7th ed.). Section 6.46. Cohen, J., Cohen, P., West, S. G., and Aiken, L. S. (2003). *Applied Multiple Regression / Correlation Analysis for the Behavioral Sciences* (3rd ed.). Lawrence Erlbaum. Gelman, A. (2008). Scaling regression inputs by dividing by two standard deviations. *Statistics in Medicine*, 27(15), 2865–2873. Gelman, A., Hill, J., and Yajima, M. (2012). Why we (usually) don't have to worry about multiple comparisons. *Journal of Research on Educational Effectiveness*, 5(2), 189–211. Greenland, S. (2017). For and against methodologies: Some perspectives on recent causal and statistical inference debates. *European Journal of Epidemiology*, 32(1), 3–20. Harrell, F. E. (2015). *Regression Modeling Strategies* (2nd ed.). Springer. Section 5.4 on multiplicity. Hosmer, D. W., Lemeshow, S., and Sturdivant, R. X. (2013). *Applied Logistic Regression* (3rd ed.). Wiley. Imbens, G. W., and Kolesár, M. (2016). Robust standard errors in small samples: Some practical advice. *Review of Economics and Statistics*, 98(4), 701–712. Long, J. S., and Ervin, L. H. (2000). Using heteroscedasticity consistent standard errors in the linear regression model. *The American Statistician*, 54(3), 217–224. Long, J. S., and Freese, J. (2014). *Regression Models for Categorical Dependent Variables Using Stata* (3rd ed.). Stata Press. McFadden, D. (1974). Conditional logit analysis of qualitative choice behavior. In P. Zarembka (Ed.), *Frontiers in Econometrics* (pp. 105–142). Academic Press. Menard, S. (2004). Six approaches to calculating standardized logistic regression coefficients. *The American Statistician*, 58(3), 218–223. Menard, S. (2011). Standards for standardized logistic regression coefficients. *Social Forces*, 89(4), 1409–1428. Mood, C. (2010). Logistic regression: Why we cannot do what we think we can do, and what we can do about it. *European Sociological Review*, 26(1), 67–82. Nagelkerke, N. J. D. (1991). A note on a general definition of the coefficient of determination. *Biometrika*, 78(3), 691–692. Olejnik, S., and Algina, J. (2003). Generalized eta and omega squared statistics: Measures of effect size for some common research designs. *Psychological Methods*, 8(4), 434–447. Pustejovsky, J. E., and Tipton, E. (2018). Small-sample methods for cluster-robust variance estimation and hypothesis testing in fixed effects models. *Journal of Business & Economic Statistics*, 36(4), 672–683. Rothman, K. J. (1990). No adjustments are needed for multiple comparisons. *Epidemiology*, 1(1), 43–46. Smithson, M. (2003). *Confidence Intervals*. Sage. Steiger, J. H. (2004). Beyond the F test: Effect size confidence intervals and tests of close fit in the analysis of variance and contrast analysis. *Psychological Methods*, 9(2), 164–182. Tjur, T. (2009). Coefficients of determination in logistic regression models — A new proposal: The coefficient of discrimination. *The American Statistician*, 63(4), 366–372. Venables, W. N., and Ripley, B. D. (2002). *Modern Applied Statistics with S* (4th ed.). Springer. Section 7.2 on profile likelihood. Wasserstein, R. L., Schirm, A. L., and Lazar, N. A. (2019). Moving to a world beyond "p < 0.05". *The American Statistician*, 73(sup1), 1–19.