Skip to contents

ezrsurvey (development version)

Answer order that survives into the table

  • crosstab() and crosstab_banner() no longer break on a question called value or n. Both used those names for their own counting, so such a column came back as a table of the wrong shape without a word: percentages for headings, a list-column, a banner group of zeros. The counting now runs under names no survey uses. The long form of crosstab() still puts its cells in a column called value, so it refuses a question of that name and says so; the wide form and the banner handle it.
  • calc_percentage() and crosstab() keep a factor’s own level order. Without a registered order, a factor came back as text in alphabetical order, so PISA’s “Strongly disagree” to “Strongly agree” read Agree, Disagree, Strongly agree, Strongly disagree. A registered order or levels = still wins; a factor’s levels now come next, and only then data order.
  • crosstab() puts its columns in level order whenever the column variable has one. tidyr names new columns by first appearance, so a registered order could still come out scrambled when the first row lacked the first answer.
  • bin_numeric(), recode_age() and recode_generation() return factors whose levels follow the bands, where they used to return text. Text sorted “100-150k” before “40-70k” in every table built from it. Code comparing the result to a string is unaffected; wrap it in as.character() where plain text is needed.
  • The banner tables vignette said a finished run deletes any checkpoint. It deletes only the one checkpoint = TRUE made; a path you chose is kept, as the help page already said.

ezrsurvey 0.7.0

A country column people typed themselves

Free-text country questions do not come back tidy. In one real open salary survey of 26,232 people, nine thousand wrote “United States” and eight thousand wrote “USA”; the bundled table matched the first and missed the second, so recode_region() resolved 49% of the column.

  • recode_region(), recode_subregion() and add_region() now fold the answer down before looking it up (case, punctuation, accents and a leading “the” are dropped) and then try a table of the spellings people actually use. It carries endonyms ("Deutschland", "Brasil", "Espana"), the constituent countries of the United Kingdom, and ISO 3166-1 alpha-2 and alpha-3 codes, so a column already coded to the standard needs no preparation. On that same survey the three functions now resolve 99.3%, and what remains are real typos worth seeing.
  • A two-letter code that is also an ordinary English word is read as a country only when it was typed in capitals, so "NO" is Norway and "no" stays unmatched. Unambiguous codes match either way.
  • country_region gains iso2 and iso3 columns, so a region breakdown can be reported against ISO 3166-1 rather than against country names. Every country carries both except Kosovo, which has no code of its own. Namibia’s alpha-2 code is the two letters "NA", which is worth knowing before filtering on the column.
  • Three countries in that table were misspelled and so could never match an answer: "Ise of Man", "America Samoa" and "Kazakstan". Bermuda was missing altogether. All four are fixed, and the duplicate rows the corrections exposed are gone, which takes the table from 182 rows to 180.

Decision bands on a scale that is not 1 to 5

Satisfaction is often collected on 0 to 100, and seven-point agreement scales are common. The band presets are written on 1-5, 0-10 and -100..100, so until now a chart on any other scale meant dividing the data to meet the thresholds, which left the axis showing a scale nobody had been asked about.

  • rescale_bands() stretches any band specification onto the scale the chart actually uses: bands_rating_3() %>% rescale_bands(to = c(0, 100)). The mapping is proportional, and labels and colours are untouched. It works on a hand-built band data frame as well as on the presets.

A registered answer order now reaches the table

register_order() set the levels of the answer column and stopped there. The levels are what a chart reads, so charts were right; a table printed to the console or written to CSV carries no levels with it, so every number anybody actually read came back in alphabetical order. On a recency scale that puts “In the past month” above “In the past week”.

  • calc_percentage() sorts its rows into the registered order. A by = breakdown keeps its groups together and orders within each one.
  • crosstab() does the same on both margins. Column order was affected too, because tidyr names new columns in order of first appearance rather than by factor level.
  • A margin with no registered order keeps the order it already had, so putting the rows right does not scramble the columns.

Charts that were drawn on the wrong axis

  • plot_ipm() pinned its performance axis to 1-5 whatever bands it was given. A study rated on 0-100 or on a seven-point scale came back as an empty panel with the bands still drawn across it, and ggplot2’s only complaint was that some rows had been removed. The axis now runs over whatever scale the bands describe, and a feature that really does fall outside them is named in a message rather than dropped in silence.

Countries the lookup could never reach

Three separate faults kept recode_region() from matching countries that were spelled perfectly. On the Stack Overflow Developer Survey, whose country column is coded to ISO 3166-1, 3,126 of 87,973 answers came back NA. They now resolve to 99.93%, and the only thing left unmatched is the answer “Nomadic”.

  • The two sides of the lookup were folded differently. The answer had its punctuation stripped and its accents transliterated; the table was only lower-cased. Every country whose name carries a hyphen, an apostrophe, a comma or brackets was therefore unreachable however it was typed: "Timor-Leste", "Guinea-Bissau", "Cote d'Ivoire", "Korea, North", "Gambia, The" and some thirty more.
  • country_region held 180 of the 249 entries in ISO 3166-1. What was missing was not obscure: Ghana, Ethiopia, Senegal, Rwanda, Yemen, Benin, Botswana, Haiti, Mali, Sierra Leone and South Sudan all returned NA. The table is now complete at 255 rows, grouped the way the package groups countries rather than the way the UN does, so no answer already built on it changes.
  • The official ISO names did not match. "Viet Nam", "Syrian Arab Republic", "Lao People's Democratic Republic", "Brunei Darussalam", "Cabo Verde", "Eswatini", "North Macedonia" and the rest are what a questionnaire built on the standard shows a respondent. They match now, in ISO’s inverted order ("Moldova, Republic of") and in the order the name is actually said ("Republic of Moldova").

A country column folded the same way on every machine

Matching a country meant stripping its accents, and that was left to iconv(to = "ASCII//TRANSLIT"), which three platforms turned out to do differently. Windows writes a German sharp s as ?; macOS leaves a circumflex where it found one, so "Cote d'Ivoire" typed with its accent matched on Linux and came back NA on a Mac. A published figure changed with the machine that produced it.

  • recode_region(), recode_subregion() and add_region() now fold the eighty-eight accented letters and four ligatures a country name can carry by hand, and reach iconv only for anything outside that range. The answer is the same on every platform.
  • Four countries were filed against no grouping scheme at all: Algeria sat in Eastern Europe, Laos in Africa, and Mauritius and Reunion in South Asia. They are now in Africa, South East Asia and Africa. The deliberate departures from UN M49 are unchanged: Egypt, Iran, Afghanistan, Pakistan, Djibouti, Kazakhstan and Uzbekistan report as Middle East, and Turkey, Cyprus, Georgia, Armenia and Azerbaijan as Europe, because that is how consumer research buys those markets.
  • The endonyms of the two most answered countries were missing while smaller countries had theirs: "Estados Unidos", "Etats-Unis", "Vereinigte Staaten", "Stati Uniti", "Reino Unido" and "Royaume-Uni" now match.

Brand fonts and the device that has to draw them

  • use_brand() says so when it sets a typeface in a session whose default device is pdf, which is what a plain Rscript run gets. That device carries its own short list of font families and fails at drawing time with “invalid font type” rather than substituting. Charts written with save_plot() were never affected.

Errors that name what went wrong

  • A mistyped column name now says which name failed and offers the nearest one the data has. calc_percentage(d, gendr) reported replacement has 0 rows, data has 3, which describes an assignment the caller never made; it now says Column `gendr` not found in the data. Did you mean `gender`?. The same applies to calc_summary(), calc_nps(), crosstab(), plot_nps() and split_multi().
  • calc_nps() works on a frame filtered down to no rows instead of failing with replacement has 1 row, data has 0. Filtering to a segment nobody fell into is ordinary, and the answer is a zero count, not an error.
  • crosstab_banner() refuses a frame with no rows in a sentence that says a filter is the usual cause, rather than reporting the same internal mismatch.
  • plot_stacked_rating() names the argument the caller actually omitted. Leaving out level reported a missing level_sym, which is not an argument it has.
  • plot_rating_grid() reports two answers that land on the same rank. recode_likert() falls back to matching on a substring, and “Neither agree nor disagree” contains “disagree”, so a five-point scale handed only four levels drew two segments both numbered 2. The chart looked plausible and was wrong; now it says which level is missing.
  • plot_quotes_tree() says which column it cannot find. Its defaults are the comment and length columns sample_comments() produces, and handed anything else it failed at drawing time with a ggplot error about an object of type <function>, because length had resolved to the base function.

Reading an export that has a second header row

  • read_folder() notices when the first row of a file is question wording rather than an answer, which is how survey platforms export the question text, and says so. Read as data it becomes respondent number one and shifts every count by one. The new question_row argument drops that row from every file (TRUE), keeps it silently (FALSE), or keeps it and warns (NULL, the default).

Smaller fixes

  • recode_age() reports answers that held no number at all. "young" and "prefer not to say" became NA in silence, which made a column that was never numeric look like a column of missing ages. New quiet argument.
  • The package has a logo. README.md had pointed at man/figures/logo.png since the first release without that file ever existing, so the front page of the README and of the documentation site both opened with a broken image.
  • The bundled PowerPoint templates are built from a blank presentation of the project’s own, not from another package’s file.
  • The documentation no longer describes the package in terms of the work it was extracted from. Nineteen references to “the original reports”, “the original survey charts” and similar reached sixteen shipped help pages, and one named a theme function that was never part of this package.

Multi-select questions packed into one cell

Google Forms and most spreadsheet exports put every answer a respondent ticked into a single cell, joined by a delimiter. That shape could not be counted before, because a respondent who ticked three options is one row, not three.

  • calc_percentage_multi() now detects a packed column and unpacks it. When the prefix matches exactly one column and a ";", "|" or "," is found inside it, the answers are separated and the answer text becomes the option label. split = "," forces a delimiter and split = FALSE turns the detection off. A block of one-column-per-option behaves exactly as before.
  • split_multi() is the new recode helper for the same shape, widening a packed column into one column per answer so that crosstab(), the plots and everything else can use it too.

calc_nps() shows how the score was reached

  • The result gains pct_detractors, pct_passives and pct_promoters, with the counts behind them in detractors, passives and promoters. A single NPS hides its own composition: +20 from 40% promoters and 20% detractors is a different picture from +20 with 25% and 5%. Counts are unweighted; the shares follow nps and so are weighted when a scheme is active.

calc_importance() offers a second and third opinion

  • New method argument. "rwa" (relative weights analysis) remains the default and is unchanged. "forest" adds random-forest permutation importance, which sees non-linear effects and interactions that relative weights cannot, so a feature ranking high there and low under "rwa" is a signal worth chasing. "correlation" needs no suggested package at all.
  • All three are rescaled to sum to 100 and returned strongest driver first, so they are directly comparable and any of them can be fed to plot_ipm(). ipm_model(method = ) passes the choice through.

plot_gauges() says what its bars are measuring

  • Each bar now prints its own band boundaries underneath, so a Net Promoter bar reads -100 0 30 70 100 and a quality bar reads 1 3 4 5. The bars share one panel but not one scale, so no axis could serve them both and they carried no scale at all: a marker two thirds along the bar said nothing about the score it marked.
  • A band name too wide for its band is now left out instead of being drawn over its neighbours or cut off at the edge of the panel. "EXCELLENT" in the 30-point band at the top of the NPS scale was printed as "EXCELLEN".

Smaller changes

  • se_prop(pctp = TRUE) returns the standard error in percentage points, the form report footnotes use, instead of leaving you to multiply by 100.
  • The promoter callout in plot_nps() is centred over the promoter bars, like the detractor and passive callouts beside it. It had been pinned to the right edge of the panel, which left "Very likely" hanging off to one side of the "% PROMOTER" line above it.
  • Help pages no longer print every example’s output twice. The examples carried a hand-written copy of their own results, which the website then rendered alongside the real thing.
  • Every plot function’s examples now draw their chart on the reference page, rather than most of them assigning the plot to a variable and showing nothing.
  • The relative-standard-error bands in rse_rating() now say exactly what the Australian Bureau of Statistics does and where ezrsurvey departs from it.

ezrsurvey 0.6.0

Attaching the package attaches seven packages, not the whole tidyverse

  • library(ezrsurvey) still puts dplyr, ggplot2, tidyr, tibble, readr, stringr and purrr on your search path, so nothing changes in a script. The package now names those seven in Depends rather than depending on the tidyverse meta-package, which had tied it to every package in that tree.

Checkpoints clean up after themselves

  • crosstab_banner(checkpoint = TRUE) now deletes its checkpoint once the table is built. A finished run has nothing left to resume from, so the file it kept under tools::R_user_dir() is removed instead of accumulating.
  • clear_checkpoints() empties that folder, for runs that were interrupted and never repeated.
  • A checkpoint path you chose yourself (checkpoint = "my-run.rds") is your file: it is left exactly where it is, and clear_checkpoints() never touches it.

The outputs folder now says where it put things

  • The first save of a session that redirects a bare file name prints the folder it used, once, so nothing goes missing in a working directory that does not contain it.
  • scaffold_report() now follows the same rule as every other writer: a bare name lands in ezrsurvey-outputs/, so the .qmd sits where its rendered output will. Pass a path naming a directory to put it somewhere else.

Rating scales: a fix that can move existing numbers

  • recode_likert() now tries the longest answer wording first. A scale whose levels nest inside one another (“Good” inside “Very good”, “Likeable” inside “Very likeable”) previously resolved any answer the exact pass missed to the shorter level, so an answer carrying an emoji or a stray character was quietly downgraded. If your data has such a scale and any untidy answers, ratings, feature averages and ipm_model() performance scores will change, and the new numbers are the correct ones. Supplied synonyms follow the same longest-first rule.
  • bin_numeric() reports values that fell outside breaks instead of turning them into NA in silence, and takes quiet = TRUE to suppress that. A closed top band under an open-ended label (35 to 40.5 labelled “35 to 40+”) silently drops everyone above it; Inf is what that label means.
  • A registered order that does not list an answer now names the answers that became NA rather than letting them appear as an unlabelled bar.

Progress on the slow helpers

  • The helpers that can run for minutes – crosstab_banner(), export_summary_xlsx(), report_deck(), read_folder(), diagnose() – now report the question, file or slide they are on, with a measured estimate of the time remaining. A full every-variable banner is one cross-tab per question per grouping variable, so it takes minutes on a wide survey and used to give no sign of life at all.
  • New progress option (ezrsurvey_options(progress = )): "auto" (default) reports in an interactive session and stays silent in scripts, vignettes and R CMD check; TRUE / FALSE force it either way.

Every output lands in one folder

  • A bare output file name now goes into ezrsurvey-outputs/, created on demand: save_plot(p, "nps.png") writes ezrsurvey-outputs/nps.png, and the same holds for save_data(), save_output(), export_xlsx(), export_summary_xlsx(), report_save() and report_deck(). Previously only the path = NULL default used that folder, so any script that named its files scattered them through the working directory.
  • A path that names a directory is written exactly as given, which is how you override it: "./nps.png" for the working directory, "charts/nps.png" for a folder of your own, or any absolute path (so tempfile() is unaffected).
  • New output_dir option (default "ezrsurvey-outputs") renames that folder for a project, or set it to "." to put bare names back in the working directory.

Confirming an automatic variable selection

  • crosstab_banner() and export_summary_xlsx() choose their own variables when none are named, and that choice decides everything they produce. Both now print the questions kept, the grouping variables and the columns skipped, and wait for a yes before a run that takes minutes. Naming rows / cols (or the variables to summarise) is your own choice and is never questioned.
  • New confirm option and argument: "auto" (default) asks in an interactive session and never asks in a script, so an unattended run cannot stall on a prompt; TRUE / FALSE force it either way. Answering no computes nothing and returns NULL.

Resuming an interrupted table

  • crosstab_banner(checkpoint = ) saves each question as it finishes, so re-running the identical call after a crash or an interrupt continues instead of starting over. A checkpoint written from different data or different arguments is reported and discarded, never blended into the new run. Use TRUE to have the file managed under tools::R_user_dir(), or name a path to choose the location. Nothing is written unless you ask.
  • Weights are computed once per dataset and scheme and reused, instead of re-running the raking loop inside every cell of a banner table. clear_weights_cache() empties the cache; clear_weights() does it too.
  • sample_comments_diverse(max_candidates = ) narrows a large corpus to a random shortlist (default 500) before comparing every comment with every other, which is what made it slow and memory-hungry on a big survey.

New

  • plot_rating_grid() builds a stacked rating chart for a whole block of prefix-sharing columns in one call: tabulate, tidy the question names, number the answers by their place on the scale, stack. It replaces an eight-line block per question block, and it reports answers that are not on the scale instead of leaving them as an unranked segment.
  • clean_label() is now exported. It is the tidying calc_percentage_multi() and ipm_model() already applied to their own output, so a hand-built table can be made to match them: labels that disagree will not join, which is what silently empties a comparison chart.
  • calc_percentage_batch(clean_names = , prefix = ) applies that same tidying to its variable column.
  • bands_nps_score() exposes the Net Promoter Score band set (needs work / good / great / excellent over -100..100) that was previously locked inside plot_nps_gauge(). Note bands_nps() is the 0-10 answer scale and is a different thing.
  • The band presets take from / to to trim themselves to the part of the scale a chart actually shows, e.g. bands_rating_3(from = 2) on a 2-5 panel.
  • report_new(keep_slides = FALSE) starts from an empty deck, dropping any boilerplate slides the template carries.
  • sample_comments(by = ) samples n comments per group and keeps the group as a column, replacing a hand-rolled loop over the groups.

ezrsurvey 0.5.0

AI summaries move to ezrintelligence

  • The language-model helpers are removed from ezrsurvey and now live in the companion ezrintelligence package: ai_chat(), ai_summarise(), ai_report_sections(), ai_context(), the prompt-template registry (list_prompts(), get_prompt(), register_prompt()) and key management (set_llm_key(), get_llm_key(), has_llm_key(), delete_llm_key(), list_llm_keys()). Install it alongside ezrsurvey and pass it any summary table ezrsurvey produces. ellmer and keyring are no longer suggested dependencies.
  • report_deck() loses its ai, ai_titles, chat, provider, model and context arguments. For AI slide text, call ezrintelligence::ai_slide_text() on the table behind a slide and place the title and bullets it returns yourself.
  • The Quarto scaffolds and the worked example lose their ai parameter and the eval-gated AI chunks; they render with placeholder narrative and their data parameter as before.

Fixes

  • export_summary_xlsx() no longer fails with “Failed to save workbook” when R_ZIPCMD names a zip tool that is not installed. openxlsx2 reaches for an external zip whenever that variable is set, and R sets it to a bare zip on Windows under R CMD whether or not a zip.exe exists; the workbook is now written with openxlsx2’s own zipping code whenever the named tool cannot be found.
  • The crosstab_banner() example that crosses every variable against every other moves to \donttest, where its runtime belongs. # ezrsurvey 0.4.0

Cross-tabs and summary workbooks

  • crosstab_banner() builds the market-research banner table: a stack of question variables down the side (rows) and one or more grouping variables across the top (cols), plus a whole-sample Overall column. Categorical questions become column-percentage blocks (each column sums to ~100 within a block); numeric questions turn into a mean/median/sd/quartile block automatically. cell = "diff" shows each cell’s distance from the Overall column, long = TRUE returns the tidy form, and flextable = TRUE renders the two-row spanning header for a slide or Word report. It generalises crosstab() from a single pair to a whole table. Pass only the data frame to cross every variable against every variable; identifier and free-text columns are skipped, and check-all-that-apply blocks (e.g. motivations_*) are folded into a single multi-select stub question with the correct question-level base.
  • export_summary_xlsx() writes a tabbed Excel workbook with one worksheet per question, each holding that question’s summary table and its chart (percentages and a bar chart for categorical questions, a calc_summary() table and a histogram for numeric ones, a calc_percentage_multi() table for a check-all-that-apply block). Called with just the data frame it writes every question at once, one multi-select block to a single sheet, skipping identifier and free-text columns. The Excel counterpart of the slide/Word report_deck(). Needs the suggested openxlsx2 package.

Output location

Working without repeating the data

  • The tidyverse metapackage is now attached with ezrsurvey. library(ezrsurvey) brings dplyr, ggplot2, tidyr, stringr and the rest with it, so scripts and reports never need to load the packages ezrsurvey wraps.
  • use_dataset() finally saves the typing it promised. With a default set, columns can be passed positionally with no data and no column =: calc_percentage(demo_gender), calc_nps(nps_value), crosstab(demo_gender, region), diagnose(starts_with("ratings_")). The helpers tell a data frame from a column by looking at the first argument, so an explicit data frame or a pipe still wins. Previously only the much longer calc_percentage(column = demo_gender) worked, which saved nothing over just passing the data.

Charts

  • plot_gauges() draws several scores as thin banded gauge bars stacked one above another – the summary-slide staple of the Net Promoter Score over the average feature quality rating, each on its own scale with a marker, instead of one fat single bar. plot_nps_gauge() remains for a single score.

Colour correctness

  • Rating colours now agree with the decision bands. A feature averaging 2.4 is inside the red BAD band (1-3) but was drawn amber, because the point colour came from a rounded 1-5 code while the band behind it came from bands_rating_3(). [plot_ipm()] now colours every point by the band it actually falls in, and pal_rating uses the same thresholds (1-2 bad, 3 ok, 4-5 good). plot_ipm() gains a bands argument.

Organisation branding

Reporting

  • One line per slide. New wrappers collapse calc -> plot -> add onto a single pipe step so a deck script reads as one line per slide: report_slide(title, content) starts a titled slide and places its content (a ggplot becomes a chart, a data frame a table, a character vector a bullet list); report_section("DEMOGRAPHICS") drops a single-word divider; and report_title_slide(title, subtitle) opens the deck. The rewritten example_report() deck is built this way as a full agency read-out – cover, chaptered sections and every question block in the questionnaire (recommendation, the six experience ratings and their drivers, motivations, the three sponsor brands, the whole respondent profile, favourite drivers, open-text comments and a methods appendix) – with each slide title the survey question it answers.

  • Slide tables fill the slide. report_add_table() now styles the table (banded header, centred cells, a readable 12pt font) and widens its columns to span the content placeholder, so a summary table fills the slide instead of sitting tiny in a corner.

  • Headlines and bullets no longer overflow. The bundled templates bring the master title/body font sizes down to deck-appropriate values and give every text placeholder shrink-to-fit, so a descriptive question headline or a long bullet list is scaled to its box rather than spilling out of it.

  • Decks are 16:9 by default, and properly designed. Without a brand template, report_new(), report_deck() and the Quarto pptx scaffold use a built-in widescreen template with a coherent navy/gold identity in place of the dated default Office colours: a full-bleed navy cover with a large left-aligned title, gold accent rule and a subtitle strapline; full-bleed navy section dividers carrying a single large section word; and content slides with a navy 24pt title over one slim rule (no stray hairlines) and a live slide number in the corner. report_title_slide() gains a subtitle argument for the cover strapline. Pass style = "plain" for the same palette with no decoration, or slide_numbers = FALSE to drop the numbering. Both templates keep the standard PowerPoint layout names (plus “Content with Caption”), so Quarto renders against either without falling back to Pandoc’s own template.

  • Bars are a constant thickness across charts. geom_col() widths are a fraction of a category slot, so a three-answer chart used to draw bars over twice as thick as a ten-answer one and a deck looked incoherent as you flicked through it. [plot_bars()] now scales the fraction with the bar count so the drawn thickness is the same everywhere (bar_width / bar_ref_items, see ezrsurvey_options()).

  • example_report() copies a complete worked report into your project: a full narrative report over the bundled podracing_survey and the matching deck script. Both are written the way a real readout is – a story that runs headline, then cause, then segment, then evidence; slide titles that state the finding rather than name the chart; and every figure in the prose computed from the data rather than typed in, so the narrative cannot drift.

  • The officer builders now work with any corporate template: slide layouts are chosen by inspecting placeholder types (not locale-dependent layout names), slide titles are skipped with a warning when a layout has none, and plots/tables are rendered at the exact size of the content placeholder with sensible fallbacks. report_layouts() lists what a template offers.

  • report_deck(ai = TRUE) drafts 3-4 takeaway bullets per slide (and, with ai_titles = TRUE, headline titles) from the data behind each item, reusing one chat across the deck; bullets land in a two-content layout when the template has one, else in the speaker notes. AI failures degrade to plain slides.

  • Quarto scaffolds rewritten as four distinct report skeletons (pptx / html / docx / pdf) with data and ai parameters, placeholder narrative, executive summary and a precision appendix; scaffold_report() gains author and reference_doc (defaulting to the brand template).

  • README documents the Google Slides workflow: build the branded .pptx and import it (File > Import slides); what survives and what to watch.

AI summaries

  • All prompt templates rewritten to an analyst standard (quote only present figures, respect margins of error, name base sizes) and five added: slide_bullets, thematic_analysis, segment_comparison, methodology and full_report.
  • ai_context() bundles sample size, question wording, base, fieldwork and precision notes (auto-derived from the raw survey) into every prompt via the new context argument of ai_summarise() / ai_report_sections() / report_deck().
  • Tables are now sent to the model as markdown pipe tables; register_prompt() gains an output_contract field pinning the answer’s format; ai_report_sections() gains chat for connection reuse.

ezrsurvey 0.3.1

Output location

  • All savers now default to a project-local outputs/ folder in the working directory instead of requiring a path: save_plot() (outputs/plot.png), save_data() (outputs/data.csv), export_xlsx() (outputs/tables.xlsx), report_save() / report_deck() (outputs/report.pptx / .docx). Missing directories are created; explicit paths behave as before.

Annotation layer

  • annotate_bands() band labels no longer sit on top of the marker line: label_offset defaults to 4% of the opposite-axis range and labels are justified into the panel, so nothing clips at the panel edge. text_size now defaults to the theme’s base font size (11 pt) instead of a fixed geom size, and multi-line band labels get a tighter line height.
  • mark_value() labels are justified inside the panel next to the marker line instead of being half-clipped at the Inf edge.
  • plot_nps() detractor/passive/promoter callouts no longer clip at the panel top or overlap each other; the promoter callout right-aligns to the panel edge.
  • plot_nps_gauge() gains a label_size argument; band labels default to the theme’s base font size.

Reproducibility

  • sample_comments() gains a seed argument (matching sample_comments_diverse()), so quote selections can be reproduced; the RNG state is restored afterwards.
  • calc_percentage(long = TRUE) no longer triggers the deprecated tidyselect external-vector warning.

ezrsurvey 0.3.0

Smarter charts and cleaner questions

  • plot_bars() now lays itself out. orientation = "auto" (the default) draws vertical columns for a few short labels and horizontal bars for many or long ones, wraps long labels with str_wrap(), steps the data-label size down as bars multiply, and orders bars so the longest sits at the top (bars) or on the left (cols). An intentional ordinal scale (an ordered factor from a registered order) keeps its order. orientation / sort / wrap / label_size and the new bar_* options (see ezrsurvey_options()) override the automatics; the old flip argument still works.
  • drop_items() plus a drop = argument on calc_percentage(), calc_percentage_multi(), calc_percentage_batch() and crosstab() remove unwanted answers (e.g. "Other", "Don't know") before counting, so the kept answers re-base to ~100%. Set a session default with ezrsurvey_options(drop_answers = ...).

Data

  • The bundled example survey is now podracing_survey (renamed from consumer_survey): a Star Wars pod-racing fan survey with international-standard demographics (ISO/IEC 5218 sex, ISCED education, ILO labour-force status, ISIC sectors, ISO 3166 countries) and diverse, entity-rich open-text comments for the text / NER helpers.
  • New shopping_survey: a turn-of-the-century (Edwardian) shopping-behaviour survey – a second, very different theme for examples.
  • register_order_presets() now ships an education_isced order (was education_us).

ezrsurvey 0.2.0

Survey weighting

  • set_weights() defines a post-stratification / raking scheme from target shares, e.g. set_weights(c(variable = "demo_gender", Male = 0.49, Female = 0.50, "Non-binary" = 0.01)), and multiple variables at once. With one variable it is exact post-stratification; with several it rakes (iterative proportional fitting) so every margin matches.
  • Once set, the summary helpers weight automatically: calc_percentage() (and calc_percentage_batch()) gain a wpct column beside n/pct, while calc_nps(), calc_summary() and crosstab() default to weighted values. Pass weights = FALSE to any of them to opt out, or weights = <spec> to weight a single call ad hoc.
  • clear_weights(), get_weights(), has_weights() manage the scheme; weight_vector() returns the per-respondent weights (and set_weights() reports the Kish design effect and effective sample size).

ezrsurvey 0.1.0

First release. A single-line-helper toolkit for everyday consumer-survey analysis, aimed at research and insights professionals.

Highlights