Publication-Ready Tables for Descriptive Statistics and Regression Models
Overview
Provides publication-ready tables for descriptive statistics and regression models: frequency tables and cross-tabulations with association measures (Cramer's V, Kendall's Tau-b, and others), categorical and continuous summary tables, by group or from a complex survey design, and regression tables for one or more models side by side, across more than thirty model classes from mixed-effects to survival and Bayesian, with robust standard errors, average marginal effects, and univariable screening. Tables follow APA conventions by default, can switch to named journal styles such as JAMA, NEJM, or The Lancet, and render identically in the console and in 'gt', 'tinytable', 'flextable', 'Word', 'Excel', or the clipboard. Declared missing values in labelled data are honored and disclosed throughout the descriptive tables. Helpers cover codebooks, variable inspection, and row-wise summaries.
table_regression() now covers more than thirty model classes and gains
a univariable screen, the summary tables get survey-design twins, six
journal styles and a French output arrive, and declared missing values
are honored package-wide. The walk-throughs live as articles at
<https://amaltawfik.github.io/spicy/>.
Breaking changes
- Declared missing values (
na_values,na_range, tagged NAs) now count as missing infreq(),cross_tab(), thetable_*()family, the row-wise helpers, andvarlist()/code_book(), so numbers change for labelled survey data. The tables disclose the exclusion in a note.user_na = FALSErestores the previous behavior. varlist(),vl(), andcode_book()use one missing definition forN_distinct,N_valid, andNAson labelled data.
Show all changes in 0.13.0Hide the rest of the changes in 0.13.0
cross_tab()counts observations at an explicitNAfactor level as a regular row or column.freq()excludes them fromn_validand the valid percent.freq()andcross_tab()replacestyledwithoutput.styled = FALSEbecomesoutput = "data.frame", andstylednow errors with the replacement.cross_tab(output = "data.frame")returns a plaindata.framewithout metadata attributes. Read them from the default object, for exampleattr(cross_tab(...), "p_value").freq()defaults torescale = FALSE, matchingcross_tab(). Userescale = TRUEfor the previous behavior.freq(),table_categorical(),table_continuous(), andtable_continuous_lm()no longer print when their result is assigned.freq()drops its unused..., so unknown arguments error.options(OutDec)no longer changes spicy's output. Every number followsdecimal_markalone, or the style or language in force.table_categorical()defaults todrop_na = FALSE, showing missing values as a"(Missing)"level. Itslabelsmust be a named vector, andp_digitsbelow 1 errors.table_categorical(output = "long")names the association columneffect_sizeand addseffect_size_type. Replaceout[["Cramer's V"]]without$effect_size.table_categorical(output = "flextable")no longer writes a.docxwhenword_pathis supplied. Useflextable::save_as_docx().table_continuous_lm(output = "data.frame")names the effect-size interval boundses_ci_lower/es_ci_upper, like the"long"output.standardized = "smart"scales continuous inputs by 2 SD and leaves binary inputs unscaled. The rule was applied inverted since 0.12.0, so those betas change.table_regression(exponentiate = TRUE)errors on links whose exponentiated coefficient is not a ratio (probit, cauchit, inverse, sqrt).keep/dropno longer match the intercept row.show_interceptalone controls it.align = "auto"is removed. Use"decimal"(the default),"center", or"right".- The
show_fit_statsinformation criteria are lowercase tokens ("aic","aicc","bic").show_fit_stats = character(0)errors. UseFALSEto suppress the block. - With several models,
show_columns = "all_b"/"all_ame"drop the CI columns. Request atomic tokens to keep them. - A robust
vcovthat cannot be computed is now an error, and aclustercontainingNAis refused. It used to warn and label the classical variance robust. Weighted nandglance()'sweighted_nobsareNAfor an unweightedglm(). They used to repeatn.as_structured()describes each row in the body itself (body$.row_role,body$.indent,cell_status). The 0.12.0 row-index vectors are removed,versionis3, and a view built by an older spicy is refused.tidy()labels AME rowsestimate_type = "ame"(was"AME").count_n()warns and returnsNAwhen the selection resolves to no usable column.mean_n()andsum_n()withmin_valid = 0returnNAfor rows with no valid values.copy_clipboard()arguments use snake_case.build_ascii_table()is no longer exported. Usespicy_print_table().- Association measures with
detail = TRUEalways include anseelement. On degenerate tables,gamma_gk(),kendall_tau_b(),kendall_tau_c(), anduncertainty_coef()returnNAwith a classed warning instead of a spurious value.conf_levelis validated everywhere. - The package ships a single vignette, *Get started*. The walk-throughs live as articles on the package site, at the same URLs, and
vignette("<name>")no longer finds them.
New features
table_regression()supports more than thirty model classes beyondlm/glm: mixed effects (lmer,glmer,glmmTMB,lme,gls), GEE, Bayesian (stan_glm(),stan_glmer(),brm()), survival (coxph,survreg,cph,flexsurvreg), ordinal (polr,clm), multinomial (multinom,mlogit), two-part counts (zeroinfl,hurdle),fixest,estimatr,ivreg,tobit,rq,rlm,glm.nb,nls,gam,betareg,selection,rms, and the design-basedsvyglm,svyolr, andsvycoxph.?table_regression_modelsis the registry. A request a class cannot honor is refused with a classed error, never rendered as an empty column.- Each family renders with its own conventions. Mixed models report their random effects as a block of rows with SE and CI, the ICC, and a boundary-correct test of the random part. Ordinal models report their thresholds. Two-part models report every component.
fixestandestimatrfits disclose their absorbed fixed effects as aFixed effects:block. Bayesian fits report posterior medians, MAD SD, and credible intervals, with no p-values and a sampler-diagnostics guard. - New
table_continuous_svy()andtable_categorical_svy()summarize asurveydesign object, with every statistic computed by survey and the design degrees of freedom throughout. Design-based regressions (svyglm(),svyolr(),svycoxph()) report both counts and name their variance estimator in the note. - New
table_outcome()summarizes one continuous outcome across several categorical variables, one block per grouping, with the group comparison and anOverallrow. - New
table_regression_uv()builds univariable screening tables forlm,glm, andcoxphoutcomes, one fit per predictor merged beside the multivariable model, with a per-predictorNcolumn. - New
inline()cites one table cell in Quarto or R Markdown text. The returned string is exactly the displayed cell, so a quoted number can never drift from the table. - New
styleargument on the four table families, andoptions(spicy.style = )for a whole document:"jama","nejm","lancet","annals","apa", and"aer". A theme applies the rules its journal publishes, as defaults, so any argument you pass wins.spicy_style()builds a style by hand or from a theme. options(spicy.language = "fr")prints table labels in French and brings French typography with it: a decimal comma and a leading zero on p-values (0,003). Machine outputs, column names, and messages stay in English.options(spicy.labels = )overrides one label at a time, andspicy_labels()lists them.- New
show_columnsfamilies"rmst"and"risk_diff"forcoxphandsurvregfits: covariate-adjusted differences in restricted mean survival time and in cumulative incidence, by g-computation with bootstrap inference, in single tables and in the univariable screen. - New
show_columnstoken"n_events"shows event counts asevents/Nbeside the estimates, for binomial outcomes andcoxphfits. - Heteroskedasticity- and cluster-robust
vcovacross the supported classes, with each class's field-standard backend."CR1S"reproduces Stata'sregress, vce(cluster)exactly. What no backend supports is refused, never approximated. ci_method = "profile"gives profile-likelihood CIs forglm,polr, andclm.ci_method = "boot_percentile"reports percentile CIs from the bootstrap replicates.nested = TRUEworks across the new classes with the correct test for each, and refuses hierarchies that are not comparable.- AME columns are available for many more classes, per outcome category for ordinal and multinomial models, and honor a robust
vcov.broom::tidy()gainsoutcome_levelfor those rows. table_categorical()andtable_continuous()gainsmd = TRUE, a standardized-mean-difference column, the balance diagnostic of a Table 1.table_continuous()gainsweightsandrescale, under a documented convention that matches Stata's[aweight]andsurvey::svyvar(). Group tests are refused under weights.table_continuous_lm()is the tool for that.table_continuous()gainsshow_columnswith median tokens ("med","q1","q3","iqr","med_iqr","med_ci"), per variable via a named list. A variable shown as a median is tested as one.selectis optional intable_categorical(), andtable_continuous()gainsdrop_na = FALSE.as_structured()reads the descriptive tables too, and carries everything the printed table shows, with a per-row identity (.variable,.level,.row_role) that survives stacking.- Seven new articles on the package site: mixed-effects, GEE, multinomial, count and two-part, survival, ordinal regression tables, and categorical predictors.
Minor improvements and bug fixes
The first eight fixes change numbers that 0.12.0 reported.
kendall_tau_b()reported wrong standard errors, confidence intervals, and p-values in every release from 0.6.0 through 0.12.0. Point estimates were correct.assoc_measures()andcross_tab()were affected too.- Binomial models fitted with a
cbind(successes, failures)response were refitted with squared weights by every internal refit. Bootstrap and jackknife inference, the default McFadden and Nagelkerke R², andstandardized = "refit"were wrong for them. Fits with a 0/1, factor, or proportion-plus-weights response were never affected. - Average marginal effects use the fit's prior weights, so AME values change for weighted fits.
- Partial effect sizes are true Type-II tests. In models with interactions, main effects no longer depend on the factor coding.
ci_method = "profile"with a robustvcovdefers to thevcovand warns.table_categorical()computes the ordinal association measures in declared level order underdrop_na = FALSE.table_continuous_lm()reports correct estimates whenbyis an ordered factor and correct"balanced"adjusted means with an ordered-factor covariate. It pins treatment contrasts, sooptions(contrasts = )no longer alters the results.cross_tab()computes weighted totals from the unrounded table.- Under
decimal_mark = ","every surface follows the mark. P-values keep their leading zero (0,018), and the star legend, the change statistics, and the association intervals read the comma. nested = TRUEno longer reports a negative chi-square with a p-value when the models are passed largest-first.cramer_v(),phi(), andcontingency_coef()returnNAwith a classed warning on a zero margin, andsomers_d(direction = "symmetric")returns0on equal concordant and discordant pairs.- The
tau_cmeasure is labelled"Stuart's Tau-c"everywhere. freq()keeps its label footer whenNA-weight rows are dropped, warns when distinct codes merge under one label, and sorts labelled variables by code undersort = "name+".cross_tab()reports excluded missing values in the table note, accepts logical weights likefreq(), and no longer swallows the warnings of its association measures.table_categorical()displays labelled columns as"[code] label"levels in every path, and keeps both the group and the margin when abylevel is named"Total". Its machine outputs carry full-precision values and the documentedChi2anddfcolumns.table_categorical(),table_continuous(), andtable_continuous_lm()resolvebydata-first, like tidyselect. Abywith no level to tabulate is refused.table_continuous()forms groups from a non-factorbyin order of first appearance, and degrades per variable when a test fails on degenerate data.table_continuous()andtable_continuous_lm()label an interval with its own coverage (97.5% CI, not98% CI).table_continuous_lm()discloses robust and resampling SEs in the note, treats a value-labelledbyas categorical, and degrades cleanly on degenerate fits.output = "gt"tables keep their note when saved or printed non-interactively. gt and flextable outputs render in Quarto and R Markdown Word, PowerPoint, and PDF documents, where they silently disappeared. Newas_flextable()returns the underlying flextable.output = "gt"escapes abylevel or model name carrying a quote, an angle bracket, or a backslash.stars = TRUEmarks the coefficients in every output, not just the console.- A cell whose statistic applies but has no number shows the console's en dash in every rich output, instead of a blank.
- A
table_categorical(by = )table carries its association note to every output. The descriptive gt and tinytable outputs also draw the title and the rule between variable blocks. - Factor levels are indented once, not twice, in the tinytable, Word, and Excel outputs.
- Tables without a confidence-interval column lose their empty header strip, multi-line notes keep one disclosure per line, and Typst output no longer forces a column gutter under grouped headers.
- Footer lines cite a model by its displayed label, not
Model 1. output = "excel"writes the significance stars, blank cells instead of#N/A, honorsalign, and sizes its columns to the text.output = "clipboard"quotes cells that contain the delimiter, ships plain text instead of Excel formulas, and errors clearly on a system without a clipboard.- Console layout survives
NAcells, empty cells, and wide characters (CJK, emoji). table_regression(m1, m2)withoutlist()errors helpfully, and anNAor colliding model name no longer crashes the table.- Factor rows follow
levels()order instead of alphabetical, and factors fit with non-default contrasts (successive differences, sum-to-zero, Helmert) group under their parent variable. - A factor level containing
:("Part-time: 50-89%") stays inside its variable block. It used to be mistaken for an interaction term. - The statistic column header follows each model's reference distribution (
zort). - Bootstrap, jackknife, and
standardized = "refit"refits no longer leak the caller's environment and work onfactor()/log()/poly()formulas. varlist()andcode_book()renderPOSIXltcolumns anddifftimeunits, and show an explicitNAfactor level as<NA>.count_n()resolvesselectandexcludethrough the same tidyselect path asmean_n()/sum_n(), and errors clearly on an unusablecountorspecial.- The tabulating and summarizing functions reject
bit64::integer64input with a classed error naming the fix. - Error messages quote values the same way on every platform, and the enum arguments raise classed errors naming the valid values.
copy_clipboard()re-emits backend messages and warnings as real R conditions.- An
estimatrfit reports R² and adjusted R² by default likelm, discloses its absorbed fixed effects, and labels its model type after itsse_type.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-04
- NOTE12 OK, 1 NOTE2026-08-01
- OK13 OK2026-06-09
- ERROR12 OK, 1 ERROR2026-06-07
- OK13 OK2026-05-20
Show 4 earlier changesHide 4 earlier changes
- ERROR11 OK, 1 ERROR2026-04-25
- OK14 OK2026-04-11
- NOTE13 OK, 1 NOTE2026-03-31
- OK14 OK2026-03-10
Documentation
The repository also keeps web-only articles.
- Help pages
- 71 (34 internal)includes a package overview page
- Examples that run
- 92%33 run in a plain check (2 of them under a condition) · 1 only when donttest examples are enabled (R CMD check --as-cran) · 2 never run
- Exported functions with examples
- 100%
- Documented parameters
- 94%
- Return-value docs
- 100%
- Help pages citing references
- 21%
Downloads
Repository
- PRs merged (all time)
- 67%
- Median days to close, 50 most recently updated closed issues
- 2
- Median days to close, 50 most recently updated closed PRs
- 0
Stars over time
Forks over time
Releases over time
Issues over time
PRs over time
Commits over time
15 development-tooling and community-health practices detected across 8 families in the upstream repository
Checks run against github.com/amaltawfik/spicy on 2026-10-22.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
GitHub Pages site last deployed 2026-10-22 · The repository has a site deploy workflow
pkgdown site last built 2026-10-22
Show all practicesHide 4 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-03-13Seen in the repository: CLAUDE.mdNamed in .Rbuildignore: .claude, CLAUDE.mdNamed in .gitignore: .claudeCommits: at least 137 crediting it in the message
- Claude Opus 4.6100 commits · Mar 2026
Some of these counts are a minimum, because the search stopped before the oldest commits.
Dependencies
Installing spicy adds 35 packages (19 with compiled code, 23 MB of source tarballs). Heaviest: stringi 11 MB, sandwich 1.4 MB, vctrs 1.0 MB.
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 0.13.0Latest2026-10-01 · current release · diff ↗ ·
removed 1 export
build_ascii_tableadded 9 exports
inline, spicy_labels, spicy_style, spicy_style_names, table_categorical_svy, table_continuous_svy, table_outcome, table_regression_models, table_regression_uv - 0.12.0
- 0.11.02026-05-04 · diff ↗
- 0.10.02026-04-27 · diff ↗
- RR 4.6.0 released · 2026-04-24
- 0.9.02026-04-20 · diff ↗
- 0.8.0
- 0.7.0
- 0.6.02026-03-23 · diff ↗ ·
added 11 exports
assoc_measures, contingency_coef, gamma_gk, goodman_kruskal_tau, kendall_tau_b, kendall_tau_c, lambda_gk, phi, somers_d, uncertainty_coef, yule_q - 0.5.0
- 0.4.22026-03-06 · diff ↗
- 0.4.1
- 0.3.0
- 0.2.12025-10-04 · diff ↗
- 0.2.0
- 0.1.02025-05-05
Show 1 earlier eventHide 1 earlier event
- RR 4.5.0 released · 2025-04-11
Package metadata
- First published
- 2025-05-05
- Total releases
- 15 / 1 yrs
- License
- MIT + file LICENSE OSI
- Minimum R
- ≥ 4.1.0
- Bundled data
- 24 KB / 1 file
- Download size
- 2.4 MB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("spicy")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-24, which the citation names so these numbers can be found later. More on citing and the projects behind them.