survey-page-conventions
Standards derived from the survey-page-conventions skill
0.1 Purpose
This standard clarifies good practice as it applies to societal mirror pages that present data from Survey Monkey.
0.2 Standard
1 Survey Page Conventions
Survey pages draw from labelled SPSS files such as (m26, m25, m24, L26, L25, L24) loaded by shared_code_26.R. They use the labelled package throughout — var_label(), to_factor(), to_character() — and differ visually from admin pages via background tint.
Alternatively use the following code:
dictionary <- spss_df |> look_for()1.1 Data Dictionary
When constructing pipelines or graphs, use ./back-end/dd_m26.json as a reference data dictionary describing the m26 survey.
When constructing pipelines or graphs, use ./back-end/dd_C26.json as a reference data dictionary describing the C26 survey.
1.2 Visual Identity: Background Tints
| Survey | Constant | Hex | Use |
|---|---|---|---|
Member (m26, m25, m24) |
member_background_tint |
#FEF7F4 | warm pinkish |
Centre/Leader (C26,L25, L24) |
leader_background_tint |
#F4FFF5 | cool greenish |
| Admin/SDB | sdb_background_tint |
#F4F4F4 | neutral grey |
Every survey ggplot must carry its tint in both backgrounds:
theme(
plot.background = element_rect(fill = member_background_tint),
panel.background = element_rect(fill = member_background_tint)
)1.3 ggplot Template: Horizontal Bar (percentages)
The dominant pattern — categories sorted by pct, labels inside bars:
data |>
mutate(
category = str_wrap(category, width = 30),
category = fct_reorder(category, pct)
) |>
ggplot(aes(category, pct)) +
geom_col(fill = dark_bar_green) +
geom_text(
aes(label = scales::percent(pct, accuracy = 1)),
y = pct / 2, # center in bar
color = "white",
fontface = "bold"
) +
scale_y_continuous(
labels = scales::percent_format(), # data is 0–1 proportions
limits = c(0, 0.8)
) +
labs(title = str_wrap("Question text here", width = 40), x = NULL, y = NULL) +
coord_flip() +
mirror_theme() +
theme(
panel.grid.major.y = element_blank(),
plot.background = element_rect(fill = member_background_tint),
panel.background = element_rect(fill = member_background_tint)
)Scale note: Survey percentages are often stored as 0–1 proportions — use scales::percent_format() and scales::percent(pct, accuracy = 1). If stored as 0–100 (e.g., after * 100), use scales::percent_format(scale = 1) and scales::percent(pct, accuracy = 1, scale = 1).
1.4 ggplot Template: Stacked Bar (multi-response fill)
For yes/maybe/no or ordered response categories:
data |>
mutate(
response = to_factor(response),
response = fct_rev(response),
var_label = fct_reorder(var_label, pct, .fun = sum)
) |>
ggplot(aes(var_label, pct, fill = response)) +
geom_col() +
geom_text(
aes(label = scales::percent(pct, accuracy = 1, scale = 1)),
color = "white",
fontface = "bold",
position = position_stack(vjust = 0.5)
) +
scale_fill_manual(values = c("Yes" = "#247467ff", "Maybe" = "#63bba9ff")) +
scale_y_continuous(
labels = scales::percent_format(scale = 1),
limits = c(0, 80)
) +
labs(title = str_wrap("...", 40), x = NULL, y = NULL, fill = NULL) +
coord_flip() +
mirror_theme() +
theme(
legend.position = "top",
panel.grid.major.y = element_blank(),
plot.background = element_rect(fill = member_background_tint),
panel.background = element_rect(fill = member_background_tint)
)1.6 labelled Package Patterns
The automatic var_label axis labels and the label-handling below depend on recent package versions. Survey pages require ggplot2 ≥ 4.0.0, haven ≥ 2.5.5, and labelled ≥ 2.16.0; on older versions the bare-column-name labelling silently falls back to variable names.
Survey data is SPSS-style labelled. Always handle labels explicitly:
# Get variable label (question text) — use as plot title
var_label(m26$q0035)
var_label(df) |> pluck(1) # from single-column df
# Convert value labels to factor for plotting
df |> to_factor() # converts all labelled cols
df |> mutate(q0035 = to_factor(q0035)) # single col
# Convert to character (for counting, joining)
df |> to_character()
# Inspect what variables exist
m26 |> look_for("keyword")
makedd() # creates m26dd, m25dd, L25dd, m24dd, L24dd in global env
# ggplot2 4.0+ picks up var_label automatically on bare column names:
ggplot(m26, aes(q0035, q0036)) + geom_point() # axis labels auto-filled
# For bulk override: labs(dictionary = unlist(var_label(df)))1.7 Variable-Specific Transformations
1.7.1 q0080 — Decade first joined (always collapse)
Whenever q0080 is used, immediately follow to_factor() with this collapse to reduce the 7-level decade variable to 4 meaningful groups:
to_factor() |>
mutate(
q0080 = fct_collapse(
q0080,
"70's-80's" = c("1970s", "1980s"),
"90's-2007" = c("1990s", "2000-2007"),
"2008-2017" = c("2008-2012", "2013-2017")
)
) |>The four resulting levels are: "70's-80's", "90's-2007", "2008-2017", "2018-2025".
1.7.2 q0032 — Relates to a centre (strip parenthetical label)
q0032 value labels for “No” and “Not sure” contain a long parenthetical that must be stripped before plotting or factoring:
mutate(
q0032 = str_remove_all(
as.character(q0032),
"\\s*\\(you will automatically skip to the next set of questions\\)"
),
q0032 = str_trim(q0032),
q0032 = factor(q0032, levels = c("Yes", "Not sure", "No"))
)Apply this after to_character() or as.character() — do not apply to the raw labelled column.
1.7.3 q0034 — Primary membership identity (filter to centre-affiliates)
q0034 asks respondents to identify as centre member, global member, friend, donor, etc. It is only meaningful for those who relate to a centre. Always filter to q0032 == 1 before using q0034:
m26 |>
filter(q0032 == 1) |>
select(q0080, q0034) |>
...q0034 has 6 long value labels — always apply str_wrap(as.character(q0034), width = 30) after to_factor() to keep legends readable. Use fct_inorder() afterward to preserve label order:
mutate(
q0034 = str_wrap(as.character(q0034), width = 30),
q0034 = fct_inorder(q0034)
)1.8 Color Palette — When to Use scale_fill_brewer
The standard green_step_2[3:1] fill works for 3-level outcomes. For variables with 4+ unordered categories (e.g., q0034 with 6 levels), use:
scale_fill_brewer(palette = "Dark2")For ordered/sequential outcomes (low → high), continue using green_step_2[3:1] (3 levels) or green_step_5 (5 levels).
1.9 Variable Naming Conventions
- Single items:
q0035,q0038,q0062 - Multi-part items:
q0037_0001throughq0037_0005— generate withsprintf("q0037_%04d", 1:5) - Binary flags (role held): coded
1= Yes - Open-ended text companions:
q0037_other(character class — excluded bydisp_var_prep) - Sub-population filter:
q0032 == 1for centre-affiliated respondents
1.10 Standard Sub-population Filters
# Set once at top of page, then use throughout
m26_relate_to_a_center <- m26 |> filter(q0032 == 1)
n_relate_to_local_center <- nrow(m26_relate_to_a_center)
# Always report n in table source notes:
tab_source_note(source_note = paste0("n = ", n_relate_to_local_center, " ..."))1.11 gt Table Template (survey)
data |>
gt() |>
cols_label(var = "Label", pct = "% of Total") |>
fmt_percent(is.numeric, decimals = 0) |>
tab_header(title = md("Title<br>Subtitle")) |>
tab_source_note(source_note = paste0("n = ", n, " respondents")) |>
mrr_gt_theme(col_label_size = 18) # note: survey tables often use 18For grouped tables with row group headers:
gt() |>
tab_style(
style = cell_text(weight = "bold"),
locations = cells_row_groups()
) |>
mrr_gt_theme()1.12 Page Structure
Survey sections follow the Capacity + Interest + Table tabset pattern:
1.13 Role Name
- (the following three colons are deliberatly spaced to avoid being interpreted as a YAML front matter block)
-
: : panel-tabset
1.14 Capacity
#| label: img-role_capacity
q <- role_subset(
m26_relate_to_a_center,
q00XX,
c("q00YY", sprintf("q00ZZ_%04d", 1:N))
)
plot_1_level(q$counts$n_q00YY)1.15 Interest in support
#| label: img-role_interest_in_support
interest_counts <- all_pct_selected |>
filter(var_label == "Role Label", str_detect(var_value, "Yes, I would"))
plot_interest(interest_counts, gtitle)1.16 Interest in support table
#| label: tbl-role_interest_table
gt_table_interest(interest_counts_full, gtitle): : :
Collapsible tables use raw HTML:
<details><summary>Click to show all responses in a table</summary>2 gt table code
</details>2.1 Chunk Label Prefixes
fig-for figurestbl-for tables- Descriptive snake_case after the prefix
2.2 show_open_endeds Flag
Set at top of page:
show_open_endeds <- FALSEUse #| eval: !expr show_open_endeds on chunks with open-ended text.