Between-subjects online experiment: Prolific, US adults, N = 3,750 recruited (750 per arm); N ~ 3,560 analytic after exclusions. Participants imagine a catered event and choose between a Tofu Bánh Mì and a Chicken Bánh Mì under one of five randomly assigned conditions:
| Arm | Description |
|---|---|
| Control | Neither option pre-selected |
| T1 | Tofu Bánh Mì pre-selected |
| T2 | Single checkbox for Tofu Bánh Mì pre-checked; uncheck to receive Chicken |
| T3 | Tofu Bánh Mì pre-selected + inclusivity framing |
| T4 | Tofu Bánh Mì pre-selected + environmental framing |
Hypotheses:
H1 (behavioral): Each treatment increases
plant-based uptake (chose_veg) relative to Control by at
least a pre-registered minimum — 10 pp for T1, 20 pp for T2, 30 pp for
T3 and T4. Each arm is evaluated independently: supported if the OLS
estimate is significant (p < 0.01) and meets or exceeds its
threshold.
H2 (reactance): The default manipulations do not
generate unacceptable dissatisfaction. For each treatment arm, supported
if the observed dissatisfaction rate on the primary reactance item
(dissat_chosen) and the upper bound of its 95% Wilson CI
both fall below a pre-registered ceiling — 20% for T1, T3, T4; 30% for
T2. We expect reactance ordered Control < T1 < T3 & T4 <
T2, and within the framed arms T4 (environmental) >= T3
(inclusivity).
Alpha = 0.01 throughout. Exclusions applied before any analysis.
For unaddressed issues we defer to Lin, Green & Coppock (2016) SOPs (v1.05) as best we can.
The Qualtrics export names each column by its export tag. The map below renames those into the analysis variables documented in the codebook. Update the right-hand keys to match the actual export header (e.g. if the T4 choice or attention check exports with a suffix because of a duplicate/blank tag).
# raw Qualtrics export tag -> meaningful name
rename_map <- c(
ResponseId = "response_id",
QID2 = "consent", # consent item (currently a blank tag -> exports as QID2)
Q11 = "attn_response", # attention check (currently blank tag -> may export as QID11)
Q6 = "age",
Q7 = "gender",
Q8 = "education_code",
Q1 = "state",
Q10 = "diet_text",
# per-arm behavioral choice items (coalesced below into meal_chosen / chose_veg)
Q13 = "choice_control",
Q15 = "choice_t1",
Q17 = "choice_t2", # single opt-out checkbox
Q19 = "choice_t3",
Q27 = "choice_t4", # confirm actual tag; collides with Q21 unless renamed in QSF
Q21 = "sat_chosen", # PRIMARY reactance
Q26 = "sat_choices",
Q24 = "sat_presentation",
Q20 = "manip_code", # 1 = Chicken, 2 = Tofu
Q22 = "norms_estimate",
Q25 = "free_text"
)
# Applied to a real export with:
# df_raw <- readr::read_csv("export.csv") |> dplyr::rename(any_of(setNames(names(rename_map), rename_map)))
# `condition` comes from the randomizer's embedded-data field, added separately.
pkgs <- c("broom", "estimatr", "dplyr", "knitr", "pwr")
invisible(lapply(pkgs, function(p) {
if (!requireNamespace(p, quietly = TRUE))
install.packages(p, repos = "https://cloud.r-project.org")
library(p, character.only = TRUE, warn.conflicts = FALSE)
}))
tidy_lm <- function(m, digits = 4) {
tidy(m) |>
select(term, estimate, std.error, p.value) |>
mutate(across(c(estimate, std.error), \(x) round(x, digits)),
p.value = formatC(p.value, format = "g", digits = 3))
}
wilson_ci <- function(x, n, conf = 0.95) {
z <- qnorm(1 - (1 - conf) / 2)
p <- x / n; d <- 1 + z^2 / n
ct <- (p + z^2 / (2 * n)) / d
mg <- z * sqrt(p * (1 - p) / n + z^2 / (4 * n^2)) / d
c(est = p, lo = ct - mg, hi = ct + mg)
}
hte_pvals <- function(m, label) {
tidy(m) |>
filter(grepl(":", term)) |>
select(term, p.value) |>
mutate(p.value = round(p.value, 3), moderator = label)
}
N ~ 712 per arm (3,560 analytic after ~5% attention-check exclusions), alpha = 0.01.
N_per_arm <- 712L
alpha <- 0.01
cat("Sensitivity across treatment thresholds (baseline = 20%):\n")
## Sensitivity across treatment thresholds (baseline = 20%):
for (mde in c(0.10, 0.20, 0.30)) {
r <- power.prop.test(p1 = 0.20, p2 = 0.20 + mde, n = N_per_arm,
sig.level = alpha, alternative = "two.sided")
cat(sprintf(" MDE = %d pp.: %.1f%% power\n", round(mde * 100), r$power * 100))
}
## MDE = 10 pp.: 96.4% power
## MDE = 20 pp.: 100.0% power
## MDE = 30 pp.: 100.0% power
P(Wilson CI upper bound < ceiling) under assumed true rates, by simulation (B = 10,000). Control shown for reference only; it is not a registered H2 arm.
B <- 10000L
h2_params <- data.frame(
condition = c("Control", "T1", "T2", "T3", "T4"),
p_true = c(0.08, 0.15, 0.22, 0.15, 0.15),
ceiling = c(0.10, 0.20, 0.30, 0.20, 0.20)
)
h2_params$power <- sapply(seq_len(nrow(h2_params)), function(i) {
draws <- rbinom(B, N_per_arm, h2_params$p_true[i])
ci_uppers <- sapply(draws, function(x) wilson_ci(x, N_per_arm)[["hi"]])
round(mean(ci_uppers < h2_params$ceiling[i]), 3)
})
kable(h2_params, col.names = c("Condition", "Assumed true rate", "Ceiling", "Power"),
caption = "H2 power under assumed true rates")
| Condition | Assumed true rate | Ceiling | Power |
|---|---|---|---|
| Control | 0.08 | 0.1 | 0.425 |
| T1 | 0.15 | 0.2 | 0.939 |
| T2 | 0.22 | 0.3 | 0.999 |
| T3 | 0.15 | 0.2 | 0.936 |
| T4 | 0.15 | 0.2 | 0.934 |
We simulate the data as the Qualtrics export will
arrive (raw tag names, one populated choice column per arm, T2
opt-outs blank, education level 7 = “Prefer not to say”), then rename
and derive exactly as we will for the real data. To run on real data,
replace the Simulate chunk with read_csv() +
the rename_map above.
set.seed(42)
N_raw <- 3750L
arms <- c("Control", "T1", "T2", "T3", "T4")
# condition := randomizer assignment (a separate embedded-data field in the real export)
condition <- factor(sample(rep(arms, each = N_raw / 5)), levels = arms)
ResponseId <- sprintf("R_%08d", seq_len(N_raw))
QID2 <- 1L # consenters (non-consenters screened out)
Q11 <- ifelse(rbinom(N_raw, 1, 0.95) == 1L, 5L, sample(1:4, N_raw, TRUE)) # attn (5 = pass)
Q6 <- pmax(18L, pmin(80L, round(rnorm(N_raw, 38, 12)))) # age
Q7 <- sample(c("Male", "Female", "Non-binary", "Prefer Not to Say", "Prefer to self-identify"),
N_raw, TRUE, prob = c(0.47, 0.47, 0.02, 0.02, 0.02)) # gender
Q8 <- sample(1:7, N_raw, TRUE, prob = c(0.03,0.18,0.22,0.10,0.27,0.18,0.02)) # education (7=PNTS)
Q1 <- sample(state.name, N_raw, TRUE) # state
diet_pool <- c("", "none", "no", "n/a", "no restrictions", "none that i know of",
"vegetarian", "I am vegetarian", "veggie", "vegan", "I'm vegan",
"plant-based / vegan", "pescatarian", "pescetarian - i eat fish",
"flexitarian", "mostly plant based, flexitarian", "omnivore",
"i eat everything", "no i eat meat", "lactose intolerant", "dairy allergy",
"gluten free / celiac", "nut allergy", "allergic to peanuts",
"halal", "kosher", "shellfish allergy")
Q10 <- sample(diet_pool, N_raw, TRUE) # dietary free text
# True uptake by arm (all above H1 thresholds)
p_veg <- c(Control = 0.20, T1 = 0.35, T2 = 0.45, T3 = 0.55, T4 = 0.50)
veg_latent <- rbinom(N_raw, 1, p_veg[as.character(condition)])
# Per-arm choice columns AS EXPORTED: radio arms 1=Tofu,2=Chicken; T2 1=Tofu, BLANK=Chicken
Q13 <- ifelse(condition == "Control", 2L - veg_latent, NA_integer_)
Q15 <- ifelse(condition == "T1", 2L - veg_latent, NA_integer_)
Q17 <- ifelse(condition == "T2" & veg_latent == 1L, 1L, NA_integer_)
Q19 <- ifelse(condition == "T3", 2L - veg_latent, NA_integer_)
Q27 <- ifelse(condition == "T4", 2L - veg_latent, NA_integer_)
# Satisfaction (1-5); means chosen to hit target dissatisfaction (<=2) ~8/15/22/15/15%
q21_mu <- c(Control = 3.9, T1 = 3.6, T2 = 3.3, T3 = 3.6, T4 = 3.6)
clamp15 <- function(mu) pmax(1L, pmin(5L, round(rnorm(N_raw, mu, 1))))
Q21 <- clamp15(q21_mu[as.character(condition)]) # chosen meal (PRIMARY)
Q26 <- clamp15((q21_mu + 0.15)[as.character(condition)]) # available choices
Q24 <- clamp15(q21_mu[as.character(condition)]) # presentation
Q22 <- pmax(0L, pmin(100L, round(rnorm(N_raw, 50, 20)))) # norms 0-100
# Manipulation check Q20: 1=Chicken, 2=Tofu; ~95% consistent with the recorded choice
Q20 <- ifelse(rbinom(N_raw, 1, 0.95) == 1L,
ifelse(veg_latent == 1L, 2L, 1L),
ifelse(veg_latent == 1L, 1L, 2L))
export_raw <- data.frame(ResponseId, QID2, Q11, Q6, Q7, Q8, Q1, Q10,
Q13, Q15, Q17, Q19, Q27, Q21, Q26, Q24, Q20, Q22,
condition, stringsAsFactors = FALSE)
df_raw <- export_raw |>
rename(
response_id = ResponseId,
consent = QID2,
attn_response = Q11,
age = Q6,
gender = Q7,
education_code = Q8,
state = Q1,
diet_text = Q10,
choice_control = Q13,
choice_t1 = Q15,
choice_t2 = Q17,
choice_t3 = Q19,
choice_t4 = Q27,
sat_chosen = Q21,
sat_choices = Q26,
sat_presentation = Q24,
manip_code = Q20,
norms_estimate = Q22
)
ed_labels <- c("Less than high school", "High school diploma or GED", "Some college",
"2 year degree", "4 year degree", "Graduate degree", "Prefer not to say")
df_raw <- df_raw |>
mutate(
attn_pass = as.integer(attn_response == 5L),
education = factor(ed_labels[education_code], levels = ed_labels),
education_num = ifelse(education_code == 7L, NA_integer_, education_code),
# Behavioral: coalesce per-arm choices. Tofu=1, Chicken=0.
# T2: box checked (==1) = Tofu; BLANK = opted out to Chicken = 0 (NOT missing).
chose_veg = case_when(
condition == "Control" ~ as.integer(choice_control == 1L),
condition == "T1" ~ as.integer(choice_t1 == 1L),
condition == "T2" ~ as.integer(!is.na(choice_t2) & choice_t2 == 1L),
condition == "T3" ~ as.integer(choice_t3 == 1L),
condition == "T4" ~ as.integer(choice_t4 == 1L)
),
meal_chosen = factor(ifelse(chose_veg == 1L, "Tofu", "Chicken"),
levels = c("Chicken", "Tofu")),
manip_meal = factor(ifelse(manip_code == 2L, "Tofu", "Chicken"),
levels = c("Chicken", "Tofu")),
manip_ok = as.integer((manip_code == 2L & chose_veg == 1L) |
(manip_code == 1L & chose_veg == 0L)),
dissat_chosen = as.integer(sat_chosen <= 2L),
dissat_choices = as.integer(sat_choices <= 2L),
dissat_presentation = as.integer(sat_presentation <= 2L)
)
# Exclusion: failed attention check (applied before any analysis)
df <- filter(df_raw, attn_pass == 1L)
cat(sprintf("Raw N: %d | Excluded: %d | Analytic N: %d\n",
nrow(df_raw), nrow(df_raw) - nrow(df), nrow(df)))
## Raw N: 3750 | Excluded: 180 | Analytic N: 3570
df |>
group_by(condition) |>
summarise(n = n(), pct_veg = round(mean(chose_veg) * 100, 1), .groups = "drop") |>
kable(caption = "Plant-based uptake by condition (simulated analytic sample)")
| condition | n | pct_veg |
|---|---|---|
| Control | 715 | 19.6 |
| T1 | 717 | 34.7 |
| T2 | 715 | 46.0 |
| T3 | 711 | 53.0 |
| T4 | 712 | 51.8 |
df |>
group_by(condition) |>
summarise(age = round(mean(age), 2),
education = round(mean(education_num, na.rm = TRUE), 2),
.groups = "drop") |>
kable(caption = "Covariate means by condition")
| condition | age | education |
|---|---|---|
| Control | 37.63 | 3.96 |
| T1 | 38.63 | 3.95 |
| T2 | 37.29 | 3.92 |
| T3 | 38.59 | 3.91 |
| T4 | 37.84 | 3.88 |
LPM, HC2 robust SEs, Control reference. Criterion: p < 0.01 AND estimate >= threshold (T1: 10 pp; T2: 20 pp; T3/T4: 30 pp). Unadjusted is primary; adjusted is a robustness check.
m1 <- lm_robust(chose_veg ~ condition, data = df, se_type = "HC2")
m1_adj <- lm_robust(chose_veg ~ condition + age + gender + education_num,
data = df, se_type = "HC2")
tidy_lm(m1) |> kable(caption = "H1 unadjusted")
| term | estimate | std.error | p.value |
|---|---|---|---|
| (Intercept) | 0.1958 | 0.0149 | 8.54e-39 |
| conditionT1 | 0.1515 | 0.0232 | 7.22e-11 |
| conditionT2 | 0.2643 | 0.0238 | 4.16e-28 |
| conditionT3 | 0.3344 | 0.0239 | 2.42e-43 |
| conditionT4 | 0.3225 | 0.0239 | 1.82e-40 |
tidy_lm(m1_adj) |> kable(caption = "H1 covariate-adjusted (robustness check)")
| term | estimate | std.error | p.value |
|---|---|---|---|
| (Intercept) | 0.1628 | 0.0391 | 3.28e-05 |
| conditionT1 | 0.1523 | 0.0234 | 8.41e-11 |
| conditionT2 | 0.2655 | 0.0241 | 7.83e-28 |
| conditionT3 | 0.3390 | 0.0241 | 9.61e-44 |
| conditionT4 | 0.3302 | 0.0242 | 2.15e-41 |
| age | 0.0006 | 0.0007 | 0.406 |
| genderMale | 0.0377 | 0.0166 | 0.0233 |
| genderNon-binary | 0.0918 | 0.0576 | 0.111 |
| genderPrefer Not to Say | -0.0204 | 0.0600 | 0.734 |
| genderPrefer to self-identify | -0.0539 | 0.0548 | 0.326 |
| education_num | -0.0030 | 0.0053 | 0.575 |
res <- tidy(m1) |> filter(grepl("^condition", term))
thresh <- c(conditionT1 = 0.10, conditionT2 = 0.20,
conditionT3 = 0.30, conditionT4 = 0.30)
res |>
mutate(threshold = thresh[term],
h1_pass = (p.value < 0.01) & (estimate >= threshold)) |>
select(term, estimate, p.value, threshold, h1_pass) |>
mutate(across(where(is.numeric), \(x) round(x, 4))) |>
kable(caption = "H1 pass/fail by arm")
| term | estimate | p.value | threshold | h1_pass |
|---|---|---|---|---|
| conditionT1 | 0.1515 | 0 | 0.1 | TRUE |
| conditionT2 | 0.2643 | 0 | 0.2 | TRUE |
| conditionT3 | 0.3344 | 0 | 0.3 | TRUE |
| conditionT4 | 0.3225 | 0 | 0.3 | TRUE |
Outcome dissat_chosen (= 1 if sat_chosen
<= 2). Criterion: point estimate AND upper Wilson CI bound both below
the pre-registered ceiling.
ceilings <- c(Control = 0.10, T1 = 0.20, T2 = 0.30, T3 = 0.20, T4 = 0.20)
df |>
group_by(condition) |>
summarise(n = n(), n_dis = sum(dissat_chosen), .groups = "drop") |>
mutate(ceiling = ceilings[as.character(condition)],
w = Map(wilson_ci, n_dis, n),
est = round(sapply(w, `[[`, "est"), 3),
ci_lo = round(sapply(w, `[[`, "lo"), 3),
ci_hi = round(sapply(w, `[[`, "hi"), 3),
h2_pass = ci_hi < ceiling) |>
select(condition, n, n_dis, est, ci_lo, ci_hi, ceiling, h2_pass) |>
kable(caption = "H2: dissatisfaction (sat_chosen) vs. ceilings")
| condition | n | n_dis | est | ci_lo | ci_hi | ceiling | h2_pass |
|---|---|---|---|---|---|---|---|
| Control | 715 | 65 | 0.091 | 0.072 | 0.114 | 0.1 | FALSE |
| T1 | 717 | 94 | 0.131 | 0.108 | 0.158 | 0.2 | TRUE |
| T2 | 715 | 142 | 0.199 | 0.171 | 0.229 | 0.3 | TRUE |
| T3 | 711 | 87 | 0.122 | 0.100 | 0.149 | 0.2 | TRUE |
| T4 | 712 | 100 | 0.140 | 0.117 | 0.168 | 0.2 | TRUE |
df |>
group_by(condition) |>
summarise(pct_correct = round(mean(manip_ok) * 100, 1), .groups = "drop") |>
kable(caption = "% whose reported meal matches their recorded choice")
| condition | pct_correct |
|---|---|
| Control | 94.8 |
| T1 | 95.4 |
| T2 | 94.3 |
| T3 | 94.9 |
| T4 | 94.8 |
Same dissatisfied-<=2 coding as the primary, descriptive by condition (no registered ceiling).
sec_dissat <- function(var, label) {
df |>
group_by(condition) |>
summarise(n = n(), n_dis = sum(.data[[var]]), .groups = "drop") |>
mutate(w = Map(wilson_ci, n_dis, n),
est = round(sapply(w, `[[`, "est"), 3),
ci_lo = round(sapply(w, `[[`, "lo"), 3),
ci_hi = round(sapply(w, `[[`, "hi"), 3),
item = label) |>
select(item, condition, n, n_dis, est, ci_lo, ci_hi)
}
bind_rows(
sec_dissat("dissat_choices", "Available choices"),
sec_dissat("dissat_presentation", "Presentation")
) |>
kable(caption = "Secondary reactance: dissatisfaction with 95% Wilson CIs")
| item | condition | n | n_dis | est | ci_lo | ci_hi |
|---|---|---|---|---|---|---|
| Available choices | Control | 715 | 38 | 0.053 | 0.039 | 0.072 |
| Available choices | T1 | 717 | 70 | 0.098 | 0.078 | 0.122 |
| Available choices | T2 | 715 | 130 | 0.182 | 0.155 | 0.212 |
| Available choices | T3 | 711 | 58 | 0.082 | 0.064 | 0.104 |
| Available choices | T4 | 712 | 92 | 0.129 | 0.107 | 0.156 |
| Presentation | Control | 715 | 52 | 0.073 | 0.056 | 0.094 |
| Presentation | T1 | 717 | 109 | 0.152 | 0.128 | 0.180 |
| Presentation | T2 | 715 | 135 | 0.189 | 0.162 | 0.219 |
| Presentation | T3 | 711 | 92 | 0.129 | 0.107 | 0.156 |
| Presentation | T4 | 712 | 94 | 0.132 | 0.109 | 0.159 |
lm_robust(norms_estimate ~ condition, data = df, se_type = "HC2") |>
tidy_lm() |>
kable(caption = "Descriptive norms (OLS, Control reference)")
| term | estimate | std.error | p.value |
|---|---|---|---|
| (Intercept) | 49.0434 | 0.7145 | 0 |
| conditionT1 | 2.7056 | 1.0305 | 0.00869 |
| conditionT2 | 0.7469 | 1.0278 | 0.467 |
| conditionT3 | 1.6838 | 1.0154 | 0.0973 |
| conditionT4 | -0.3987 | 1.0410 | 0.702 |
lapply(c("T1", "T2", "T3", "T4"), function(ref) {
lm_robust(chose_veg ~ condition,
data = mutate(df, condition = relevel(condition, ref = ref)),
se_type = "HC2") |>
tidy_lm() |> mutate(reference = ref)
}) |>
bind_rows() |>
filter(grepl("^condition", term)) |>
kable(caption = "Cross-condition comparisons (exploratory)")
| term | estimate | std.error | p.value | reference |
|---|---|---|---|---|
| conditionControl | -0.1515 | 0.0232 | 7.22e-11 | T1 |
| conditionT2 | 0.1129 | 0.0258 | 1.23e-05 | T1 |
| conditionT3 | 0.1830 | 0.0258 | 1.7e-12 | T1 |
| conditionT4 | 0.1710 | 0.0258 | 4.22e-11 | T1 |
| conditionControl | -0.2643 | 0.0238 | 4.16e-28 | T2 |
| conditionT1 | -0.1129 | 0.0258 | 1.23e-05 | T2 |
| conditionT3 | 0.0701 | 0.0264 | 0.00804 | T2 |
| conditionT4 | 0.0581 | 0.0264 | 0.028 | T2 |
| conditionControl | -0.3344 | 0.0239 | 2.42e-43 | T3 |
| conditionT1 | -0.1830 | 0.0258 | 1.7e-12 | T3 |
| conditionT2 | -0.0701 | 0.0264 | 0.00804 | T3 |
| conditionT4 | -0.0120 | 0.0265 | 0.651 | T3 |
| conditionControl | -0.3225 | 0.0239 | 1.82e-40 | T4 |
| conditionT1 | -0.1710 | 0.0258 | 4.22e-11 | T4 |
| conditionT2 | -0.0581 | 0.0264 | 0.028 | T4 |
| conditionT3 | 0.0120 | 0.0265 | 0.651 | T4 |
No inferential claims. Case-insensitive regex coding of
diet_text; finalized after inspecting real responses.
classify_diet <- function(x) {
x <- tolower(trimws(x))
case_when(
grepl("vegan", x) ~ "Vegan",
grepl("vegetar|veggie", x) ~ "Vegetarian",
grepl("pesc", x) ~ "Pescatarian",
grepl("flexi", x) ~ "Flexitarian",
grepl("omnivore|eat everything|eat meat", x) ~ "Omnivore (stated)",
x %in% c("", "none", "no", "n/a", "no restrictions", "none that i know of")
~ "None stated",
grepl("allerg|intoleran|celiac|gluten|nut|dairy|shellfish|lactose", x)
~ "Allergy/intolerance only",
grepl("halal", x) ~ "Halal",
grepl("kosher", x) ~ "Kosher",
TRUE ~ "Other/uncoded"
)
}
df <- df |> mutate(diet_cat = classify_diet(diet_text))
df |>
count(diet_cat, sort = TRUE) |>
mutate(pct = round(n / sum(n) * 100, 1)) |>
kable(caption = "Dietary identity (regex-coded, exploratory)")
| diet_cat | n | pct |
|---|---|---|
| Allergy/intolerance only | 840 | 23.5 |
| None stated | 831 | 23.3 |
| Omnivore (stated) | 390 | 10.9 |
| Vegan | 371 | 10.4 |
| Vegetarian | 353 | 9.9 |
| Flexitarian | 267 | 7.5 |
| Pescatarian | 260 | 7.3 |
| Halal | 132 | 3.7 |
| Kosher | 126 | 3.5 |
No inferential claims. Interaction p-values only.
bind_rows(
hte_pvals(lm_robust(chose_veg ~ condition * gender, data = df, se_type = "HC2"), "gender"),
hte_pvals(lm_robust(chose_veg ~ condition * education_num, data = df, se_type = "HC2"), "education"),
hte_pvals(lm_robust(chose_veg ~ condition * I(age >= median(age)),
data = df, se_type = "HC2"), "age (above vs. below median)")
) |>
select(moderator, term, p.value) |>
kable(caption = "HTE interaction p-values (exploratory)")
| moderator | term | p.value |
|---|---|---|
| gender | conditionT1:genderMale | 0.570 |
| gender | conditionT2:genderMale | 0.567 |
| gender | conditionT3:genderMale | 0.434 |
| gender | conditionT4:genderMale | 0.828 |
| gender | conditionT1:genderNon-binary | 0.384 |
| gender | conditionT2:genderNon-binary | 0.826 |
| gender | conditionT3:genderNon-binary | 0.815 |
| gender | conditionT4:genderNon-binary | 0.490 |
| gender | conditionT1:genderPrefer Not to Say | 0.971 |
| gender | conditionT2:genderPrefer Not to Say | 0.775 |
| gender | conditionT3:genderPrefer Not to Say | 0.675 |
| gender | conditionT4:genderPrefer Not to Say | 0.792 |
| gender | conditionT1:genderPrefer to self-identify | 0.118 |
| gender | conditionT2:genderPrefer to self-identify | 0.009 |
| gender | conditionT3:genderPrefer to self-identify | 0.785 |
| gender | conditionT4:genderPrefer to self-identify | 0.356 |
| education | conditionT1:education_num | 0.018 |
| education | conditionT2:education_num | 0.777 |
| education | conditionT3:education_num | 0.271 |
| education | conditionT4:education_num | 0.265 |
| age (above vs. below median) | conditionT1:I(age >= median(age))TRUE | 0.252 |
| age (above vs. below median) | conditionT2:I(age >= median(age))TRUE | 0.355 |
| age (above vs. below median) | conditionT3:I(age >= median(age))TRUE | 0.041 |
| age (above vs. below median) | conditionT4:I(age >= median(age))TRUE | 0.176 |
H1 and H2 will be analyzed precisely as specified; any change will be noted in the text. Exploratory analyses (cross-condition comparisons, dietary identity, HTE) may be modified, and additional exploratory tests may be added. Variable names follow the accompanying codebook; the shared dataset will use those names so it is legible without the survey.