code_book() documents the variables of a data frame: one row per
variable (position, name, label, type, valid and missing counts,
summary statistics), and one row per category of its categorical and
logical variables (code, label, count, percentages). The codebook
prints as the list of variables, and output writes it to an Excel
workbook or to a PDF.
Counts and percentages are unweighted: they describe the data file and are not estimates for a population.
Usage
code_book(
x,
...,
title = "Codebook",
subtitle = NULL,
authors = NULL,
notes = NULL,
source = NULL,
values = 100,
range = TRUE,
factor_levels = c("all", "observed"),
user_na = TRUE,
decimal_mark = NULL,
font = NULL,
font_code = NULL,
font_size = 10,
colors = NULL,
paper = c("a4", "letter"),
index_columns = NULL,
output = NULL
)
# S3 method for class 'spicy_codebook'
print(x, ...)Arguments
- x
A data frame or tibble. For
print(), aspicy_codebook.- ...
Optional tidyselect-style column selectors (e.g.
starts_with("bmi"),where(is.numeric)). Columns can be selected or reordered, but renaming selections is not supported.- title
Title of the codebook, such as the name of the study. The PDF adds the word "Codebook" above it on the cover and before it in the page header, unless the title already contains the word. Defaults to
"Codebook".NULLleaves the console and the Excel file without a title; the cover of the PDF then shows "Codebook" once.- subtitle
Subtitle of the codebook, under the title: the wave, the edition, the extract (
"Wave 3, 2026, public-use file").Authors of the codebook:
NULL(the default), a character vector whose names are the authors and whose values are their affiliations (c("Jane Doe" = "University of Somewhere"); an unnamed element is a name without affiliation), or a list of lists withnameand the optionalaffiliationandorcid. An ORCID given as itshttps://orcid.org/address is kept as the identifier alone.- notes
Character vector of notes on the data (source, exclusions, coding rules, ...), one note per element; blank notes are dropped. In the PDF, an element that starts with
"- "or"* "is a list item, consecutive items making one list, and any other element is a paragraph:notes = c("Wave 3 only.", "- Weight: design weight.", "- BMI: kg/m2."). A note may carry*italics*,**bold**, and`code`(not nested), and a web address or adoi:becomes a link. The PDF renders these, turns straight apostrophes into typographic ones and, in French, the space before:,;,!, or?into a non-breaking space; the console and the Excel file show the words without the marks.- source
Named character vector mapping the current column names to the codes they had in the source file, the vector
dplyr::rename(all_of())takes. Its names must be selected columns, and each code must be non-empty.- values
The maximum number of categories listed per variable in the
valuestable; underfactor_levels = "all", unused levels and codes count. A variable with more lists its firstvaluescategories in their order, with their counts and percentages of the whole variable, then its declared and system missing values;n_categoriesinvariablesgives the total, and the PDF adds a row for the categories not listed. Defaults to100;Inflists them all.- range
Logical. If
TRUE(the default),variablesgives the minimum and maximum of each numeric variable and the earliest and latest date of each date;FALSEdrops those four columns.- factor_levels
Character.
"all"(the default;varlist()uses"observed") lists every declared level of a factor, every labelled code, every code ofna_values, and both values of a logical, with a count of 0 when unused."observed"lists only the values present in the data.- user_na
Logical. If
TRUE(the default), declared missing values count as missing: they are left out ofn_valid, counted inn_missingandn_declared_missing, and listed invalueswithdeclared_missing = TRUE. IfFALSE, they count as valid values. See the "Declared missing values" section.- decimal_mark
Decimal mark of the numbers in the PDF, a single character such as
"."or",".NULL(the default) takes the mark ofoptions(spicy.style), then the one of the language (options(spicy.language = "fr")gives the comma), then".". The console list prints counts only, and the Excel file keeps numbers as numbers.- font, font_code
Fonts of the PDF:
fontfor the text, the values and their codes included, andfont_codefor the variable names and the source codes.NULL(the default) uses New Computer Modern and DejaVu Sans Mono, which Typst embeds, so the PDF looks the same on every machine for the characters these fonts cover; any other character falls back to a font found on the machine. Any other font must be one Typst finds, named exactly asquarto typst fontslists it; a.typoutput keeps the name as given, unchecked. Afontalso sets the font of the Excel file, which otherwise keeps its default font.- font_size
Size of the text of the PDF, in points:
10(the default), or a number from 6 to 24. The title of the cover and the small size of a long name follow it; the margins do not.- colors
Named character vector of
"#RRGGBB"colors replacing part of the palette of the PDF:primary(title, headings, and the text of table headers),accent(the word "Codebook" above the title, and the links),band(behind table headers),band_dark(the band of each variable, under white text),zebra(behind the label rows of a variable),grid(rules),text, andmuted. Abandgiven withoutzebrabrings a lighter tint of itself aszebra, and aband_darkgiven withoutgrida light tint of itself asgrid. The headers of the Excel file takeprimaryandbandtoo.- paper
Paper size of the PDF:
"a4"(the default) or"letter".- index_columns
Columns of the index of variables at the end of the PDF:
1or2.NULL(the default) sets two columns past 40 variables when no name exceeds 40 characters, one column otherwise. A name longer than that may overflow a column of two.- output
NULL(the default) returns the codebook, which prints as the list of variables. A path writes the codebook to that file, in the format of its extension, and returns it invisibly:.xlsxfor an Excel workbook (this requiresopenxlsx2),.pdffor a PDF (this requires thequartopackage and Quarto 1.7 or later, found on the PATH or through theQUARTO_PATHenvironment variable),.typfor the Typst source of that PDF. The path names a file, in a directory that exists.
Value
A spicy_codebook object, returned invisibly when output is
given: a list with
headerA list:
title,subtitle,authors(a tibble withname,affiliation, andorcid),date,n_obs,n_vars,notes(blank notes dropped), anddeclared_missing, a tibble of the declared missing values found in the data and, underfactor_levels = "all", of the declared codes no observation carries, sorted by code then label (code,label,variables,n_variables).variablesA tibble, one row per variable:
position(the column's position inx),name,label,type,class,source,n_valid,n_missing,n_declared_missing,declared_codes(thena_valuesandna_rangeof ahaven_labelled_spssvector, as text, the two parts separated by a semicolon;NAwithout them or underuser_na = FALSE),n_distinct(the distinct values observed, of any variable),n_categories(the categories a categorical or logical variable declares, its levels, codes, orTRUEandFALSE, whethervalueslists them all or not;NAfor a numeric, text, or date variable; it exceedsn_distinctwhen a category has no observation, and the Excel workbook shows it as "Categories" next to "Distinct values"), thenmin,max,mean,sd, andmedianfor numeric variables andearliestandlatestfor dates and times.range = FALSEdropsmin,max,earliest, andlatest.valuesA tibble, one row per value:
positionandvariable(of the variable),code,label,declared_missing,n,pct_total, andpct_valid. The row of the system missing values (code = "NA") exists only for a variable that has missing values.
The attributes language and decimal_mark record the language and
the decimal mark the codebook was built with, and appearance the look
of its PDF: a list of font, font_code, font_size, colors (all
eight), paper, and index_columns.
Details
The type of a variable is read off its R class, never guessed: a factor
is categorical (nominal), an ordered factor categorical (ordinal), a
haven_labelled vector categorical (labelled codes), an integer or
double vector numeric, a logical, character, or Date vector logical,
text, or date, a POSIXct or POSIXlt vector date-time, and an hms
vector time. The level of measurement comes from the declaration alone:
a factor whose order was not declared with ordered() is nominal. A
vector of any other class is shown by its first class (difftime,
...), without statistics. The R class stays in its own column. A haven_labelled
vector without value labels is numeric, or text when it stores
characters, and so is one whose value labels all sit on declared
missing codes, with user_na = TRUE.
values lists the categories of factors and labelled vectors and the
two values of a logical, then the declared missing values of the
variable and, when it has any, a row for its system missing values
(code = "NA"). Numeric, text, and date variables have no category
rows: they appear in values only through their declared missing
values. The codes of a labelled vector come in code order, the levels
of a factor in level order. An explicit NA level of a factor (from
addNA()) counts as missing, in the row of the system missing values.
Dates are written as YYYY-MM-DD, date-times as
YYYY-MM-DD HH:MM:SS followed by the name of the time zone the
variable carries, or UTC when it carries none, and times as
HH:MM:SS.
The words the codebook adds (column headers, types, field and sheet
names) follow options(spicy.language) when the codebook is built (see
spicy_labels()); the variable and value labels of the data are never
translated.
print() shows a line break or a tab in a label as a space, and
shortens long variable labels, counting display columns, when that
makes the list fit the console; the object keeps the labels whole.
Excel output
The workbook has three worksheets, named in the language of the
codebook. The first, codebook, holds the header as field-value pairs:
title, subtitle, one row per author, date, numbers of observations and
variables, declared missing values, notes, and the versions of spicy and
R that wrote it, with the date and time of writing. The other two,
variables and values, hold the two tables of the object from the
first row, under the column headers the console and the PDF show
(code is "Value", n_valid "Valid"), with a frozen header and
filters; in variables, the position, name, and label stay in view
while the statistics scroll. The workbook always has the same columns,
in the same order: a column that does not apply to a variable, or to
any variable of the file, is empty, never left out. Numbers stay
numeric cells at their full precision, shown as the PDF shows them:
the percentages to one decimal, the mean, SD, and median of a variable
to three significant digits of its SD; a statistic that is not finite
(of a column holding Inf) is an empty cell, and dates stay text,
written as above. Each worksheet prints in landscape, fitted to the
width of the page, its header row on every page.
PDF output
The PDF opens on a cover: the word "Codebook" above the title (unless the title already contains it), the subtitle, the authors with their affiliations and ORCID addresses, the date, and at the foot the versions of spicy and R that made it. A page about the data follows (numbers of observations and variables, then the notes and the declared missing values), then the list of variables with the page of each.
One sheet per variable comes next, under the heading "Variable sheets":
a dark band with the position, name, and type (a name over 45
characters takes a row of its own), rows for the label, the declared
missing codes, and the source code, then the counts (valid, missing,
and distinct values), the statistics, and the table of the values. The
number of categories is not among the counts: the table of values
shows them, one row each, and a row "Other categories (n)" the rest
when values caps the list; n_categories is a column of the object
and of the Excel workbook only. On a sheet that lists declared missing
codes, a Missing column marks them with M; the row of the system
missing values reads "System missing". Minimum and maximum are written
at the precision of the data; mean, SD, and median at three significant
digits of the SD. A negative statistic takes the minus sign of a printed
table, and a code keeps the hyphen the data has, since a reader types it
back into a filter; spicy_style(minus_sign = "ascii"), set through
options(spicy.style), keeps the hyphen on the statistics too. A sheet
breaks across pages only when it does not fit on one; its table of
values then repeats its header under "name (continued)". An index of
the variables closes the document, sorted by name in byte order, where
uppercase letters come before lowercase ones.
code_book() writes the Typst source and compiles it with the Typst
that Quarto bundles; Typst warnings, such as a character missing from
the fonts, arrive as one R warning of class spicy_typst_warning.
Without Quarto, output = "<path>.typ" writes the same source,
self-contained: typst compile makes the PDF on any machine with Typst
0.12 or later.
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). haven::read_sav() keeps the na_values / na_range
declaration only with user_na = TRUE: its default turns the
declared codes into NA on import.
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() counts
them as missing in N_valid / NAs / N_distinct while still
listing the declared codes in Values; code_book() counts them in
n_declared_missing and flags them in its values table.
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
varlist() to explore the variables in the Viewer; freq() for the
frequency table of one variable; the article
Explore variables and build codebooks.
Other variable inspection:
label_from_names(),
varlist()
Examples
code_book(sochealth)
#> Codebook
#>
#> Date: 2026-11-01
#> Observations: 1200
#> Variables: 29
#> Note: Counts and percentages are unweighted: they describe the data file and are not estimates for a population.
#>
#> Pos. │ Variable Label Type Valid Missing
#> ────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#> 1 │ sex Sex categorical (nominal) 1200 0
#> 2 │ age Age (years) numeric 1200 0
#> 3 │ age_group Age group categorical (ordinal) 1200 0
#> 4 │ education Highest education level categorical (ordinal) 1200 0
#> 5 │ social_class Subjective social class categorical (ordinal) 1200 0
#> 6 │ region Region of residence categorical (nominal) 1200 0
#> 7 │ employment_status Employment status categorical (nominal) 1200 0
#> 8 │ income_group Household income group categorical (ordinal) 1182 18
#> 9 │ income Monthly household income (CHF) numeric 1200 0
#> 10 │ smoking Current smoker categorical (nominal) 1175 25
#> 11 │ physical_activity Regular physical activity categorical (nominal) 1200 0
#> 12 │ dentist_12m Dentist visit in last 12 months categorical (nominal) 1200 0
#> 13 │ info_doctor Doctor or other health professional logical 1164 36
#> 14 │ info_pharmacist Pharmacist logical 1164 36
#> 15 │ info_internet Internet, websites or apps logical 1164 36
#> 16 │ info_family Family or friends logical 1164 36
#> 17 │ info_media Television, radio or newspapers logical 1164 36
#> 18 │ self_rated_health Self-rated health categorical (ordinal) 1180 20
#> 19 │ wellbeing_score WHO-5 wellbeing index (0-100) numeric 1200 0
#> 20 │ bmi Body mass index numeric 1188 12
#> 21 │ bmi_category BMI category categorical (ordinal) 1188 12
#> 22 │ institutional_trust Trust in institutions categorical (ordinal) 1200 0
#> 23 │ political_position Political position (0 = left, 10 = right) numeric 1185 15
#> 24 │ life_sat_health Satisfaction with health (1-5) numeric 1192 8
#> 25 │ life_sat_work Satisfaction with work (1-5) numeric 1192 8
#> 26 │ life_sat_relationships Satisfaction with relationships (1-5) numeric 1192 8
#> 27 │ life_sat_standard Satisfaction with standard of living (1-5) numeric 1192 8
#> 28 │ response_date Survey response date date-time 1200 0
#> 29 │ weight Survey design weight numeric 1200 0
cb <- code_book(
sochealth,
starts_with("bmi"),
title = "Social health survey",
subtitle = "Body mass index",
authors = c("Jane Doe" = "University of Somewhere"),
notes = "Simulated data (see ?sochealth)."
)
cb
#> Social health survey
#> Body mass index
#> Jane Doe – University of Somewhere
#>
#> Date: 2026-11-01
#> Observations: 1200
#> Variables: 2
#> Note: Counts and percentages are unweighted: they describe the data file and are not estimates for a population.
#> Note: Simulated data (see ?sochealth).
#>
#> Pos. │ Variable Label Type Valid Missing
#> ────────┼────────────────────────────────────────────────────────────────────────────────
#> 20 │ bmi Body mass index numeric 1188 12
#> 21 │ bmi_category BMI category categorical (ordinal) 1188 12
cb$variables
#> # A tibble: 2 × 19
#> position name label type class source n_valid n_missing n_declared_missing
#> <int> <chr> <chr> <chr> <chr> <chr> <int> <int> <int>
#> 1 20 bmi Body… nume… nume… NA 1188 12 0
#> 2 21 bmi_ca… BMI … cate… orde… NA 1188 12 0
#> # ℹ 10 more variables: declared_codes <chr>, n_distinct <int>,
#> # n_categories <int>, min <dbl>, max <dbl>, mean <dbl>, sd <dbl>,
#> # median <dbl>, earliest <chr>, latest <chr>
cb$values
#> # A tibble: 4 × 8
#> position variable code label declared_missing n pct_total pct_valid
#> <int> <chr> <chr> <chr> <lgl> <int> <dbl> <dbl>
#> 1 21 bmi_category Normal… NA FALSE 465 38.8 39.1
#> 2 21 bmi_category Overwe… NA FALSE 569 47.4 47.9
#> 3 21 bmi_category Obesity NA FALSE 154 12.8 13.0
#> 4 21 bmi_category NA NA FALSE 12 1 NA
# Labelled survey data: declared missing codes count as missing and are
# flagged in `values`.
if (requireNamespace("haven", quietly = TRUE)) {
trust <- haven::labelled_spss(
c(1, 2, 2, 3, 4, 8, 9, 1, NA, 2),
labels = c(
"Not at all" = 1, "A little" = 2, "Somewhat" = 3, "A lot" = 4,
"Don't know" = 8, "Refused" = 9
),
na_values = c(8, 9),
label = "Trust in the health system"
)
code_book(tibble::tibble(trust))$values
}
#> # A tibble: 7 × 8
#> position variable code label declared_missing n pct_total pct_valid
#> <int> <chr> <chr> <chr> <lgl> <int> <dbl> <dbl>
#> 1 1 trust 1 Not at all FALSE 2 20 28.6
#> 2 1 trust 2 A little FALSE 3 30 42.9
#> 3 1 trust 3 Somewhat FALSE 1 10 14.3
#> 4 1 trust 4 A lot FALSE 1 10 14.3
#> 5 1 trust 8 Don't know TRUE 1 10 NA
#> 6 1 trust 9 Refused TRUE 1 10 NA
#> 7 1 trust NA NA FALSE 1 10 NA
if (requireNamespace("openxlsx2", quietly = TRUE)) {
path <- tempfile(fileext = ".xlsx")
code_book(sochealth, output = path)
}
# The Typst source of the PDF, which `output = "<path>.pdf"` compiles
# when Quarto is installed.
code_book(sochealth, output = tempfile(fileext = ".typ"))