Skip to contents

Tabulates a "check all that apply" question: one row per item, with the number of respondents who chose it and their percentage, a row for the respondents who chose none of the items when there are any (see none_row), then a row with the number of respondents the percentages are computed on. The items are the indicator columns of the question, selected with tidyselect syntax.

With by, the counts and percentages are given per group and in a Total margin, each item gets a test across the groups (the chi-squared, or Fisher's exact test when an expected count is below 5), and the question gets a global test of all its items together, valid although one respondent can choose several items.

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_multiresponse(
  data,
  select,
  by = NULL,
  counted = NULL,
  percent = c("cases", "responses", "both"),
  base = c("valid", "answered", "all"),
  none_row = TRUE,
  sort = c("", "+", "-", "name+", "name-"),
  test = c("auto", "chisq", "fisher", "midp", "n1"),
  p_value = NULL,
  global_test = c("rs2", "bootstrap"),
  boot_n = 1999L,
  p_adjust = "holm",
  statistic = FALSE,
  labels = getOption("spicy.var_labels", FALSE),
  note_labels = NULL,
  percent_digits = 1,
  p_digits = 3,
  decimal_mark = ".",
  align = c("decimal", "center", "right"),
  title = NULL,
  note = NULL,
  note_add = NULL,
  output = c("default", "data.frame", "long", "tinytable", "gt", "flextable", "excel",
    "clipboard", "word"),
  excel_path = NULL,
  excel_sheet = NULL,
  clipboard_delim = "\t",
  word_path = NULL,
  style = NULL
)

Arguments

data

A data frame, one row per respondent.

select

The indicator columns of the question, one per item, in tidyselect syntax or as a character vector of column names. Their order is the order of the rows, unless sort sorts them. The by column is not part of the selection: a selection helper skips it, and naming it, or giving its position, is an error. A name that is not a column of data is an error (class spicy_missing_column), and so is an empty selection.

by

Optional grouping column, unquoted or as a string. Factor levels keep their declared order, a declared level with no respondent included; any other column is sorted, as in table_categorical(): text in the C locale, FALSE before TRUE, numbers ascending, whatever the order of the rows, and a labelled column is named and ordered by its value labels as there. The missing answers to the question are counted (see base and none_row), as a table that counts does (see drop_na in table_categorical()), but a missing by has no group to be counted in: rows with a missing by are removed from the whole table, and the note lists by with their count ("Missing values removed: sex (2)."), so this table has no drop_na; a by that no observation holds is an error (class spicy_invalid_data). A level displayed with the label of the Total margin is an error, as in table_categorical().

counted

The value that marks a choice. NULL (the default) reads logical columns (TRUE is chosen) and numeric columns coded 0 and 1 (1 is chosen) as they are; any other column needs counted, and the error names it. When supplied, counted applies to every selected column: a factor or character column is chosen where it equals counted (counted = "Yes"), a labelled column where its code or its value label does (counted = 1 or counted = "Yes"). Any other non-missing value is "not chosen", and a missing value stays missing. A counted that matches no value of any selected column is an error.

percent

The percentage shown beside each count: "cases" (the default), the percentage of respondents, which can sum to more than 100; "responses", the percentage of all the choices made in the group, which sums to 100; or "both", two columns.

base

The respondents the percentages of cases are computed on; the note names the base the table uses.

  • "valid" (the default): the respondents with at least one non-missing indicator, so that a respondent who chose nothing counts and one who skipped the question does not, as expss counts its valid cases.

  • "answered": the respondents who chose at least one item, the default of SPSS MULT RESPONSE (MISSING = TABLE) and of Stata's mrtab, whose include option gives "valid" (Jann, 2005, pp. 98 and 107-108), where choosing nothing cannot be told from skipping.

  • "all": every row of the group, as questionr::multi.table() counts.

In a "check all that apply" question, an item left unchecked cannot tell "none applies" from "skipped": "valid" reads a respondent with no item chosen as "none applies", "answered" (the convention of SPSS and Stata) as nonresponse. "answered" suits a questionnaire with neither a "None of these" option nor a required answer. An export that leaves every unchecked box missing gives both bases the same respondents, and only "answered" describes them in the note. A filter code that is not declared missing (for example -1 "not applicable" kept as an ordinary value) counts as "not chosen" and keeps the filtered respondents in the base: recode it to NA first (SPSS user-missing values declared as such are already read as missing).

none_row

Logical. If TRUE (the default), the respondents of the base who chose no item get a row of their own, after the items and before the Respondents row: "(No item chosen)" under base = "valid" and "all", and, under "all", "(No data for the question)" for the respondents with every indicator missing. Each row appears only when its count is above 0. base = "answered" leaves both groups out of the base and shows neither row. A row gives the count and the percentage of cases of each group and of the margin, on the base of the column, as the items do. Its percentage of responses and its test cells show a dash: these respondents gave no answer, and the rows take no part in the tests or in sort. The note then no longer counts the respondents a row shows. FALSE gives these counts in the note only.

sort

Sorting method for items, as in freq():

  • "" - no sorting (default)

  • "+" - increasing frequency

  • "-" - decreasing frequency

  • "name+" - alphabetical A-Z

  • "name-" - alphabetical Z-A

With no sorting, the rows keep the order in which select delivers the columns: their order in data with a helper such as starts_with(), or the order written in c(). The function cannot know the questionnaire, so its order comes from the order of the columns or the order written in select. The frequency is the total count of each item, and items with the same count keep the order of select. The alphabetical sort reads the labels displayed in the rows (the column names, or the labels set by labels) in the C locale, the same on every platform: capitals before lower case, accented letters after z.

test

The test of each item's p-value, as in table_categorical(): "auto" (the default) takes Pearson's chi-squared test when every expected count of the item's table is at least 5 and Fisher's exact test otherwise; "chisq" and "fisher" take one test for every item; "midp" takes the Fisher mid-p test for an item of a two-group table; "n1" takes the N-1 chi-squared test for an item of a two-group table whose expected counts are all at least 1, and Fisher's exact test for one with a smaller count. With more groups, "midp" and "n1" follow "auto". The note names the test of each item. See Tests.

p_value

Logical or NULL. With by, NULL (the default) and TRUE show the item p column and the global test in the note. FALSE removes both from every output, the p column of the data frame and the p.value column of the long output included. p_value and statistic are independent, as in table_continuous(). Ignored without by, with a warning when TRUE.

global_test

The global test: "rs2" (the default), the sum of the item chi-squared statistics with the second-order Rao-Scott correction; or "bootstrap", the same sum referred to its bootstrap distribution. See Tests.

boot_n

Number of bootstrap resamples for global_test = "bootstrap": a positive whole number, as in table_regression() and table_continuous_lm(). Defaults to 1999, the default B of MI.test() in the MRCV package for the same bootstrap test (reference manual of MRCV 0.4-0). Typed with global_test = "rs2", or in a table without a global test, it is ignored with a warning (class spicy_ignored_arg).

p_adjust

Multiple-comparison adjustment of the item p column, applied to the family of the items tested: "holm" (the default), "none", or another method of stats::p.adjust() ("hochberg", "hommel", "bonferroni", "BH" / "fdr", "BY"), as in table_categorical(). See Multiple-comparison adjustment. Typing it when the table has no test to adjust (no by, or p_value = FALSE) is an error.

statistic

Logical. If TRUE, adds a Test column with each item's chi-squared statistic and its degrees of freedom, with or without the p-values of p_value; an item whose p-value comes from Fisher's exact test or its mid-p version has no statistic and shows a dash, and an item tested by the N-1 chi-squared shows that statistic. Defaults to FALSE.

labels

How each variable is named in the table: in the rows, in the title, and in the notes. Four forms:

  • labels = FALSE shows the column name.

  • labels = TRUE shows the column's label attribute (as set by haven, labelled, or label_from_names()) when it is present, not NA, and not empty, and the column name otherwise.

  • labels = c(bmi = "Body mass index"), a named character vector, gives the columns it names these labels. Every other column follows options(spicy.var_labels): its name when the option is unset or FALSE, its label attribute when it is TRUE.

  • labels = var_labels(bmi = "BMI (kg/m2)") gives the columns it names these labels, and every other column its label attribute, whatever the option says. var_labels() alone is labels = TRUE.

Names that match no column are ignored, so one vector can serve every table of a project. The default reads options(spicy.var_labels), and a TRUE or FALSE typed in the call wins over it. Any other value, NULL included, is an error. A variable label documents the data file; a published table carries the wording you set here. note_labels gives a variable a shorter label in the title and the notes.

note_labels

The label a variable takes where the table names it in a sentence, in the title and in the notes, in place of the one labels gives it: a named character vector, note_labels = c(sex = "sex at birth"), or a var_labels() object, which reads as one. labels can then name a row with a whole question while the note says "Missing values removed: sex at birth (2).". The rows, the column headers and the spanners keep the labels of labels, and so do the title and the notes for a variable note_labels does not name. Names that match no column are ignored, as in labels, and NULL (the default) renames nothing. A note label that another variable of the table shows is an error (class spicy_label_collision): choose another one, or relabel the other variable.

percent_digits

Number of digits for percentages in report outputs. Defaults to 1. A whole number: a fraction is an error, as for every *_digits argument.

p_digits

Integer >= 1. Number of decimal places used to render p-values in the p column (default: 3, the APA Publication Manual standard). Both the displayed precision and the small-p threshold derive from this argument: p_digits = 3 prints .045 and <.001; p_digits = 4 prints .0451 and <.0001. Leading zeros are always stripped, following APA convention.

decimal_mark

The decimal mark, a single character: "." (the default) or "," in most styles; the Lancet style of spicy_style() uses the middle dot.

align

Horizontal alignment of the numeric columns in the console and in the rendered outputs: "decimal" (the default), "center", or "right", as in table_categorical().

title

The table title. NULL (the default) gives "Multiple responses", followed by "by" and the by variable under the labels contract (or its label in note_labels); a single string replaces it, and FALSE removes it, as in table_regression().

note

The table note. NULL (the default) builds it, opening with "Note.": what the percentages sum to, the base with the respondents it holds or leaves out, the rows removed for a missing by and the items with no data in a group, then the tests, and the sentences of note_add. A single string replaces it, printed as typed after the lead-in ("Note.", unless the string opens with one), note_add after it, and FALSE removes it, as in every table function.

note_add

Sentences to add to the note: a character vector of one or more strings, each printed as typed (no period is added) and joined to the note by a space. They close its paragraph, after the definitions of the abbreviations and before the legend of the stars, in every output and in attr(x, "note"), and the sentences the table writes stay. With a string in note, they follow it, so a wrapper can add a house sentence to every table while one table replaces its note. An NA or an empty string is an error, and so is note_add with note = FALSE, which leaves no note to add to.

output

Output format. One of:

  • "default" (an ASCII table object, printed when the call is bare)

  • "data.frame" (the displayed table, numeric)

  • "long" (a long numeric data.frame)

  • "tinytable" (requires tinytable)

  • "gt" (requires gt)

  • "flextable" (requires flextable)

  • "excel" (requires openxlsx2)

  • "clipboard" (requires clipr)

  • "word" (requires flextable and officer)

excel_path

Path for output = "excel", ending in ".xlsx", in a directory that exists. Defaults to NULL.

excel_sheet

Sheet name for Excel export. NULL (the default) uses "Multiple responses".

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 to NULL.

style

A journal style: a theme name ("jama", "nejm", "lancet", "annals", "apa", "aer"), a spicy_style() object, or NULL (the default). A style only changes DEFAULTS – any argument you pass explicitly wins over it. Set options(spicy.style = ) for document-wide scope. A theme covers numeric formatting, the word that opens the note and the abbreviations the note defines, not full editorial conformity; ?spicy_style lists the exact rules each one encodes and the official document they come from. An unknown name is an error.

Value

Depends on output:

  • "default": a spicy_multiresponse_table (a data.frame subclass), with the rendering metadata as attributes, returned visibly, so a bare call prints the ASCII table.

  • "data.frame": the displayed table with its numbers, what as.data.frame() of the default object returns: one row per item, the rows of none_row, and a last row with the base. Columns: variable (the item's column, "none", "no_data", or "nobs" on the base row), label (the label the row displays), .row_role (what the row is: "summary" for an item, "none" for "(No item chosen)", "missing" for "(No data for the question)", "fit_stat" for the base, the roles of as_structured()), then the printed columns: the count and the percentage of each group of by and of the Total margin (<group> n and <group> %, or <group> % of cases and <group> % of responses under percent = "both"; n and % without by), Test (the chi-squared of the item, with df beside it) under statistic = TRUE, and p with p_value.

  • "long": one row per item and group, then one per row of none_row and group, with columns variable (the item's column, "none" or "no_data" on the rows of none_row), label (the label the row displays), .row_role as above, with by group, .group_role (the role of group: "group", or "total" for the margin) and total (TRUE on the rows of the margin), n, base (the respondents of the group; in the margin, those of the groups with data for the item), the percentage named by its base: pct_cases (of the respondents) or pct_responses (of the choices made), as percent asks, both under percent = "both", and, when the item tests ran (for p_value or for statistic), test_type (the test of the item, as in table_categorical(): "chi_squared", "n1_chi_squared", "fisher_exact", "fisher_midp", "fisher_exact_mc"), statistic and df (NA for an item tested by an exact test), p.value with p_value, and p_unadjusted under an adjustment. The rows of none_row are NA in the test columns.

  • "tinytable", "gt", "flextable": the rendered table object.

  • "excel" / "word": writes to disk and returns the file path invisibly.

  • "clipboard": copies the table and returns the display data.frame invisibly.

The default object and the data frames carry the note as printed in attr(x, "note") (its lead-in included, NULL under note = FALSE) and, when the table has a global test, attr(x, "global_test"): a one-row data frame with test, statistic, df, p.value, boot_n, n_items (the items tested), n_groups, rs_mean and rs_cv (the design-effect summary of global_test = "rs2"), and boot_redraws (the resamples drawn again under global_test = "bootstrap"); see Tests. as_structured() reads the default object, with the same .row_role and a .variable of "none" and "no_data" on the rows of none_row; tidy() reads the items only.

Tests

With by, the table tests multiple marginal independence: that the probability of choosing each item is the same in every group. Each item gets the Pearson chi-squared statistic of its \(r \times 2\) table (group by chose the item) with \(r - 1\) degrees of freedom: the arithmetic of chisq.test(correct = FALSE), with no continuity correction and no constant added to an empty cell (Agresti and Liu, 1999).

The item p-value comes from the test test chooses, as in table_categorical(). Under "auto", the default, it is that chi-squared test when every expected count of the item's table is at least 5, and Fisher's exact test (stats::fisher.test()) otherwise; when the exact enumeration fails, the p-value is its Monte Carlo estimate over 2000 tables, which draws from the session's RNG. An item tested by Fisher's exact test has no statistic: its Test cell shows a dash, and statistic and df are NA in the long output. Without p-values (p_value = FALSE), statistic = TRUE shows the chi-squared statistic of every item. The global test sums the Pearson statistics of the items whatever test chooses for their p-values, since its correction and its bootstrap are defined for that sum.

With two groups, an item's table is 2x2. test = "midp" then gives it the Fisher mid-p test of Fagerland, Lydersen and Laake (2017, section 4.4.6), and test = "n1" the N-1 chi-squared test of Campbell (2007) when its expected counts are all at least 1 and Fisher's exact test otherwise, both defined under Tests in table_categorical(). With more groups, the items follow "auto". An item tested by the mid-p test has no statistic (a dash); one tested by the N-1 chi-squared shows that statistic, on 1 degree of freedom.

The global test sums the item statistics, \(X^2_S = \sum_j X^2_j\), a value that does not depend on which answer of an item is coded as chosen (Agresti and Liu, 1999). One respondent can choose several items, so the items are correlated and \(X^2_S\) is not chi-squared under the null (Loughin and Scherer, 1998): its asymptotic distribution is a weighted sum of chi-squared variables, the weights being the eigenvalues of a design-effect matrix (Bilder, Loughin and Nettleton, 2000, Appendix A). global_test = "rs2" matches its first two moments, the second-order Rao-Scott correction of Thomas and Decady (2004, pp. 48 and 58-59):

$$X^2_{RS2} = \frac{c(r-1)\,X^2_S}{\sum_k \lambda_k^2}, \qquad \nu = \frac{c^2(r-1)^2}{\sum_k \lambda_k^2}$$

referred to a chi-squared distribution with \(\nu\) degrees of freedom, usually fractional. Here \(c\) is the number of items tested and the \(\lambda_k\) are the eigenvalues of the design-effect matrix, in the second-order form of Bilder and Loughin (2004). The within-group covariance of the indicators is centered at the group means, the unrestricted estimate of Bilder and Loughin (2004) that the MRCV package also uses, whereas Thomas and Decady (2004) center it at the pooled proportions with divisor \(n(n - 1)\), which gives p = .068 instead of .063 on the Kansas farmer data of Loughin and Scherer (1998). The result is deterministic. attr(x, "global_test") reports rs_mean, the mean of the \(\lambda_k\), which is 1 in the population under the null and which the correction takes as 1 (Thomas and Decady, 2004, p. 48), and rs_cv, the \(\hat a\) of Thomas and Decady (2004, p. 59), defined by \(1 + \hat a^2 = \sum_k \lambda_k^2 / (c(r-1))\): the coefficient of variation of the \(\lambda_k\) when their mean is 1, and NA when that ratio falls below 1.

global_test = "bootstrap" refers \(X^2_S\) to its distribution over boot_n resamples of whole response vectors, drawn with replacement from the pooled respondents with the group sizes held fixed (Loughin and Scherer, 1998). The p-value is the share of resamples whose statistic is at least the observed one. A resample in which an item is chosen by every respondent or by none gives that item no statistic: it is discarded and drawn again, the practice of Bilder, Loughin and Nettleton (2000, p. 1298) for resamples whose marginal table has an empty row or column, and attr(x, "global_test")$boot_redraws counts the resamples drawn again. When 100 times boot_n draws do not yield boot_n usable resamples, the function stops with an error. The bootstrap held its level in most published settings, with exceptions reported by Bilder, Loughin and Nettleton (2000, p. 1305), and no study of multiple marginal independence compares it with the second-order test. It is random: call set.seed() first to reproduce it.

An item chosen by every respondent, or by none, has no test: its p cell shows a dash, it leaves the global test, and the note names it. Its row stays. An item with no data in a group (see Base and missing values) is tested on the groups with data for it, has no test with fewer than two (its p cell is then blank), and leaves the global test, which compares every group on the same items. With fewer than two groups holding respondents there is nothing to compare, and the table shows no test. The note names the global test with its statistic, degrees of freedom, and p-value.

Multiple-comparison adjustment

The items of one question form a natural family of tests, read together, so the item p column is adjusted by Holm's method by default. In table_categorical(), each test belongs to a different variable and the default is "none". Holm's method controls the family-wise error rate whatever the dependence between the items, and its smallest adjusted p-value equals the Bonferroni global p-value of Agresti and Liu (1999). The raw p-values stay in the data frames as p_unadjusted, and in col_meta$p$p_unadjusted of as_structured(). The note names the method and the number of items adjusted together, in the sentence of table_categorical(). The global test is never adjusted.

Base and missing values

Within the base, a missing indicator counts as not chosen, for the percentages and for the tests alike, so the table and its tests describe the same respondents: those with at least one non-missing indicator under base = "valid", those who chose at least one item under base = "answered", every row under base = "all". The counts are the same under the three bases; the percentages of cases and the tests are not. The Respondents row gives the base of each group, and the note says which base the table uses, with two counts, all groups together: the respondents with no data for the question (every indicator missing) and those with no item chosen (data, but no item chosen). The base holds the second and leaves out the first under "valid", leaves out both under "answered", and holds both under "all"; a count of zero is not mentioned. Under none_row = TRUE, the default, the respondents of these two kinds that the base holds are rows of the table, "(No item chosen)" and "(No data for the question)", with their count and percentage in each group, and the note counts only those the base leaves out. An item displayed with the label of a row the table adds is an error (class spicy_label_collision): choose another label in labels, or give the row another label, for example options(spicy.labels = list(row_multiresponse_base = "Base")). Declared missing values of a labelled column (na_values, na_range) are missing, as under the user_na = TRUE default of table_categorical().

An item missing for every respondent of a group (typically an item not asked there) has no data in that group: its cells there show a dash, its Total counts the other groups only, and the note says so, in one sentence for the items with no data in the same groups. Without by, an item with no observed value at all shows a dash. A group with no data for any item was not asked the question: all its cells, Respondents included, show a dash, and its respondents leave the base, the Total, the tests and the count of the respondents with no data for the question.

A percentage carries no more digits than its denominator, so a base of fewer than 100 respondents takes whole percentages (American Medical Association, 2020, section 4.1.8): percent_digits = 0 prints them.

References

Agresti, A., & Liu, I.-M. (1999). Modeling a categorical variable allowing arbitrarily many category choices. Biometrics, 55(3), 936-943. doi:10.1111/j.0006-341x.1999.00936.x

American Medical Association. (2020). AMA Manual of Style: A Guide for Authors and Editors (11th ed.). Oxford University Press. doi:10.1093/jama/9780190246556.001.0001

Bilder, C. R., & Loughin, T. M. (2004). Testing for marginal independence between two categorical variables with multiple responses. Biometrics, 60(1), 241-248. doi:10.1111/j.0006-341x.2004.00147.x

Bilder, C. R., Loughin, T. M., & Nettleton, D. (2000). Multiple marginal independence testing for pick any/c variables. Communications in Statistics - Simulation and Computation, 29(4), 1285-1316. doi:10.1080/03610910008813665

Campbell, I. (2007). Chi-squared and Fisher-Irwin tests of two-by-two tables with small sample recommendations. Statistics in Medicine, 26(19), 3661-3675. doi:10.1002/sim.2832

Fagerland, M. W., Lydersen, S., & Laake, P. (2017). Statistical Analysis of Contingency Tables. Chapman and Hall/CRC. doi:10.1201/9781315374116

Jann, B. (2005). Tabulation of multiple responses. The Stata Journal, 5(1), 92-122. doi:10.1177/1536867x0500500113

Loughin, T. M., & Scherer, P. N. (1998). Testing for association in contingency tables with multiple column responses. Biometrics, 54(2), 630-637. doi:10.2307/3109769

Thomas, D. R., & Decady, Y. J. (2004). Testing for association using multiple response survey data: Approximate procedures based on the Rao-Scott approach. International Journal of Testing, 4(1), 43-59. doi:10.1207/s15327574ijt0401_3

See also

table_categorical() for single-response variables; the article Multiple-response questions.

Examples

# The five info_* items of sochealth answer one "check all that apply"
# question: the sources of health information used in the past 12 months.
table_multiresponse(sochealth, select = starts_with("info_"))
#> Multiple responses
#> 
#>  Variable           │   n       %    
#> ────────────────────┼────────────────
#>  info_doctor        │   721    61.9  
#>  info_pharmacist    │   256    22.0  
#>  info_internet      │   523    44.9  
#>  info_family        │   360    30.9  
#>  info_media         │   141    12.1  
#>  (No item chosen)   │    67     5.8  
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents        │  1164          
#> 
#> Note. Multiple answers possible: percentages can sum to more than 100%. Base: respondents with data for the question (excluded: 36 with no data for the question).

# The labels of the items, sorted by decreasing count.
table_multiresponse(
  sochealth,
  select = starts_with("info_"),
  labels = TRUE,
  sort = "-"
)
#> Multiple responses
#> 
#>  Variable                              │   n       %    
#> ───────────────────────────────────────┼────────────────
#>  Doctor or other health professional   │   721    61.9  
#>  Internet, websites or apps            │   523    44.9  
#>  Family or friends                     │   360    30.9  
#>  Pharmacist                            │   256    22.0  
#>  Television, radio or newspapers       │   141    12.1  
#>  (No item chosen)                      │    67     5.8  
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents                           │  1164          
#> 
#> Note. Multiple answers possible: percentages can sum to more than 100%. Base: respondents with data for the question (excluded: 36 with no data for the question).

# By age group: a test per item and a global test of the question.
table_multiresponse(
  sochealth,
  select = starts_with("info_"),
  by = age_group
)
#> Multiple responses by age_group
#> 
#>  Variable         │ 25-34 n  25-34 %  35-49 n  35-49 %  50-64 n  50-64 % 
#> ──────────────────┼──────────────────────────────────────────────────────
#>  info_doctor      │   113     48.5      208     55.6      215     67.8   
#>  info_pharmacist  │    53     22.7       74     19.8       69     21.8   
#>  info_internet    │   156     67.0      200     53.5      109     34.4   
#>  info_family      │    72     30.9      119     31.8       99     31.2   
#>  info_media       │     9      3.9       36      9.6       41     12.9   
#>  (No item chosen) │    12      5.2       22      5.9       22      6.9   
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents      │   233               374               317            
#> 
#>  Variable         │ 65-75 n  65-75 %  Total n  Total %    p   
#> ──────────────────┼───────────────────────────────────────────
#>  info_doctor      │   185     77.1      721     61.9    <.001 
#>  info_pharmacist  │    60     25.0      256     22.0     .982 
#>  info_internet    │    58     24.2      523     44.9    <.001 
#>  info_family      │    70     29.2      360     30.9     .982 
#>  info_media       │    55     22.9      141     12.1    <.001 
#>  (No item chosen) │    11      4.6       67      5.8     –    
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents      │   240              1164                   
#> 
#> Note. Multiple answers possible: percentages can sum to more than 100%. Base: respondents with data for the question (excluded: 36 with no data for the question). Item p-values: Pearson chi-squared test of each item by group. P-values adjusted for 5 tests (Holm method). Overall group comparison: Rao-Scott second-order corrected chi-squared test of multiple marginal independence, χ²(14.40) = 203.03, p < .001.

# A descriptive table by sex, on the respondents who chose at least one
# source: percentages of cases and of responses, no tests.
table_multiresponse(
  sochealth,
  select = starts_with("info_"),
  by = sex,
  percent = "both",
  base = "answered",
  p_value = FALSE
)
#> Multiple responses by sex
#> 
#>  Variable        │ Female n  Female % of cases  Female % of responses  Male n 
#> ─────────────────┼────────────────────────────────────────────────────────────
#>  info_doctor     │   377           66.4                 34.9            344   
#>  info_pharmacist │   137           24.1                 12.7            119   
#>  info_internet   │   268           47.2                 24.8            255   
#>  info_family     │   219           38.6                 20.3            141   
#>  info_media      │    78           13.7                  7.2             63   
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents     │   568                                                529   
#> 
#>  Variable        │ Male % of cases  Male % of responses  Total n 
#> ─────────────────┼───────────────────────────────────────────────
#>  info_doctor     │      65.0               37.3            721   
#>  info_pharmacist │      22.5               12.9            256   
#>  info_internet   │      48.2               27.7            523   
#>  info_family     │      26.7               15.3            360   
#>  info_media      │      11.9                6.8            141   
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents     │                                        1097   
#> 
#>  Variable        │ Total % of cases  Total % of responses 
#> ─────────────────┼────────────────────────────────────────
#>  info_doctor     │       65.7                36.0         
#>  info_pharmacist │       23.3                12.8         
#>  info_internet   │       47.7                26.1         
#>  info_family     │       32.8                18.0         
#>  info_media      │       12.9                 7.0         
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents     │                                        
#> 
#> Note. Multiple answers possible: percentages of cases can sum to more than 100%, and percentages of responses sum to 100%. Base: respondents with at least one item chosen (excluded: 36 with no data for the question and 67 with no item chosen).

# Items coded "Yes" and "No" need `counted`, the value that marks a choice.
yes_no <- as.data.frame(lapply(
  sochealth[startsWith(names(sochealth), "info_")],
  function(x) factor(x, levels = c(FALSE, TRUE), labels = c("No", "Yes"))
))
table_multiresponse(yes_no, select = everything(), counted = "Yes")
#> Multiple responses
#> 
#>  Variable           │   n       %    
#> ────────────────────┼────────────────
#>  info_doctor        │   721    61.9  
#>  info_pharmacist    │   256    22.0  
#>  info_internet      │   523    44.9  
#>  info_family        │   360    30.9  
#>  info_media         │   141    12.1  
#>  (No item chosen)   │    67     5.8  
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents        │  1164          
#> 
#> Note. Multiple answers possible: percentages can sum to more than 100%. Base: respondents with data for the question (excluded: 36 with no data for the question).

# Bootstrap global test: set a seed to reproduce its p-value.
set.seed(1)
table_multiresponse(
  sochealth,
  select = starts_with("info_"),
  by = sex,
  global_test = "bootstrap",
  boot_n = 999
)
#> Multiple responses by sex
#> 
#>  Variable         │ Female n  Female %  Male n  Male %  Total n  Total %    p   
#> ──────────────────┼─────────────────────────────────────────────────────────────
#>  info_doctor      │   377       62.9     344     60.9     721     61.9    1.000 
#>  info_pharmacist  │   137       22.9     119     21.1     256     22.0    1.000 
#>  info_internet    │   268       44.7     255     45.1     523     44.9    1.000 
#>  info_family      │   219       36.6     141     25.0     360     30.9    <.001 
#>  info_media       │    78       13.0      63     11.2     141     12.1    1.000 
#>  (No item chosen) │    31        5.2      36      6.4      67      5.8     –    
#> ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
#>  Respondents      │   599                565             1164                   
#> 
#> Note. Multiple answers possible: percentages can sum to more than 100%. Base: respondents with data for the question (excluded: 36 with no data for the question). Item p-values: Pearson chi-squared test of each item by group. P-values adjusted for 5 tests (Holm method). Overall group comparison: bootstrap test of multiple marginal independence (999 resamples), χ² = 20.38, p = .002.

# Wide data frame.
table_multiresponse(
  sochealth,
  select = starts_with("info_"),
  by = sex,
  output = "data.frame"
)
#>          variable            label .row_role Female n  Female % Male n
#> 1     info_doctor      info_doctor   summary      377 62.938230    344
#> 2 info_pharmacist  info_pharmacist   summary      137 22.871452    119
#> 3   info_internet    info_internet   summary      268 44.741235    255
#> 4     info_family      info_family   summary      219 36.560935    141
#> 5      info_media       info_media   summary       78 13.021703     63
#> 6            none (No item chosen)      none       31  5.175292     36
#> 7            nobs      Respondents  fit_stat      599        NA    565
#>      Male % Total n   Total %            p
#> 1 60.884956     721 61.941581 1.000000e+00
#> 2 21.061947     256 21.993127 1.000000e+00
#> 3 45.132743     523 44.931271 1.000000e+00
#> 4 24.955752     360 30.927835 9.285636e-05
#> 5 11.150442     141 12.113402 1.000000e+00
#> 6  6.371681      67  5.756014           NA
#> 7        NA    1164        NA           NA

# \donttest{
if (requireNamespace("tinytable", quietly = TRUE)) {
  tt <- table_multiresponse(
    sochealth,
    select = starts_with("info_"),
    by = sex,
    labels = TRUE,
    output = "tinytable"
  )
}
# }