Single-Line Helpers for Consumer Survey Analysis

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) and reports the design effect of Kish (1965, ISBN:978-0-471-10949-5); driver models use the relative weights of Johnson (2000) or the random forests of Breiman (2001) ; 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) .


ezrsurvey

ezrsurvey: tidyverse helpers for everyday consumer-survey work, designed so common tasks require minimal R experience.

ezrsurvey packages the patterns a research manager reaches for again and again: loading and stacking survey exports, turning questions into percentages without the count() %>% mutate() %>% pivot_wider() ritual, recoding Likert and NPS scales, importance–performance modelling, survey precision diagnostics, and a generalised plotting toolkit (clean themes, semantic palettes, dynamic axes and automatic "BAD / OK / GOOD" decision bands). It ships with two simulated survey datasets -- podracing_survey (a Star Wars pod-racing fan survey) and shopping_survey (an Edwardian shopping survey) -- so every example runs out of the box.

Installation

# install.packages("pak")
pak::pak("mashud37/ezr-research/ezr-survey")

The analysis core depends only on dplyr, ggplot2, tidyr, tibble, readr, stringr and purrr, which it attaches for you. A few extras unlock optional features: rwa (importance analysis), treemapify (quote treemaps) and ggrepel (non-overlapping IPM labels).

To draft the narrative that goes around these tables with a language model, add the companion ezrintelligence package; it takes any summary table ezrsurvey produces.

The 60-second tour

library(ezrsurvey)   # attaches dplyr, ggplot2 and the rest with it

# 1. Percentages, no count/mutate/pivot_wider
calc_percentage(podracing_survey, demo_gender, sort = "desc")
#> # A tibble: 3 x 3
#>   demo_gender     n   pct
#>   <fct>       <int> <dbl>
#> 1 Male          525    55
#> 2 Female        399    42
#> 3 Non-binary     27     3

# Drop catch-all answers; the kept ones re-base to ~100%
calc_percentage(podracing_survey, demo_job, drop = "Unemployed")

# Check-all-that-apply questions, by prefix
calc_percentage_multi(podracing_survey, "motivations_", id = respondent_id, sort = "desc")

# ...or packed into one cell per respondent, the way spreadsheets export them.
# The delimiter (";", "|" or ",") is found for you
packed <- data.frame(respondent = 1:3,
                     motivations = c("Speed; Drivers", "Speed", "Betting"))
calc_percentage_multi(packed, "motivations", id = respondent)

# split_multi() widens that column into one column per answer instead, so
# crosstabs, plots and everything else can use it too
split_multi(packed, motivations)

# Grouped + pivoted to a wide cross-tab
calc_percentage(podracing_survey, satis_return, by = region, wide = TRUE)

# A master banner table: questions down the side, groups across the top, an
# Overall column, and column percentages (numeric questions turn into a
# mean/median/sd/quartile block automatically)
crosstab_banner(podracing_survey, rows = c(satis_return, nps_value),
                cols = c(demo_gender, region))

# Pass only the data frame to cross every variable against every variable
# (identifier and free-text columns are skipped automatically)
crosstab_banner(podracing_survey)

# 2. A labelled bar chart in one pipe -- orientation, wrapping and order
#    are chosen automatically (here: many long driver names -> horizontal bars)
calc_percentage(podracing_survey, fav_driver) %>%
  plot_bars()

# A whole block of rating questions as one stacked chart, features ordered
# by their average and coloured worst-to-best
plot_rating_grid(podracing_survey, "ratings_")

# 3. NPS and the gauge
nps <- calc_nps(podracing_survey, nps_value)$nps
plot_nps_gauge(nps)

# 4. Importance / performance modelling
ipm_model(podracing_survey, nps_value, "ratings_") %>%
  plot_ipm()

# 5. Survey precision diagnostics (the appendix "data info" block, as one call)
diagnose(podracing_survey, demo_gender, dplyr::starts_with("ratings_"))

# 6. Quick saves, pipe-friendly, format inferred from the extension
calc_percentage(podracing_survey, demo_gender) %>% save_data("gender.xlsx")
plot_bars(calc_percentage(podracing_survey, demo_gender)) %>% save_plot("gender.svg")

# A tabbed Excel workbook: one sheet per question, each with its table and chart
export_summary_xlsx(podracing_survey, demo_gender, satis_return, nps_value,
                    path = "summary.xlsx")

What's in the box

Area Functions
Import read_folder(), select_prefix(), select_suffix(), parse_filename()
Recode na_blank(), drop_items(), ensure_numeric(), bin_numeric(), recode_age(), recode_generation(), recode_likert(), nps_group(), split_multi(), clean_label()
Country → region add_region(), recode_region(), recode_subregion(), country_region
Config / profile ezrsurvey_options(), reset_ezrsurvey_options(), use_ezrsurvey_profile(), load_ezrsurvey_profile()
Comments sample_comments(), sample_comments_diverse()
Summaries calc_percentage(), calc_percentage_multi(), calc_percentage_batch(), calc_summary(), crosstab(), crosstab_banner()
Modelling calc_nps(), calc_importance(), ipm_model()
Diagnostics se_mean(), se_prop(), rse(), margin_of_error(), diagnose()
Scales nice_max(), scale_y_pct(), label_pct()
Decisions annotate_bands(), mark_value(), bands_rating_3(), bands_rating_5(), bands_nps(), bands_nps_score()
Themes & palettes theme_ezrsurvey() (+ _x/_y/_xy, transparent), pal_rating, pal_nps, scale_fill_rating(), scale_fill_nps()
Branding use_brand(), brand_info(), clear_brand(), pal_brand(), scale_fill_brand()/scale_colour_brand()
Plots plot_bars(), plot_stacked_rating(), plot_rating_grid(), plot_nps(), plot_nps_gauge(), plot_gauges(), plot_ipm(), plot_quotes_tree()
Reporting report_new(), report_layouts(), report_slide()/report_section()/report_title_slide(), report_add_slide()/_plot()/_table(), report_deck(), scaffold_report() (Quarto pptx/html/pdf/docx), example_report()
Quick save save_plot() (png/svg/pdf), save_data() (csv/tsv/xlsx), save_output() (auto-dispatch), export_xlsx() (multi-tab), export_summary_xlsx() (table + chart per question)
Data podracing_survey (1,000 simulated pod-racing fans), shopping_survey (800 Edwardian shoppers)

Two things that are not obvious

  • Name the dataset once. use_dataset(podracing_survey) at the top of a script lets every helper drop the data argument entirely -- calc_percentage(demo_gender), calc_nps(nps_value), diagnose(starts_with("ratings_")). An explicit data frame or a pipe always wins over the default, and clear_dataset() ends it.
  • Column completion needs the pipe. podracing_survey %>% calc_percentage(<Tab> offers demo_gender and the rest; the un-piped calc_percentage(df, <Tab> cannot. That is the mechanism RStudio and Positron use, not a limitation of this package: they only offer a data frame's columns inside a pipe chain, for any package including the tidyverse.

Where output goes

Every save writes the file you name, where you name it:

save_plot(p, "nps.png")                       # nps.png in the working directory
save_plot(p, "charts/nps.png")                # charts/nps.png, folder created
report_deck(items, path = "q2.pptx")
scaffold_report("html", path = "report.qmd")

To collect bare names in one folder, set ezrsurvey_options(output_dir = "outputs"), for a session or in your profile.

Articles

The front page is the tour. The details live in their own articles:

Article What it covers
Getting started A whole analysis end to end on the bundled data
Banner tables The client's working table: cell types, weighting, automatic variable choice, checkpointed runs
Countries, regions and currency Free-text country columns, ISO 3166-1 in and out, converting money people answered in their own currency
PowerPoint decks One-call decks, slide-at-a-time decks, your org template, Google Slides
Quarto reports A report you edit and re-render each wave, in html, pdf, docx or pptx

License

MIT © Andreas Schellewald

Reference manual

It appears you don't have a PDF plugin for this browser. You can click here to download the reference manual.