| Title: | Single-Line Helpers for Consumer Survey Analysis |
|---|---|
| Description: | Simplifies the day-to-day work of research and insights professionals who analyse consumer and customer survey data but have limited coding experience. Wraps common 'tidyverse' patterns into single-line functions for the tasks these teams repeat constantly: loading and combining survey exports, turning questions into percentages and summaries, recoding rating and Net Promoter scales, deriving demographics such as age cohorts and regions, modelling the drivers of satisfaction, checking sampling precision, selecting illustrative open-text comments, and producing presentation-ready charts and reports. A simulated consumer-survey dataset is included for learning and examples. Weighting rakes to target margins as in Deming and Stephan (1940) <doi:10.1214/aoms/1177731829> and reports the design effect of Kish (1965, ISBN:978-0-471-10949-5); driver models use the relative weights of Johnson (2000) <doi:10.1207/S15327906MBR3501_1> or the random forests of Breiman (2001) <doi:10.1023/A:1010933404324>; the Net Promoter Score follows Reichheld (2003) <https://hbr.org/2003/12/the-one-number-you-need-to-grow>; and diverse comments are chosen by maximal marginal relevance as in Carbonell and Goldstein (1998) <doi:10.1145/290941.291025>. |
| Authors: | Andreas Schellewald [aut, cre] |
| Maintainer: | Andreas Schellewald <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.7.1 |
| Built: | 2026-11-05 01:31:59 UTC |
| Source: | https://github.com/cran/ezrsurvey |
Convenience wrapper around convert_currency() that appends a converted
column to data. The source currency can be a fixed code or a per-row column.
add_currency(data = NULL, amount, from, to = "USD", into = NULL, rates = NULL)add_currency(data = NULL, amount, from, to = "USD", into = NULL, rates = NULL)
data |
A data frame. |
amount |
The amount column to convert (unquoted). |
from |
Source currency: either a bare column name holding per-row codes
(e.g. |
to |
Target currency code. Defaults to |
into |
Name of the new column. Defaults to |
rates |
Passed to |
The from argument is clever about whether you mean a column or a constant: a
bare name (from = currency) is read as a per-row source-currency column,
while a quoted string (from = "EUR") is a fixed code applied to every row.
This is the common case where each respondent reported spend in their own
currency. The new column defaults to <amount>_<to> (e.g. spend_usd);
override with into. Conversion uses convert_currency(), so the same rates,
aliases and warnings apply.
data with the converted column added.
Other currency:
convert_currency(),
list_currencies()
df <- tibble::tibble(spend = c(100, 200, 50), currency = c("EUR", "GBP", "JPY")) # per-row source currency (a column) add_currency(df, spend, from = currency, to = "USD") # a fixed source currency (a constant), custom output name add_currency(df, spend, from = "EUR", to = "USD", into = "spend_usd")df <- tibble::tibble(spend = c(100, 200, 50), currency = c("EUR", "GBP", "JPY")) # per-row source currency (a column) add_currency(df, spend, from = currency, to = "USD") # a fixed source currency (a constant), custom output name add_currency(df, spend, from = "EUR", to = "USD", into = "spend_usd")
Convenience wrapper that appends a region column (and optionally a
subregion) to a data frame by looking up an existing country column with
recode_region().
add_region( data = NULL, country, region_to = "region", subregion = FALSE, quiet = FALSE )add_region( data = NULL, country, region_to = "region", subregion = FALSE, quiet = FALSE )
data |
A data frame. |
country |
The country column (unquoted). |
region_to |
Name of the region column to add. Defaults to |
subregion |
If |
quiet |
Passed to |
This is the data-frame-friendly form of recode_region(): point it at an
existing country column and it appends a region column (named via
region_to) and, optionally, a subregion. Handy right after read_folder()
to enrich raw exports before grouping by region. The lookup, matching and
unmatched-country warning behave exactly as in recode_region().
data with the new column(s) added.
recode_region(), country_region.
Other recode:
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
df <- tibble::tibble(demo_country = c("Germany", "Japan", "Brazil")) add_region(df, demo_country, subregion = TRUE)df <- tibble::tibble(demo_country = c("Germany", "Japan", "Brazil")) add_region(df, demo_country, subregion = TRUE)
Programmatically adds the BAD / OK / GOOD style decision markers survey
charts carry: for each band a thin coloured marker line runs
along one axis, with a bold centred label. This turns the copy-pasted
geom_rect() + annotate("text", x = median(seq(from, to, .1)), ...) blocks
into one call, so any chart can be given consistent "where's good, where's
bad" guidance.
annotate_bands( plot, bands, axis = c("x", "y"), at = NULL, label_offset = NULL, labels = TRUE, text_colour = NULL, text_size = NULL, linewidth = 1 )annotate_bands( plot, bands, axis = c("x", "y"), at = NULL, label_offset = NULL, labels = TRUE, text_colour = NULL, text_size = NULL, linewidth = 1 )
plot |
A ggplot object. |
bands |
A band specification: a data frame (or one of |
axis |
Which axis the bands run along: |
at |
Position on the opposite axis at which to draw the marker line and
labels (e.g. the top of a bar chart). If |
label_offset |
Distance to nudge labels away from the marker line, in
data units of the opposite axis. If |
labels |
Whether to draw the band labels. Defaults to |
text_colour, text_size
|
Label appearance. |
linewidth |
Marker line width. Defaults to |
Bands turn a chart into a decision aid: a thin coloured marker line runs along
one axis for each band, with a bold centred label, so a reader instantly sees
where "good" and "bad" lie. Supply a band spec (a data frame with from,
to, label and optional colour, or one of the bands_rating_3()
presets), the axis the bands run along, and at – the position on the
opposite axis for the marker line (usually the top of the panel; inferred from
the plot when omitted).
The plot with the band layers added.
mark_value() to add a single "you are here" marker.
Other decisions:
bands_rating_3(),
mark_value(),
rescale_bands()
# the performance axis of an IPM chart is the 1-5 rating scale the # bands describe, so "where is good" lands where a reader expects it ipm_model(podracing_survey, nps_value, "ratings_") %>% plot_ipm() %>% annotate_bands(bands_rating_3(), axis = "x")# the performance axis of an IPM chart is the 1-5 rating scale the # bands describe, so "where is good" lands where a reader expects it ipm_model(podracing_survey, nps_value, "ratings_") %>% plot_ipm() %>% annotate_bands(bands_rating_3(), axis = "x")
Turns a vector into an ordered factor using a registered order, selected
either by order name or by looking up the order linked to a variable name.
apply_order(x, name = NULL, var = NULL)apply_order(x, name = NULL, var = NULL)
x |
A vector. |
name |
Order name (see |
var |
A variable name to look up via |
A factor with the registered levels, or x unchanged if no order is
found.
register_order(), order_for().
Other config:
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order("size", c("S", "M", "L")) apply_order(c("L", "S", "M"), name = "size") remove_order("size")register_order("size", c("S", "M", "L")) apply_order(c("L", "S", "M"), name = "size") remove_order("size")
Ready-made band specifications for annotate_bands().
bands_rating_3(from = NULL, to = NULL) bands_rating_5(from = NULL, to = NULL) bands_nps(from = NULL, to = NULL) bands_nps_score(from = NULL, to = NULL)bands_rating_3(from = NULL, to = NULL) bands_rating_5(from = NULL, to = NULL) bands_nps(from = NULL, to = NULL) bands_nps_score(from = NULL, to = NULL)
from, to
|
Optional axis limits. A band reaching past them is trimmed and a band entirely outside them is dropped, so a preset can be reused on a chart that shows only part of the scale. |
bands_rating_3() – BAD / OK / GOOD over a 1-5 rating axis.
bands_rating_5() – per-point labels (1 Very bad .. 5 Very good) over 1-5.
bands_nps() – Detractor / Passive / Promoter over a 0-10 answer axis.
bands_nps_score() – Needs work / Good / Great / Excellent over a
-100..100 Net Promoter Score axis (the scale a headline NPS sits on, not
the 0-10 answers it is computed from).
Pass from / to when the chart's panel is narrower than the full scale.
Plotting a 2-5 range with the untrimmed bands_rating_3() would open the BAD
band at 1, off the left edge, and stretch the panel to reach it.
A tibble with from, to, label and colour.
Other decisions:
annotate_bands(),
mark_value(),
rescale_bands()
bands_rating_3() # only the part of the scale a 2-5 panel actually shows bands_rating_3(from = 2) bands_nps_score()bands_rating_3() # only the part of the scale a 2-5 panel actually shows bands_rating_3(from = 2) bands_nps_score()
A thin, survey-friendly wrapper around base::cut() that returns a factor in
band order and uses left-closed, right-open intervals by default
so that age bands like 18-21 behave intuitively. Generalises the
case_when(age %in% seq(...)) age-grouping pattern.
bin_numeric(x, breaks, labels, right = FALSE, quiet = FALSE)bin_numeric(x, breaks, labels, right = FALSE, quiet = FALSE)
x |
A numeric vector. |
breaks |
Numeric vector of cut points. With |
labels |
Character vector of group labels, length |
right |
If |
quiet |
If |
Bands are built with base::cut() using include.lowest = TRUE, so the very
lowest break is included. With right = FALSE (the default) a band runs from
its lower break up to but not including the next – i.e. [18, 25) – which
is what you want for age groups like "18 to 24". Text input is salvaged with
ensure_numeric(), so "27 years" bins correctly. The result is a factor
whose levels follow labels, so tables and charts built from it list the
bands low to high ("40-70k" before "100-150k") rather than alphabetically.
Wrap it in as.character() if you need plain text.
A value below the first break or above the last becomes NA and drops out of
every chart built from the result, so the function says how many did and what
their range was. A closed top band such as 35 to 40.5 under a label of
"35 to 40+" is the usual cause; Inf is what that label means.
A factor whose levels are labels in the order given (values
outside the range or NA become NA).
recode_age() for a ready-made age-band wrapper, ensure_numeric().
Other recode:
add_region(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
bin_numeric(c(15, 19, 27, 41), breaks = c(0, 18, 25, 35, Inf), labels = c("<18", "18-24", "25-34", "35+"))bin_numeric(c(15, 19, 27, 41), breaks = c(0, 18, 25, 35, Inf), labels = c("<18", "18-24", "25-34", "35+"))
Show the active brand settings
brand_info()brand_info()
An ezrsurvey_brand list with elements colors, color_primary,
font_major, font_minor, template_pptx, template_docx (each NULL
when unset).
Other brand:
clear_brand(),
pal_brand(),
scale_fill_brand(),
use_brand()
brand_info()brand_info()
Ranks how much each predictor drives an outcome such as the NPS rating, as importance scores summing to 100. Relative weights analysis is the default; a random forest and a plain correlation are available as alternatives.
calc_importance( data = NULL, outcome, predictors, method = c("rwa", "forest", "correlation"), recode = TRUE, likert_levels = c("Very bad", "Bad", "Ok", "Good", "Very good") )calc_importance( data = NULL, outcome, predictors, method = c("rwa", "forest", "correlation"), recode = TRUE, likert_levels = c("Very bad", "Bad", "Ok", "Good", "Very good") )
data |
A data frame. |
outcome |
The outcome column (unquoted), e.g. |
predictors |
The predictor columns, using tidyselect (e.g.
|
method |
How to measure importance. |
recode |
If |
likert_levels |
Scale wordings passed to |
All three methods answer the same question and are scaled the same way, so
each feature's importance reads as "this feature accounts for X% of what
drives the outcome" and any of them can be fed to plot_ipm(). They differ in
what they can see:
"rwa", relative weights analysis (Johnson's epsilon), shares out the
linear model's explained variance between correlated predictors, which
ordinary regression coefficients handle poorly. This is the default because
rating batteries are almost always heavily correlated.
"forest" grows a random forest and measures how much prediction error
rises when each predictor is permuted, so it picks up non-linear effects and
interactions that relative weights cannot. It is the useful cross-check: if
a feature ranks high here and low under "rwa", its effect is not linear.
Being a random method, it moves a little between runs; call set.seed()
first for a figure you intend to publish.
"correlation" is the crude one, ignoring the other predictors entirely, so
correlated features double-count. Its merit is that it needs no suggested
package and cannot fail to converge, which makes it a reasonable sanity
check on the other two.
Only complete cases are used. Worded rating columns are recoded to 1-5
automatically (recode = TRUE). Negative importances (which permutation
importance can produce for a predictor that is pure noise) are floored at
zero before rescaling. Most users call ipm_model(), which pairs this with
performance for the importance/performance matrix.
A tibble with feature and importance, the
latter rescaled to sum to 100 whichever method produced it, strongest
driver first.
Other modelling:
calc_nps(),
ipm_model()
calc_importance(podracing_survey, nps_value, starts_with("ratings_")) # a cross-check that can see non-linear effects set.seed(1) calc_importance(podracing_survey, nps_value, starts_with("ratings_"), method = "forest")calc_importance(podracing_survey, nps_value, starts_with("ratings_")) # a cross-check that can see non-linear effects set.seed(1) calc_importance(podracing_survey, nps_value, starts_with("ratings_"), method = "forest")
Computes NPS from a 0-10 recommendation question. The score is
% promoters - % detractors, equivalently 100 * mean(nps_group(x)) over the
signed -1/0/1 coding (see nps_group()).
calc_nps(data = NULL, value, by = NULL, weights = NULL)calc_nps(data = NULL, value, by = NULL, weights = NULL)
data |
A data frame. |
value |
The 0-10 recommendation column (unquoted). |
by |
Optional grouping column(s); see |
weights |
Survey weighting: |
Respondents are bucketed by nps_group() into detractors (0–6), passives
(7–8) and promoters (9–10); the score is the percentage of promoters minus
the percentage of detractors, which equals 100 * mean() of the signed
-1/0/1 coding. Missing or out-of-range scores are dropped before the mean.
Text answers are coerced with ensure_numeric(). Use plot_nps() for the
full 0–10 distribution chart and plot_nps_gauge() for a single-number
gauge.
The three group counts and shares are reported alongside the score because a
single NPS hides how it was reached: +20 from 40% promoters and 20%
detractors is a different picture from +20 with 25% and 5%. Counts are always
unweighted respondent counts; the shares follow nps, so they are weighted
when a weighting scheme is active. All three shares and nps are rounded to
whole numbers independently, so pct_promoters - pct_detractors can land one
point away from nps; the identity holds exactly before rounding.
A tibble with n (valid responses), nps (an
integer from -100 to 100), the three group shares (pct_detractors,
pct_passives, pct_promoters) and the counts behind them (detractors,
passives, promoters), one row per group when by is given. The shares
come first because they are what a report quotes.
nps_group(), plot_nps(), plot_nps_gauge().
Other modelling:
calc_importance(),
ipm_model()
calc_nps(podracing_survey, nps_value) calc_nps(podracing_survey, nps_value, by = region)calc_nps(podracing_survey, nps_value) calc_nps(podracing_survey, nps_value, by = region)
The headline helper: replaces the ubiquitous
count(x) %>% mutate(pct = round(n / sum(n) * 100)) %>% select(-n) dance with a
single call. Returns both the raw counts and the percentages, optionally
grouped by one or more variables and optionally pivoted to a wide
cross-tabulation.
calc_percentage( data = NULL, column, by = NULL, sort = c("none", "desc", "asc"), levels = NULL, digits = 0, wide = FALSE, na_rm = TRUE, drop = NULL, weights = NULL )calc_percentage( data = NULL, column, by = NULL, sort = c("none", "desc", "asc"), levels = NULL, digits = 0, wide = FALSE, na_rm = TRUE, drop = NULL, weights = NULL )
data |
A data frame. If omitted, the session default set with
|
column |
The categorical column to tabulate (unquoted). |
by |
Optional grouping column(s). Percentages are computed within each
group so they sum to ~100 per group. Pass one bare name ( |
sort |
Level ordering of |
levels |
Optional character vector giving an explicit level order for
|
digits |
Decimal places for the percentage. Defaults to |
wide |
If |
na_rm |
If |
drop |
Optional character vector of answer values to remove before
counting (e.g. |
weights |
Survey weighting for this call: |
Percentages are computed within each group, so they sum to about 100 per
group (subject to rounding). The level order of column is decided in this
order of precedence: an explicit levels argument; then a non-"none"
sort; then a registered order for the variable (see register_order());
then the column's own levels, when it is a factor; otherwise data order. When by is omitted, the default_by option is used if
set (see ezrsurvey_options()), so you can apply a standard breakdown without
repeating it. wide = TRUE pivots to one row per group with a column per
answer – the shape you want for a slide table or an Excel tab.
A tibble with column, n and pct (and wpct
when weighting is active) in long form, or one row per group with an answer
column each (wide form).
calc_percentage_multi() for check-all-that-apply questions,
calc_percentage_batch() for many questions at once, calc_summary() for
numeric variables, register_order() for reusable level orders.
Other summaries:
calc_percentage_batch(),
calc_percentage_multi(),
calc_summary(),
clear_checkpoints(),
crosstab(),
crosstab_banner()
calc_percentage(podracing_survey, demo_gender) # largest first calc_percentage(podracing_survey, demo_gender, sort = "desc") # grouped and pivoted to a wide cross-tab calc_percentage(podracing_survey, satis_return, by = region, wide = TRUE)calc_percentage(podracing_survey, demo_gender) # largest first calc_percentage(podracing_survey, demo_gender, sort = "desc") # grouped and pivoted to a wide cross-tab calc_percentage(podracing_survey, satis_return, by = region, wide = TRUE)
Runs calc_percentage() over several columns and stacks the results into one
tidy table with a variable column identifying the source question and a
shared answer column – handy for tabulating a whole block of questions
(e.g. every demo_ variable) in a single call.
calc_percentage_batch( data = NULL, ..., by = NULL, sort = c("none", "desc", "asc"), digits = 0, na_rm = TRUE, drop = NULL, weights = NULL, clean_names = FALSE, prefix = NULL )calc_percentage_batch( data = NULL, ..., by = NULL, sort = c("none", "desc", "asc"), digits = 0, na_rm = TRUE, drop = NULL, weights = NULL, clean_names = FALSE, prefix = NULL )
data |
A data frame. |
... |
Columns to tabulate, using tidyselect (e.g. |
by, sort, digits, na_rm, drop, weights
|
Passed to |
clean_names |
If |
prefix |
Optional question prefix to strip from |
Each selected column is tabulated with calc_percentage() and the results are
row-bound, with a variable column naming the source question and the answers
collected into a shared character answer column (factor levels differ
between questions, so they are coerced to character when stacked). This is the
tidy long shape you want for faceted plots or a single export of a whole
question block. To send each question to its own Excel tab instead, see
export_xlsx().
variable is the raw column name by default. Set clean_names = TRUE (or
name a prefix) to get the same tidied wording calc_percentage_multi() and
ipm_model() produce, which is what lets a batch table join to a model table
on the question name.
A tibble with variable, answer, n, pct
(plus any by columns).
calc_percentage(), clean_label(), export_xlsx().
Other summaries:
calc_percentage(),
calc_percentage_multi(),
calc_summary(),
clear_checkpoints(),
crosstab(),
crosstab_banner()
calc_percentage_batch(podracing_survey, demo_gender, demo_job) calc_percentage_batch(podracing_survey, starts_with("demo_")) # tidy question names, with the block's prefix removed calc_percentage_batch(podracing_survey, starts_with("ratings_"), prefix = "ratings_")calc_percentage_batch(podracing_survey, demo_gender, demo_job) calc_percentage_batch(podracing_survey, starts_with("demo_")) # tidy question names, with the block's prefix removed calc_percentage_batch(podracing_survey, starts_with("ratings_"), prefix = "ratings_")
Multi-select questions arrive as a block of columns sharing a prefix, each holding the chosen option (or blank). This computes, per option, the share of respondents who selected it – so the percentages can (and usually do) sum to more than 100. The denominator is the number of distinct respondents who selected at least one option.
calc_percentage_multi( data = NULL, prefix, id = NULL, by = NULL, sort = c("none", "desc", "asc"), digits = 0, drop = NULL, clean_names = TRUE, split = "auto" )calc_percentage_multi( data = NULL, prefix, id = NULL, by = NULL, sort = c("none", "desc", "asc"), digits = 0, drop = NULL, clean_names = TRUE, split = "auto" )
data |
A data frame. |
prefix |
Common column-name prefix identifying the option block, e.g.
|
id |
Optional respondent identifier column (unquoted) used as the distinct-respondent denominator. If omitted, each row is treated as one respondent. |
by |
Optional grouping column(s); percentages are computed within group. |
sort, digits
|
As in |
drop |
Optional character vector of option labels to remove before
counting (matched against the cleaned labels, case-insensitive). Defaults to
the |
clean_names |
If |
split |
How to handle a question that arrives packed into a single cell
per respondent ( |
The crucial difference from calc_percentage() is the denominator: it is the
number of distinct respondents who picked any option in the block, not the
number of ticks. So if 700 of 1,000 respondents selected at least one
motivation, each option's percentage is "out of 700", and the percentages can
(and usually do) sum to more than 100. Supply id to count distinct
respondents exactly; without it, each row counts as one respondent. With
clean_names = TRUE the option labels are stripped of prefix and exporter
artefacts like "A...B" are turned back into "A / B".
Two export shapes are handled. The usual one is a block of columns, one per
option, named with a shared prefix; that is what prefix selects. The other
is a single column holding every answer a respondent ticked, joined by a
delimiter, which is what Google Forms and most spreadsheet exports produce. If
prefix matches exactly one column and a delimiter is found in it, that
column is unpacked automatically and the answer text itself becomes the option
label (clean_names is not applied, since the answers are real wording rather
than mangled column names). Behaviour on a column block is unchanged. Use
split_multi() when you want those unpacked columns kept for other work.
A tibble with option, n (respondents choosing
it) and pct.
calc_percentage(), split_multi(), plot_bars().
Other summaries:
calc_percentage(),
calc_percentage_batch(),
calc_summary(),
clear_checkpoints(),
crosstab(),
crosstab_banner()
calc_percentage_multi(podracing_survey, "motivations_", id = respondent_id, sort = "desc") # a spreadsheet export that packs every answer into one cell packed <- data.frame( respondent = 1:4, motivations = c("Speed; Drivers", "Speed", "", "Betting; Speed") ) calc_percentage_multi(packed, "motivations", id = respondent, sort = "desc")calc_percentage_multi(podracing_survey, "motivations_", id = respondent_id, sort = "desc") # a spreadsheet export that packs every answer into one cell packed <- data.frame( respondent = 1:4, motivations = c("Speed; Drivers", "Speed", "", "Betting; Speed") ) calc_percentage_multi(packed, "motivations", id = respondent, sort = "desc")
The numeric counterpart to calc_percentage(): a one-liner for the
summarise(mean, median, sd) blocks used on age and other continuous
variables, with optional grouping.
calc_summary(data = NULL, column, by = NULL, na_rm = TRUE, weights = NULL)calc_summary(data = NULL, column, by = NULL, na_rm = TRUE, weights = NULL)
data |
A data frame. |
column |
Numeric column to summarise (unquoted). |
by |
Optional grouping column(s); see |
na_rm |
Drop |
weights |
Survey weighting: |
The column is passed through ensure_numeric() first, so a text age column
like "25 years" still summarises. n counts non-missing values (after that
coercion). When a weighting scheme is active the statistics are weighted (and
missing values are dropped); n remains the unweighted respondent count. For a
measure of how precise the mean is, pair this with diagnose() or
se_mean().
A tibble with n (non-missing count), mean,
median and sd, one row per group when by is supplied.
calc_percentage() for categorical variables, diagnose() for
precision.
Other summaries:
calc_percentage(),
calc_percentage_batch(),
calc_percentage_multi(),
clear_checkpoints(),
crosstab(),
crosstab_banner()
calc_summary(podracing_survey, demo_age) calc_summary(podracing_survey, demo_age, by = region)calc_summary(podracing_survey, demo_age) calc_summary(podracing_survey, demo_age, by = region)
Turns the mangled column names survey tools produce back into the wording a
reader expects: "Broadcast.quality...audio" becomes
"Broadcast quality / audio". Drop a question prefix at the same time with
prefix, so a block of ratings_* columns becomes plain feature names.
clean_label(x, prefix = NULL)clean_label(x, prefix = NULL)
x |
A character vector of column names. |
prefix |
Optional prefix to strip from the start of each name before
tidying, e.g. |
Exporters replace every character they cannot put in a column name with a
dot, so a slash surrounded by spaces arrives as three dots and a space
arrives as one. This restores both, in that order, then squishes the result.
It is the same cleaning calc_percentage_multi() and ipm_model() apply to
their own output, exported so a hand-built table can match them: labels that
disagree will not join, which is what silently empties a comparison chart.
A character vector of labels, the same length as x.
calc_percentage_batch(), calc_percentage_multi().
Other recode:
add_region(),
bin_numeric(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
clean_label("Broadcast.quality...audio") clean_label(c("ratings_Camera.work", "ratings_Talent...analysis"), prefix = "ratings_")clean_label("Broadcast.quality...audio") clean_label(c("ratings_Camera.work", "ratings_Talent...analysis"), prefix = "ratings_")
Resets every brand_* option so charts and reports return to the neutral
ezrsurvey defaults.
clear_brand()clear_brand()
Invisibly TRUE.
Other brand:
brand_info(),
pal_brand(),
scale_fill_brand(),
use_brand()
clear_brand()clear_brand()
Removes the checkpoint files that crosstab_banner(checkpoint = TRUE) keeps
under tools::R_user_dir(). A finished run clears its own checkpoint, so
anything left is from a run that was interrupted and never repeated; this
empties the folder in one call.
clear_checkpoints()clear_checkpoints()
Only checkpoints the package named for itself are stored there. A checkpoint
you pointed somewhere yourself (checkpoint = "my-run.rds") is your file and
is never touched, by this function or by a completed run.
Invisibly the number of files removed.
crosstab_banner(), clear_weights_cache().
Other summaries:
calc_percentage(),
calc_percentage_batch(),
calc_percentage_multi(),
calc_summary(),
crosstab(),
crosstab_banner()
withr::with_envvar(c(R_USER_CACHE_DIR = tempdir()), clear_checkpoints())withr::with_envvar(c(R_USER_CACHE_DIR = tempdir()), clear_checkpoints())
Empties the session's cache of computed weight vectors. Weighting the same rows under the same scheme always gives the same numbers, so ezrsurvey works them out once and reuses them; a table crossing every question against every group would otherwise re-run the raking for each cell.
clear_weights_cache()clear_weights_cache()
The cache keys on the weighting columns' own contents, so editing the data
produces different weights without any action from you. Clearing it by hand
is only needed to reclaim memory, or after changing a weighting variable
in place inside an object the cache has already seen. clear_weights()
clears it too.
TRUE, invisibly.
set_weights(), weight_vector().
Other weighting:
get_weights(),
set_weights(),
weight_vector()
clear_weights_cache()clear_weights_cache()
Joins a current and a previous table on a key column and computes the
difference of a metric – for example feature performance from two
ipm_model() outputs, or pct from two calc_percentage() tables for two
events. The result feeds straight into plot_diff().
compare_values(current, previous, by = "feature", value = "performance")compare_values(current, previous, by = "feature", value = "performance")
current, previous
|
Data frames sharing a key column and a metric column. |
by |
Name of the key column to join on. Default |
value |
Name of the metric column to compare. Default |
The join is a left join keyed on by with previous on the left, so the row
set follows the previous period and difference is current - previous (a
positive number means it went up). Keys present only in current are dropped;
keys missing from current get an NA difference. The inputs are deliberately
generic two-column-or-more tables, so the same function compares feature
performance from two ipm_model() runs, pct from two calc_percentage()
tables for two events, or any other keyed metric.
A tibble with the key column, previous, current
and difference (current - previous).
plot_diff(), ipm_model(), calc_percentage().
Other compare:
plot_diff()
a <- data.frame(feature = c("price", "quality"), performance = c(3.1, 4.2)) b <- data.frame(feature = c("price", "quality"), performance = c(2.8, 4.4)) compare_values(current = b, previous = a)a <- data.frame(feature = c("price", "quality"), performance = c(3.1, 4.2)) b <- data.frame(feature = c("price", "quality"), performance = c(2.8, 4.4)) compare_values(current = b, previous = a)
Converts monetary amounts from one currency to another using a table of
exchange rates. By default it uses the bundled currency_rates snapshot
(units per US dollar); pass your own rates for up-to-date or custom figures.
Because rates share the USD base, any pair is converted as a cross-rate
through USD automatically (e.g. EUR to CNY goes EUR -> USD -> CNY in one call).
amount, from and to are vectorised and recycled, so you can convert a
whole column with per-row source currencies at once.
convert_currency(amount, from, to = "USD", rates = NULL)convert_currency(amount, from, to = "USD", rates = NULL)
amount |
Numeric amounts (text like |
from |
Source currency code(s), e.g. |
to |
Target currency code(s). Defaults to |
rates |
Exchange rates: |
Each rate is stored as units of the currency per 1 US dollar, so a conversion
is amount / rate(from) * rate(to) – which means any pair is handled as a
cross-rate through USD without you doing anything. The bundled
currency_rates table is a dated, approximate snapshot meant for convenience,
not live trading; for accuracy pass your own rates (a named vector or a
currency/per_usd data frame). Amounts are coerced with ensure_numeric(),
codes are upper-cased and resolved through a small alias map ("RMB" ->
"CNY", "EURO" -> "EUR", ...), and any unknown code yields NA with a
warning rather than a silent wrong number.
A numeric vector of converted amounts; unknown currency codes yield
NA with a warning.
add_currency(), list_currencies(), currency_rates.
Other currency:
add_currency(),
list_currencies()
convert_currency(100, "EUR", "USD") convert_currency(c(100, 50), from = c("EUR", "GBP"), to = "USD") convert_currency(100, "EUR", "CNY") # cross-rate via USD convert_currency(100, "EUR", "RMB") # alias -> CNY, same resultconvert_currency(100, "EUR", "USD") convert_currency(c(100, 50), from = c("EUR", "GBP"), to = "USD") convert_currency(100, "EUR", "CNY") # cross-rate via USD convert_currency(100, "EUR", "RMB") # alias -> CNY, same result
A tidy lookup table mapping country names to a world region and a finer
subregion, used by recode_region() / add_region() to turn a country
column into regions. Each country also carries its ISO 3166-1 codes, so a
result can be reported against the standard.
country_regioncountry_region
A tibble with 255 rows and 5 columns, one per entry in ISO 3166-1 that has a UN M49 grouping, plus Kosovo:
Country name.
ISO 3166-1 alpha-2 code (e.g. "DE"). NA for Kosovo, which
has no code of its own.
ISO 3166-1 alpha-3 code (e.g. "DEU").
World region (Africa, Asia, Europe, Middle East, North America, Oceania, South America).
Finer subregion (e.g. Western Europe, East Asia).
Note that Namibia's alpha-2 code is the two letters "NA", not a missing
value, which is worth knowing before filtering on the column.
Countries and codes are ISO 3166-1. The regions are the package's
own grouping, which follows the UN M49 sub-regions but treats the Middle
East as a region and splits the Americas north and south, because that is
how consumer research reports them. See data-raw/make_country_region.R.
recode_region(), add_region().
Other data:
currency_rates,
podracing_survey,
shopping_survey
head(country_region) recode_region("Germany")head(country_region) recode_region("Germany")
Builds a crosstab from two categorical columns: x forms the rows and y the
columns. The cell contents are chosen with cell – counts or row/column/total
percentages – or, when a numeric value column is supplied, an aggregate of
that column (e.g. the mean spend) for each x by y combination. Registered
orders (see register_order()) are applied to x and y automatically, and
a factor column keeps its own level order when no order is registered.
crosstab( data = NULL, x, y, cell = c("count", "row_pct", "col_pct", "total_pct"), value = NULL, fn = mean, wide = TRUE, digits = NULL, na_rm = TRUE, drop = NULL, weights = NULL )crosstab( data = NULL, x, y, cell = c("count", "row_pct", "col_pct", "total_pct"), value = NULL, fn = mean, wide = TRUE, digits = NULL, na_rm = TRUE, drop = NULL, weights = NULL )
data |
A data frame. |
x |
Row variable (unquoted). |
y |
Column variable (unquoted). |
cell |
What to put in each cell when |
value |
Optional numeric column (unquoted) to aggregate per cell instead of counting. |
fn |
Aggregation function used with |
wide |
If |
digits |
Decimal places for percentage / aggregated cells. Default |
na_rm |
Drop blanks / non-answers in |
drop |
Optional character vector of answer values to remove from both |
weights |
Survey weighting: |
Choose what the cells mean with cell: "row_pct" makes each row sum to
100 (the distribution of y within each x), "col_pct" makes each
column sum to 100, "total_pct" is the share of the whole table, and
"count" is the raw frequency. Supplying a numeric value switches the cells
to an aggregate of that column – by default the mean – which answers
questions like "what is the average NPS for each region by gender?". Blanks
and non-answers in x/y are dropped when na_rm = TRUE. Any registered
orders (register_order()) set the row/column ordering automatically; a
factor without one keeps its own level order; anything else stays in data
order.
A tibble: wide (x plus one column per y level) or
long (x, y, value). The long form refuses an x or y that is itself
called value, since the two columns would share a name.
calc_percentage(), compare_values(), register_order().
Other summaries:
calc_percentage(),
calc_percentage_batch(),
calc_percentage_multi(),
calc_summary(),
clear_checkpoints(),
crosstab_banner()
# counts crosstab(podracing_survey, demo_gender, region) # row percentages: gender split within each region (each row ~ 100) crosstab(podracing_survey, region, demo_gender, cell = "row_pct") # mean NPS for each region x gender cell crosstab(podracing_survey, region, demo_gender, value = nps_value, fn = mean)# counts crosstab(podracing_survey, demo_gender, region) # row percentages: gender split within each region (each row ~ 100) crosstab(podracing_survey, region, demo_gender, cell = "row_pct") # mean NPS for each region x gender cell crosstab(podracing_survey, region, demo_gender, value = nps_value, fn = mean)
Produces the market-research "banner" table: one master table with a stack of
question variables down the side (rows) and one or more grouping variables
across the top (cols), plus an Overall column for the whole sample.
Categorical questions become column-percentage blocks (each banner column sums
to ~100 within the block); numeric questions become a mean / median / sd /
quartile block. It generalises crosstab() from a single pair to a whole
table, and picks the cell content per question automatically.
crosstab_banner( data = NULL, rows, cols, cell = c("auto", "pct", "count", "mean", "diff"), stats = c("mean", "median", "sd", "p25", "p75"), total = TRUE, digits = NULL, na_rm = TRUE, drop = NULL, weights = NULL, max_levels = 20, long = FALSE, flextable = FALSE, checkpoint = NULL, confirm = NULL )crosstab_banner( data = NULL, rows, cols, cell = c("auto", "pct", "count", "mean", "diff"), stats = c("mean", "median", "sd", "p25", "p75"), total = TRUE, digits = NULL, na_rm = TRUE, drop = NULL, weights = NULL, max_levels = 20, long = FALSE, flextable = FALSE, checkpoint = NULL, confirm = NULL )
data |
A data frame. If omitted, the session default ( |
rows |
Stub variables, down the side (tidyselect, e.g.
|
cols |
Banner / grouping variables, across the top (tidyselect). Pass several to get several spanning column groups. If omitted, every eligible variable is used, giving a full variable-by-variable banner. |
cell |
What each body cell holds: |
stats |
For numeric questions, which statistics form the block. Any of
|
total |
Include the whole-sample Overall column. Default |
digits |
Decimal places. |
na_rm |
Drop blanks / non-answers in the questions and banner variables.
Default |
drop |
Answer values to remove before tabulating (see |
weights |
Survey weighting: |
max_levels |
When |
long |
If |
flextable |
If |
checkpoint |
Save each question as it finishes, so re-running the same
call picks up where an interrupted run stopped. |
confirm |
When |
A tibble has a single header row, so the two-row banner header (each grouping
variable's name spanning its items) cannot be stored in the data itself: the
wide return carries the grouping in the "banner_spanners" attribute and
flextable = TRUE renders the real spanning header. Column percentages are
computed within each banner column, so every column (Overall included) sums
to about 100 down each question block. Registered orders (register_order())
set the item and banner-column ordering automatically. Passing the same
selection to rows and cols gives the full every-question-by-every-question
matrix (a variable is never crossed with itself). Supplying only the data
frame does this automatically: every variable with at most max_levels
distinct answers becomes both a stub and a banner group, numeric scales are
added as stub statistics blocks, and identifier / free-text columns are
skipped (and named in a message). That selection decides the whole table, so
an interactive session prints it and waits for a yes before starting the run
(see confirm). Check-all-that-apply blocks (columns sharing
a prefix that each hold one option or blank, such as motivations_*) are
recognised as a single multi-select stub question and tabulated with
calc_percentage_multi(), whose base is the respondents who picked any option
(so those rows can sum past 100); those stub rows are unweighted. Across the
top, a binary-encoded column gives one column of the respondents who ticked
it, and the columns of a multi-select block share one spanner named after the
block. Those columns overlap, since a respondent can tick several. A lone
binary column is used across the top only, never as a stub.
A full every-variable banner is one crosstab() per question per grouping
variable, so a wide survey takes minutes rather than seconds. In an
interactive session it reports which question it is on, with an estimate of
the time left; ezrsurvey_options(progress = FALSE) turns that off, and
scripts are silent by default. checkpoint = TRUE additionally saves each
question as it completes, so re-running the identical call after an
interruption resumes instead of starting again. The managed file is named
after the run's own fingerprint, so a call finds its own interrupted run and
a call with different data or arguments never sees it, and it is deleted as
soon as the table is built. Pass a path instead if you would rather choose
the location; that file is yours, so the package neither deletes it nor
counts it as one of its own. clear_checkpoints() empties the managed
folder if a run was interrupted and never repeated.
By default a wide tibble: variable, item,
Overall, then one column per banner item. The banner grouping (which
column belongs to which top-row variable) is stored in the
"banner_spanners" attribute, which flextable = TRUE turns into a
spanning two-row header. With long = TRUE, the tidy long form.
crosstab() for a single pair, calc_percentage_batch() for stacked
one-variable percentages, export_summary_xlsx(), register_order().
Other summaries:
calc_percentage(),
calc_percentage_batch(),
calc_percentage_multi(),
calc_summary(),
clear_checkpoints(),
crosstab()
# gender and region banner over two questions (column percentages) crosstab_banner(podracing_survey, rows = c(satis_return, demo_edu), cols = c(demo_gender, region)) # numeric questions become a mean / median / sd / quartile block crosstab_banner(podracing_survey, rows = c(nps_value, demo_age), cols = demo_gender) # each cell as its difference from the Overall column crosstab_banner(podracing_survey, rows = satis_return, cols = region, cell = "diff") # full variable-by-variable banner: just pass the data frame crosstab_banner(podracing_survey)# gender and region banner over two questions (column percentages) crosstab_banner(podracing_survey, rows = c(satis_return, demo_edu), cols = c(demo_gender, region)) # numeric questions become a mean / median / sd / quartile block crosstab_banner(podracing_survey, rows = c(nps_value, demo_age), cols = demo_gender) # each cell as its difference from the Overall column crosstab_banner(podracing_survey, rows = satis_return, cols = region, cell = "diff") # full variable-by-variable banner: just pass the data frame crosstab_banner(podracing_survey)
A dated, approximate reference table of exchange rates used by
convert_currency() / add_currency() when no custom rates are supplied.
Rates are expressed as units of the currency per 1 US dollar (base USD).
These are a convenience snapshot, not live rates – pass your own rates
to the conversion helpers when accuracy matters.
currency_ratescurrency_rates
A tibble with one row per currency and columns:
ISO 4217 code (e.g. "EUR").
Currency name.
Units of the currency per 1 US dollar.
The snapshot date is stored in attr(currency_rates, "snapshot_date").
Approximate reference snapshot. See
data-raw/make_currency_rates.R.
convert_currency(), add_currency(), list_currencies().
Other data:
country_region,
podracing_survey,
shopping_survey
head(currency_rates) attr(currency_rates, "snapshot_date")head(currency_rates) attr(currency_rates, "snapshot_date")
The headline diagnostics wrapper: for each selected column it auto-detects whether it is a point estimate (numeric, e.g. a 1-5 rating) or a proportion (categorical), and returns a tidy table of sample size, estimate, standard error, relative standard error and margin of error – i.e. the whole appendix "Data info" block as one call.
diagnose( data = NULL, ..., type = c("auto", "mean", "prop"), by = NULL, z = 1.96, digits = 2 )diagnose( data = NULL, ..., type = c("auto", "mean", "prop"), by = NULL, z = 1.96, digits = 2 )
data |
A data frame. |
... |
Columns to diagnose, using tidyselect (e.g.
|
type |
Force the estimate type: |
by |
Optional grouping column(s); diagnostics are computed within group. |
z |
Margin-of-error multiplier passed to |
digits |
Decimal places for the numeric output columns. Defaults to |
For categorical columns the reported estimate is the largest answer
category's share, which gives a concrete +/- percentage-point margin to
quote in a report footnote.
Each column is classified (or forced via type) as a point estimate or a
proportion. For numeric columns the estimate is the mean and the error is in
rating points; for categorical columns the estimate is the largest answer
category's share, which gives a single concrete +/- percentage-point
margin to quote (the largest category is also the worst case among the
observed categories, so it is a sensible headline). The precision column is
the five-band rating from rse_rating(). This is the per-variable form of the
report-appendix "Data info" block; for the narrative bullet-point version see
precision_summary().
A tibble with one row per column (per group), with
columns variable, type, unit, n, estimate, se, rse, moe and
precision. For type == "mean", unit is "points"; for proportions it
is "ppt" (percentage points) and estimate is a percentage.
precision_summary(), rse_rating(), se_mean(), se_prop().
Other diagnostics:
margin_of_error(),
precision_summary(),
rse(),
rse_rating(),
se_mean(),
se_prop()
diagnose(podracing_survey, demo_gender, nps_value) diagnose(podracing_survey, starts_with("ratings_"))diagnose(podracing_survey, demo_gender, nps_value) diagnose(podracing_survey, starts_with("ratings_"))
Removes specific answer values – typically catch-all or uninformative options
such as "Other", "Don't know" or "Not applicable" – by turning them into
NA, so they fall out of counts, percentages and charts and the remaining
percentages re-base on the answers you keep. The companion to na_blank()
(which targets blanks and standard non-answers): use drop_items() for the
substantive categories you simply do not want to show.
drop_items(x, items, trim = TRUE)drop_items(x, items, trim = TRUE)
x |
A character (or factor) vector. |
items |
Character vector of exact answer values to drop. Matching is case-insensitive. |
trim |
Whether to trim surrounding whitespace before comparing. Defaults
to |
x is coerced to character (so factors are handled) and, by default,
whitespace-trimmed; any value equal (ignoring case) to one of items becomes
NA. The summary helpers (calc_percentage(), calc_percentage_multi(),
calc_percentage_batch()) and crosstab() take a drop = argument that
applies this for you before counting, so the kept answers re-base to ~100%;
set a session-wide default with ezrsurvey_options(drop_answers = ...).
A character vector the same length as x, with any value matching
items replaced by NA.
na_blank(), calc_percentage().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
drop_items(c("Yes", "No", "Other", "Don't know"), items = c("Other", "Don't know"))drop_items(c("Yes", "No", "Other", "Don't know"), items = c("Other", "Don't know"))
Opens your ezrsurvey YAML profile in the editor (like
usethis::edit_r_profile() for .Rprofile), creating it from a template
first if it does not exist. After saving, reload it with
load_ezrsurvey_profile() or by restarting the session.
edit_ezrsurvey_profile(path = NULL)edit_ezrsurvey_profile(path = NULL)
path |
Profile path. If |
Invisibly the path opened.
use_ezrsurvey_profile(), save_ezrsurvey_profile(),
load_ezrsurvey_profile().
Other config:
apply_order(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
tmp <- tempfile(fileext = ".yml") edit_ezrsurvey_profile(tmp)tmp <- tempfile(fileext = ".yml") edit_ezrsurvey_profile(tmp)
A defensive helper for the common survey-export headache where a column that
should be numeric arrives as text – "25 years", "8 - very likely",
"3.5/5". It returns x unchanged if it is already numeric; otherwise it
pulls the first number out of each value (via a digit pattern) and coerces to
numeric, leaving genuinely unparseable entries as NA. The ezrsurvey
functions that need numbers (e.g. calc_nps(), calc_summary()) call this
for you, so they "just work" on lightly messy data.
ensure_numeric(x, name = NULL, quiet = FALSE)ensure_numeric(x, name = NULL, quiet = FALSE)
x |
A vector. Numeric input is returned as-is. |
name |
Optional column/argument name, used only to make the message clearer. |
quiet |
If |
The first numeric token in each value is extracted with the pattern
-?[0-9]*\.?[0-9]+, which captures negatives and decimals but takes only the
first number it finds – so "8 - very likely" becomes 8, not -8. A
value that is genuinely numeric already is returned untouched (no copy, no
message). When quiet = FALSE a single informative message reports that
coercion happened and how many values could not be parsed; the internal
callers (calc_nps(), calc_summary(), ipm_model(), bin_numeric(), ...)
pass quiet = TRUE so they don't spam your console. Truly blank entries
(""/NA) are not counted as parse failures.
A numeric vector the same length as x.
recode_age(), recode_likert(), na_blank().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
ensure_numeric(c("25 years", "31", "forty"), quiet = TRUE) ensure_numeric(c("8 - very likely", "10", "3.5/5"), quiet = TRUE) ensure_numeric(c(1, 2, 3)) # already numeric, returned as-isensure_numeric(c("25 years", "31", "forty"), quiet = TRUE) ensure_numeric(c("8 - very likely", "10", "3.5/5"), quiet = TRUE) ensure_numeric(c(1, 2, 3)) # already numeric, returned as-is
Drops the package's complete worked example next to your data: a full
Quarto report over the bundled podracing_survey (every chart type, real
narrative) plus the matching slide-deck script on the officer path. Render
/ run them as they are, then swap in your own data.
example_report(dir, overwrite = FALSE)example_report(dir, overwrite = FALSE)
dir |
Destination folder, created if missing. |
overwrite |
Overwrite existing files of the same names. Defaults to
|
Two files are copied: podracing-report.qmd (render with
quarto render podracing-report.qmd) and podracing-deck.R
(run with Rscript podracing-deck.R;
writes a 16:9 deck to ezrsurvey-outputs/). Unlike the blank
scaffold_report()
skeletons, the example ships finished narrative over the bundled data, so
you can see every visual and slide element in a finished state before
adapting it.
Invisibly the paths written.
scaffold_report() for blank skeletons, report_deck().
Other reporting:
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
dir <- file.path(tempdir(), "ezrsurvey-example") example_report(dir) list.files(dir)dir <- file.path(tempdir(), "ezrsurvey-example") example_report(dir) list.files(dir)
Writes one Excel workbook with a worksheet per question, each holding that
question's summary table and its chart. Numeric questions get a
calc_summary() table and a histogram; categorical questions get a
calc_percentage() table and a plot_bars() chart; check-all-that-apply
blocks get a calc_percentage_multi() table (based on the respondents who
picked any option) and a bar chart. This is the "hand the client a tabbed
workbook" counterpart to the slide/Word builders (report_deck()). Requires
the suggested openxlsx2 package (the only xlsx writer here that can embed
images).
export_summary_xlsx( data = NULL, ..., path, by = NULL, chart = TRUE, sort = c("none", "desc", "asc"), digits = 0, weights = NULL, width = 6, height = 3.4, confirm = NULL )export_summary_xlsx( data = NULL, ..., path, by = NULL, chart = TRUE, sort = c("none", "desc", "asc"), digits = 0, weights = NULL, width = 6, height = 3.4, confirm = NULL )
data |
A data frame. If omitted, the session default ( |
... |
Variables to summarise, using tidyselect (e.g.
|
path |
Output |
by |
Optional grouping column(s) for the tables (passed to
|
chart |
Include the chart beneath each table. Default |
sort |
Level ordering for categorical tables, passed to
|
digits |
Decimal places for percentages. Default |
weights |
Survey weighting, passed to the table and chart helpers (see
|
width, height
|
Chart size in inches. Default |
confirm |
When no variables are named, show which questions were chosen
and which were skipped, and wait for a yes before writing. |
Worksheets are named from each question (cleaned to valid, unique Excel names).
The table is written from cell A1; the chart, when included, is rendered to a
temporary PNG and embedded a few rows below it. A numeric column (after
ensure_numeric()) is treated as a scale question (summary + histogram);
columns that share a prefix and each hold a single option or blank (e.g.
motivations_*) are treated as one check-all-that-apply question; anything
else is categorical (percentages + bar chart). With no ..., every question in
the data is written and identifier / free-text columns are skipped (and named
in a message). For a single table or a pre-named list of tables in one workbook
without charts, see export_xlsx() / save_data().
Invisibly the path written.
export_xlsx(), save_data(), report_deck(),
calc_percentage_batch().
Other save:
export_xlsx(),
save_data(),
save_output(),
save_plot()
# named questions tmp <- tempfile(fileext = ".xlsx") export_summary_xlsx(podracing_survey, demo_gender, satis_return, nps_value, path = tmp) # or every question at once (identifier / free-text columns are skipped) tmp2 <- tempfile(fileext = ".xlsx") export_summary_xlsx(podracing_survey, path = tmp2)# named questions tmp <- tempfile(fileext = ".xlsx") export_summary_xlsx(podracing_survey, demo_gender, satis_return, nps_value, path = tmp) # or every question at once (identifier / free-text columns are skipped) tmp2 <- tempfile(fileext = ".xlsx") export_summary_xlsx(podracing_survey, path = tmp2)
A quick way to drop a handful of summaries into a single .xlsx, one per
worksheet – e.g. export_xlsx(calc_percentage(d, a), calc_percentage(d, b), path = "out.xlsx"). Tabs are named from the argument names you give, else
from each table's question/first column, else Sheet1, Sheet2, ...
export_xlsx(..., path, sheet_names = NULL)export_xlsx(..., path, sheet_names = NULL)
... |
Data frames to write, one per tab. Name them to set tab names
(e.g. |
path |
Output |
sheet_names |
Optional character vector of tab names (overrides argument names). |
Each argument becomes one worksheet. Tab names are taken from the argument
names you give (gender = ...), otherwise from each table's variable column
or first column, otherwise Sheet1, Sheet2, ...; they are then cleaned to
valid, unique Excel names (<= 31 characters, with [ ] : * ? / \\ stripped).
This is the quick way to drop a set of summaries into one workbook for a
colleague. Requires the suggested writexl package.
[ ]: R:%20
Invisibly path.
save_data() for a single table or a pre-named list.
Other save:
export_summary_xlsx(),
save_data(),
save_output(),
save_plot()
tmp <- tempfile(fileext = ".xlsx") export_xlsx( gender = calc_percentage(podracing_survey, demo_gender), calc_percentage(podracing_survey, demo_job), path = tmp ) file.exists(tmp)tmp <- tempfile(fileext = ".xlsx") export_xlsx( gender = calc_percentage(podracing_survey, demo_gender), calc_percentage(podracing_survey, demo_job), path = tmp ) file.exists(tmp)
ezrsurvey reads a handful of defaults from R options (prefixed ezrsurvey.),
so you can tune behaviour once instead of repeating arguments. Call with no
arguments to see the current effective values; call with name = value pairs
to set them for the session. Persist them across sessions with a YAML profile
(see use_ezrsurvey_profile()).
ezrsurvey_options(...)ezrsurvey_options(...)
... |
Either nothing (to read all values) or named |
Recognised options:
pct_axis_unitRounding step for percentage y-axes (default 25).
pct_axis_maxFixed percentage y-axis maximum, e.g. 100; NULL
(default) means dynamic via nice_max().
bar_cols_max_items, bar_cols_max_label
Thresholds that switch
plot_bars() (orientation = "auto") from vertical columns to horizontal
bars: more than bar_cols_max_items bars (6) or any label longer than
bar_cols_max_label characters (12).
bar_wrap_cols, bar_wrap_bars
stringr::str_wrap() widths used by
plot_bars() for column (12) and bar (28) labels.
bar_label_size, bar_size_step, bar_size_min
Data-label text
sizing in plot_bars(): base size (3.5), reduction per item past the
item threshold (0.15) and the floor (2.4).
bar_width, bar_ref_items
Bar thickness. Bars are drawn at
bar_width (0.66) of a category slot when a chart has bar_ref_items
(6) bars, and the fraction scales with the bar count so the drawn
thickness stays the same on every chart. Raise bar_ref_items for
thinner bars throughout, lower it for chunkier ones.
age_breaks, age_labels
Default bands used by recode_age().
na_answersStrings treated as non-answers by na_blank().
drop_answersAnswer values dropped by the drop = argument of
calc_percentage() and friends when drop is not given; NULL (default)
means drop nothing. See drop_items().
generation_schemeDefault scheme for recode_generation().
current_yearReference year for age-to-cohort conversion; NULL
uses the system year.
default_byDefault grouping column name(s) applied by
calc_percentage() / calc_percentage_multi() when by is omitted;
NULL (default) means no default grouping.
brand_colors, brand_color_primary
Organisation brand colours:
a vector of accent hexes and the primary accent used as the default
single-series fill. Usually set by use_brand(), but can be set by hand
or in a profile. NULL (default) keeps the neutral ezrsurvey look.
brand_font_major, brand_font_minor
Brand heading and body
typefaces. brand_font_minor becomes the default base_family of
theme_ezrsurvey() when the font is installed on this machine.
brand_fonts_enabledSet FALSE to keep brand colours but ignore
brand fonts (default TRUE).
brand_template_pptx, brand_template_docx
Paths to the brand
PowerPoint / Word template used as the default reference document by
report_new(), report_deck() and scaffold_report(). These are
machine-specific absolute paths – if you persist them, prefer the
project-level .ezrsurvey.yml profile over the user-level one.
progressWhether the slow helpers report which item they are on.
"auto" (default) reports in an interactive session and stays silent in
scripts, vignettes and R CMD check; TRUE or FALSE force it either
way. See crosstab_banner().
confirmWhether a call that picks its own variables shows the
selection and waits for a yes before a long run. "auto" (default) asks
in an interactive session and never asks in a script; TRUE or FALSE
force it either way. See crosstab_banner().
output_dirFolder that a bare output file name is written into,
created on demand. The default "." writes it where it is named: in the
working directory. Set a folder, say "outputs", and
save_plot(p, "chart.png") writes outputs/chart.png, so a script's
results collect in one place. A path that names a directory
("./chart.png", "charts/chart.png", anything absolute) is always
written exactly where it says.
Options are ordinary R options under the ezrsurvey. prefix, so anything you
can do with options() works here too, and a setting lasts for the session.
Reading (ezrsurvey_options() with no arguments) returns every recognised
option's current effective value; setting returns the previous values
invisibly, so you can restore them. To make settings permanent, write them to
a YAML profile – edit it with edit_ezrsurvey_profile() or dump the current
session with save_ezrsurvey_profile() – which is loaded automatically at
package start-up.
When reading, a named list of all current values. When setting, the previous values, invisibly.
use_ezrsurvey_profile(), load_ezrsurvey_profile(),
reset_ezrsurvey_options().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
ezrsurvey_options() # view all defaults ezrsurvey_options(pct_axis_max = 100) ezrsurvey_options()$pct_axis_max # 100 reset_ezrsurvey_options() # back to defaultsezrsurvey_options() # view all defaults ezrsurvey_options(pct_axis_max = 100) ezrsurvey_options()$pct_axis_max # 100 reset_ezrsurvey_options() # back to defaults
At load time ezrsurvey reads a YAML profile from each of these paths in turn (later paths override earlier ones), if present: the per-user config directory, your home directory, then the current project.
ezrsurvey_profile_paths()ezrsurvey_profile_paths()
A character vector of candidate profile paths.
use_ezrsurvey_profile(), load_ezrsurvey_profile().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
ezrsurvey_profile_paths()ezrsurvey_profile_paths()
Returns the birth-year boundaries for a named generational scheme, for use by
recode_generation(). The default "pew" scheme follows the widely cited
Pew Research Center cohort definitions.
generation_scheme(scheme = c("pew"))generation_scheme(scheme = c("pew"))
scheme |
Scheme name. Currently |
The "pew" scheme uses the Pew Research Center boundaries: Silent (1928–45),
Baby Boomer (1946–64), Gen X (1965–80), Millennial (1981–96), Gen Z
(1997–2012) and Gen Alpha (2013+). To use different cut-offs, build your own
tibble with from and label columns and pass it to recode_generation()'s
scheme argument.
A tibble with label, from (first birth year) and
to (last birth year; Inf for the open-ended youngest cohort).
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
generation_scheme("pew")generation_scheme("pew")
Companions to use_dataset() for inspecting and resetting the session's
default dataset.
get_dataset() has_dataset() clear_dataset() dataset_vars()get_dataset() has_dataset() clear_dataset() dataset_vars()
has_dataset() is the safe way to check before calling get_dataset(), which
errors when nothing is set. clear_dataset() removes the default so that
subsequent calls require an explicit data again – useful at the end of a
script or between analyses of different datasets.
get_dataset() returns the default data frame (error if none is set);
has_dataset() returns a logical; clear_dataset() returns TRUE
invisibly; dataset_vars() returns a character vector of column names.
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
has_dataset() use_dataset(podracing_survey) has_dataset() nrow(get_dataset()) clear_dataset()has_dataset() use_dataset(podracing_survey) has_dataset() nrow(get_dataset()) clear_dataset()
Get the levels of a registered order
get_order(name)get_order(name)
name |
Order name. |
The character vector of levels.
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order("yesno", c("No", "Yes")) get_order("yesno") remove_order("yesno")register_order("yesno", c("No", "Yes")) get_order("yesno") remove_order("yesno")
Companions to set_weights(): read the stored scheme, test whether one is set,
or remove it.
get_weights() has_weights() clear_weights()get_weights() has_weights() clear_weights()
has_weights() is the safe check before get_weights(), which errors when no
scheme is set. clear_weights() turns weighting off again, so the helpers
return purely unweighted results.
get_weights() returns the scheme (a named list of target vectors;
error if none is set); has_weights() returns a logical; clear_weights()
returns TRUE invisibly.
set_weights(), weight_vector().
Other weighting:
clear_weights_cache(),
set_weights(),
weight_vector()
has_weights() set_weights(c(variable = "region", Europe = 0.5, "North America" = 0.5)) get_weights() clear_weights()has_weights() set_weights(c(variable = "region", Europe = 0.5, "North America" = 0.5)) get_weights() clear_weights()
Combines driver importance (against outcome) with
feature performance (mean rating) into the tidy table consumed by
plot_ipm(). Worded rating columns are recoded to 1-5 automatically.
ipm_model( data = NULL, outcome, rating_prefix, method = c("rwa", "forest", "correlation"), recode = TRUE, likert_levels = c("Very bad", "Bad", "Ok", "Good", "Very good") )ipm_model( data = NULL, outcome, rating_prefix, method = c("rwa", "forest", "correlation"), recode = TRUE, likert_levels = c("Very bad", "Bad", "Ok", "Good", "Very good") )
data |
A data frame. |
outcome |
The outcome column (unquoted), e.g. |
rating_prefix |
Column-name prefix identifying the rating block, e.g.
|
method |
How to measure importance, passed to |
recode |
If |
likert_levels |
Scale wordings passed to |
An importance/performance model answers two questions per feature at once:
how much does it drive the outcome (importance, via calc_importance()) and
how well are we doing on it (performance, the mean 1–5 rating). Plotting one
against the other (plot_ipm()) reveals priorities: high-importance,
low-performance features are where to invest. Worded rating columns are mapped
to 1–5 with recode_likert() automatically (recode = TRUE); the prefix is
stripped from feature names; and perf_class buckets performance into a 1–5
factor by its integer part (a 3.57 mean is band 3, not 4) for colouring.
Importance comes from calc_importance(), so method chooses between
relative weights (the default, requiring the suggested rwa package), a
random forest and a plain correlation.
A tibble with feature, importance,
performance and perf_class (the performance band as a 1-5 factor, from
its integer part so a 3.57 mean sits in band 3, used for colouring).
calc_importance(), plot_ipm(), compare_values().
Other modelling:
calc_importance(),
calc_nps()
ipm_model(podracing_survey, nps_value, "ratings_")ipm_model(podracing_survey, nps_value, "ratings_")
Returns a function that turns numbers into percent strings, e.g. 42
becomes "42%". The input is assumed to already be on a 0-100 scale (as
produced by calc_percentage()), not a 0-1 proportion.
label_pct(digits = 0, suffix = "%")label_pct(digits = 0, suffix = "%")
digits |
Number of decimal places to keep. Defaults to |
suffix |
String appended after the number. Defaults to |
label_pct() returns a function (a closure), not formatted strings – this
is the form ggplot2 scales expect for their labels argument. The number is
rounded to digits decimal places with formatC() and suffix appended.
Because the input is assumed to be on a 0–100 scale, label_pct()(42) is
"42%", not "4200%".
A function suitable for the labels argument of a ggplot2 scale or
for use with geom_text().
Other scales:
nice_max(),
scale_y_pct()
f <- label_pct() f(c(0, 33.4, 100)) label_pct(1)(33.45)f <- label_pct() f(c(0, 33.4, 100)) label_pct(1)(33.45)
List the currencies in a rate table
list_currencies(rates = NULL)list_currencies(rates = NULL)
rates |
Rate table; |
A quick way to see which codes convert_currency() will accept from the
bundled snapshot (or from a rates table you pass). Aliases such as "RMB"
are not listed – they resolve to their ISO code (here "CNY").
A character vector of currency codes.
currency_rates, convert_currency().
Other currency:
add_currency(),
convert_currency()
list_currencies()list_currencies()
List registered orders
list_orders()list_orders()
A tibble with name, n_levels, vars and
prefixes (the last two comma-joined).
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order("yesno", c("No", "Yes"), vars = "agree") list_orders() remove_order("yesno")register_order("yesno", c("No", "Yes"), vars = "agree") list_orders() remove_order("yesno")
Available Quarto report templates
list_report_templates()list_report_templates()
A character vector of the output formats ezrsurvey ships a Quarto skeleton for.
Other reporting:
example_report(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
list_report_templates()list_report_templates()
Reads a YAML file of option: value pairs and applies them with
ezrsurvey_options(). Called automatically when the package loads (over the
standard ezrsurvey_profile_paths()); call it manually to load a specific
file. Requires the suggested yaml package; without it, the function is a
no-op.
load_ezrsurvey_profile(path = NULL, quiet = TRUE)load_ezrsurvey_profile(path = NULL, quiet = TRUE)
path |
Profile path(s). If |
quiet |
If |
Invisibly TRUE if any profile was applied, else FALSE.
use_ezrsurvey_profile() to create a template.
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
tmp <- tempfile(fileext = ".yml") writeLines("pct_axis_max: 100", tmp) load_ezrsurvey_profile(tmp, quiet = FALSE) ezrsurvey_options()$pct_axis_max reset_ezrsurvey_options()tmp <- tempfile(fileext = ".yml") writeLines("pct_axis_max: 100", tmp) load_ezrsurvey_profile(tmp, quiet = FALSE) ezrsurvey_options()$pct_axis_max reset_ezrsurvey_options()
z * se, the half-width of a confidence interval. The default z = 1.96
gives an approximate 95% interval; the survey reports used the 2 * se
rule of thumb, which is z = 2.
margin_of_error(se, z = 1.96)margin_of_error(se, z = 1.96)
se |
A standard error (scalar or vector). |
z |
The critical value / multiplier. Defaults to |
A confidence interval is the estimate plus or minus this margin. z = 1.96
gives the standard 95% interval; z = 2 reproduces the "two standard errors"
rule of thumb often quoted in survey reports; z = 2.58 gives 99%. The result
is on the same scale as se, so for a proportion error in percentage points,
pass se_prop(p, n, pctp = TRUE).
The +/- margin of error, on the same scale as se.
se_mean(), se_prop(), diagnose().
Other diagnostics:
diagnose(),
precision_summary(),
rse(),
rse_rating(),
se_mean(),
se_prop()
margin_of_error(se_prop(0.33, 1184, pctp = TRUE)) # in percentage points margin_of_error(0.02, z = 2)margin_of_error(se_prop(0.33, 1184, pctp = TRUE)) # in percentage points margin_of_error(0.02, z = 2)
Adds a reference line (and optional label) at a single value – the "you are here" marker used on the NPS and quality gauges.
mark_value( plot, value, axis = c("x", "y"), colour = "black", linewidth = 1, label = NULL, ... )mark_value( plot, value, axis = c("x", "y"), colour = "black", linewidth = 1, label = NULL, ... )
plot |
A ggplot object. |
value |
Numeric position of the marker. |
axis |
Axis the value is on: |
colour, linewidth
|
Line appearance. |
label |
Optional text label drawn at the marker. |
... |
Passed to |
The plot with the marker added.
Other decisions:
annotate_bands(),
bands_rating_3(),
rescale_bands()
# a target line across the percentage axis of a bar chart calc_percentage(podracing_survey, demo_gender) %>% plot_bars() %>% mark_value(20, axis = "y", label = "Target 20%")# a target line across the percentage axis of a bar chart calc_percentage(podracing_survey, demo_gender) %>% plot_bars() %>% mark_value(20, axis = "y", label = "Target 20%")
NA
Survey exports are littered with empty strings and standard "non-answer"
options such as "Prefer not to answer". This helper turns those into real
NA values so they drop out of counts and summaries, replacing the
repetitive filter(x != "", x != "Prefer not to answer") idiom.
na_blank(x, also = ezrsurvey_default("na_answers"), trim = TRUE)na_blank(x, also = ezrsurvey_default("na_answers"), trim = TRUE)
x |
A character (or factor) vector. |
also |
Additional exact strings to treat as missing. Defaults to the
|
trim |
Whether to trim surrounding whitespace before comparing.
Defaults to |
The input is coerced to character (so factors are handled), optionally
whitespace-trimmed, and any value equal to "" or to one of also becomes
NA. The default also reads the na_answers option, so you can change what
counts as a non-answer package-wide with
ezrsurvey_options(na_answers = ...). Most of the calc_* helpers call
na_blank() internally when na_rm = TRUE, so you usually get this clean-up
for free; call it directly when you want to blank a column in a pipeline.
A character vector the same length as x, with blanks and
non-answers replaced by NA.
ensure_numeric(), ezrsurvey_options().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
na_blank(c("Yes", "", "Prefer not to answer", "No")) # add your own non-answers na_blank(c("a", "n/a", "N/A"), also = c("n/a", "N/A"))na_blank(c("Yes", "", "Prefer not to answer", "No")) # add your own non-answers na_blank(c("a", "n/a", "N/A"), also = c("n/a", "N/A"))
Picks the smallest multiple of unit that is greater than or equal to
max(x). This reproduces the dynamic y-axis trick used throughout the
original survey reports, where bar charts are given a tidy ceiling in units
of 25 (percentages) or 5 (NPS counts) so that data labels never collide with
the top of the panel.
nice_max(x, unit = 25, pad = 0)nice_max(x, unit = 25, pad = 0)
x |
A numeric vector. |
unit |
Size of the rounding step (e.g. |
pad |
Extra headroom added to the result, in the same units as |
The ceiling is (floor(max(x) / unit) + 1) * unit + pad: the next
multiple of unit strictly above max(x), always leaving headroom even
when the max already sits exactly on a multiple (e.g. nice_max(75) is
100, not 75). Use pad for additional headroom on top of that, e.g.
for annotations above the tallest bar. An all-NA logical column (a common
shape for an empty survey field) is tolerated and returns NA. This is the
engine behind scale_y_pct() and the percentage plot wrappers, which is
why their bars never quite touch the top of the panel.
A single numeric value: the padded, rounded-up ceiling. Returns NA
if x is empty or all NA.
Other scales:
label_pct(),
scale_y_pct()
nice_max(c(12, 63, 40)) nice_max(80, unit = 25) # already need >75, so jumps to 100 nice_max(75, unit = 25) # exact multiple still advances, for headroom nice_max(c(8, 17), unit = 5, pad = 10) # next multiple of 5 (20) + 10nice_max(c(12, 63, 40)) nice_max(80, unit = 25) # already need >75, so jumps to 100 nice_max(75, unit = 25) # exact multiple still advances, for headroom nice_max(c(8, 17), unit = 5, pad = 10) # next multiple of 5 (20) + 10
Collapses an 0-10 "how likely to recommend" question into the three standard NPS groups: detractors (0-6), passives (7-8) and promoters (9-10).
nps_group(x, labels = FALSE)nps_group(x, labels = FALSE)
x |
A numeric vector of 0-10 ratings (character input is coerced). |
labels |
If |
The signed -1/0/1 coding is deliberate: the mean of it, times 100, is
exactly the Net Promoter Score (% promoters minus % detractors), which is how
calc_nps() computes the headline number. Inputs are run through
ensure_numeric() first, so worded answers like "9 - very likely" are
classified correctly. Values outside 0–10 (and NA) return NA.
Either an integer vector (-1/0/1) or a character vector, the same
length as x. Out-of-range or missing values become NA.
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
nps_group(c(0, 6, 7, 8, 9, 10)) nps_group(c(3, 8, 10), labels = TRUE)nps_group(c(0, 6, 7, 8, 9, 10)) nps_group(c(3, 8, 10), labels = TRUE)
Looks up the registered order for a column: an exact vars match wins,
otherwise the first matching prefixes entry. Used internally by
calc_percentage() to apply orders automatically; exposed so you can check
what would be applied.
order_for(var)order_for(var)
var |
A column name. |
The matching level vector, or NULL if none is registered.
register_order(), apply_order().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order("edu", c("low", "mid", "high"), prefixes = "edu_") order_for("edu_level") remove_order("edu")register_order("edu", c("low", "mid", "high"), prefixes = "edu_") order_for("edu_level") remove_order("edu")
Returns the organisation's accent colours set by use_brand() (or the
brand_colors option), ready for manual scales or direct use. Without a
brand, it falls back to pal_sequential_blue so it always returns something
usable.
pal_brand(n = NULL)pal_brand(n = NULL)
n |
Number of colours wanted. |
Only the categorical brand accents live here. The semantic palettes (pal_rating, pal_nps) keep their red-amber-green vocabulary regardless of brand – recolouring "bad to good" with corporate accents would destroy the meaning the colours carry.
A character vector of hex colours.
use_brand(), scale_fill_brand().
Other brand:
brand_info(),
clear_brand(),
scale_fill_brand(),
use_brand()
pal_brand() # sequential blue until a brand is set pal_brand(2)pal_brand() # sequential blue until a brand is set pal_brand(2)
A small set of named, semantic palettes for survey reporting. Each is a
plain character vector of hex colours (some named) that you
can use directly or via the scale_*_rating() / scale_fill_nps() helpers.
pal_rating pal_nps pal_sequential_blue pal_neutralpal_rating pal_nps pal_sequential_blue pal_neutral
Each palette is a character vector of hex colour strings:
pal_ratingFive-point red -> amber -> green scale for 1-5 ratings,
named "1".."5".
pal_npsThree NPS-group colours, named "-1", "0", "1"
(detractor / passive / promoter).
pal_sequential_blueFour-step sequential blue, light to dark, for ordered brand questions.
pal_neutralA single neutral grey used for unbranded single-series bars.
These are the semantic colour vocabularies behind the plot helpers: red ->
amber -> green for ratings (worse to better), a matching three-colour NPS
scale, a sequential blue for ordered brand questions, and a neutral grey for
plain single-series bars. Use them directly, or via the
scale_fill_rating() / scale_fill_nps() ggplot2 scales.
pal_rating follows the same thresholds as bands_rating_3(): ratings of 1
and 2 are BAD (red), 3 is OK (amber), 4 and 5 are GOOD (green). Keeping the
two in step matters – a point coloured amber sitting inside a red band is
the kind of contradiction that quietly misleads a reader.
Each palette is a character vector of hex colour strings, named by
the value it colours where that matters (pal_rating by rating "1" to
"5", pal_nps by NPS group "-1", "0", "1"), ready to pass as
values to a ggplot2 manual scale.
Other themes:
scale_fill_nps(),
scale_fill_rating(),
theme_ezrsurvey(),
theme_transparent()
pal_rating scales::show_col(pal_rating)pal_rating scales::show_col(pal_rating)
Survey exports often encode metadata in the filename (e.g.
"podracing_wave1_NA_2026.csv"). This splits a filename column on a
separator into named metadata columns, dropping the file extension first.
Replaces the brittle str_split(file, "_")[[1]][n] indexing this saves you.
parse_filename( data = NULL, col = "file", into, sep = "_", drop_ext = TRUE, remove = FALSE )parse_filename( data = NULL, col = "file", into, sep = "_", drop_ext = TRUE, remove = FALSE )
data |
A data frame. |
col |
Name of the column holding the filename (string or unquoted).
Defaults to |
into |
Character vector of new column names, one per field you expect after splitting. |
sep |
Separator to split on (a fixed string, not a regex). Defaults to
|
drop_ext |
If |
remove |
If |
Pairs naturally with read_folder(), whose file column carries the source
filename: split it once and you have event, wave, locale, year, etc. as real
columns to group by. The file extension is stripped first (drop_ext), the
split is on a fixed string (not a regex), and rows with fewer fields than
into are padded with NA so a stray filename never derails the parse.
The data frame with the new metadata columns added.
Other import:
read_folder(),
select_prefix(),
select_suffix()
df <- tibble::tibble(file = c("podracing_wave1_NA_2026.csv")) parse_filename(df, into = c("survey", "wave", "locale", "year"))df <- tibble::tibble(file = c("podracing_wave1_NA_2026.csv")) parse_filename(df, into = c("survey", "wave", "locale", "year"))
Plots the output of calc_percentage() (or calc_percentage_multi()) as a
labelled bar chart, with a tidy auto-scaled axis (nice_max()) and the
ezrsurvey theme. By default the layout is chosen for you: vertical columns
for a few short labels, horizontal bars for many or long ones, with long
labels wrapped, the text size stepped down as bars multiply, and the bars
ordered so the longest sits at the top (bars) or on the left (cols).
plot_bars( data, label = NULL, value = pct, orientation = c("auto", "cols", "bars"), sort = c("auto", "none", "asc", "desc"), wrap = NULL, flip = NULL, avg_line = FALSE, axis_labels = FALSE, unit = ezrsurvey_default("pct_axis_unit"), max = ezrsurvey_default("pct_axis_max"), fill = NULL, title = NULL, label_size = NULL )plot_bars( data, label = NULL, value = pct, orientation = c("auto", "cols", "bars"), sort = c("auto", "none", "asc", "desc"), wrap = NULL, flip = NULL, avg_line = FALSE, axis_labels = FALSE, unit = ezrsurvey_default("pct_axis_unit"), max = ezrsurvey_default("pct_axis_max"), fill = NULL, title = NULL, label_size = NULL )
data |
A data frame with a category column and a value column. |
label |
Category column (unquoted). If |
value |
Value column (unquoted). Defaults to |
orientation |
|
sort |
|
wrap |
Label wrap width in characters ( |
flip |
Deprecated back-compat shortcut: |
avg_line |
If |
axis_labels |
If |
unit |
Axis rounding step passed to |
max |
Optional fixed y-axis maximum (e.g. |
fill |
Bar fill colour. |
title |
Optional plot title. |
label_size |
Data-label text size. |
Expects a summary table (from calc_percentage() and friends), not raw survey
rows. The label column is auto-detected as the first non-n, non-value column,
so a piped percentage table just works. The layout decisions all read from the
bar_* options (ezrsurvey_options()): orientation = "auto" switches to
horizontal bars once there are many or long labels, labels are wrapped to
bar_wrap_cols / bar_wrap_bars, the data-label size steps down with the
bar count, and sort = "auto" puts the longest bar at the top (bars) or on the
left (cols) – unless the label column is an ordered factor (a registered or
explicit order), which is preserved. The axis ceiling is chosen by nice_max()
/ the pct_axis_* options so data labels never collide with the panel top, and
the % axis is hidden unless axis_labels = TRUE. Add decision guidance with
annotate_bands().
Bars are drawn to a constant thickness regardless of how many there are
(bar_width / bar_ref_items, see ezrsurvey_options()), so a three-answer
chart and a ten-answer chart look like they belong in the same deck instead
of the first one's bars turning into slabs.
A ggplot object.
calc_percentage(), scale_y_pct(), annotate_bands().
Other plots:
plot_gauges(),
plot_ipm(),
plot_nps(),
plot_nps_gauge(),
plot_quotes_tree(),
plot_rating_grid(),
plot_stacked_rating()
# auto layout: few short labels -> vertical columns, largest on the left calc_percentage(podracing_survey, demo_gender) %>% plot_bars() # many long labels -> horizontal bars, wrapped, largest on top calc_percentage(podracing_survey, fav_driver) %>% plot_bars() # an ordinal scale keeps its order; force horizontal with orientation calc_percentage(podracing_survey, demo_edu) %>% plot_bars(orientation = "bars")# auto layout: few short labels -> vertical columns, largest on the left calc_percentage(podracing_survey, demo_gender) %>% plot_bars() # many long labels -> horizontal bars, wrapped, largest on top calc_percentage(podracing_survey, fav_driver) %>% plot_bars() # an ordinal scale keeps its order; force horizontal with orientation calc_percentage(podracing_survey, demo_edu) %>% plot_bars(orientation = "bars")
Plots a column of differences (e.g. from compare_values()) as a sorted,
diverging horizontal bar chart: positives in green, negatives in amber, signed
value labels, and a zero reference line. This is the standard "what improved,
what slipped" change chart.
plot_diff( data, label = feature, difference = difference, limits = NULL, digits = 2, pos_colour = "#A7C23D", neg_colour = "#FFCB3E", title = NULL )plot_diff( data, label = feature, difference = difference, limits = NULL, digits = 2, pos_colour = "#A7C23D", neg_colour = "#FFCB3E", title = NULL )
data |
A data frame with a label column and a difference column. |
label |
Category/label column (unquoted). Default |
difference |
Difference column (unquoted). Default |
limits |
Numeric length-2 y-axis limits. If |
digits |
Decimal places for the value labels. Default |
pos_colour, neg_colour
|
Bar colours for positive / negative differences. |
title |
Optional plot title. |
Bars are sorted by the difference and drawn horizontally (largest gain at the
top), coloured green for positive and amber for negative changes, with signed
value labels (+0.20 / -0.30) nudged to sit just outside each bar and a
zero reference line. When limits is NULL the y-axis is made symmetric
around zero from the data so gains and losses are visually comparable; pass
limits = c(-1, 1) (say) to fix it. Feed it the output of compare_values(),
or any data frame with a label column and a numeric difference column.
A ggplot object.
Other compare:
compare_values()
a <- data.frame(feature = c("price", "quality", "service"), performance = c(3.1, 4.2, 3.5)) b <- data.frame(feature = c("price", "quality", "service"), performance = c(2.8, 4.4, 3.9)) compare_values(b, a) %>% plot_diff()a <- data.frame(feature = c("price", "quality", "service"), performance = c(3.1, 4.2, 3.5)) b <- data.frame(feature = c("price", "quality", "service"), performance = c(2.8, 4.4, 3.9)) compare_values(b, a) %>% plot_diff()
Draws two or more scores as thin banded gauge bars stacked one above another, each on its own scale with a "you are here" marker. The headline summary slide: the Net Promoter Score on top and the average feature quality rating below it, so both key numbers sit together instead of one fat bar filling the slide.
plot_gauges( scores, scales = NULL, title = NULL, height = 0.5, label_size = NULL )plot_gauges( scores, scales = NULL, title = NULL, height = 0.5, label_size = NULL )
scores |
A named numeric vector; each name labels a gauge and each value is placed on its scale. The first is drawn at the top. |
scales |
Which band scale each gauge uses: |
title |
Optional title. |
height |
Bar thickness in plot units (row spacing is 1). Defaults to
|
label_size |
Band-label text size. |
Each gauge is normalised to its own scale, so an NPS of +23 and a quality
rating of 3.4 line up on a shared 0..1 panel with the band boundaries drawn
where each scale puts them (the NPS gauge reuses the plot_nps_gauge() bands;
the rating gauge uses bands_rating_3()). The score for each gauge is printed
beside its label, and a black marker shows where it lands. Because the bars
are thin and stacked, this is the summary-slide companion to the detailed
plot_nps() distribution and the plot_ipm() driver matrix.
Since the bars share one panel but not one scale, no axis can serve them
both. Each bar therefore carries its own band boundaries as small numbers
underneath, so an NPS bar reads -100 0 30 70 100 and a rating bar reads
1 3 4 5; without them a marker two thirds along says nothing about the
score it marks. A band too narrow to hold its name is left unlabelled rather
than having the text run over its neighbours, and those numbers are what
still place it.
A ggplot object.
plot_nps_gauge(), calc_nps(), ipm_model().
Other plots:
plot_bars(),
plot_ipm(),
plot_nps(),
plot_nps_gauge(),
plot_quotes_tree(),
plot_rating_grid(),
plot_stacked_rating()
nps <- calc_nps(podracing_survey, nps_value)$nps quality <- 3.4 plot_gauges(c("Net Promoter Score" = nps, "Average quality rating" = quality))nps <- calc_nps(podracing_survey, nps_value)$nps quality <- 3.4 plot_gauges(c("Net Promoter Score" = nps, "Average quality rating" = quality))
Plots an ipm_model() table as a scatter of feature performance (x) vs.
importance (y), coloured by performance band, with decision bands across the
top: the importance / performance slide of a survey deck, in one call.
plot_ipm(model, title = NULL, repel = TRUE, bands = bands_rating_3())plot_ipm(model, title = NULL, repel = TRUE, bands = bands_rating_3())
model |
An |
title |
Optional title; defaults to the average performance. |
repel |
Use |
bands |
Decision-band spec drawn across the top and used to colour the
points. Defaults to |
Each feature is a point: performance on the x-axis (mean 1–5 rating, with a
reference line at the average) and importance on the y-axis (relative weight,
as a percentage). Each point takes the colour of the decision band it falls
in, so a feature averaging 2.4 is red because it sits in the BAD band – the
point and the band behind it can never disagree. Read it by quadrant –
high-importance, low-performance features (upper left) are the priorities to
fix, while high-importance, high-performance features (upper right) are
strengths to protect. Feed it an ipm_model() table; uses ggrepel for
non-overlapping labels when available.
A ggplot object.
ipm_model(), compare_values().
Other plots:
plot_bars(),
plot_gauges(),
plot_nps(),
plot_nps_gauge(),
plot_quotes_tree(),
plot_rating_grid(),
plot_stacked_rating()
ipm_model(podracing_survey, nps_value, "ratings_") %>% plot_ipm()ipm_model(podracing_survey, nps_value, "ratings_") %>% plot_ipm()
The canonical Net Promoter Score chart: the full 0-10 recommendation distribution as labelled bars coloured by NPS group, with the detractor / passive / promoter shares called out in coloured boxes across the top and the overall NPS in the title: the NPS slide of a survey deck, in one call.
plot_nps(data = NULL, value, title = NULL)plot_nps(data = NULL, value, title = NULL)
data |
A data frame. If omitted, the session default ( |
value |
The 0-10 recommendation column (unquoted). Text such as
|
title |
Optional title; defaults to |
Unlike plot_nps_gauge() (which takes a single score), this reads the raw
0–10 column and draws the full distribution: one labelled bar per score,
coloured red/amber/green by NPS group, with the detractor/passive/promoter
shares called out across the top and the overall score in the title. It is the
detailed companion slide to the gauge.
A ggplot object.
Other plots:
plot_bars(),
plot_gauges(),
plot_ipm(),
plot_nps_gauge(),
plot_quotes_tree(),
plot_rating_grid(),
plot_stacked_rating()
plot_nps(podracing_survey, nps_value)plot_nps(podracing_survey, nps_value)
A horizontal gauge that places a single score (NPS or mean rating) onto a banded scale, with a "you are here" marker: the NPS and quality gauges a summary slide carries.
plot_nps_gauge( score, scale = c("nps", "rating"), title = NULL, height = 0.5, label_size = NULL )plot_nps_gauge( score, scale = c("nps", "rating"), title = NULL, height = 0.5, label_size = NULL )
score |
The score to mark (NPS on -100..100, or a mean rating on 1..5). |
scale |
|
title |
Optional title; a sensible default is generated from |
height |
Bar thickness in plot units. Defaults to |
label_size |
Band-label text size. |
Takes a single number (not a dataset) and places it on a coloured band scale
with a black marker, for the headline "where do we stand" slide. "nps" uses
a -100..100 scale banded from needs-work to excellent; "rating" uses a 1..5
scale with BAD/OK/GOOD bands. Pair it with calc_nps() for the score.
A ggplot object.
Other plots:
plot_bars(),
plot_gauges(),
plot_ipm(),
plot_nps(),
plot_quotes_tree(),
plot_rating_grid(),
plot_stacked_rating()
nps <- calc_nps(podracing_survey, nps_value)$nps plot_nps_gauge(nps) plot_nps_gauge(3.8, scale = "rating")nps <- calc_nps(podracing_survey, nps_value)$nps plot_nps_gauge(nps) plot_nps_gauge(3.8, scale = "rating")
A treemap where each tile is a comment sized by length – the quote slide of
a survey deck. Requires the suggested treemapify package.
plot_quotes_tree(data, label = comment, area = length, colour = "black")plot_quotes_tree(data, label = comment, area = length, colour = "black")
data |
A data frame of quotes, e.g. from |
label |
Text column (unquoted). Defaults to |
area |
Tile-size column (unquoted). Defaults to |
colour |
Tile border colour. Defaults to |
Lays selected verbatims out as a treemap, each tile sized by comment length,
so a slide can show real customer voice at a glance. Pair it with
sample_comments() or sample_comments_diverse(), which produce the
comment/length columns it expects. Requires the suggested treemapify
package.
A ggplot object.
sample_comments(), sample_comments_diverse().
Other plots:
plot_bars(),
plot_gauges(),
plot_ipm(),
plot_nps(),
plot_nps_gauge(),
plot_rating_grid(),
plot_stacked_rating()
sample_comments(podracing_survey, nps_com, show_com, n = 5) %>% plot_quotes_tree()sample_comments(podracing_survey, nps_com, show_com, n = 5) %>% plot_quotes_tree()
The one-line form of plot_stacked_rating() for the common case: a block of
columns sharing a prefix, each asking the same rating question about a
different feature or brand. Tabulates the block, tidies the question names,
numbers the answers by their position on the scale and stacks the result.
plot_rating_grid(data = NULL, prefix, levels = NULL, digits = 2, ...)plot_rating_grid(data = NULL, prefix, levels = NULL, digits = 2, ...)
data |
A data frame. If omitted, the session default ( |
prefix |
The block's shared column prefix, e.g. |
levels |
The scale's answer wordings, worst first. |
digits |
Decimal places kept in the underlying percentages. Default |
... |
Passed to |
Answers are numbered by their position in levels, which is what orders the
features by their weighted mean and colours the segments worst-to-best. A
wording that is not on the scale cannot be numbered, so it is reported rather
than being silently dropped into an unlabelled segment: check it against the
raw export, since exports often differ from a questionnaire by a typo.
Registering the block once with
register_order("likeability", levels, prefixes = "partner_likeability_")
removes the levels argument from every later call.
A ggplot object.
plot_stacked_rating(), calc_percentage_batch(),
register_order().
Other plots:
plot_bars(),
plot_gauges(),
plot_ipm(),
plot_nps(),
plot_nps_gauge(),
plot_quotes_tree(),
plot_stacked_rating()
plot_rating_grid(podracing_survey, "ratings_") plot_rating_grid(podracing_survey, "partner_likeability_", levels = c("Very unlikeable", "Unlikeable", "Likeable", "Very likeable"))plot_rating_grid(podracing_survey, "ratings_") plot_rating_grid(podracing_survey, "partner_likeability_", levels = c("Very unlikeable", "Unlikeable", "Likeable", "Very likeable"))
Draws a 100%-stacked bar per feature across an ordinal rating scale, labels each segment, and orders features by their weighted mean rating – the likeability, purchase and feature-rating charts a survey deck is built from.
plot_stacked_rating( data, feature, level, value = pct, palette = NULL, label_min = 1, show_average = TRUE )plot_stacked_rating( data, feature, level, value = pct, palette = NULL, label_min = 1, show_average = TRUE )
data |
Long data with one row per feature x rating level. |
feature |
Feature column (unquoted). |
level |
Ordered rating-level column (unquoted), e.g. a factor like
|
value |
Percentage column (unquoted). Defaults to |
palette |
Fill colours, named by the level labels. Defaults to |
label_min |
Hide segment labels below this percentage. Defaults to |
show_average |
Append the weighted mean to each feature label. Defaults
to |
Each feature becomes a 100%-stacked bar across the rating scale, and features
are ordered by their weighted-mean rating (the leading digit of each level
is the weight), so the best-rated feature sits at the top. The weighted mean
is appended to each feature label when show_average = TRUE. Feed it a long
table with one row per feature x rating level; the partner_* likeability and
purchase questions in the source reports are the canonical use.
A ggplot object.
calc_percentage(), scale_fill_rating().
Other plots:
plot_bars(),
plot_gauges(),
plot_ipm(),
plot_nps(),
plot_nps_gauge(),
plot_quotes_tree(),
plot_rating_grid()
rating_long <- podracing_survey %>% select(starts_with("ratings_")) %>% tidyr::pivot_longer(everything(), names_to = "feature", values_to = "level") %>% filter(level != "") %>% mutate(level = paste0(recode_likert(level), " - ", level)) %>% count(feature, level) %>% group_by(feature) %>% mutate(pct = n / sum(n) * 100) %>% ungroup() plot_stacked_rating(rating_long, feature, level)rating_long <- podracing_survey %>% select(starts_with("ratings_")) %>% tidyr::pivot_longer(everything(), names_to = "feature", values_to = "level") %>% filter(level != "") %>% mutate(level = paste0(recode_likert(level), " - ", level)) %>% count(feature, level) %>% group_by(feature) %>% mutate(pct = n / sum(n) * 100) %>% ungroup() plot_stacked_rating(rating_long, feature, level)
A fictional consumer-feedback survey from a Boonta Eve-style pod-racing
meeting – an affectionate Star Wars parody used throughout the ezrsurvey
examples and tests (the package's literal answer to starwars, but
survey-shaped). A latent enjoyment drives correlated attribute ratings and the
recommend score, the demographics use international-standard categories
(ISO/IEC 5218 sex, ISCED education, ILO labour-force status, ISIC sectors,
ISO 3166 countries), and the multi-select, sponsor and open-text columns mirror
the shapes the helpers consume. The open-text comments deliberately name many
drivers, places and races, so they exercise the text / NER helpers. Blanks and
"Prefer not to answer" responses are sprinkled in so cleaning helpers have
something to do.
podracing_surveypodracing_survey
A tibble with 1,000 rows and 32 columns:
Unique respondent id (character).
Survey source: email, panel, socials or in_app.
Age in years (integer, 16-70).
Sex/gender: Male, Female, Non-binary (ISO/IEC 5218, extended), some blank / "Prefer not to answer".
Highest education level (ISCED 2011 broad categories).
Country of residence (ISO 3166 names).
World region derived from demo_country.
Labour-force status (ILO categories).
Industry sector (ISIC Rev.4 sections).
Which meeting they attended (e.g. "Boonta Eve Classic"); long labels that exercise the auto bar layout.
Favourite pod-racer (e.g. "Anakin Skywalker", "Sebulba").
0-10 "how likely to recommend attending" rating (integer).
Likelihood of returning next season (Very unlikely .. Very likely).
Worded 1-5 attribute ratings (Very bad .. Very good).
Multi-select reasons for
attending; each holds its option text when chosen, otherwise "".
Sponsor recall per brand (Sponsor / Not a sponsor / Don't know this brand).
Sponsor likeability (Very likeable .. Very unlikeable).
Open-text comments (mostly blank).
Simulated. See data-raw/make_podracing_survey.R for the generator.
Other data:
country_region,
currency_rates,
shopping_survey
calc_percentage(podracing_survey, demo_gender, sort = "desc") calc_nps(podracing_survey, nps_value)calc_percentage(podracing_survey, demo_gender, sort = "desc") calc_nps(podracing_survey, nps_value)
Distils a whole survey's sampling precision into a few bullet points – the narrative "Data info" paragraph from the report appendix, generated automatically. It scans the dataset, assesses the numeric (point-scale) and categorical (proportion) variables, and reports the typical sampling margins and an overall reliability verdict.
precision_summary(data = NULL, ..., z = 1.96)precision_summary(data = NULL, ..., z = 1.96)
data |
A data frame. If omitted, the session default ( |
... |
Optional columns to restrict the assessment to (tidyselect). If omitted, all suitable columns are used: every numeric column, and every categorical column that is not free text or an identifier. |
z |
Confidence multiplier for the margins of error. Default |
Rather than make you choose which variables to quote precision for, this picks
them: numeric columns become point estimates (margins in rating points) and
categorical columns become proportions (margins in percentage points), with
free-text and id-like columns skipped. It then reports the typical and
worst margins across each group, plus an overall verdict from the median
relative standard error via rse_rating(). For proportions it also gives the
worst-case +/- percentage-point margin at a 50/50 split, which is the widest
any percentage in the study can be. See vignette("ezrsurvey") for the
rationale behind the relative-standard-error reliability bands.
An object of class ezrsurvey_precision (a list with n, the
per-variable table, overall_rse, rating and the bullets). Printing it
shows the bullet points.
Other diagnostics:
diagnose(),
margin_of_error(),
rse(),
rse_rating(),
se_mean(),
se_prop()
precision_summary(podracing_survey)precision_summary(podracing_survey)
Loads all CSV files matching pattern in path, reading each as character
(so survey codes never get silently coerced), tags every row with its source
filename, and row-binds the lot into one tibble. Generalises the
list.files() %>% map(read.csv) %>% bind_rows() opening of a survey script.
read_folder( path, pattern = "\\.csv$", id = "file", all_character = TRUE, question_row = NULL, locale = readr::locale(encoding = "UTF-8"), ... )read_folder( path, pattern = "\\.csv$", id = "file", all_character = TRUE, question_row = NULL, locale = readr::locale(encoding = "UTF-8"), ... )
path |
Directory to read from. |
pattern |
Regular expression matching the files to load. Defaults to
|
id |
Name of the column in which to record each row's source filename.
Defaults to |
all_character |
If |
question_row |
What to do with a second header row of question wording.
|
locale |
A |
... |
Further arguments passed to |
Every matching file is read and the results are row-bound, so a folder of
monthly or per-event exports becomes one dataset. By default every column is
read as text (all_character = TRUE); this is deliberate, because survey
exports are ragged (a column that is numeric in one file may carry "Prefer
not to answer" in another), and stacking text columns never fails. Recode the
types you need afterwards with recode_*() / ensure_numeric(). The id
column records which file each row came from – split it into metadata with
parse_filename(). For legacy encodings pass a locale, e.g.
readr::locale(encoding = "windows-1252").
Survey platforms commonly write the question wording as a second header row,
which reads back as respondent number one and quietly skews every count by
one. A file whose first row is long, spaced prose in most columns at once
and numeric in none is reported as such; question_row = TRUE drops that
row from every file, and FALSE keeps it without comment.
A single tibble of all files stacked together. If
columns differ across files, missing columns are filled with NA.
select_prefix(), parse_filename().
Other import:
parse_filename(),
select_prefix(),
select_suffix()
# build a tiny folder of CSVs, then read them back dir <- tempfile(); dir.create(dir) readr::write_csv(head(podracing_survey, 3), file.path(dir, "a.csv")) readr::write_csv(head(podracing_survey, 2), file.path(dir, "b.csv")) read_folder(dir)[, c("file", "respondent_id")]# build a tiny folder of CSVs, then read them back dir <- tempfile(); dir.create(dir) readr::write_csv(head(podracing_survey, 3), file.path(dir, "a.csv")) readr::write_csv(head(podracing_survey, 2), file.path(dir, "b.csv")) read_folder(dir)[, c("file", "respondent_id")]
Convenience wrapper over bin_numeric() using the standard survey age
bands. Also tolerates messy inputs such as "25 years" by
extracting the first run of digits.
recode_age( x, breaks = ezrsurvey_default("age_breaks"), labels = ezrsurvey_default("age_labels"), quiet = FALSE )recode_age( x, breaks = ezrsurvey_default("age_breaks"), labels = ezrsurvey_default("age_labels"), quiet = FALSE )
x |
A numeric or character vector of ages. |
breaks, labels
|
Override the default bands if needed; the defaults come
from the |
quiet |
If |
Ages are first passed through ensure_numeric(), so messy entries like
"22 years" or "age: 31" are handled, then binned with bin_numeric()
using left-closed bands. An answer with no number in it at all ("young",
"prefer not to say") becomes NA and is counted in a message, so a column
that was never numeric does not pass for a column of missing ages. The
default bands come from the age_breaks /
age_labels options, so you can set your study's standard cohorts once with
ezrsurvey_options(age_breaks = ..., age_labels = ...) (or a profile) instead
of passing them on every call. For generational cohorts (Gen Z, Millennial,
...) use recode_generation() instead.
A factor of age-band labels, levels in band order.
bin_numeric(), recode_generation(), ezrsurvey_options().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_generation(),
recode_likert(),
recode_region(),
split_multi()
recode_age(c("17", "22 years", "31", "47"))recode_age(c("17", "22 years", "31", "47"))
Maps either an age (converted to a birth year using a reference year) or a
birth year directly onto a generational cohort such as Millennial or
Gen Z. Text input like "34 years" is salvaged with ensure_numeric().
recode_generation(x, input = c("age", "year"), year = NULL, scheme = NULL)recode_generation(x, input = c("age", "year"), year = NULL, scheme = NULL)
x |
A numeric (or coercible) vector of ages or birth years. |
input |
What |
year |
Reference year for the age-to-birth-year conversion. If |
scheme |
Either a scheme name passed to |
When input = "age", the birth year is year - age, where year defaults to
the current_year option or the system year – so set
ezrsurvey_options(current_year = 2026) to keep results stable across runs.
When input = "year", x is treated as the birth year directly. The birth
year is then bucketed into the scheme's cohorts (see generation_scheme());
birth years before the earliest cohort return NA. Use this for
generational reporting; for simple age bands use recode_age().
A factor of cohort labels, levels from the oldest cohort to the
youngest; values outside the scheme's range become NA.
generation_scheme(), recode_age(), ezrsurvey_options().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_likert(),
recode_region(),
split_multi()
recode_generation(c(1990, 2001, 1968), input = "year") # from age, with an explicit reference year recode_generation(c(36, 25), input = "age", year = 2026)recode_generation(c(1990, 2001, 1968), input = "year") # from age, with an explicit reference year recode_generation(c(36, 25), input = "age", year = 2026)
Maps the worded answers of an ordinal rating question (e.g. "Very bad" ...
"Very good") onto integers 1:length(levels). Matching is case-insensitive
and tolerant of common synonyms supplied via synonyms, in place of a
case_when(str_detect("Very bad") ...) recoding block.
recode_likert( x, levels = c("Very bad", "Bad", "Ok", "Good", "Very good"), synonyms = NULL )recode_likert( x, levels = c("Very bad", "Bad", "Ok", "Good", "Very good"), synonyms = NULL )
x |
A character (or factor) vector of worded answers. |
levels |
Character vector of the scale's answer wordings in ascending
order. The first element maps to |
synonyms |
Optional named list mapping a canonical level (one of
|
Matching happens in three passes, in order: (1) an exact, case-insensitive,
trimmed match against levels; (2) a substring fallback, so prefixed exports
like "4 - Good" still resolve; (3) any synonyms you supply. This makes the
function robust to the small wording differences between survey tools (e.g.
"Dissatisfied" vs "Bad"). Turning ratings into integers is the first step of
ipm_model() and lets you average a scale with calc_summary().
The substring passes try the longest wording first, so a scale whose levels nest inside one another ("Likeable" inside "Very likeable", "Good" inside "Very good") resolves to the level the answer actually names. Without that, any answer the exact pass misses, such as one carrying an emoji or a stray character, would quietly land on the shorter level.
An integer vector the same length as x; unmatched values become
NA.
nps_group(), ipm_model(), calc_summary().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_region(),
split_multi()
recode_likert(c("Very bad", "Ok", "Good", "Very good")) # substring fallback copes with numbered labels recode_likert("4 - Good") # nested wordings resolve to the level the answer names, not the shorter one recode_likert(c("\U0001F642 Very good", "\U0001F610 Good")) # map another tool's wording onto the same scale recode_likert(c("Dissatisfied", "Satisfied"), synonyms = list(Bad = "Dissatisfied", Good = "Satisfied"), levels = c("Very bad", "Bad", "Ok", "Good", "Very good"))recode_likert(c("Very bad", "Ok", "Good", "Very good")) # substring fallback copes with numbered labels recode_likert("4 - Good") # nested wordings resolve to the level the answer names, not the shorter one recode_likert(c("\U0001F642 Very good", "\U0001F610 Good")) # map another tool's wording onto the same scale recode_likert(c("Dissatisfied", "Satisfied"), synonyms = list(Bad = "Dissatisfied", Good = "Satisfied"), levels = c("Very bad", "Bad", "Ok", "Good", "Very good"))
Vectorised lookup from country name to world region or finer subregion,
using the bundled country_region table. Matching is case-insensitive and
whitespace-tolerant, and understands the spellings people type as well as
ISO 3166-1 alpha-2 and alpha-3 codes, so "USA", "U.S.", "us" and
"United States" all resolve alike; blanks and non-answers (see
na_blank()) become NA, and unmatched countries become NA with a
one-line warning so spelling mismatches are easy to spot.
recode_region(x, which = c("region", "subregion"), quiet = FALSE) recode_subregion(x, quiet = FALSE)recode_region(x, which = c("region", "subregion"), quiet = FALSE) recode_subregion(x, quiet = FALSE)
x |
A character vector of country names. |
which |
|
quiet |
If |
A country column that people typed themselves is rarely tidy: in one real
open salary survey, nine thousand respondents wrote "United States" and
eight thousand wrote "USA". So the name is folded down before it is looked
up – case, stray punctuation, accents and a leading "the" are all dropped
– and then tried against the bundled country_region table (182
countries), then against a table of the spellings people actually use.
That table carries endonyms ("Deutschland", "Brasil"), the constituent
countries of the United Kingdom, and ISO 3166-1 alpha-2 and alpha-3 codes
("US", "USA", "DE", "DEU"), so a column already coded to the
standard needs no preparation at all.
A two-letter code is only read as a code when it was typed in capitals,
because "NO" is Norway but "no" is an answer to a different question.
Blanks and non-answers are blanked with na_blank() first, and anything
still unmatched returns NA with a warning listing the offenders, so a
spelling the table does not carry is easy to spot and add. Set
quiet = TRUE inside pipelines where you have already checked the coverage.
region is the coarse level (e.g. "Europe"); subregion is finer (e.g.
"Western Europe").
A character vector of regions (or subregions), NA where unmatched.
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
split_multi()
recode_region(c("Germany", "Japan", "Brazil")) # the spellings a free-text country question actually collects recode_region(c("USA", "U.S.", "England", "Deutschland", "Holland")) # a column already coded to ISO 3166-1 recode_region(c("US", "GBR", "DE", "JPN")) recode_subregion(c("Germany", "Japan")) # unmatched -> NA (warning suppressed here) recode_region(c("Germany", "Atlantis"), quiet = TRUE)recode_region(c("Germany", "Japan", "Brazil")) # the spellings a free-text country question actually collects recode_region(c("USA", "U.S.", "England", "Deutschland", "Holland")) # a column already coded to ISO 3166-1 recode_region(c("US", "GBR", "DE", "JPN")) recode_subregion(c("Germany", "Japan")) # unmatched -> NA (warning suppressed here) recode_region(c("Germany", "Atlantis"), quiet = TRUE)
Survey scales (education, Likert, frequency, ...) have a natural order that is
tedious to retype as levels = c(...) every time. Register an order once,
link it to the variable names and/or column prefixes it applies to, and the
tabulating helpers (e.g. calc_percentage()) will look it up and apply it
automatically. Persist your orders in a profile with save_ezrsurvey_profile().
register_order(name, levels, vars = NULL, prefixes = NULL, overwrite = TRUE)register_order(name, levels, vars = NULL, prefixes = NULL, overwrite = TRUE)
name |
Short name for the order (e.g. |
levels |
Character vector of the levels in ascending order. |
vars |
Optional exact column names this order applies to (e.g.
|
prefixes |
Optional column-name prefixes this order applies to (e.g.
|
overwrite |
Overwrite an existing order of the same name. Default |
An order links a set of levels to the columns it applies to, by exact name
(vars) and/or by prefix (prefixes). Once registered, calc_percentage(),
calc_percentage_multi() and crosstab() look it up automatically (via
order_for()) whenever you don't pass an explicit levels/sort, so a
question always comes out in the right order without retyping it. Register
interactively for the session, or store orders in a profile with
save_ezrsurvey_profile() so they load every session.
Invisibly the order name.
order_for(), apply_order(), list_orders(),
register_order_presets().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order( "education", levels = c("Primary or less", "Lower secondary", "Upper secondary", "Short-cycle tertiary", "Bachelor or equivalent", "Master or equivalent", "Doctoral or equivalent"), vars = "demo_edu" ) calc_percentage(podracing_survey, demo_edu) # rows now in education order remove_order("education")register_order( "education", levels = c("Primary or less", "Lower secondary", "Upper secondary", "Short-cycle tertiary", "Bachelor or equivalent", "Master or equivalent", "Doctoral or equivalent"), vars = "demo_edu" ) calc_percentage(podracing_survey, demo_edu) # rows now in education order remove_order("education")
A convenience that registers a handful of frequently used orders, linked to
sensible default variable names/prefixes, so common surveys work out of the
box. Override any of them afterwards with register_order().
register_order_presets()register_order_presets()
Registers: likert_bad_good, likert_agree, frequency, likelihood and
education_isced.
Invisibly the names registered.
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order_presets() list_orders()register_order_presets() list_orders()
Remove a registered order
remove_order(name)remove_order(name)
name |
Order name. |
Invisibly TRUE.
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
register_order("yesno", c("No", "Yes")) remove_order("yesno")register_order("yesno", c("No", "Yes")) remove_order("yesno")
Renders a ggplot to an image and places it in the current slide's content placeholder (pptx) or as a new figure (docx).
report_add_plot(doc, plot, width = NULL, height = NULL, dpi = 150)report_add_plot(doc, plot, width = NULL, height = NULL, dpi = 150)
doc |
A document from |
plot |
A ggplot object (any grid grob also works, e.g. an aligned
plot from |
width, height
|
Image size in inches. |
dpi |
Raster resolution. Defaults to |
On slides the image is rendered at exactly the content placeholder's size, so nothing is stretched or letterboxed; when the template does not define placeholder geometry the slide size (minus margins) is used instead.
The updated document.
Other reporting:
example_report(),
list_report_templates(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
p <- calc_percentage(podracing_survey, demo_gender) %>% plot_bars() doc <- report_new("pptx") %>% report_add_slide("Gender") %>% report_add_plot(p) class(doc)p <- calc_percentage(podracing_survey, demo_gender) %>% plot_bars() doc <- report_new("pptx") %>% report_add_slide("Gender") %>% report_add_plot(p) class(doc)
For a pptx document this starts a new slide and sets its title; for a
docx document it adds a heading paragraph.
report_add_slide( doc, title = NULL, layout = NULL, master = NULL, heading_level = 1 )report_add_slide( doc, title = NULL, layout = NULL, master = NULL, heading_level = 1 )
doc |
A document from |
title |
Optional slide title / heading text. |
layout, master
|
PowerPoint layout and master names. |
heading_level |
Word heading level (1-3). Defaults to |
Automatic selection scores every layout of the template: a title placeholder plus a single content placeholder is the ideal, and the layout's placeholders decide – not its (language-dependent) name. If the chosen layout has no title placeholder, the title is skipped with a warning rather than failing the build.
The updated document.
report_new(), report_layouts().
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
doc <- report_new("pptx") %>% report_add_slide("Audience") class(doc)doc <- report_new("pptx") %>% report_add_slide("Audience") class(doc)
Adds a data frame as a flextable – in the current slide's content placeholder (pptx) or as a new table (docx).
report_add_table(doc, data, font_size = 12, autofit = TRUE)report_add_table(doc, data, font_size = 12, autofit = TRUE)
doc |
A document from |
data |
A data frame / tibble. |
font_size |
Cell font size in points. Defaults to |
autofit |
Word only: auto-size columns to content. Defaults to |
On a slide the table is styled (a clean banded header, centred cells, a readable font) and its columns are widened to span the content placeholder – roughly in proportion to each column's contents – so it fills the slide meaningfully instead of sitting tiny in a corner. In Word it is added as a plain auto-fitted flextable.
The updated document.
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
tbl <- calc_percentage(podracing_survey, demo_gender) doc <- report_new("docx") %>% report_add_table(tbl) class(doc)tbl <- calc_percentage(podracing_survey, demo_gender) doc <- report_new("docx") %>% report_add_table(tbl) class(doc)
Add a paragraph of text to a report
report_add_text(doc, text, ...)report_add_text(doc, text, ...)
doc |
A document from |
text |
Text to add; a character vector becomes one paragraph (bullet) per element on slides. |
... |
Passed to |
The updated document.
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
doc <- report_new("docx") %>% report_add_text("Key findings follow.") class(doc)doc <- report_new("docx") %>% report_add_text("Key findings follow.") class(doc)
Turns a named list of ggplots / data frames into a titled-slide deck (or Word document) and saves it. Names become slide titles; each item lands on its own slide, sized to the template's content placeholder.
report_deck( items, path, format = c("pptx", "docx"), title = NULL, template = NULL, style = c("elevated", "plain") )report_deck( items, path, format = c("pptx", "docx"), title = NULL, template = NULL, style = c("elevated", "plain") )
items |
A named list; names become slide titles. Each element is either a ggplot (added as a plot) or a data frame (added as a table). |
path |
Output file path. A bare file name is written to the working
directory, or into the folder set by the |
format |
|
title |
Optional title-slide / document-title text. |
template |
Optional reference document; |
style |
Built-in template to fall back on: |
Every chart is rendered at the exact size of the template's content
placeholder and centred in it, and plot_bars() draws bars to a constant
thickness whatever the category count, so the deck reads consistently as you
flick through it.
For a deck built a slide at a time, with section dividers and per-slide
control over the layout, use report_new() and the report_add_*() family
instead.
Invisibly path.
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
tmp <- tempfile(fileext = ".pptx") report_deck( list( "Gender" = plot_bars(calc_percentage(podracing_survey, demo_gender)), "NPS" = calc_nps(podracing_survey, nps_value) ), path = tmp ) file.exists(tmp)tmp <- tempfile(fileext = ".pptx") report_deck( list( "Gender" = plot_bars(calc_percentage(podracing_survey, demo_gender)), "NPS" = calc_nps(podracing_survey, nps_value) ), path = tmp ) file.exists(tmp)
Shows what a template offers the report builders: every layout, its master,
and the placeholders it carries. Useful to decide which layout to name in
report_add_slide(), or to sanity-check an organisation template before
building a deck against it.
report_layouts(template = NULL, style = c("elevated", "plain"))report_layouts(template = NULL, style = c("elevated", "plain"))
template |
Path to a |
style |
Built-in template to inspect when no |
The report builders pick layouts automatically by inspecting placeholders,
so most decks never need this – but when a corporate template offers
several content layouts, report_layouts() plus an explicit
report_add_slide(layout = ...) gives you full control. Requires the
suggested officer package.
A tibble with one row per layout: layout,
master, has_title, n_body, body_width, body_height (inches of
the first body placeholder, NA when the layout has none).
report_new(), report_add_slide(), use_brand().
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
report_layouts()report_layouts()
Opens an officer document you can build up with the report_add_*()
helpers and write out with report_save(). This is the "build slides/Word
directly from R" path; for a Quarto-based workflow see scaffold_report().
report_new( format = c("pptx", "docx"), template = NULL, style = c("elevated", "plain"), slide_numbers = TRUE, keep_slides = TRUE )report_new( format = c("pptx", "docx"), template = NULL, style = c("elevated", "plain"), slide_numbers = TRUE, keep_slides = TRUE )
format |
|
template |
Optional path to a |
style |
Which built-in template to fall back on when no |
slide_numbers |
If |
keep_slides |
PowerPoint only. If |
This is the "build it from R" path: you get an officer document and
add to it slide by slide (pptx) or section by section (docx) with the
report_add_*() helpers, then write it out with report_save(). Slide
layouts are chosen by inspecting the template's placeholders, so any
organisation template works – see report_layouts() for what yours
contains. For a quick deck from a list of charts and tables, report_deck()
does the whole thing in one call; for a document-driven workflow,
scaffold_report() gives you a Quarto template instead. Requires the
suggested officer package.
An officer document object (rpptx or rdocx).
report_add_slide(), report_add_plot(), report_add_table(),
report_save(), report_deck(), report_layouts(), use_brand().
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_save(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
doc <- report_new("pptx") class(doc) # any .pptx of your own replaces the built-in template own <- system.file("templates", "ezrsurvey-16x9-plain.pptx", package = "ezrsurvey") doc <- report_new("pptx", template = own)doc <- report_new("pptx") class(doc) # any .pptx of your own replaces the built-in template own <- system.file("templates", "ezrsurvey-16x9-plain.pptx", package = "ezrsurvey") doc <- report_new("pptx", template = own)
Writes a deck or document built with report_new() and the report_* verbs,
and returns the path it used, so a build pipeline can carry on from it.
report_save(doc, path)report_save(doc, path)
doc |
A document from |
path |
Output file path ( |
The format follows the document, not the file name: a deck is written as
PowerPoint and a Word document as Word, so give path the matching .pptx
or .docx extension.
Invisibly path.
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_section(),
report_slide(),
report_title_slide(),
scaffold_report()
tmp <- tempfile(fileext = ".pptx") report_new("pptx") %>% report_add_slide("Hi") %>% report_save(tmp) file.exists(tmp)tmp <- tempfile(fileext = ".pptx") report_new("pptx") %>% report_add_slide("Hi") %>% report_save(tmp) file.exists(tmp)
A one-line section break: a slide on the template's "Section Header" layout
(falling back to a plain titled slide when the template has none). Use a
short, single-word label – "DEMOGRAPHICS", "RATINGS", "APPENDIX" – to
chapter a deck.
report_section(doc, title, layout = "Section Header", master = NULL)report_section(doc, title, layout = "Section Header", master = NULL)
doc |
A document from |
title |
Section label. |
layout |
Section layout name. Defaults to |
master |
Optional master name. |
The updated document.
report_slide(), report_title_slide().
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_slide(),
report_title_slide(),
scaffold_report()
doc <- report_new("pptx") %>% report_section("DEMOGRAPHICS") class(doc)doc <- report_new("pptx") %>% report_section("DEMOGRAPHICS") class(doc)
The one-line-per-slide wrapper: starts a new slide, sets its title, and
places content on it in a single call, so a deck script reads as one line
per slide. content is dispatched by type – a ggplot becomes a chart, a
data frame a table, a character vector a bulleted text box.
report_slide( doc, title = NULL, content = NULL, layout = NULL, master = NULL, ... )report_slide( doc, title = NULL, content = NULL, layout = NULL, master = NULL, ... )
doc |
A document from |
title |
Slide title (typically the survey question the slide answers). |
content |
A ggplot, a data frame / tibble, or a character vector. |
layout, master
|
Passed to |
... |
Passed on to |
Equivalent to report_add_slide() followed by the matching report_add_*()
call, but as one pipe-friendly step so calc -> plot -> add collapses onto a
single line:
report_slide("How likely to recommend?", plot_nps(nps_value)).
The updated document.
report_section(), report_title_slide(), report_add_slide().
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_title_slide(),
scaffold_report()
doc <- report_new("pptx") %>% report_slide("Who follows pod racing?", plot_bars(calc_percentage(podracing_survey, demo_gender))) class(doc)doc <- report_new("pptx") %>% report_slide("Who follows pod racing?", plot_bars(calc_percentage(podracing_survey, demo_gender))) class(doc)
A one-line title slide, placed on the template's title layout (the one with a centre-title placeholder).
report_title_slide(doc, title, subtitle = NULL, layout = NULL, master = NULL)report_title_slide(doc, title, subtitle = NULL, layout = NULL, master = NULL)
doc |
A document from |
title |
Deck title. |
subtitle |
Optional strapline placed in the layout's subtitle
placeholder – the respondent base and fieldwork period, say. |
layout, master
|
Optional overrides; |
The updated document.
report_slide(), report_section().
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
scaffold_report()
doc <- report_new("pptx") %>% report_title_slide("Pod-Racing Fan Survey", subtitle = "1,000 fans | Fieldwork 2026") class(doc)doc <- report_new("pptx") %>% report_title_slide("Pod-Racing Fan Survey", subtitle = "1,000 fans | Fieldwork 2026") class(doc)
The band presets are written on the scales survey work uses most: 1-5 for a
rating, 0-10 for the Net Promoter answer, -100 to 100 for the score. Plenty
of real questionnaires do not use those. Satisfaction is often collected on
0-100, and a seven-point agreement scale is common. rescale_bands()
stretches a band specification onto the scale the chart is actually drawn on,
so the thresholds can stay where they are instead of the data being divided
to meet them.
rescale_bands(bands, to, from = NULL)rescale_bands(bands, to, from = NULL)
bands |
A band specification: one of |
to |
Length-2 numeric: the scale the chart uses, as |
from |
Length-2 numeric: the scale |
The mapping is linear and proportional: a band covering the top fifth of its own scale covers the top fifth of the new one. Rescale rather than divide the data, so the axis a reader sees is still the scale the question was asked on.
bands with from and to moved onto the new scale. Labels and
colours are untouched.
Other decisions:
annotate_bands(),
bands_rating_3(),
mark_value()
# satisfaction collected 0-100, thresholds still where they belong bands_rating_3() %>% rescale_bands(to = c(0, 100)) # a seven-point agreement scale bands_rating_3() %>% rescale_bands(to = c(1, 7))# satisfaction collected 0-100, thresholds still where they belong bands_rating_3() %>% rescale_bands(to = c(0, 100)) # a seven-point agreement scale bands_rating_3() %>% rescale_bands(to = c(1, 7))
Reset all ezrsurvey options to their built-in defaults
reset_ezrsurvey_options()reset_ezrsurvey_options()
Invisibly TRUE.
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
save_ezrsurvey_profile(),
use_dataset(),
use_ezrsurvey_profile()
ezrsurvey_options(pct_axis_max = 100) reset_ezrsurvey_options() ezrsurvey_options()$pct_axis_max # NULL againezrsurvey_options(pct_axis_max = 100) reset_ezrsurvey_options() ezrsurvey_options()$pct_axis_max # NULL again
The standard error expressed as a percentage of the estimate,
se / estimate * 100. A common rule of thumb treats an RSE below ~5% as very
good precision.
rse(estimate, se)rse(estimate, se)
estimate |
The point estimate (mean or proportion). |
se |
Its standard error, on the same scale as |
Dividing the standard error by the estimate makes precision comparable across
measures on different scales (a 1–5 rating vs a percentage). A common
rule of thumb treats an RSE under ~5% as very good precision, which is the
threshold diagnose() uses for its precision flag.
The relative standard error, in percent.
se_mean(), se_prop(), margin_of_error(), diagnose().
Other diagnostics:
diagnose(),
margin_of_error(),
precision_summary(),
rse_rating(),
se_mean(),
se_prop()
rse(estimate = 4.1, se = se_mean(c(4, 5, 3, 4, 5)))rse(estimate = 4.1, se = se_mean(c(4, 5, 3, 4, 5)))
Turns a relative standard error (RSE, in percent) into a plain-language reliability rating, using a five-band scheme.
rse_rating(rse)rse_rating(rse)
rse |
Relative standard error(s), in percent (see |
The bands are: under 5% "high precision", under 10% "precise", under 15%
"satisfactory", under 25% "use with caution", and 25% or more "likely
reliability issues". Rating estimates by their relative standard error is how
national statistical agencies signal whether a survey number is solid enough
to publish: the Australian Bureau of Statistics treats an RSE of 25% or more
as "not reliable for most purposes", flags 25–50% with an asterisk as
estimates to use with caution, and suppresses anything above 50%. The 25%
boundary here is that same one. The tighter bands below it are specific to
consumer-survey work, where the question is usually which of several
adequately precise numbers to lead a report with. This drives the precision
column of diagnose() and the overall verdict of precision_summary().
A character vector of ratings.
rse(), diagnose(), precision_summary().
Other diagnostics:
diagnose(),
margin_of_error(),
precision_summary(),
rse(),
se_mean(),
se_prop()
rse_rating(c(3, 8, 12, 20, 40))rse_rating(c(3, 8, 12, 20, 40))
Pulls a tidy, presentation-ready sample of comments out of one or more
free-text columns, applying the usual clean-ups: drop blanks and very short
answers, optionally exclude comments matching unwanted terms, and truncate to
a maximum length. The result feeds straight into plot_quotes_tree().
sample_comments( data = NULL, ..., n = 8, min_chars = 30, max_chars = 400, exclude = NULL, by_column = TRUE, by = NULL, seed = NULL )sample_comments( data = NULL, ..., n = 8, min_chars = 30, max_chars = 400, exclude = NULL, by_column = TRUE, by = NULL, seed = NULL )
data |
A data frame. |
... |
One or more comment columns, using tidyselect (e.g. |
n |
Number of comments to sample. With |
min_chars |
Drop comments with this many characters or fewer. Default
|
max_chars |
Truncate longer comments to this length. Default |
exclude |
Optional character vector of (case-insensitive) regex terms;
comments matching any are dropped (e.g. |
by_column |
If |
by |
Optional grouping column (unquoted), e.g. an NPS group. Sampling
then takes |
seed |
Optional integer for reproducible sampling (the RNG state is restored afterwards). |
Verbatims need clean-up before they go on a slide: this drops blanks and very
short answers, optionally removes comments matching unwanted terms (exclude),
and truncates the long ones. With by_column = TRUE you get n from each
source column (e.g. an even split across nps_com and show_com); with
FALSE, n from the pooled set. The result feeds plot_quotes_tree(). For a
set that is varied rather than uniformly random, use sample_comments_diverse().
A tibble with source, comment and length,
plus the by column when one is given.
sample_comments_diverse() for a diversity-aware sample,
plot_quotes_tree().
Other comments:
sample_comments_diverse()
sample_comments(podracing_survey, nps_com, show_com, n = 3) # three quotes per NPS group, with the group kept as a column podracing_survey %>% mutate(group = nps_group(nps_value, labels = TRUE)) %>% filter(!is.na(group)) %>% sample_comments(nps_com, n = 3, by = group)sample_comments(podracing_survey, nps_com, show_com, n = 3) # three quotes per NPS group, with the group kept as a column podracing_survey %>% mutate(group = nps_group(nps_value, labels = TRUE)) %>% filter(!is.na(group)) %>% sample_comments(nps_com, n = 3, by = group)
A smarter alternative to sample_comments(): instead of sampling uniformly,
it scores each comment and selects a set that is both informative and
varied, so a quote slide isn't five ways of saying the same thing. Scoring
is lexical – comments are turned into TF-IDF vectors, an information score is
computed (Shannon entropy of the word distribution, or TF-IDF mass), and a
randomised maximal-marginal-relevance pass trades off informativeness against
similarity to already-picked comments.
sample_comments_diverse( data = NULL, ..., n = 8, min_chars = 30, max_chars = 400, exclude = NULL, lambda = 0.6, method = c("mmr", "entropy"), score = c("entropy", "tfidf"), stopwords = NULL, max_candidates = 500, seed = NULL )sample_comments_diverse( data = NULL, ..., n = 8, min_chars = 30, max_chars = 400, exclude = NULL, lambda = 0.6, method = c("mmr", "entropy"), score = c("entropy", "tfidf"), stopwords = NULL, max_candidates = 500, seed = NULL )
data |
A data frame. |
... |
One or more comment columns, using tidyselect (e.g. |
n |
Number of comments to sample. With |
min_chars |
Drop comments with this many characters or fewer. Default
|
max_chars |
Truncate longer comments to this length. Default |
exclude |
Optional character vector of (case-insensitive) regex terms;
comments matching any are dropped (e.g. |
lambda |
Diversity/informativeness trade-off in |
method |
|
score |
By which to measure information: |
stopwords |
Stop-word handling: |
max_candidates |
Largest number of comments compared with one another
(default |
seed |
Optional integer for reproducible sampling (the RNG state is restored afterwards). |
A tibble with source, comment, length and the
info score, ordered by selection.
sample_comments(), plot_quotes_tree().
Other comments:
sample_comments()
sample_comments_diverse(podracing_survey, nps_com, show_com, n = 5, seed = 1)sample_comments_diverse(podracing_survey, nps_com, show_com, n = 5, seed = 1)
A pipe-friendly one-liner for dropping a summary table to disk. The format is
taken from the file extension. For .xlsx, pass a single data frame for one
sheet, or a named list of data frames to write one sheet per element.
Returns its input invisibly so it can sit mid-pipeline.
save_data(data, path, na = "", ...)save_data(data, path, na = "", ...)
data |
A data frame / tibble, or (for |
path |
Output path; the extension sets the format ( |
na |
String to write for missing values. Default |
... |
Passed to the underlying writer ( |
The format follows the file extension: .csv/.tsv via readr, .xlsx
via the suggested writexl package. For an Excel workbook, pass one data
frame for a single sheet, or a named list of data frames for one sheet each.
Returns its input invisibly so it can sit mid-pipeline. To send several
separate calls to their own tabs in one line, see export_xlsx().
data, invisibly.
save_plot(), save_output(), export_xlsx().
Other save:
export_summary_xlsx(),
export_xlsx(),
save_output(),
save_plot()
tab <- calc_percentage(podracing_survey, demo_gender) tmp <- tempfile(fileext = ".csv") save_data(tab, tmp) file.exists(tmp)tab <- calc_percentage(podracing_survey, demo_gender) tmp <- tempfile(fileext = ".csv") save_data(tab, tmp) file.exists(tmp)
Writes your current non-default ezrsurvey_options() and all registered
orders (see register_order()) to a YAML profile, so they persist across
sessions. Requires the suggested yaml package.
save_ezrsurvey_profile( path = NULL, include_options = TRUE, include_orders = TRUE )save_ezrsurvey_profile( path = NULL, include_options = TRUE, include_orders = TRUE )
path |
Destination path. If |
include_options |
Write changed options. Default |
include_orders |
Write registered orders. Default |
Invisibly the path written.
load_ezrsurvey_profile(), edit_ezrsurvey_profile(),
register_order().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
use_dataset(),
use_ezrsurvey_profile()
ezrsurvey_options(pct_axis_max = 100) tmp <- tempfile(fileext = ".yml") save_ezrsurvey_profile(tmp, include_orders = FALSE) readLines(tmp) reset_ezrsurvey_options()ezrsurvey_options(pct_axis_max = 100) tmp <- tempfile(fileext = ".yml") save_ezrsurvey_profile(tmp, include_orders = FALSE) readLines(tmp) reset_ezrsurvey_options()
Convenience dispatcher: routes ggplots to save_plot() and data frames /
lists to save_data(), choosing by the object type. Handy at the end of a
pipe when you do not want to think about which saver to call.
save_output(x, path, ...)save_output(x, path, ...)
x |
A ggplot, a data frame, or a (named) list of data frames. |
path |
Output path; the extension picks the format. A bare file name is
written to the working directory, or into the folder set by the
|
... |
Passed to |
x, invisibly.
Other save:
export_summary_xlsx(),
export_xlsx(),
save_data(),
save_plot()
tmp_csv <- tempfile(fileext = ".csv") calc_percentage(podracing_survey, demo_gender) %>% save_output(tmp_csv) tmp_png <- tempfile(fileext = ".png") plot_bars(calc_percentage(podracing_survey, demo_gender)) %>% save_output(tmp_png)tmp_csv <- tempfile(fileext = ".csv") calc_percentage(podracing_survey, demo_gender) %>% save_output(tmp_csv) tmp_png <- tempfile(fileext = ".png") plot_bars(calc_percentage(podracing_survey, demo_gender)) %>% save_output(tmp_png)
A thin, pipe-friendly wrapper around ggplot2::ggsave() that picks the device
from the file extension and uses presentation-friendly defaults (transparent
background, generous size). Returns the plot invisibly, so it drops into a
pipeline without breaking it.
save_plot( plot, path, width = 8, height = 4.5, dpi = 300, bg = "transparent", ... )save_plot( plot, path, width = 8, height = 4.5, dpi = 300, bg = "transparent", ... )
plot |
A ggplot object. |
path |
Output path; the extension sets the format ( |
width, height
|
Size in inches. Default |
dpi |
Raster resolution for PNG/JPG/TIFF. Default |
bg |
Background fill. Default |
... |
Passed to |
The device is chosen from the file extension, and the plot is returned
invisibly so the call slots into a pipeline without breaking it
(p %>% save_plot("p.png") %>% print()). Defaults suit slides: a transparent
background (matching the ezrsurvey themes) and a generous size; pass
bg = "white" for a solid background. SVG output needs the suggested
svglite package.
The plot, invisibly.
Other save:
export_summary_xlsx(),
export_xlsx(),
save_data(),
save_output()
p <- plot_bars(calc_percentage(podracing_survey, demo_gender)) tmp <- tempfile(fileext = ".png") save_plot(p, tmp) file.exists(tmp)p <- plot_bars(calc_percentage(podracing_survey, demo_gender)) tmp <- tempfile(fileext = ".png") save_plot(p, tmp) file.exists(tmp)
Copies a ready-to-render Quarto report skeleton into your project, wired up
with ezrsurvey helpers against the bundled podracing_survey data: an
executive summary, per-question sections with placeholder narrative, and a
methodology appendix built from the
precision diagnostics. Swap in your own data via the data parameter (or
at the top of the file) and render with Quarto. This is the document-driven
counterpart to the report_new() / report_deck() officer builders.
scaffold_report( format = c("pptx", "html", "pdf", "docx"), path, title = "Survey Report", author = NULL, reference_doc = NULL, overwrite = FALSE )scaffold_report( format = c("pptx", "html", "pdf", "docx"), path, title = "Survey Report", author = NULL, reference_doc = NULL, overwrite = FALSE )
format |
Output format: one of |
path |
Destination |
title |
Title inserted into the template's YAML header. |
author |
Author name for the YAML header. |
reference_doc |
Path to a PowerPoint / Word template used as the
Quarto |
overwrite |
Overwrite |
Each scaffold declares one Quarto parameter: data, the path to a CSV of
your survey; empty means the bundled example data.
Corporate templates: Quarto/Pandoc fills a reference-doc by looking
for the standard layout names ("Title Slide", "Title and Content", "Two
Content", ...). A corporate template whose layouts keep those names works
directly; one with renamed layouts will render broken or blank slides here
– build the deck with report_deck() instead, which inspects placeholders
and works with any template.
Invisibly the path written.
report_deck() for building decks directly without Quarto;
use_brand() to register a default reference document.
Other reporting:
example_report(),
list_report_templates(),
report_add_plot(),
report_add_slide(),
report_add_table(),
report_add_text(),
report_deck(),
report_layouts(),
report_new(),
report_save(),
report_section(),
report_slide(),
report_title_slide()
tmp <- tempfile(fileext = ".qmd") scaffold_report("html", path = tmp, title = "Q2 Customer Survey") file.exists(tmp)tmp <- tempfile(fileext = ".qmd") scaffold_report("html", path = tmp, title = "Q2 Customer Survey") file.exists(tmp)
Discrete ggplot2 scales using the brand accent palette from pal_brand().
scale_color_brand() is an alias of scale_colour_brand().
scale_fill_brand(...) scale_colour_brand(...) scale_color_brand(...)scale_fill_brand(...) scale_colour_brand(...) scale_color_brand(...)
... |
Passed to |
The palette function interpolates when a plot needs more colours than the
brand defines, so the scale never runs out. Without a brand set, the scales
fall back to the pal_sequential_blue defaults via pal_brand().
A ggplot2 scale.
Other brand:
brand_info(),
clear_brand(),
pal_brand(),
use_brand()
df <- calc_percentage(podracing_survey, demo_gender) ggplot(df, aes(demo_gender, pct, fill = demo_gender)) + geom_col() + scale_fill_brand()df <- calc_percentage(podracing_survey, demo_gender) ggplot(df, aes(demo_gender, pct, fill = demo_gender)) + geom_col() + scale_fill_brand()
Manual fill scale mapping the NPS-group coding ("-1" / "0" / "1") onto the
pal_nps palette.
scale_fill_nps(...)scale_fill_nps(...)
... |
Passed to |
Maps the NPS-group coding from nps_group() ("-1" detractor, "0" passive,
"1" promoter) onto the pal_nps red/amber/green palette, so the colours
carry their usual meaning. Used by plot_nps().
A ggplot2 scale.
pal_nps, nps_group(), plot_nps().
Other themes:
pal_rating,
scale_fill_rating(),
theme_ezrsurvey(),
theme_transparent()
df <- data.frame(score = 0:2, pct = c(20, 30, 50), group = c(-1, 0, 1)) ggplot(df, aes(score, pct, fill = factor(group))) + geom_col() + scale_fill_nps()df <- data.frame(score = 0:2, pct = c(20, 30, 50), group = c(-1, 0, 1)) ggplot(df, aes(score, pct, fill = factor(group))) + geom_col() + scale_fill_nps()
Manual ggplot2 scales mapping a 1-5 rating (as a factor or its character
codes "1".."5") onto the pal_rating red-amber-green palette.
scale_fill_rating(distinct = FALSE, ...) scale_colour_rating(distinct = FALSE, ...) scale_color_rating(distinct = FALSE, ...)scale_fill_rating(distinct = FALSE, ...) scale_colour_rating(distinct = FALSE, ...) scale_color_rating(distinct = FALSE, ...)
distinct |
If |
... |
Passed to |
Maps the rating codes "1".."5" onto pal_rating, which uses the
bands_rating_3() thresholds (1-2 bad, 3 ok, 4-5 good). Set
distinct = TRUE for a fully distinct 5-colour ramp, which reads better on
stacked bars where adjacent segments must be separable.
scale_color_rating() is an alias of scale_colour_rating().
A ggplot2 scale.
Other themes:
pal_rating,
scale_fill_nps(),
theme_ezrsurvey(),
theme_transparent()
df <- data.frame(feature = c("a", "b"), pct = c(60, 40), rating = c(4, 2)) ggplot(df, aes(feature, pct, fill = factor(rating))) + geom_col() + scale_fill_rating()df <- data.frame(feature = c("a", "b"), pct = c(60, 40), rating = c(4, 2)) ggplot(df, aes(feature, pct, fill = factor(rating))) + geom_col() + scale_fill_rating()
A thin wrapper around ggplot2::scale_y_continuous() that sets the upper
limit with nice_max() and, by default, formats the axis as percentages.
Designed for the percentage bar charts produced by calc_percentage().
scale_y_pct(values = NULL, unit = 25, pad = 0, max = NULL, labels = TRUE, ...)scale_y_pct(values = NULL, unit = 25, pad = 0, max = NULL, labels = TRUE, ...)
values |
Numeric vector used to determine the axis ceiling, typically
the column being plotted. If |
unit, pad
|
Passed to |
max |
Optional fixed upper limit. If supplied (e.g. |
labels |
One of:
|
... |
Further arguments passed to |
The upper limit is chosen in one of three ways, in order of precedence: a
fixed max if you supply one; otherwise nice_max() of values; otherwise
left to ggplot2. Labels default to whole-percent strings via label_pct().
plot_bars() calls this for you, reading its unit and max from the
pct_axis_unit / pct_axis_max options (see ezrsurvey_options()), so you
rarely add it by hand – but it is exported for custom charts.
A ggplot2 scale you can add to a plot with +.
nice_max(), label_pct(), plot_bars().
Other scales:
label_pct(),
nice_max()
df <- calc_percentage(podracing_survey, demo_gender) ggplot(df, aes(demo_gender, pct)) + geom_col() + scale_y_pct(df$pct) # axis capped at a tidy multiple, "%" labelsdf <- calc_percentage(podracing_survey, demo_gender) ggplot(df, aes(demo_gender, pct)) + geom_col() + scale_y_pct(df$pct) # axis capped at a tidy multiple, "%" labels
sd(x) / sqrt(n), the sampling error of a mean such as an average 1-5
rating. Packages the inline sem calculation from the report appendix.
se_mean(x)se_mean(x)
x |
A numeric vector. |
The standard error of the mean is the sample standard deviation divided by the
square root of the (non-missing) sample size, sd(x) / sqrt(n). It shrinks as
the sample grows, and is the basis of the margin of error you quote for an
average rating. Fewer than two values gives NA (no spread to estimate).
A single numeric standard error, or NA if fewer than two non-missing
values are present.
se_prop(), rse(), margin_of_error(), diagnose().
Other diagnostics:
diagnose(),
margin_of_error(),
precision_summary(),
rse(),
rse_rating(),
se_prop()
se_mean(c(4, 5, 3, 4, 5, 2, 4))se_mean(c(4, 5, 3, 4, 5, 2, 4))
sqrt(p * (1 - p) / n), the sampling error of a percentage. Packages the
inline proportion-error calculation from the report appendix.
se_prop(p, n, pctp = FALSE)se_prop(p, n, pctp = FALSE)
p |
The proportion, either as a fraction in |
n |
The sample size (number of respondents). |
pctp |
Return the answer in percentage points instead of on the |
For a percentage from a survey, the sampling error is sqrt(p * (1 - p) / n).
It is largest when p = 0.5 (a 50/50 split is the hardest to pin down) and
shrinks towards the extremes. The result is on the same 0–1 scale as a
fractional p; pass pctp = TRUE for percentage points, the form used in
report footnotes and the one margin_of_error() needs if you want the margin
in points. Inputs above 1 are treated as percentages and divided by 100, so
se_prop(33, n) and se_prop(0.33, n) agree.
The standard error of the proportion, on the same [0, 1] scale as a
fractional p, or in percentage points when pctp = TRUE.
se_mean(), rse(), margin_of_error(), diagnose().
Other diagnostics:
diagnose(),
margin_of_error(),
precision_summary(),
rse(),
rse_rating(),
se_mean()
se_prop(0.33, 1184) se_prop(0.33, 1184, pctp = TRUE)se_prop(0.33, 1184) se_prop(0.33, 1184, pctp = TRUE)
Keeps a handful of id columns plus every column sharing a prefix – the
select(id, starts_with("demo_")) idiom – in one tidy call.
select_prefix(data = NULL, prefix, keep = NULL)select_prefix(data = NULL, prefix, keep = NULL)
data |
A data frame. If omitted, the session default ( |
prefix |
One or more column-name prefixes to keep (e.g. |
keep |
Optional character vector of additional column names to retain in front of the prefixed block (e.g. an id column). |
Survey questionnaires are usually organised by prefix (demo_, ratings_,
partner_, ...), so this is a quick way to grab one block plus a couple of id
columns without naming every variable. Order is keep first, then the
prefix-matching columns in their original order.
A data frame with keep columns followed by all prefix-matching
columns.
read_folder(), calc_percentage_multi().
Other import:
parse_filename(),
read_folder(),
select_suffix()
select_prefix(podracing_survey, "demo_", keep = "respondent_id") select_prefix(podracing_survey, c("ratings_", "partner_"))select_prefix(podracing_survey, "demo_", keep = "respondent_id") select_prefix(podracing_survey, c("ratings_", "partner_"))
The mirror of select_prefix(): keeps a handful of id columns plus every
column sharing a suffix – the select(id, ends_with("_com")) idiom – in
one tidy call.
select_suffix(data = NULL, suffix, keep = NULL)select_suffix(data = NULL, suffix, keep = NULL)
data |
A data frame. If omitted, the session default ( |
suffix |
One or more column-name suffixes to keep (e.g. |
keep |
Optional character vector of additional column names to retain in front of the suffixed block (e.g. an id column). |
Some questionnaire blocks are marked by a trailing tag rather than a leading
one (_com open-text follow-ups, _score derived columns), so this grabs
one such block plus a couple of id columns without naming every variable.
Order is keep first, then the suffix-matching columns in their original
order.
A data frame with keep columns followed by all suffix-matching
columns.
select_prefix(), read_folder().
Other import:
parse_filename(),
read_folder(),
select_prefix()
select_suffix(podracing_survey, "_com", keep = "respondent_id")select_suffix(podracing_survey, "_com", keep = "respondent_id")
Defines target shares for one or more categorical variables (e.g. a known
population split by gender or region). Once set, the summary helpers
(calc_percentage(), calc_nps(), calc_summary(), crosstab()) weight
their results automatically, so the weighted margins match your targets. Pass
weights = FALSE to any of them to opt out for a single call.
set_weights(...)set_weights(...)
... |
One scheme per variable. Two forms are accepted:
|
With a single variable the weights are exact post-stratification:
target_share / sample_share for each category. With several variables the
weights are found by raking (iterative proportional fitting) so every variable's
weighted margin matches its targets. Weights are normalised to mean 1 (so the
weighted base equals the sample size), and respondents whose weighting value is
blank / a non-answer (see na_blank()) are left unadjusted. A category that
appears in the data but is missing from your targets is an error – give every
observed category a share. If a default dataset is set, this prints the Kish
design effect and the effective sample size so you can see the precision cost.
Invisibly, the parsed scheme (a named list of normalised targets).
clear_weights(), get_weights(), weight_vector(),
calc_percentage().
Other weighting:
clear_weights_cache(),
get_weights(),
weight_vector()
set_weights(c(variable = "demo_gender", "Male" = 0.49, "Female" = 0.50, "Non-binary" = 0.01)) calc_percentage(podracing_survey, satis_return) # gains a wpct column clear_weights()set_weights(c(variable = "demo_gender", "Male" = 0.49, "Female" = 0.50, "Non-binary" = 0.01)) calc_percentage(podracing_survey, satis_return) # gains a wpct column clear_weights()
A fictional survey of patrons of a turn-of-the-century (c. 1905) general emporium, written in an Edwardian register for colour. It behaves like a real survey export – a latent satisfaction drives the worded attribute ratings and the recommend score together, demographics use standard sex/country categories alongside period social-class and occupation items, and the multi-select and open-text columns mirror the shapes the helpers consume. A companion to podracing_survey for examples that want a second, very different theme.
shopping_surveyshopping_survey
A tibble with 800 rows and 25 columns:
Unique patron id (character).
Age in years (integer, 18-80).
Sex (Female / Male, some "Prefer not to answer").
Country of residence (ISO 3166 names).
World region derived from demo_country.
Edwardian social class (Upper class .. Poor).
Period occupation (Clerk, Domestic servant, ...).
Number in the household (integer).
Weekly spend at the emporium, in shillings (numeric).
How they pay: Cash, On account or Barter.
How they travel to the shop (On foot, Horse and cart, ...).
How often they visit (Never .. Always).
0-10 "would recommend to a neighbour" rating (integer).
Worded 1-5 attribute ratings (Very bad .. Very good).
Multi-select reasons for patronage; each
holds its option text when chosen, otherwise "".
Open-text comment (mostly blank).
Simulated. See data-raw/make_shopping_survey.R for the generator.
Other data:
country_region,
currency_rates,
podracing_survey
calc_percentage(shopping_survey, social_class, sort = "desc") calc_summary(shopping_survey, weekly_spend, by = payment)calc_percentage(shopping_survey, social_class, sort = "desc") calc_summary(shopping_survey, weekly_spend, by = payment)
Turns the single cell a spreadsheet export gives a "tick all that apply"
question ("Speed; Drivers; Betting") into the one-column-per-option layout
the rest of the package expects, so calc_percentage_multi(), crosstab()
and the plots all work on it.
split_multi(data = NULL, column, split = "auto", prefix = NULL)split_multi(data = NULL, column, split = "auto", prefix = NULL)
data |
A data frame. If omitted, the session default ( |
column |
The packed column (unquoted). |
split |
The delimiter between answers inside a cell. |
prefix |
Prefix for the new column names. Defaults to the column's own
name followed by an underscore, e.g. |
Google Forms, Google Sheets and most survey platforms export a multi-select question as one cell per respondent holding every answer they ticked, joined by a delimiter. That shape cannot be counted directly, because a respondent who ticked three options is one row, not three. This function unpacks it into the indicator layout the package's own datasets use.
The new columns are named prefix plus the answer text verbatim, which keeps
the labels readable all the way through to a chart: calc_percentage_multi()
strips the prefix back off and uses what remains as the option label. Answer
text is rarely a syntactic R name, so refer to the new columns with backticks
if you need them individually. Surrounding whitespace is trimmed and empty
answers are dropped, so "Speed; ; Drivers" yields two options. When no cell
contains the delimiter, each cell is treated as a single answer, which is the
right answer for a multi-select question everyone happened to answer once.
If you only want the percentages, skip this step: calc_percentage_multi()
detects a packed column and splits it for you.
The data frame with one new column per distinct answer, each holding
the answer text for respondents who chose it and "" for those who did
not, ordered most-chosen first. The packed column is kept.
calc_percentage_multi(), na_blank().
Other recode:
add_region(),
bin_numeric(),
clean_label(),
drop_items(),
ensure_numeric(),
generation_scheme(),
na_blank(),
nps_group(),
recode_age(),
recode_generation(),
recode_likert(),
recode_region()
packed <- data.frame( respondent = 1:4, motivations = c("Speed; Drivers", "Speed", "", "Betting; Speed") ) split_multi(packed, motivations) split_multi(packed, motivations) %>% calc_percentage_multi("motivations_", id = respondent, sort = "desc")packed <- data.frame( respondent = 1:4, motivations = c("Speed; Drivers", "Speed", "", "Betting; Speed") ) split_multi(packed, motivations) split_multi(packed, motivations) %>% calc_percentage_multi("motivations_", id = respondent, sort = "desc")
A clean, presentation-oriented theme family for survey reporting. The base
theme strips chart junk – no ticks,
no gridlines, centred bold titles – which suits labelled bar charts that
carry their own data labels. The _x / _y / _xy variants add back faint
major gridlines on the named axes.
theme_ezrsurvey(base_size = 11, base_family = NULL, transparent = FALSE) theme_ezrsurvey_x(base_size = 11, base_family = NULL, transparent = FALSE) theme_ezrsurvey_y(base_size = 11, base_family = NULL, transparent = FALSE) theme_ezrsurvey_xy(base_size = 11, base_family = NULL, transparent = FALSE)theme_ezrsurvey(base_size = 11, base_family = NULL, transparent = FALSE) theme_ezrsurvey_x(base_size = 11, base_family = NULL, transparent = FALSE) theme_ezrsurvey_y(base_size = 11, base_family = NULL, transparent = FALSE) theme_ezrsurvey_xy(base_size = 11, base_family = NULL, transparent = FALSE)
base_size |
Base font size in points. Defaults to |
base_family |
Base font family. |
transparent |
If |
A clean, presentation-first look: no axis ticks, no chart junk, bold centred
titles, and faint gridlines only where you ask for them. The base
theme_ezrsurvey() strips gridlines entirely (best for bar charts that carry
their own data labels); the _x / _y / _xy variants add back faint major
gridlines on the named axes. transparent = TRUE makes the backgrounds
transparent for slides. All the plot_*() helpers apply one of these for you.
A ggplot2::theme() object to add to a plot with +.
Other themes:
pal_rating,
scale_fill_nps(),
scale_fill_rating(),
theme_transparent()
df <- calc_percentage(podracing_survey, demo_gender) ggplot(df, aes(demo_gender, pct)) + geom_col() + theme_ezrsurvey() ggplot(df, aes(demo_gender, pct)) + geom_col() + theme_ezrsurvey_y()df <- calc_percentage(podracing_survey, demo_gender) ggplot(df, aes(demo_gender, pct)) + geom_col() + theme_ezrsurvey() ggplot(df, aes(demo_gender, pct)) + geom_col() + theme_ezrsurvey_y()
A small ggplot2::theme() fragment that makes the plot, panel and legend
backgrounds transparent. Add it to any theme for slide-friendly exports; the
transparent argument of theme_ezrsurvey() applies it for you.
theme_transparent()theme_transparent()
A ggplot2::theme() object.
Other themes:
pal_rating,
scale_fill_nps(),
scale_fill_rating(),
theme_ezrsurvey()
p <- calc_percentage(podracing_survey, demo_gender) %>% plot_bars() p + theme_transparent()p <- calc_percentage(podracing_survey, demo_gender) %>% plot_bars() p + theme_transparent()
Reads the colour and font theme out of your organisation's .pptx / .docx
template and makes it the session default for everything ezrsurvey produces:
charts pick up the brand accent colours and body font, and the template file
itself becomes the default reference document for report_new(),
report_deck() and scaffold_report(). One call, and analysis output is
on-brand.
use_brand(template = NULL, colors = NULL, fonts = NULL, quiet = FALSE)use_brand(template = NULL, colors = NULL, fonts = NULL, quiet = FALSE)
template |
Path to a |
colors |
Optional character vector of hex colours overriding the extracted accents. A single colour sets only the primary; a vector sets the full accent palette (first entry = primary). Invalid entries are dropped with a warning. |
fonts |
Optional character vector overriding the extracted typefaces:
either a single body font, or |
quiet |
If |
Extraction reads theme1.xml inside the template (requires the suggested
xml2 package): accent colours 1-6 become brand_colors (first accent =
brand_color_primary, the default fill of plot_bars()), and the minor
(body) typeface becomes the default base_family of theme_ezrsurvey() –
but only when that font is actually installed on this machine, so charts
never render with substituted glyph boxes. Set
ezrsurvey_options(brand_fonts_enabled = FALSE) to keep brand colours but
ignore brand fonts.
A brand font also needs a graphics device that reads the machine's fonts.
The pdf and postscript devices do not: they carry their own short list of
families, and a chart printed to one fails with "invalid font type". That is
the default device in a plain Rscript run, so use_brand() says so when it
sets a font there. Charts written with save_plot() are unaffected, because
it saves through a device that does read system fonts.
Everything lands in ordinary ezrsurvey_options() (brand_* keys), so you
can equally set the values by hand or persist them in a YAML profile; a
later use_brand() call simply overwrites them. Semantic palettes
(pal_rating, pal_nps) deliberately keep their meaning-carrying colours
and are not rebranded. Use clear_brand() to return to the neutral look.
Invisibly a ezrsurvey_brand list (see brand_info()).
brand_info(), clear_brand(), pal_brand(),
scale_fill_brand(), report_new(), scaffold_report().
Other brand:
brand_info(),
clear_brand(),
pal_brand(),
scale_fill_brand()
tmp <- tempfile(fileext = ".pptx") print(officer::read_pptx(), target = tmp) use_brand(tmp, quiet = TRUE) brand_info() clear_brand() # Or set brand values directly, no template needed: use_brand(colors = c("#0B5394", "#E69138"), fonts = "Georgia", quiet = TRUE) clear_brand()tmp <- tempfile(fileext = ".pptx") print(officer::read_pptx(), target = tmp) use_brand(tmp, quiet = TRUE) brand_info() clear_brand() # Or set brand values directly, no template needed: use_brand(colors = c("#0B5394", "#E69138"), fonts = "Georgia", quiet = TRUE) clear_brand()
Registers a data frame as the session's default, so the analysis helpers can
be called without repeating data every time. Set it once at the top of a
script or Quarto document and then omit data (or pass it by pipe).
use_dataset(data)use_dataset(data)
data |
A data frame to use as the default. |
With a default set you can drop data entirely and name the column
positionally, the way you would in the tidyverse:
calc_percentage(demo_gender) – data taken from the default;
podracing_survey %>% calc_percentage(demo_gender) – explicit pipe;
calc_percentage(other_survey, demo_gender) – an explicit data frame
always wins over the default.
The helpers tell a column from a data frame by looking at the first argument:
a data frame is used as the data; anything else (a bare column name) is taken
as the first column and the data comes from the default. Helpers that select
several columns through ... (e.g. diagnose(), calc_percentage_batch())
work the same way – diagnose(starts_with("ratings_")) needs no data.
The default applies to the analysis helpers that read a raw survey
(calc_percentage(), calc_summary(), calc_nps(), diagnose(),
crosstab(), ipm_model(), sample_comments(), ...). It does not apply
to plot/report helpers that consume an already-summarised table. The setting
lives only in the current R session; clear it with clear_dataset().
data, invisibly (so it can sit in a pipe).
get_dataset(), clear_dataset(), has_dataset().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_ezrsurvey_profile()
use_dataset(podracing_survey) calc_percentage(demo_gender) # data taken from the default calc_nps(nps_value) clear_dataset()use_dataset(podracing_survey) calc_percentage(demo_gender) # data taken from the default calc_nps(nps_value) clear_dataset()
Writes a commented YAML profile (pre-filled with the current defaults) you can
edit to set persistent options. By default it is written to the per-user
config directory (tools::R_user_dir()), which is where ezrsurvey looks first.
use_ezrsurvey_profile(path = NULL, overwrite = FALSE)use_ezrsurvey_profile(path = NULL, overwrite = FALSE)
path |
Destination path. If |
overwrite |
Overwrite an existing file. Defaults to |
Invisibly the path written.
load_ezrsurvey_profile(), ezrsurvey_options().
Other config:
apply_order(),
edit_ezrsurvey_profile(),
ezrsurvey_options(),
ezrsurvey_profile_paths(),
get_dataset(),
get_order(),
list_orders(),
load_ezrsurvey_profile(),
order_for(),
register_order(),
register_order_presets(),
remove_order(),
reset_ezrsurvey_options(),
save_ezrsurvey_profile(),
use_dataset()
tmp <- tempfile(fileext = ".yml") use_ezrsurvey_profile(tmp) head(readLines(tmp))tmp <- tempfile(fileext = ".yml") use_ezrsurvey_profile(tmp) head(readLines(tmp))
Returns the weight vector a scheme implies for a dataset – the same numbers the summary helpers use internally – so you can inspect them, attach them as a column, or feed them to another tool.
weight_vector(data = NULL, weights = NULL)weight_vector(data = NULL, weights = NULL)
data |
A data frame. If omitted, the session default ( |
weights |
A weighting scheme (any form accepted by |
This is the function behind the automatic weighting: each respondent's weight is
target_share / sample_share for their category (raked across variables when
there are several), normalised so the weighted base equals the unweighted one.
Pair it with set_weights()'s reported design effect to judge how much
precision the weighting costs.
A numeric vector of length nrow(data), normalised to mean 1.
set_weights(), calc_percentage().
Other weighting:
clear_weights_cache(),
get_weights(),
set_weights()
target <- c(variable = "demo_gender", "Male" = 0.49, "Female" = 0.50, "Non-binary" = 0.01) w <- weight_vector(podracing_survey, target) # The sample under-represents women, so their weights are above 1 and the # weighted share moves towards the target. Categories the target does not # mention keep a weight of 1, which is why their shares do not move. podracing_survey %>% mutate(weight = w) %>% group_by(demo_gender) %>% summarise( unweighted_pct = round(n() / nrow(podracing_survey) * 100, 1), weighted_pct = round(sum(weight) / nrow(podracing_survey) * 100, 1) )target <- c(variable = "demo_gender", "Male" = 0.49, "Female" = 0.50, "Non-binary" = 0.01) w <- weight_vector(podracing_survey, target) # The sample under-represents women, so their weights are above 1 and the # weighted share moves towards the target. Categories the target does not # mention keep a weight of 1, which is why their shares do not move. podracing_survey %>% mutate(weight = w) %>% group_by(demo_gender) %>% summarise( unweighted_pct = round(n() / nrow(podracing_survey) * 100, 1), weighted_pct = round(sum(weight) / nrow(podracing_survey) * 100, 1) )