spicy 0.14.1
- Three tests compare their floating-point results with a tolerance, so that they pass under an alternative BLAS such as MKL. No change to the code.
spicy 0.14.0
CRAN release: 2026-10-31
This release adds table_multiresponse() for “check all that apply” questions and makes code_book() a codebook you can deliver, as an object, an Excel workbook, or a PDF. The descriptive tables share one contract for labels, p-values, notes, and machine outputs, and come out ready to publish: decked heads, the minus sign in Typst and LaTeX, decimals per variable, and notes you can replace, extend, or relabel. Some numbers change: table_categorical() computes its percentages on the observed values and picks its test per variable. The tables show column names unless labels = TRUE, and their machine outputs take the broom names.
Breaking changes
Percentages and counts
table_categorical()computes its percentages on the observed values: the(Missing)row shows its count and no percentage.percent_base = "total"gives back the percentages of 0.13.0.A variable never observed in a group of
byshows a dash there, not missing values, and the group leaves itsTotaland test: “No data for X in G.”, or “No data for X.” withoutby(#11).
Tests and effect sizes
table_categorical()defaults totest = "auto": chi-squared when all expected counts are 5 or more or weighted counts are not whole (with a warning), else Fisher’s exact test;"chisq"restores 0.13.0.effect_sizetakes"eta2","epsilon2","cohens_d", and"hedges_g", ases_typedoes; the old values ("eta_sq","d", …) andTRUEorFALSE(use"auto","none") are errors.table_categorical()andspicy_style()renamev_digitstoeffect_size_digits(v_digitsis aspicy_defuncterror), and a style’s value now reaches the association measure and the SMD.
Labels and names
The descriptive tables show column names by default:
labels = TRUEoroptions(spicy.var_labels = TRUE)reads thelabelattribute. The regression tables print as before but refuselabels = NULL.A labeled
byheads its groups with their value labels ([1] Women n, not1 n) in every table, as do the levels of a labeledselectintable_outcome().The descriptive tables refuse a level read as their
(Missing)row orTotalmargin, andtable_outcome()a variable shown as “Overall”: rename it, or relabel the row withoptions(spicy.labels).An unknown name in
labels,note_labels, a nameddigits, or ashow_columnslist warns (classspicy_unknown_name) and is left out, where some tables refused it.
Machine outputs
tidy()oftable_categorical_svy()reportsproportion, between 0 and 1, with its interval on that scale and in table order (it gave the percentage); its"long"output was its wide frame.The long outputs,
tidy(), andglance()take thebroomnames (see?spicy):test_type,statistic,df,df.residual, andp.value(werestatistic_type,chi2,df1,df2,p).The effect size is
es_type,es_value,es_ci_lower, andes_ci_upper(wereeffect_size_type,effect_size,assoc_*,ci_lower,ci_upper), and the SMDsmd_value(smd).The sizes are
nobs,weighted_nobs, andweighted_n(weren_total,n_obs, orn,weighted.nobs,weighted.n), and the fitr.squaredandadj.r.squared(r2,adj_r2).tidy()gives a mean or coefficient, its standard error, and its interval asestimate,std.error,conf.low, andconf.high; the long outputs keepmean,se,ci_lower, andci_upper.variableholds the column name andlabelthe label;tidy()andglance()calledvariableoutcomeintable_categorical(),table_continuous(), andtable_continuous_lm().output = "data.frame"andas.data.frame()give the printed table with its numbers: the keys (variable,label,level,group,.row_role), then the columns as printed (95% CI LL).That frame splits a cell of several numbers (
q1,q3). It was the long frame intable_continuous(), its twin, andtable_outcome(), text intable_regression(), and levels intable_categorical().table_categorical()had nolabelcolumn and gave the label inoutcomeand in thevariableof"long"; theChi2of its old data frame is thestatisticof"long".The long outputs of
table_categorical()andtable_continuous()keep the effect-size and SMD columns,NA, when not requested;table_outcome()drops its always-NASMD columns.glance()oftable_categorical()lists the variables in the order ofselectand gives each measure ines_type("phi","cramer_v");tidy()keeps theTotalmargin (filter ontotal).table_continuous_lm()namesbyin the attributesgroup_varandgroup_label; a t, z, or chi-squared has its owndfand an emptydf.residual, andglance()gives the test the table shows.Every table carries its whole note in
attr(x, "note"), on the printed object and on the frames ofoutput = "data.frame"and"long"; themissing_noteattribute is nownote.as_structured()reads contract version 4 and refuses a view built by an older spicy; theformat_specoftable_categorical()sayseffect_size_digits(wasv_digits).
Headers and columns
The continuous tables name the median
Mdnin headers and data frames (Mdn [Q1, Q3],Mdn 95% CI LL; wasMed), and printMedianandMeanunderjamaandlancet: a script indexingMedmust change.In French, the effect size of
table_continuous()is headedTaille d'effet(wasES, which now heads the standard errors of the regression tables).table_continuous_lm()with a categoricalbysetsMover the group labels and heads the differenceΔin every output, the note giving its order (Δ = B − A.), where it printedΔ (B - A).table_categorical()heads one association measure with its symbol (V, φ, τb, γ), also in its data frame, and its bounds95% CI LLand95% CI UL; theheader_ci_*keys ofspicy.labelsare gone.
Order of groups and levels
The tables sort a character
byin the C locale (capitals first, alike on every machine), putFALSEfirst in a logicalby, and sort a numeric one ascending, where they took the order of appearance.A labeled
byfollows its value labels, or its codes when one is unlabeled.table_continuous_lm()sorted text in the machine’s locale, which chose its reference group.The levels in
table_categorical()and its survey twin, the groupings oftable_outcome(), and the values of a character variable infreq()follow the same order.
inline()
inline()selects a group ofbywithmodelin every table,inline(tbl, bmi, model = "Female")(column = "emmean"for a mean oftable_continuous_lm());levelis aspicy_defuncterror there.inline()cites a statistic of the variable (p, test, effect size, SMD) withoutmodel(the categorical tables also take a group), and in French picks an unnamed model by"Modèle 1", not"Model 1".
Notes
Every table note is one paragraph, opening with the lead-in of the style (“Note.” by default): the data, the missing values, the tests, the definitions. The stars legend follows on its own line (#11).
The descriptive notes open with the sample size, unweighted (“N = 159: Women (n = 80), Men (n = 79).”), name the test behind the p-values (“P-values:”, was “Group comparison:”), and flag weighting.
By default, a note ends with the definitions of the abbreviations of its headers and fit rows (“AIC = Akaike information criterion”), leaving M, SD, SE, B, OR, p, and the like undefined.
The
jamaandlancetthemes use no lead-in word:jamalists the abbreviations first (“Abbreviations: HR, hazard ratio.”),lancetat the end (“HR=hazard ratio.”), neither defining SD, SE, or CI.The missing values of
byare listed with the others (“Missing values removed: score (3), sex (2).”), and each association measure oftable_categorical()gets a sentence (“Phi: smoking.”).
Input checks
The tables refuse, with one error, a
by, weights, or cluster column inselect, the outcome amongpredictors, and the covariates inselect, which they crossed, dropped, or described as variables.A position in
selectcounts every column,byincluded; a position naming such a column is refused, andeverything()and the other helpers skip it, as the survey twins skip the columns of the design.A column not in the data is an error (class
spicy_missing_column) that names it and the close name it may be a typo of; a name in a characterexcludewas ignored.The tables refuse data with no rows, a
bythat no observation holds underdrop_na = TRUE, aselectwith no column of the right type, and, intable_categorical(), aNaNweight.The tables refuse a fractional
digitsorboot_n,p_digits = 0, an output path not ending in.xlsxor.docxor in no existing directory, and unknown...arguments oftable_regression_uv().
Regression tables
table_regression(nested = TRUE)with a robust or cluster-robustvcovtests each added block with a Wald test on that matrix, so the change statistics differ; the classical tests are unchanged.table_regression()refuses a robustvcovfor the extendedmgcvfamilies other thannb()(tw(),betar(), …), whose score is not the GLM score.table_regression()refuses a cluster-robust type a class does not compute: all but"CR1"forsurvreg,gam,polr, and the like, all but"CR0"forcoxphandrms. The note saysCR1, notCL.table_regression()andtable_regression_uv()return the file path, invisibly, withoutput = "excel"or"word", and name an interval column with a point whateverdecimal_mark(97.5% CI LL).table_regression()words itsp_adjustnote for the reader (within each model), opens a Bayesian interval note with the model’s label, and prints anotestring after the lead-in, above the stars legend.
Codebook and data
code_book()returns aspicy_codebookobject, printed as the list of variables, instead of opening aDTwidget;outputwrites the file, andfilenameandinclude_naare defunct.In
code_book(),valuesis a number of categories (default 100), an explicitNAlevel of a factor counts as missing, anddifftimecolumns no longer list their values.sochealthgains a multiple-response question, five logicalinfo_*items afterdentist_12m: later columns move five places, andtable_categorical(sochealth)tabulates 18 variables, not 13.
New features
Multiple-response questions
New
table_multiresponse(), experimental, tabulates a “check all that apply” question: one row per item with its count and the percentage of respondents who chose it;inline()cites its cells (#10, #11).table_multiresponse()gives the percentages of respondents, of responses, or both (percent), a row for the base (“respondents who answered the question”) and one for those who chose no item.With
by,table_multiresponse()tests each item (Holm-adjusted by default) and runs a global test that allows for several answers per respondent, Rao-Scott corrected or bootstrapped.
Descriptive tables
table_categorical(),table_continuous(),table_outcome(), and the survey twins gainp_adjust: the note adds “Adjusted for 3 tests (Holm method).”, and the raw p-values stay asp_unadjusted.table_categorical()gainsp_value(#9):FALSEdrops the test from every output butglance(), which keeps its columnsNA; withassoc_measure = "none"the table is purely descriptive.table_categorical()gainspercent = "row", whose percentages sum to 100 across the groups; underby, its note and that of its survey twin say which percentages they show.table_categorical(total_row = TRUE)closes each variable with a Total row, set apart by a thin rule, which under row percentages gives the base rate they are read against.table_categorical()gainstest = "fisher"(Monte Carlo for large tables), and for 2x2 tables"midp"(Fisher mid-p) and"n1"(Campbell’s N-1 chi-squared); the long output gainstest_type.In
table_continuous(),table_continuous_svy(), andtable_continuous_lm(),digitstakes a named vector: one value per variable for the numbers on its scale, the others at the default.
Labels, notes, and styles
labelstakes four forms in every table:FALSE,TRUE, a named vector (c(bmi = "Body mass index")), or the newvar_labels(), which keeps thelabelattribute of the columns it does not name.Every table function takes
note, whose string replaces the general note, andnote_add, which adds sentences at its end; each keeps the title it prints inattr(x, "title").Every table function gains
note_labels, the label a variable takes in the title and the notes, solabelscan keep a whole question in the rows:note_labels = c(sex = "sex at birth").spicy_style()gainsnote_label, the word opening a note (“Note.” by default, “Note:”, or""), andabbreviations, which abbreviations a note defines (the theme’s rule,"all", or"none").
Outputs and engines
Typst and LaTeX output (LaTeX only for
gt) and the PDF ofcode_book()write a negative number with the minus sign (U+2212);spicy_style(minus_sign = "ascii")keeps the hyphen.The long output and
tidy()mark added rows by keys (.row_role,.group_role,total),tidy()of a table takingbyalways has agroupcolumn, and every result inheritsspicy_table.tidy()oftable_continuous()andtable_outcome()gainsweighted_n,median,q1, andq3, andglance()oftable_categorical()gainssmd_typeandsmd_value.
Regression tables
With
labels = TRUE, the regression tables name the outcome, numeric terms, and factor headers by theirlabel(“Linear regression: Body mass index”); levels and interactions keep their names.table_regression()supports cumulative-link mixed models,ordinal::clmm()fits andglmmTMB::glmmTMB()fits of the ordinal family, with their thresholds and random effects.table_regression(vcov = "CR0")gives cluster-robust standard errors forglmmTMB()fits (glmmTMB>= 1.1.15), clustered by default on the top grouping factor of the random effects.
Codebook
code_book()builds a codebook in three parts (header,variables,values), whichoutputwrites to Excel, Typst source, or a PDF (cover, variable sheets, index) through the Typst that Quarto bundles.code_book()gainssubtitle,authors,notes(with italics, bold, code, and links),source,range,decimal_mark, and PDF settings (font,font_size,colors,paper, …).code_book()documents declared missing values (rows invalues, a summary,declared_codes) and names each type in plain words, in English or French (categorical nominal, numeric, date, …).
French output
Under
options(spicy.language = "fr"), the regression and continuous tables write French headers (ES,IC à 95 %overLIandLS,R² ajusté,Modèle 1); column names stay English.table_regression()writes(réf.)in French (reference_labeldefaults to the label of the language) and translates the reference footer; a label you supply is printed as typed.table_regression()writes in French the standard-error line of every model class (#7), the p-value line, the notes on adjustment, exponentiation, and standardization, and the Bayesian and GEE notes.In French,
table_regression()translates its random-effects test and nested models (modèles emboîtés),table_regression_uv()its title, andtable_continuous_lm()andtable_outcome()writeajusté sur.
Minor improvements and bug fixes
All tables
The table functions warn (class
spicy_ignored_arg) when a typed argument has no effect, such asexcel_pathwithoutoutput = "excel"oradjustmentwithout covariates.Errors name the function called, not a helper, word each argument check one way, and carry the classes listed in
?spicy; a nameddigitswhere one number is expected is refused, with a hint.A column name given to a table function, or to the
selectofmean_n(),sum_n(), andcount_n(), means that column even when an object of that name exists in the calling environment.The notes name a variable as the rows and title do (not by its column name), join a list with semicolons when a label holds a comma, and no longer double a label’s own “?” (“Do you smoke?.”).
Labels that state a subtraction take the minus sign (Q3 − Q1, the SMD direction), a range in a note an en dash (12–30), the singular-fit note ±1, and Cramér’s V its accent in English.
The console no longer draws a rule above a row whose label holds “Total” (“Total score”), and the Excel files leave an empty cell empty rather than holding an empty text.
Rendering
The lead-in of a note is in italics in every output, and LaTeX output keeps the Greek letters and decimal alignment under all three engines, with Cohen’s f² in math.
LaTeX output of
tinytablecompiles when a note holds%or_, centers each spanner over its columns, and scales a table wider than the text block down to it, note included.output = "gt"sets the title at the size of the table text (the descriptive tables used 125%), flush left in LaTeX, where a one-column header has no rule and a wide table is scaled down.Typst output prints the note as text (a URL or the stars legend broke it), keeps words whole (“Fe-male”), aligns wrapped level labels, and widens or spans columns under long labels (#11).
inline()
inline()gives one message for a column the table does not carry (“No column with token”p” in this table.”), and says how to select a group withmodelwhen a variable has no levels.inline()matches a label exactly before it reads a fit statistic (a term “N” cited the number of observations), and refuses avariablethat matches two rows, with an address for each.inline()cites thepartial_chi2oftable_regression(), and refuses atable_continuous_lm()row of means withoutcolumn, giving the address of a mean.
Categorical tables
table_categorical()titles a grouped table with the label ofby(“Categorical table by Sex”), no longer fails when two variables share a label, and shows a column named twice inselectonce.table_categorical()reads a list inlevels_keepper variable (list(smoking = "Yes")), as its survey twin does, and itsglance()leaves the association intervalNAunlessassoc_ci = TRUE.table_categorical()and its survey twin show a dash, not a blank, in the test and association cells of a variable they cannot compare, and name no association measure for it in the note.table_categorical()reports the rows a missing weight removed in its note, where it warned up to three times; it andtable_continuous()no longer warn aboutoptions(spicy.rescale).table_categorical(output = "gt")shows the note on the association measure and the SMD and gives each spanner its own id;output = "excel"draws the rule between variable blocks.table_categorical_svy()gives a percentage of 0 or 100 the exact binomial interval on its unweighted count, whateverci_method, and says so, where it printed a zero-width interval or a dash.table_categorical_svy()withoutselecttabulates only categorical columns; its note counts a declared missing value once and no longer says it removed what its(Missing)row shows.table_categorical_svy()names its test only when one ran, in the note andglance(), whosenobscounts the whole sample underinclude_total = FALSE; its note spells “chi-squared”.With
labels = TRUE, the survey twins keep their labels when rows with a missingbyare dropped, andtable_categorical_svy()heads the bounds of a proportion95% CI LLand95% CI UL.
Continuous tables
table_continuous(),table_continuous_svy(),table_outcome(), andtable_continuous_lm()show a dash in a comparison cell they cannot compute; a blank cell only means one that does not apply.These tables draw no inference on a constant variable, nor a parametric one on groups each constant: the test, p, and effect size show a dash and warn (
spicy_undefined_stat), not p = 1.000 or noise.table_continuous()no longer warns whendrop_na = TRUEremoves rows with a missingby, which the note counts; an effect size printed with its interval is headedES [95% CI].With
ci = FALSE,table_continuous(), its survey twin, andtable_continuous_lm()render one header row, as dotable_outcome()without intervals andtable_categorical_svy()withoutby.With
p_value = FALSE,output = "long"keeps no test column intable_continuous(),table_outcome(), the survey twins, andtable_continuous_lm()(defaultNULL);glance()keeps themNA.table_continuous_lm()fits no model to an outcome with no variance and shows a dash for what an exact fit leaves undefined, and no longer stops when a level ofbyhas no complete observation.table_continuous_lm()lists its covariates by label, keeps its note ingt::gtsave(), leaves out the contrast column undercontrast = "none", and accepts a bootstrap of 10 replicates.table_continuous_svy()compares a numericbyas groups (it tested a slope), compares an orderedbyof two levels, gives each comparison its own degrees of freedom, and warns aboutdrop_na.The note of
table_outcome()names its marginal row as relabeled, no longer names a test that failed, and says each block compares the outcome “without controlling for the other variables”.
Regression tables
table_regression(nested = TRUE)divides the deviance difference by the dispersion forglm(),mgcv::gam(), andmgcv::bam()fits that estimate one, and keeps themgcvtest when df round under one (#6).table_regression()computes the cluster-robust standard errors ofmgcv::gam()andmgcv::bam()fits itself: only agam()with a canonical link and a fixed dispersion had them right.With a cluster-robust
vcov,table_regression()uses that matrix for the joint tests (partial_chi2) and the standardized coefficients oflmerandlmefits, which were model-based.table_regression()handlesglmer()fits with a Gamma, inverse-Gaussian, or non-identity gaussian family, and no longer fails on them or onglmer.nb()fits oncemerDerivis loaded.table_regression()titles aglmer.nb()fit “Negative-binomial mixed-effects regression”, gives the null theta two decimals, and calls a non-identity gaussian null “gaussian regression”.table_regression()calls thepartial_chi2of mixed fits a Wald test, labels themgcvstandard errors “Bayesian (mgcv posterior covariance)”, and words those ofglm.nb()as those of a glm.table_regression()names the link of a non-logit zero part, speaks of the interval in the exponentiation note only when shown, writes the cloglog formula in math signs, and names the classes it supports.
Other functions
cross_tab()sets<apart in its note (p < .001), heads its cornerValeursin French, and refuses a string where a column is expected (cross_tab(df, "sex", smoking)tabulated “sex”).freq()writes a large declared missing code as it is (100000, not1e+05), andvarlist()andcode_book()keep only the selected columns of ansfobject.
spicy 0.13.0
CRAN release: 2026-10-01
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.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 labeled"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.
spicy 0.12.0
CRAN release: 2026-05-19
New features
-
New
table_regression(): publication-ready coefficient summary for one or more fittedlmorglmmodels, side by side. APA Manual 7 formatting is the default. Highlights:- Robust variance: classical, HC, cluster-robust (CR) with Satterthwaite df, bootstrap, jackknife. Per-model
vcovaccepted for SE-comparison tables. - Standardization:
refit,posthoc,basic,smart,pseudo(the lastglmonly). - Average marginal effects (AME) as separate columns; AME inference shares the coefficient’s variance estimator so B and AME are reported on the same inferential footing.
- Partial effect sizes: f², η², ω² for
lm(noncentral-F CIs); partial χ² forglm. - GLM response-scale reporting via
exponentiate = TRUE, with family-appropriate labels (OR, IRR, HR, RR, MR, exp(B)) and optional profile-likelihood CIs (ci_method = "profile"). - Multiplicity correction via
p_adjust(anystats::p.adjust()method). - Hierarchical comparison via
nested = TRUE(ΔR² / F-change forlm; LRT forglm). - Display controls: variable filtering, intercept and factor placement, reference-row styles, multi-model labels, stars, decimal mark, per-column digits.
- Outputs: console,
data.frame, long tibble,gt,flextable,tinytable, Excel, Word, clipboard.broom::tidy()andbroom::glance()methods supported.
See
?table_regressionandvignette("table-regression"). - Robust variance: classical, HC, cluster-robust (CR) with Satterthwaite df, bootstrap, jackknife. Per-model
table_continuous_lm()gains additive covariate adjustment via the newcovariatesargument. Two estimands for the per-group adjusted means:"proportional"(G-computation, default) and"balanced"(equal-weight synthetic grid). Under adjustment,f²andω²become partial effect sizes;dandgraise an explanatory error. The auto-built footer documents the covariates and the estimand. Seevignette("table-continuous-lm").New exported
as_structured()accessor returns a typed view of atable_regression()result for programmatic use: raw numerics, CI split intoLL/ULcolumns, and a column-level format specification.
Breaking changes
-
code_book()no longer silently truncates the export filename to 120 characters. Very long titles now surface a clear OS-level error. Migration: shorten the title or pass an explicitfilename =argument.
Bug fixes
-
table_categorical()no longer over-truncates a p-value in the interval(10^-p_digits, 0.001)whenp_digits >= 4. Example:p = 0.000108now correctly prints as".0001"atp_digits = 4(was"<.0001"). -
count_n(special = ...)returns a length-nrow(data)zero vector when no usable column survives the list-column filter, matching the documented contract and thecount = ...branch (wasnumeric(0), which brokedplyr::mutate()pipelines). -
lambda_gk()andgoodman_kruskal_tau()emitspicy_undefined_statand return a fully-NAresult on rank-1 contingency tables (constant predicted variable), matching the existing pattern ingamma_gk(),kendall_tau_b(),somers_d(), andyule_q(). -
cross_tab()no longer silently overwrites a user’s y-variable level named"N","Total"or"Values". The conflicting reserved column is auto-renamed with a numbered suffix and a singlespicy_renamed_columnwarning is emitted. -
broom::glance()on aspicy_continuous_lm_tablekeepsdf.residualnumeric, so Satterthwaite degrees of freedom fromvcov = "CR2"/"CR3"are preserved verbatim instead of being truncated throughas.integer().
Minor improvements
- Console en-dash alignment: non-numeric placeholders (en-dash, “NA”) sit at the decimal-mark column instead of the integer- part column (APA Manual 7 §7.13). Integer cells in mixed- precision columns (
nrow alongsideR²) keep their right- aligned placement. -
R/source is byte-pure ASCII (tools::showNonASCIIfile()reports zero hits package-wide). -
openxlsx2::wb_add_border()calls now passNULLon unused sides, preventing the default"thin"from being applied to all four sides of a cell when only one rule is intended.
spicy 0.11.0
CRAN release: 2026-05-04
New features
table_continuous_lm()
- Cluster-robust SEs via
clusterand fourvcovchoices ("CR0"–"CR3"), dispatched toclubSandwichwith Satterthwaite df (clubSandwichinSuggests). -
vcov = "bootstrap"(nonparametric or cluster) andvcov = "jackknife"(leave-one-out / leave-one-cluster-out) variance estimators in pure base R, controlled byboot_n. - Three new
effect_sizechoices alongside"f2": Cohen’s"d", Hedges’"g"(two-group only), Hays’"omega2". Neweffect_size_ciadds noncentral t / F CIs rendered inline as0.18 [0.07, 0.30]. -
HC*estimators delegate tosandwich::vcovHC(); rank-deficient fits return a clean rank-by-rank covariance.
Harmonization across the table family
- Shared reporting vocabulary (
decimal_mark,p_digits,align, named-labels) now spanscross_tab(),freq()and the threetable_*()helpers, including APA-style p-value notation (<.001/.045, no leading zero). -
table_categorical()’sassoc_measureaccepts a per-variable spec. When measures differ across rows the column collapses to"Effect size"and an APA-styleNote.line documents the per-variable measure;phion a non-2x2 errors. - All three
table_*()functions gainas.data.frame(),tibble::as_tibble(),broom::tidy()andbroom::glance()methods (broominSuggests).
Quality and robustness
-
Classed conditions. Errors and warnings now carry stable classes (
spicy_error/spicy_warningplus 11 leaf classes documented in?spicy), so downstream code can dispatch viatryCatch()/withCallingHandlers()instead of matching message strings.rlang (>= 1.1.0)required. -
Structured cli messages. Multi-line errors and warnings (vcov fallbacks, bootstrap/jackknife failures,
paddingmigration,labelslength mismatch) render as cli bullets. -
Locale-deterministic ordering. Sorts in
varlist(),freq(),cross_tab()andtable_*()usemethod = "radix". Output is byte-stable across locales and platforms, matching Stata / SPSS guarantees. -
Edge-case hardening. A new length-guarded sort helper makes
varlist()/code_book()/cross_tab()/freq()survive zero-length or all-NADate/POSIXct/charactercolumns and factors with no observed levels. -
Snapshot-locked rendering.
tests/testthat/test-snapshots.Rpins the exact console output of every spicy print method, so any unintended formatting drift surfaces as a PR diff. -
API stability contract.
?spicydocuments which exports are stable, stabilizing or internal. pkgdown reference groups exports via four@familytags. -
Cross-software validation. All 13 association measures agree with PSPP 2.0 (
CROSSTABS /STATISTICS=ALL, 65 / 65 statistics on four datasets); Cohen’s d and Hedges’ g noncentral CIs are tested numerically againsteffectsize::cohens_d()/effectsize::hedges_g()(tolerance = 1e-6); point-estimate formulas and asymptotic standard errors followDescTools(Signorell et al.).
Improvements
-
cross_tab()warns whencorrect = TRUEis ignored on a non-2x2 sub-table, whenweightscontainsNA, and notes statistics computed on a sub-table after empty rows / columns are pruned. -
cross_tab()validatesdecimal_mark,p_digitsandsimulate_Bup front;freq()validatesdecimal_markand tightensdigitsto a non-negative integer. - A user category literally named
"N"or"Total"is no longer mis-rendered as the totals row incross_tab(). -
table_continuous_lm(output = "long")returnsn,df1,df2as integer columns;predictor_labelpreserved on the degenerate-model fallback path. -
cramer_v()/phi()doc states the CI uses the Fisher z-transformation (point estimate and p-value identical toDescTools/ SPSS). -
uncertainty_coef()doc states entropy uses0 log 0 = 0(matching SPSS, PSPP, Stata, Cover & Thomas).
Bug fixes
-
label_from_names()raises actionable errors on duplicate or empty new column names; trims whitespace and preserves the input class. -
table_continuous_lm(output = "data.frame")names contrast CI columns fromci_level(was hardcoded to 95 %). - The categorical-predictor global Wald F degrades to
NAon a singular coefficient covariance submatrix. - The degenerate-table branch of
cramer_v(),yule_q(),gamma_gk(),kendall_tau_b()andsomers_d()respectsdetail: scalarNA_real_by default, fully shapedspicy_assoc_detailwhendetail = TRUE. -
uncertainty_coef()returns a finite estimate (wasNaN) when a marginal is zero. -
somers_d(direction = "symmetric")returns the harmonic mean of the two asymmetric values, matching SPSS / PSPPCROSSTABS. -
print.spicy_assoc_detail()/print.spicy_assoc_table()use APA-strict<.001/.045notation, matching the rest of the package. -
varlist()/code_book()honorfactor_levels = "all"forhaven_labelledcolumns: declared-but-unobserved labels appear in theValuessummary. -
copy_clipboard()rejectsrow.names.as.colvectors of length ≠ 1 and empty strings; accumulates all messages fromclipr::write_clip()instead of overwriting. -
mean_n()/sum_n()reject non-integermin_valid >= 1andmin_valid > ncol; theirdigitsrequires a non-negative integer.
Breaking changes
-
table_continuous_lm()andtable_categorical()default to decimal-point alignment for numeric columns (align = "decimal"). Passalign = "auto"for the previous behavior. -
build_ascii_table()/spicy_print_table():paddingswitches from a string enum to a non-negative integer. Default2L(was+5L); printed tables are roughly 40 % narrower. Migration:"compact" -> 0L,"normal" -> 2L,"wide" -> 4L. -
table_categorical(assoc_measure = "auto")on a 2x2 table picksphiinstead ofcramer_v. Numeric value unchanged (|phi| = V on 2x2); only the column label changes. -
freq()drops observations withNAweights (with a warning) instead of recoding them to zero. Aligns withcross_tab(). -
table_continuous_lm(output = "long")returnsNAines_type/es_valuewheneffect_size = "none"(was"f2"), and renamessum_wtoweighted_n.
spicy 0.10.0
CRAN release: 2026-04-27
New features
code_book()now accepts tidyselect-style variable selectors through..., matchingvarlist()andvl().code_book()gains afilenameargument for the base name of CSV, Excel, and PDF exports. WhenNULL(the default), the filename is derived fromtitleand falls back to"Codebook"when needed. Filenames are sanitized to portable ASCII consistently across platforms.varlist()now summarizes matrix and array columns by their dimensions, and counts valid, missing, and distinct observations by rows.freq()gains afactor_levelsargument that mirrorsvarlist()andcode_book(). Withfactor_levels = "all", declared-but-unobserved factor and labelled levels appear in the output withn = 0, matching SPSSFREQUENCIES; the default"observed"preserves the previous Statatab-style behavior.
Improvements
varlist()now displays missing values as<NA>and<NaN>in theValuessummary wheninclude_na = TRUE, and quotes literal"NA","NaN", and empty-string values so they cannot be confused with the missing markers.varlist()now emits a column-named warning and marks the failing cell as<error: ...>when a column cannot be summarized, instead of silently writing"Invalid or unsupported format". Remaining columns are unaffected.varlist()produces more precise Viewer titles for extraction, pipe, and literalget("name")expressions, while keeping ambiguous dynamic calls anonymous (vl: <data>).code_book()now rejects partial-match names in...(e.g.val = TRUE,tit = "x") that would otherwise be silently treated as tidyselect expressions, and surfacesvarlist()selection errors directly.freq()now resolves theweightsargument via tidy-eval, so column references nested in compound expressions (e.g.weights = if (use_w) col else NULL) work as expected. Qualified expressions likeweights = df2$wcontinue to take precedence over column lookup.freq()validatesdigits,sort,weights, and the logical scalar arguments (valid,cum,rescale,styled) more strictly at the public boundary, with clearer error messages for non-finite values,NA, multi-element inputs, and non-numeric weight vectors.freq()now documents the interaction ofweightscontainingNAwithrescale = TRUE(Statapweightsemantics) and the dropping of unused factor / labelled levels (Statatabsemantics, withcode_book(factor_levels = "all")as the schema-style alternative).
Bug fixes
varlist()now displays labelled values in the same prefixed-label order for compact andvalues = TRUEsummaries; previously the compact summary used data order.varlist(values = TRUE)now deduplicates element types when summarizing list-columns. Previouslylist(1L, 2L, "a")produced"List(3): character, integer, integer"; now produces"List(3): character, integer".include_na = TRUEnow correctly appends<NA>markers for list-columns in bothvarlist()modes; previously it had no effect on this column type.varlist()now validates column names up front and gives clearer errors for missing, empty,NA, or duplicate names.varlist()now errors clearly when tidyselect expressions try to rename columns;...is for selecting variables, not renaming.freq(data, x, weights = NULL)now correctly treats the explicitNULLas “no weighting” instead of emitting a misleading"variable 'NULL' not found"error. Parameterized patterns likeweights = if (use_w) wts else NULLare now supported.print()forspicy_freq_tableno longer crashes when thevar_labelattribute isNA_character_, numeric, or multi-element; theLabel:line is silently skipped for any value that is not a single non-empty string.freq()no longer surfaces the name of the ignoreddatavector in the printed footer when bothdataandxare passed as vectors. The footer now consistently shows the analyzed vector’s name.
spicy 0.9.0
CRAN release: 2026-04-20
Breaking changes
table_continuous()now enables inferential output by default whenbyis supplied. With a grouping variable, thepcolumn fromtestis shown automatically (previous default hid it). This aligns the two table helpers:table_continuous()stays descriptive whenbyis absent, and reports the test p-value whenbyis supplied, matchingtable_continuous_lm()’s inferential default. To preserve the previous behavior, passp_value = FALSEexplicitly.statisticandeffect_sizeremainFALSEby default and must still be enabled consciously.varlist()now displays observed factor levels by default inValues, matching its role as a quick inspection of the current data. Usefactor_levels = "all"to display unused factor levels as well, which was the previous default behavior and remains the default incode_book().
Minor improvements
code_book()gains afactor_levelsargument. It defaults to"all"so exported codebooks continue to document all declared factor levels, including unused levels; use"observed"to mirrorvarlist()output.freq()now prints theFreq.column as integers regardless ofdigits, which continues to control percentage precision. This matches the convention of SPSS, Stata, and SASPROC FREQfor weighted counts and keeps the two numeric concepts (discrete counts vs. continuous percentages) visually distinct.freq(..., styled = FALSE)now returns a genuinely plaindata.framewith nospicy_freq_tablerendering metadata clinging to it, sostr(),dput(), and downstream programmatic use see only the tabulation columns. The metadata attributes (digits,data_name,var_name,var_label,class_name,n_total,n_valid,weighted,rescaled,weight_var) are now documented in@returnand remain available on the invisibly returnedspicy_freq_tableobject whenstyled = TRUE(the default).table_continuous_lm()documentation now clarifies whyp_value = TRUEandr2 = "r2"are the defaults, and robust-variance fallback warnings are now more explicit when a model matrix is singular.
Bug fixes
freq()now correctly resolves qualified weight expressions such asweights = other$worweights = other[["w"]]even when the referenced column name also exists indata. Previously the bare-name fallback could silently pull the weight vector from the wrong data frame when column names collided.freq()withsortand missing values now keeps theNArow at the end of the tabulation so the printedCum. PercentandCum. Valid Percentcolumns stay monotonic and match the Valid → Missing → Total display layout. Sorting previously could push theNArow between valid rows and make cumulative percentages appear to jump.varlist()now preserves literal"NA"and empty-string values in theValuessummary instead of removing them as if they were missing values.varlist()now distinguishes actualNAvalues fromNaNin theValuessummary wheninclude_na = TRUE.varlist(values = TRUE)now preserves factor level order in theValuessummary, matching the default compact factor display.varlist()now validatesvalues,tbl, andinclude_naup front and gives a clear error when one of them is notTRUEorFALSE.
spicy 0.8.0
CRAN release: 2026-04-10
New features
-
table_continuous_lm()adds APA-style bivariate linear-model tables for continuous outcomes. It acts as the model-based companion totable_continuous()for reporting fitted mean comparisons or slopes in anlmframework, with one predictor per model, model-based means for categorical predictors, optional case weights, classical or HC0-HC5 variance estimators, multiple output formats (ASCII, tinytable, gt, flextable, Excel, clipboard, and Word),output = "data.frame"for the wide raw table,output = "long"for the analytic long table, and configurable display of tests, confidence intervals, fit statistics, and effect sizes.
Minor improvements
Installed package vignettes now avoid embedding heavy HTML table and codebook widgets during CRAN builds, reducing package size while preserving rich pkgdown article rendering.
Website and vignette coverage now includes
table_continuous_lm(), using the bundledsochealthdata throughout and adding a dedicated article for model-based continuous summary tables.table_continuous()andtable_continuous_lm()now support dedicated display precision for effect-size columns, andtable_continuous_lm()also supports separate precision forR²columns, so model fit and effect sizes can be formatted independently from descriptive values and test statistics.table_continuous_lm()now keepsnas the unweighted analytic sample size in wide and rendered outputs, and can optionally add a separateWeighted ncolumn reporting the sum of case weights.
spicy 0.7.0
CRAN release: 2026-03-30
New features
table_continuous()is a new helper for continuous summary tables. It computes descriptive statistics (mean, SD, min, max, confidence interval of the mean, and n) for numeric variables, with tidyselect column selection, optional grouping viaby, and multiple output formats (ASCII, tinytable, gt, flextable, Excel, clipboard, and Word).table_continuous()gainseffect_sizeandeffect_size_ciarguments. Whenbyis used,effect_size = TRUEadds an “ES” column with the appropriate measure (Hedges’ g, eta-squared, rank-biserialr_rb, or epsilon-squared) chosen automatically based on the test method and number of groups, andeffect_size_ci = TRUEappends the confidence interval in brackets.table_continuous()gains atestargument ("welch","student", or"nonparametric") to choose the group-comparison method, along with independentp_valueandstatisticdisplay toggles so users can request either or both outputs whenbyis used.ASCII console tables now split oversized outputs into stacked horizontal panels, repeating the left-most identifier columns so wide
freq(),cross_tab(),table_categorical(), andtable_continuous()prints stay readable in narrow consoles.
Breaking changes
table_categorical()replacestable_apa()as the public helper for categorical summary tables. It usesselectandby, supports grouped cross-tabulation or one-way frequency-style tables whenby = NULL, and consolidates output formats under a singleoutputargument. Migrate existingtable_apa()calls totable_categorical(), useoutput = "default"for ASCII tables andoutput = "data.frame"for plain data frames, and replace formeroutput = "wide"/style = "report"paths with the formatted output engines.Excel export now uses
openxlsx2instead ofopenxlsxfor a lighter dependency footprint (no Rcpp compilation required).
Minor improvements
Package citation metadata now uses the current package title and CRAN DOI, so
citation("spicy")matchesDESCRIPTIONand points to the package DOI.table_categorical()andtable_continuous()now print shorter ASCII titles without appending the input data frame name, and no longer requireofficerforoutput = "flextable"alone;officeris now required only for Word export paths that actually write.docxfiles.table_continuous()now accepts tidyselect syntax inexcludein addition to character vectors, and no longer warns thattestis ignored when it is still needed to compute effect sizes.
spicy 0.6.0
CRAN release: 2026-03-23
New features
New family of association measure functions for contingency tables:
assoc_measures(),contingency_coef(),gamma_gk(),goodman_kruskal_tau(),kendall_tau_b(),kendall_tau_c(),lambda_gk(),phi(),somers_d(),uncertainty_coef(), andyule_q(). Each returns a numeric scalar by default; passdetail = TRUEfor a named vector with estimate, confidence interval, and p-value.cross_tab()gainsassoc_measureandassoc_ciarguments. When both variables are ordered factors, it automatically selects Kendall’s Tau-b instead of Cramer’s V. The note format changes fromChi-2: 18.0 (df = 4)toChi-2(4) = 18.0. Numeric attributes (chi2,df,p_value,assoc_measure,assoc_value,assoc_result) are now attached to the output data frame.table_apa()now dynamically labels the association measure column based on the measure used, instead of always showing “Cramer’s V”. Newassoc_measureandassoc_ciarguments are passed through tocross_tab().table_apa()gainsoutput = "gt"to produce agt_tblobject with APA-style formatting, column spanners, and alignment.table_apa()now correctly centers spanner labels over their column pairs intinytableandflextableoutput.All association measure functions and
assoc_measures()gain adigitsargument (default 3) that controls the number of decimal places when printed. The p-value always uses 3 decimal places or< 0.001.detail = TRUEresults now print with formatted output (aligned columns, fixed decimal places) via a newprint.spicy_assoc_detail()method.assoc_measures()output uses a newprint.spicy_assoc_table()method with the same formatting.New bundled dataset
sochealth: a simulated social-health survey (n = 1200, 24 variables) with variable labels, ordered factors, survey weights, and missing values. Includes four Likert-scaled life satisfaction items (life_sat_health,life_sat_work,life_sat_relationships,life_sat_standard) for demonstratingmean_n(),sum_n(), andcount_n().
Bug fixes
count_n()now correctly countsNAvalues whencount = NAandstrict = TRUEare both used. List columns are now reported in verbose mode instead of causing silent errors.cross_tab()rescale logic now operates on complete cases only, so the weighted total N matches the unweighted N when missing values are present (consistent with Stata behavior).freq()now uses trueNAconsistently (instead of the"<NA>"string) in both weighted and unweighted paths.cum_valid_propis now correctlyNAfor missing rows. Invaliddigitsandsortvalues are rejected with clear error messages.mean_n()andsum_n()now validatemin_validanddigitsarguments, rejecting non-numeric, negative, or multi-element values.mean_n(),sum_n(), andcount_n()no longer trigger a tidyselect deprecation warning whenselectreceives a character vector. Character vectors are now automatically wrapped withall_of().table_apa()now preserves the original factor level order in row variables instead of sorting alphabetically. Whendrop_na = FALSE, the(Missing)category is placed at the bottom of each variable’s levels.percent_digits,p_digits, andv_digitsare now validated.table_apa()p-values no longer wrap across lines intinytableHTML output.
Breaking changes
-
cramer_v()now accepts adetailargument. By default it returns a numeric scalar (as before). Passdetail = TRUEto get a 4-element named vector (estimate,ci_lower,ci_upper,p_value), ordetail = TRUE, conf_level = NULLfor a 2-element vector (estimate,p_value) without CI.
spicy 0.5.0
CRAN release: 2026-03-14
New features
- New
table_apa()helper to build APA-ready cross-tab reports with multiple output formats (wide,long,tinytable,flextable,excel,clipboard,word). -
table_apa()exposes keycross_tab()controls for weighting and inference (weights,rescale,correct,simulate_p,simulate_B) and now handles missing values explicitly whendrop_na = FALSE.
Bug fixes
-
count_n()no longer crashes whenspecial = "NaN"is used with non-numeric columns. Passingcount = NAnow errors with a message directing tospecial = "NA". -
cross_tab()fixes a spurious rescale warning for explicit all-ones weights and aligns the Cramer’s V formula withcramer_v(). -
table_apa()no longer leaks global options on error. Thesimulate_pdefault is aligned toFALSE. -
varlist()title generation no longer crashes on unrecognizable expressions.
Minor improvements
-
copy_clipboard()parametermessagerenamed toshow_message. -
freq()now dispatches printing correctly via S3. - Removed unused
collapseandstringifromImports.
spicy 0.4.2
CRAN release: 2026-03-06
-
cross_tab()hardening: improved vector-mode detection (including labelled vectors), stricter weight validation, safer rescaling, and clearer early errors (e.g., explicity = NULL). -
cross_tab()statistics are now computed on non-empty margins in grouped tables, avoiding spuriousNAresults; internal core path refactored to removedplyr/tibblefrom computation while preserving user-facing behavior. -
freq()now errors clearly whenxis missing for data.frame input and validates rescaling when weight sums are zero/non-finite. -
count_n(),mean_n(), andsum_n()regex mode is hardened (regex = TRUEnow validates/defaultsselectsafely). -
mean_n()andsum_n()now returnNA(with warning) when no numeric columns are selected. -
label_from_names()now validates input type (data.frame/tibble required). -
cramer_v()now returnsNAwith warning for degenerate tables. - Dependency optimization:
DTandcliprmoved toSuggests; optional runtime checks added incode_book()andcopy_clipboard(). - Tests expanded with regression coverage for all the above edge cases.
spicy 0.4.1
CRAN release: 2025-12-21
- Fixed CRAN incoming check notes by removing non-standard top-level files.
spicy 0.4.0
Print methods have been fully redesigned to produce clean, aligned ASCII tables inspired by Stata’s layout. The new implementation improves formatting, adds optional color support, and provides more consistent handling of totals and column spacing.
Output from
freq()andcross_tab()now benefits from the enhancedprint.spicy()formatting, offering clearer, more readable summary tables.Documentation and internal tests were updated for clarity and consistency.
cross_tab()gains an explicitcorrectargument to control the use of Yates’ continuity correction for Chi-squared tests in 2x2 tables. The default behavior remains unchanged.The documentation of
cross_tab()was refined and harmonized, with a clearer high-level description, improved parameter wording, and expanded examples.Minor cosmetic improvements were made to
varlist()output: the title prefix now usesvl:instead ofVARLIST, and the column nameNdist_valwas renamed toN_distinctfor improved readability and consistency.Minor cosmetic improvement: ASCII table output no longer includes a closing bottom rule by default.
spicy 0.3.0
CRAN release: 2025-10-22
- New function
code_book(), which generates a comprehensive variable codebook that can be viewed interactively and exported to multiple formats (copy, print, CSV, Excel, PDF).
spicy 0.2.1
CRAN release: 2025-10-04
-
label_from_names()now correctly handles edge cases when the separator appears in the label or is missing.
spicy 0.2.0
CRAN release: 2025-09-25
- New function
label_from_names()to derive and assign variable labels from headers of the form"name<sep>label"(e.g."name. label"). Especially useful for LimeSurvey CSV exports (Export results -> CSV -> Headings: Question code & question text), where the default separator is". ".
spicy 0.1.0
CRAN release: 2025-05-05
Initial release
- Introduces a collection of tools for variable inspection, descriptive summaries, and data exploration.
- Provides functions to:
- Extract variable metadata and display compact summaries (
varlist()). - Compute frequency tables (
freq()), cross-tabulations (cross_tab()), and Cramer’s V for categorical associations (cramer_v()). - Generate descriptive statistics such as means (
mean_n()), sums (sum_n()), and counts (count_n()) with automatic handling of missing data. - Copy data (
copy_clipboard()) directly to the clipboard for quick export.
- Extract variable metadata and display compact summaries (