Builds a publication-ready frequency or cross-tabulation table for one or many categorical variables selected with tidyselect syntax.
With by, produces grouped cross-tabulation summaries (using
cross_tab() internally) with Chi-squared p-values and optional
association measures.
Without by, produces one-way frequency-style summaries.
Multiple output formats are available via output: a printed ASCII
table ("default"), a wide or long numeric data.frame
("data.frame", "long"), or publication-ready tables
("tinytable", "gt", "flextable", "excel", "clipboard",
"word").
Usage
table_categorical(
data,
select = tidyselect::everything(),
by = NULL,
labels = NULL,
levels_keep = NULL,
include_total = TRUE,
drop_na = FALSE,
weights = NULL,
rescale = FALSE,
correct = FALSE,
simulate_p = FALSE,
simulate_B = 2000,
percent_digits = 1,
p_digits = 3,
v_digits = 2,
assoc_measure = "auto",
assoc_ci = FALSE,
smd = FALSE,
decimal_mark = ".",
align = c("decimal", "center", "right"),
output = c("default", "data.frame", "long", "tinytable", "gt", "flextable", "excel",
"clipboard", "word"),
indent_text = " ",
indent_text_excel_clipboard = strrep(" ", 6),
add_multilevel_header = TRUE,
blank_na_wide = FALSE,
excel_path = NULL,
excel_sheet = NULL,
clipboard_delim = "\t",
word_path = NULL,
user_na = TRUE,
style = NULL
)Arguments
- data
A data frame.
- select
Columns to include as row variables. Supports tidyselect syntax and character vectors of column names. When omitted, defaults to every eligible categorical column in
data: factor, character, logical, and labelled (haven/labelled) columns, excluding thebycolumn – matching the select-less defaults oftable_continuous()andtable_continuous_lm(). An explicitselectis taken verbatim (numeric columns included), so numeric-coded categorical variables can still be tabulated by naming them.- by
Optional grouping column used for columns/groups. Accepts an unquoted column name or a single character column name. Factor levels keep their declared order; any other
by(character, numeric, haven labelled) forms group columns in order of first appearance in the data – the same convention astable_continuous(). For a haven labelledby, the group headers are the raw codes (value labels are not used for group headers – the family convention shared withtable_continuous()andtable_continuous_lm()); declared missing values followuser_naas usual.- labels
An optional named character vector of variable labels whose names match column names in
data(e.g.c(smoking = "Current smoker")) – the same contract astable_continuous()andtable_continuous_lm(). Only listed columns are relabelled. For the remaining columns (and whenlabels = NULL, the default), labels are auto-detected from the variable's label attribute (e.g. fromhaven); if none is found, the column name is used. Unnamed (positional) label vectors, accepted before 0.13.0, now raise an error.- levels_keep
Optional character vector of levels to keep/order for row modalities. If
NULL, all observed levels are kept. Entries must match the level strings the table displays (for labelled columns these are the"[code] label"strings, not the bare label text). When nothing matches for a selected variable, that variable is dropped from the table with a classed warning (spicy_no_selection) listing the available level strings.- include_total
Logical. If
TRUE(the default), includes aTotalgroup when available.- drop_na
Logical. If
FALSE(the default), missing values are displayed as a dedicated"(Missing)"level (and, underby, a"(Missing)"group column) – the field convention for descriptive tables (gtsummary's "Unknown" row, janitor'sNArow; see the Epidemiologist R Handbook, Descriptive tables). IfTRUE, rows withNAin the tabulated variable (and inby, when supplied) are removed BEFORE each cross-tabulation, and the removal is disclosed in a table note ("Missing values removed: ...") rather than silent. Before 0.13.0 the default wasTRUEwith no disclosure.- weights
Optional weights. Either
NULL(the default), a numeric vector of lengthnrow(data), or a single column indatasupplied as an unquoted name or a character string.- rescale
Logical. If
FALSE(the default), weights are used as-is. IfTRUE, rescales weights so total weighted N matches raw N. Passed tospicy::cross_tab(). When the argument is not supplied, the default is read fromoptions(spicy.rescale)(falling back toFALSE), matchingcross_tab().- correct
Logical. If
FALSE(the default), no continuity correction is applied. IfTRUE, applies Yates correction in 2x2 chi-squared contexts. Passed tospicy::cross_tab().- simulate_p
Logical. If
FALSE(the default), uses asymptotic p-values. IfTRUE, uses Monte Carlo simulation. Passed tospicy::cross_tab().- simulate_B
Integer. Number of Monte Carlo replicates when
simulate_p = TRUE. Defaults to2000.- percent_digits
Number of digits for percentages in report outputs. Defaults to
1.- p_digits
Integer >= 1. Number of decimal places used to render p-values in the
pcolumn (default:3, the APA Publication Manual standard). Both the displayed precision and the small-p threshold derive from this argument:p_digits = 3prints.045and<.001;p_digits = 4prints.0451and<.0001. Leading zeros are always stripped, following APA convention.- v_digits
Number of digits for the association measure. Defaults to
2.- assoc_measure
Which association measure to report alongside the chi-squared p-value. Accepts four input shapes:
"none"– drop the column entirely."auto"(the default) – pick a measure per row variable based on the variable type: a 2x2 table (binary row variable vs. binaryby) usesphi, a pair of ordered factors usestau_b, every other case usescramer_v.a single string from
c("cramer_v", "phi", "gamma", "tau_b", "tau_c", "somers_d", "lambda")– applied uniformly to every row variable.a character vector with one entry per row variable. Both named (
c(smoking = "phi", health = "tau_b"), recommended; unnamed variables fall back to"auto") and unnamed positional (c("phi", "tau_b", "auto"), paired up withselect) are accepted. Named is more robust to reordering ofselect.
When a single measure is used for every row, the column header is that measure's name (e.g.
"Cramer's V"). When multiple measures are used (typically with"auto"on a heterogeneousselect), the header collapses to"Effect size"and an APA-styleNote.line is appended documenting which measure was used for which variable.phirequires a 2x2 table; if explicitly requested for a non-2x2 variable, an error is raised so the user can choose another measure or fall back to"auto".- assoc_ci
Passed to
cross_tab(). IfTRUE, includes the confidence interval of the association measure. In wide raw outputs ("data.frame","excel","clipboard"), two extra columnsCI lower/CI upperare added; in the long raw output ("long") the bounds appear asci_lower/ci_upper. In rendered formats ("gt","tinytable","flextable","word"), the CI is shown inline (e.g.,.14 [.08, .19]). Defaults toFALSE.- smd
Logical. If
TRUE, adds anSMDcolumn holding the standardized mean difference between the two groups ofby, the balance diagnostic of the Table 1 literature. Requires exactly two groups; the value sits on the variable row besidep, never on a level row. Signed for a two-category variable (group 1 minus group 2 on the second category), unsigned for three or more, where it is a distance. No confidence interval and no p-value, by design. Rounded withv_digits. See the "Standardized mean difference" section below. Defaults toFALSE.- decimal_mark
Decimal separator (
"."or","). Defaults to".".- align
Horizontal alignment of numeric columns in the printed ASCII table and in the
tinytable,gt,flextable,word, andclipboardoutputs. The first column (Variable) is always left-aligned. One of:"decimal"(default): align numeric columns on the decimal mark, the standard scientific-publication convention used by SPSS, SAS, and LaTeXsiunitx. Numeric cells are pre-padded with figure-spaces (U+2007, digit-width) so every string in a column has the same width with the decimal mark at the same internal position; centring those uniform-width strings then stacks the decimal points vertically. The same pad-then-centre strategy is applied on every rendering engine (gt,tinytable,flextable,word, ASCII print) for a homogeneous rendering, matchingtable_regression()andtable_continuous_lm(). Theclipboardoutput is delimited text meant to be parsed rather than read at a fixed width, so its cells travel unpadded (a padded number pastes as text next to an unpadded number)."center": center-align all numeric columns."right": right-align all numeric columns.
In the
exceloutput,"center"centres the numeric columns and"right"is the same rendering as the default: cell-string padding does not align decimals under a proportional font, so"decimal"right-aligns instead, which combined with the per-columnnumfmtalready produces dot-aligned columns. Same default and same three values astable_continuous()/table_continuous_lm(), whose workbooks resolve"decimal"differently:table_continuous()right-aligns only the counts and the p-value there, andtable_continuous_lm()applies that convention at everyalign.- output
Output format. One of:
"default"(an ASCII table object, printed when the call is bare)"data.frame"(a wide numericdata.frame)"long"(a long numericdata.frame)"tinytable"(requirestinytable)"gt"(requiresgt)"flextable"(requiresflextable)"excel"(requiresopenxlsx2)"clipboard"(requiresclipr)"word"(requiresflextableandofficer)
- indent_text
Prefix used for modality labels in report table building. Defaults to
" "(two spaces).- indent_text_excel_clipboard
Stronger indentation used in Excel and clipboard exports. Defaults to six non-breaking spaces.
- add_multilevel_header
Logical. If
TRUE(the default), merges top headers in Excel export. Only consulted foroutput = "excel"on a grouped table (bysupplied); like the other output-scoped presentation arguments (excel_sheet,clipboard_delim, ...), it is silently unused in every other output and in one-way tables, which have a single header row to begin with.- blank_na_wide
Logical. If
FALSE(the default),NAvalues are kept as-is in wide raw output. IfTRUE, replaces them with empty strings.- excel_path
Path for
output = "excel". Defaults toNULL.- excel_sheet
Sheet name for Excel export.
NULL(the default) uses"Categorical".- clipboard_delim
Delimiter for clipboard text export. Defaults to
"\t". A cell holding the delimiter itself, a double quote or a line break is quoted RFC 4180-style, so the grid survives whatever delimiter you choose.- word_path
File path for
output = "word". Defaults toNULL. Before 0.13.0, supplying it withoutput = "flextable"also wrote a.docxas a side effect; it is now used exclusively byoutput = "word"(the contract shared with the rest of the table family) and is ignored, with a warning, underoutput = "flextable".- user_na
Logical. If
TRUE(the default), declared missing values in the row variables and inbyare treated as missing: they join the"(Missing)"level underdrop_na = FALSE, and underdrop_na = TRUEthey are removed with a dedicated disclosure line (Declared missing values removed: ...). IfFALSE, the declared codes stay valid categories. See the "Declared missing values" section offreq().- style
A journal style: a theme name (
"jama","nejm","lancet","annals","apa","aer"), aspicy_style()object, orNULL(the default). A style only changes DEFAULTS – any argument you pass explicitly wins over it. Setoptions(spicy.style = )for document-wide scope. A theme covers numeric formatting conformity only, not full editorial conformity;?spicy_stylelists the exact rules each one encodes and the official document they come from. An unknown name is an error.
Value
Depends on output:
"default": the underlyingdata.framecarrying the rendering metadata as attributes (S3 class"spicy_categorical_table"). The object is returned visibly, so a baretable_categorical(...)call auto-prints the styled ASCII table at the console whilet <- table_categorical(...)stays silent (printtto display the table)."data.frame": a widedata.framewith one row per variable–level combination. Whenbyis used, the columns areVariable,Level, and one pair ofn/\%columns per group level (plusTotalwheninclude_total = TRUE), followed byChi2,df,p, and the association measure column. Whenby = NULL, the columns areVariable,Level,n,\%."long": a longdata.framewith columnsvariable,level,n,pct(plusgroup,chi2,df,pwhenbyis used). The association measure is always calledeffect_size, whichever measure it is, andeffect_size_typenames that measure per row ("cramer_v","phi", ...), or isNAon the rows of a variable givenassoc_measure = "none". The wide outputs instead name the column after the measure, orEffect sizewhen the row variables do not share one. Withsmd = TRUEthis output also carriessmdandsmd_type("binary"or"multinomial", the kernel the value came from); the wide outputs name that columnSMD. Like the association columns, both are ABSENT when the statistic is not requested."tinytable": atinytableobject."gt": agt_tblobject."flextable": aflextableobject."excel"/"word": writes to disk and returns the file path invisibly."clipboard": copies the table and returns the displaydata.frameinvisibly.
The drop_na = TRUE disclosure travels with the table on every
route, not just the console: "default" prints it under the ASCII
table, "tinytable" / "gt" / "flextable" / "word" carry it as
a table note, "excel" writes it below the body, and
"data.frame" keeps the sentence verbatim in the
missing_note attribute (attr(x, "missing_note"), NULL when
nothing was removed) so a pipeline that renders the numbers itself
can still state what left the table. On the "tinytable" route the
note is set one size down; options(spicy.note_style) governs that
(see table_regression()).
The Excel sheet carries the same title the console prints on its first row; the table itself starts on row 3.
Tests
When by is used, each selected variable is cross-tabulated
against the grouping variable with cross_tab() and the omnibus
chi-squared p-value is reported in the p column. See
@param correct / simulate_p to switch on Yates' continuity
correction or Monte Carlo p-values, and @param assoc_measure
for the per-row dispatch table used by "auto" (2x2 -> Phi,
both ordered -> Kendall's Tau-b, otherwise Cramer's V). Without
by, the table reports the marginal frequency distribution of
each variable with no inferential statistics.
For model-based comparisons (cluster-robust SE, weighted contrasts,
fitted means) on continuous outcomes, see table_continuous_lm().
For descriptive (empirical) comparisons on continuous outcomes, see
table_continuous().
Standardized mean difference
smd = TRUE adds an SMD column with the balance diagnostic of
the Table 1 literature, on the variable row beside p. For a
two-category variable it is the Bernoulli form,
$$\mathrm{SMD} = \frac{p_1 - p_2}{\sqrt{(p_1(1-p_1) + p_2(1-p_2)) / 2}}$$
with \(p\) the proportion of the SECOND category, signed,
group 1 minus group 2 in the order the table displays them. Note
the denominator: the Bernoulli variance \(p(1-p)\) at n, not
var() at \(n-1\), which would be 19% off on a small table.
"Second category" is the order the table shows, which is worth
knowing for a logical variable: spicy displays TRUE then
FALSE, so the sign is taken on FALSE, where tableone and
cobalt coerce with factor() (FALSE, TRUE) and take it on
TRUE – the same magnitude with the opposite sign. Convert to a
factor with the level order you want if the direction matters.
For three or more categories it is the multivariate form of Yang and Dalton (2012, SAS Global Forum 335-2012),
$$\mathrm{SMD} = \sqrt{T' S^{-} T}$$
with \(T\) the difference of the two profiles of proportions
(first category dropped) and \(S\) the mean of their multinomial
covariance matrices. This is a Mahalanobis distance: it is
unsigned, it is not bounded by 1, and \(S^{-}\) is a
pseudo-inverse, because a declared-but-unobserved category makes
\(S\) singular and solve() would abort where the
pseudo-inverse returns exactly the value that category's absence
implies. Which kernel a row took is published as smd_type in the
"long" output, and the unsigned reading is stated in the table
note whenever a variable has more than two categories. The
MASS package is needed for this arm only.
Two profiles with no category in common have an infinite
standardized distance. The pseudo-inverse would quietly publish a
finite number there, so the cell is an en-dash and a classed
warning says why. The same applies when each group is constant on
a different category, where the naive route publishes 0 –
"perfectly balanced" for the most imbalanced variable possible.
Conventions shared with table_continuous(): exactly two groups
(three or more are refused, not averaged over pairs); complete
cases on the observed groups, so a drop_na = FALSE "(Missing)"
level is displayed and never enters the diagnostic; no confidence
interval and no p-value, by design. Under weights the profiles
are the weighted proportions, which makes this column agree with
both the frequency and the survey-design readings – a profile of
proportions is invariant to a global rescaling of the weights, so
rescale cannot move it. (Only the continuous arm has a
convention to choose there.)
The SMD cell keeps its leading zero where the association cell
drops it: the APA strip belongs to a bounded measure, and this one
is not bounded. The two columns therefore print 0.45 and .45
side by side, on purpose.
Two limits of the current grammar. This function has no p_value
argument, so the p column cannot be switched off here as it can in
table_continuous(); a complete balance table mixing continuous
and categorical variables will show a categorical p beside a
continuous column you removed. And inline() cannot quote this
SMD cell: like p and the association measure, it lives on the
variable row, which inline() cannot address on a variable that
has levels. The continuous SMD cell is quotable
(inline(tbl, x, "A", column = "smd")); for the categorical one,
read output = "long".
Display conventions
Decimal alignment, p-value formatting, and required suggested
packages per output engine are documented under @param align,
@param p_digits, and @param output respectively.
Counts are displayed as integers: weighted counts are rounded
(ties half to even, the R convention) at display time only, in
cells and margins alike – the SPSS Crosstabs convention. Cells
and margins are rounded independently, so small display
discrepancies are possible (e.g. two cells of exactly 0.5 each
display as 0 while their Total of 1.0 displays as 1). The
machine outputs ("data.frame", "long") carry the exact
weighted counts and full-precision percentages.
Declared missing values
Survey files imported with haven often carry declared missing
values: codes such as 8 = Don't know or 9 = Refused that the
source file marks as missing while keeping them distinct from a
plain NA. Two kinds of declaration exist: na_values / na_range
metadata on haven::labelled_spss() vectors, and tagged missing
values created by haven::tagged_na() (the Stata .a, .b, ...
convention).
spicy honors the declaration by default (user_na = TRUE):
declared missing values are excluded from every statistic exactly
like NA – valid percentages, means, chi-squared tests,
association measures, row-wise summaries, and group definitions –
but they are not erased from display. freq() lists each observed
declared value as its own row of the Missing block, with its value
label; cross_tab(), table_categorical(), and
table_continuous() disclose the exclusion in the table note
(Declared missing values removed: x (2).); varlist() and
code_book() count them as missing in N_valid / NAs /
N_distinct while still listing the declared codes in Values.
Every function involved offers the same escape hatch: set
user_na = FALSE to ignore the declaration and treat the declared
codes as valid values (the behavior of spicy before 0.13.0).
Tagged missing values are genuine NAs either way; for them,
user_na = FALSE only collapses the per-tag breakdown back into
the regular NA count.
See also
table_continuous() for empirical comparisons on
continuous outcomes; table_continuous_lm() for the model-based
companion (heteroskedasticity-consistent / cluster-robust /
bootstrap / jackknife SE, fitted means, weighted contrasts);
cross_tab() for two-way cross-tabulations; freq() for
one-way frequency tables.
Other spicy tables:
table_continuous(),
table_continuous_lm()
Examples
# --- Basic usage ---------------------------------------------------------
# Default: ASCII console table grouped by sex.
table_categorical(
sochealth,
select = c(smoking, physical_activity),
by = sex
)
#> Categorical table by sex
#>
#> Variable │ Female n Female % Male n Male % Total n
#> ───────────────────────────┼─────────────────────────────────────────────
#> Current smoker │
#> No │ 475 76.6 451 77.8 926
#> Yes │ 131 21.1 118 20.3 249
#> (Missing) │ 14 2.3 11 1.9 25
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Regular physical activity │
#> No │ 334 53.9 316 54.5 650
#> Yes │ 286 46.1 264 45.5 550
#>
#> Variable │ Total % p Phi
#> ───────────────────────────┼────────────────────
#> Current smoker │ .713 .01
#> No │ 77.2
#> Yes │ 20.8
#> (Missing) │ 2.1
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Regular physical activity │ .832 .01
#> No │ 54.2
#> Yes │ 45.8
# One-way frequency-style table (no `by`).
table_categorical(
sochealth,
select = c(smoking, physical_activity)
)
#> Categorical table
#>
#> Variable │ n %
#> ─────────────────────────────┼───────────────
#> Current smoker │
#> No │ 926 77.2
#> Yes │ 249 20.8
#> (Missing) │ 25 2.1
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Regular physical activity │
#> No │ 650 54.2
#> Yes │ 550 45.8
# Pretty labels keyed by column name.
table_categorical(
sochealth,
select = c(smoking, physical_activity),
by = education,
labels = c(
smoking = "Current smoker",
physical_activity = "Physical activity"
)
)
#> Categorical table by education
#>
#> Variable │ Lower secondary n Lower secondary % Upper secondary n
#> ───────────────────┼─────────────────────────────────────────────────────────
#> Current smoker │
#> No │ 179 68.6 415
#> Yes │ 78 29.9 112
#> (Missing) │ 4 1.5 12
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Physical activity │
#> No │ 177 67.8 310
#> Yes │ 84 32.2 229
#>
#> Variable │ Upper secondary % Tertiary n Tertiary % Total n
#> ───────────────────┼────────────────────────────────────────────────────
#> Current smoker │
#> No │ 77.0 332 83.0 926
#> Yes │ 20.8 59 14.8 249
#> (Missing) │ 2.2 9 2.2 25
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Physical activity │
#> No │ 57.5 163 40.8 650
#> Yes │ 42.5 237 59.2 550
#>
#> Variable │ Total % p Cramer's V
#> ───────────────────┼────────────────────────────
#> Current smoker │ <.001 .14
#> No │ 77.2
#> Yes │ 20.8
#> (Missing) │ 2.1
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Physical activity │ <.001 .21
#> No │ 54.2
#> Yes │ 45.8
# Survey weights with rescaling.
table_categorical(
sochealth,
select = c(smoking, physical_activity),
by = education,
weights = "weight",
rescale = TRUE
)
#> Categorical table by education
#>
#> Variable │ Lower secondary n Lower secondary %
#> ───────────────────────────┼──────────────────────────────────────
#> Current smoker │
#> No │ 176 68.1
#> Yes │ 79 30.6
#> (Missing) │ 4 1.4
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Regular physical activity │
#> No │ 174 67.2
#> Yes │ 85 32.8
#>
#> Variable │ Upper secondary n Upper secondary % Tertiary n
#> ───────────────────────────┼──────────────────────────────────────────────────
#> Current smoker │
#> No │ 419 76.6 325
#> Yes │ 114 21.0 60
#> (Missing) │ 13 2.4 10
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Regular physical activity │
#> No │ 315 57.7 166
#> Yes │ 231 42.3 229
#>
#> Variable │ Tertiary % Total n Total % p Cramer's V
#> ───────────────────────────┼─────────────────────────────────────────────────
#> Current smoker │ <.001 .13
#> No │ 82.2 919 76.6
#> Yes │ 15.2 254 21.1
#> (Missing) │ 2.6 27 2.2
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Regular physical activity │ <.001 .19
#> No │ 41.9 655 54.6
#> Yes │ 58.1 545 45.4
# Confidence interval for the association measure.
table_categorical(
sochealth,
select = smoking,
by = education,
assoc_ci = TRUE
)
#> Categorical table by education
#>
#> Variable │ Lower secondary n Lower secondary % Upper secondary n
#> ────────────────┼─────────────────────────────────────────────────────────
#> Current smoker │
#> No │ 179 68.6 415
#> Yes │ 78 29.9 112
#> (Missing) │ 4 1.5 12
#>
#> Variable │ Upper secondary % Tertiary n Tertiary % Total n Total %
#> ────────────────┼─────────────────────────────────────────────────────────────
#> Current smoker │
#> No │ 77.0 332 83.0 926 77.2
#> Yes │ 20.8 59 14.8 249 20.8
#> (Missing) │ 2.2 9 2.2 25 2.1
#>
#> Variable │ p Cramer's V CI lower CI upper
#> ────────────────┼───────────────────────────────────────
#> Current smoker │ <.001 .14 .08 .19
#> No │
#> Yes │
#> (Missing) │
# --- Per-variable association measure ----------------------------------
# Default (`assoc_measure = "auto"`): one measure per row variable based on
# the variable type (2x2 -> Phi, both ordered factors -> Kendall's Tau-b,
# otherwise Cramer's V). When the chosen measures differ across rows, the
# column header collapses to `"Effect size"` and an APA-style `Note.` line
# documents which measure was used for which variable.
table_categorical(
sochealth,
select = c(smoking, education),
by = sex
)
#> Categorical table by sex
#>
#> Variable │ Female n Female % Male n Male % Total n Total %
#> ─────────────────────────┼──────────────────────────────────────────────────────
#> Current smoker │
#> No │ 475 76.6 451 77.8 926 77.2
#> Yes │ 131 21.1 118 20.3 249 20.8
#> (Missing) │ 14 2.3 11 1.9 25 2.1
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Highest education level │
#> Lower secondary │ 134 21.6 127 21.9 261 21.8
#> Upper secondary │ 290 46.8 249 42.9 539 44.9
#> Tertiary │ 196 31.6 204 35.2 400 33.3
#>
#> Variable │ p Effect size
#> ─────────────────────────┼───────────────────
#> Current smoker │ .713 .01
#> No │
#> Yes │
#> (Missing) │
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Highest education level │ .344 .04
#> Lower secondary │
#> Upper secondary │
#> Tertiary │
#>
#> Note. Phi: Current smoker; Cramer's V: Highest education level.
# Force a uniform measure across all row variables.
table_categorical(
sochealth,
select = c(smoking, education),
by = sex,
assoc_measure = "cramer_v"
)
#> Categorical table by sex
#>
#> Variable │ Female n Female % Male n Male % Total n Total %
#> ─────────────────────────┼──────────────────────────────────────────────────────
#> Current smoker │
#> No │ 475 76.6 451 77.8 926 77.2
#> Yes │ 131 21.1 118 20.3 249 20.8
#> (Missing) │ 14 2.3 11 1.9 25 2.1
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Highest education level │
#> Lower secondary │ 134 21.6 127 21.9 261 21.8
#> Upper secondary │ 290 46.8 249 42.9 539 44.9
#> Tertiary │ 196 31.6 204 35.2 400 33.3
#>
#> Variable │ p Cramer's V
#> ─────────────────────────┼──────────────────
#> Current smoker │ .713 .01
#> No │
#> Yes │
#> (Missing) │
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Highest education level │ .344 .04
#> Lower secondary │
#> Upper secondary │
#> Tertiary │
# Per-variable override (recommended named form).
table_categorical(
sochealth,
select = c(smoking, education, self_rated_health),
by = sex,
assoc_measure = c(
smoking = "phi", # binary x binary
education = "cramer_v", # multi-category nominal
self_rated_health = "tau_b" # ordinal x binary, Tau-b
)
)
#> Categorical table by sex
#>
#> Variable │ Female n Female % Male n Male % Total n Total %
#> ─────────────────────────┼──────────────────────────────────────────────────────
#> Current smoker │
#> No │ 475 76.6 451 77.8 926 77.2
#> Yes │ 131 21.1 118 20.3 249 20.8
#> (Missing) │ 14 2.3 11 1.9 25 2.1
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Highest education level │
#> Lower secondary │ 134 21.6 127 21.9 261 21.8
#> Upper secondary │ 290 46.8 249 42.9 539 44.9
#> Tertiary │ 196 31.6 204 35.2 400 33.3
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Self-rated health │
#> Poor │ 31 5.0 30 5.2 61 5.1
#> Fair │ 143 23.1 123 21.2 266 22.2
#> Good │ 282 45.5 276 47.6 558 46.5
#> Very good │ 154 24.8 141 24.3 295 24.6
#> (Missing) │ 10 1.6 10 1.7 20 1.7
#>
#> Variable │ p Effect size
#> ─────────────────────────┼───────────────────
#> Current smoker │ .713 .01
#> No │
#> Yes │
#> (Missing) │
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Highest education level │ .344 .04
#> Lower secondary │
#> Upper secondary │
#> Tertiary │
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#> Self-rated health │ .849 .01
#> Poor │
#> Fair │
#> Good │
#> Very good │
#> (Missing) │
#>
#> Note. Phi: Current smoker; Cramer's V: Highest education level; Kendall's Tau-b: Self-rated health.
# --- Output formats -----------------------------------------------------
# The rendered outputs below all wrap the same call:
# table_categorical(sochealth,
# select = c(smoking, physical_activity),
# by = sex)
# only `output` changes. Assign each result to a variable -- some
# engines auto-print as a console-friendly text fallback inside
# the `?` help viewer.
# Wide data.frame (one row per modality).
table_categorical(
sochealth,
select = c(smoking, physical_activity),
by = sex,
output = "data.frame"
)
#> Variable Level Female n Female % Male n Male %
#> 1 Current smoker No 475 76.612903 451 77.758621
#> 2 Current smoker Yes 131 21.129032 118 20.344828
#> 3 Current smoker (Missing) 14 2.258065 11 1.896552
#> 4 Regular physical activity No 334 53.870968 316 54.482759
#> 5 Regular physical activity Yes 286 46.129032 264 45.517241
#> Total n Total % Chi2 df p Phi
#> 1 926 77.166667 0.1357733 1 0.7125196 0.010749501
#> 2 249 20.750000 0.1357733 1 0.7125196 0.010749501
#> 3 25 2.083333 0.1357733 1 0.7125196 0.010749501
#> 4 650 54.166667 0.0451784 1 0.8316763 0.006135851
#> 5 550 45.833333 0.0451784 1 0.8316763 0.006135851
# Long data.frame (one row per (modality x group)).
table_categorical(
sochealth,
select = c(smoking, physical_activity),
by = sex,
output = "long"
)
#> variable level group n pct chi2 df
#> 1 Current smoker No Female 475 76.612903 0.1357733 1
#> 2 Current smoker No Male 451 77.758621 0.1357733 1
#> 3 Current smoker No Total 926 77.166667 0.1357733 1
#> 4 Current smoker Yes Female 131 21.129032 0.1357733 1
#> 5 Current smoker Yes Male 118 20.344828 0.1357733 1
#> 6 Current smoker Yes Total 249 20.750000 0.1357733 1
#> 7 Current smoker (Missing) Female 14 2.258065 0.1357733 1
#> 8 Current smoker (Missing) Male 11 1.896552 0.1357733 1
#> 9 Current smoker (Missing) Total 25 2.083333 0.1357733 1
#> 10 Regular physical activity No Female 334 53.870968 0.0451784 1
#> 11 Regular physical activity No Male 316 54.482759 0.0451784 1
#> 12 Regular physical activity No Total 650 54.166667 0.0451784 1
#> 13 Regular physical activity Yes Female 286 46.129032 0.0451784 1
#> 14 Regular physical activity Yes Male 264 45.517241 0.0451784 1
#> 15 Regular physical activity Yes Total 550 45.833333 0.0451784 1
#> p effect_size effect_size_type
#> 1 0.7125196 0.010749501 phi
#> 2 0.7125196 0.010749501 phi
#> 3 0.7125196 0.010749501 phi
#> 4 0.7125196 0.010749501 phi
#> 5 0.7125196 0.010749501 phi
#> 6 0.7125196 0.010749501 phi
#> 7 0.7125196 0.010749501 phi
#> 8 0.7125196 0.010749501 phi
#> 9 0.7125196 0.010749501 phi
#> 10 0.8316763 0.006135851 phi
#> 11 0.8316763 0.006135851 phi
#> 12 0.8316763 0.006135851 phi
#> 13 0.8316763 0.006135851 phi
#> 14 0.8316763 0.006135851 phi
#> 15 0.8316763 0.006135851 phi
# \donttest{
# Rendered HTML / docx objects -- best viewed inside a
# Quarto / R Markdown document or a pkgdown article.
if (requireNamespace("tinytable", quietly = TRUE)) {
tt <- table_categorical(
sochealth, select = c(smoking, physical_activity), by = sex,
output = "tinytable"
)
}
if (requireNamespace("gt", quietly = TRUE)) {
tbl <- table_categorical(
sochealth, select = c(smoking, physical_activity), by = sex,
output = "gt"
)
}
if (requireNamespace("flextable", quietly = TRUE)) {
ft <- table_categorical(
sochealth, select = c(smoking, physical_activity), by = sex,
output = "flextable"
)
}
# Excel and Word: write to a temporary file.
if (requireNamespace("openxlsx2", quietly = TRUE)) {
tmp <- tempfile(fileext = ".xlsx")
table_categorical(
sochealth, select = c(smoking, physical_activity), by = sex,
output = "excel", excel_path = tmp
)
unlink(tmp)
}
if (
requireNamespace("flextable", quietly = TRUE) &&
requireNamespace("officer", quietly = TRUE)
) {
tmp <- tempfile(fileext = ".docx")
table_categorical(
sochealth, select = c(smoking, physical_activity), by = sex,
output = "word", word_path = tmp
)
unlink(tmp)
}
# }
if (FALSE) { # \dontrun{
# Clipboard: writes to the system clipboard.
table_categorical(
sochealth, select = c(smoking, physical_activity), by = sex,
output = "clipboard"
)
} # }