Show R code
source("R/fast-tech-shared.R")WEEE Forum Fast-Tech Consumer Study
Survey design, fieldwork, sample composition, weighting, data processing and ethical safeguards.
4 September 2026
The WEEE Forum and its participating members commissioned a cross-sectional survey of adults living in 12 European countries. The study examines the purchase, ownership, use and end-of-life treatment of fast-tech devices, as well as purchasing intentions, perceptions, social norms, willingness to pay for durability and support for regulation.
The survey is descriptive and observational. Population calibration improves the correspondence between the achieved sample and selected demographic margins, but it does not turn associations into causal effects or fully remove online-panel coverage and self-selection bias.
Fast tech does not yet have a widely accepted operational definition. To avoid priming respondents or introducing the evaluative connotations associated with fast fashion, the questionnaire did not use the term fast tech. Instead, we referred to small and inexpensive electronic devices, with an indicative maximum cost of €10 (€15 for Switzerland) or €20. This neutral description captures the physical and market characteristics of the products without presupposing their quality, lifespan, accessibility or environmental impact. We made sure it could be translated consistently across the participating countries.
For the purposes of this study, the devices were grouped into four categories:
The first three categories form the core definition used in the analyses of purchasing and buyer status. Disposable e-vapes were treated separately, since their use and purchasing context differ from those of the other devices. They were included in the questions on household ownership and quantities disposed of, but excluded from the principal measures of purchasing behaviour and from the buyer/non-buyer classification.
The definitive questionnaire is available as a PDF. The following section provides a summary.
Q1. How old are you? Numeric response from 18; younger respondents were terminated.
Q2. Including yourself, how many people live in your household? Numeric response.
Q2a. How many of them are under 18 years old? Numeric response.
Q3. How many of the following items did you personally buy over the last 12 months? Charging and lighting devices; high-tech devices; gadgets and lifestyle items. Response bands: none, 1, 2–5, 6–9, 10–19, 20 or more.
Q4. How many of the following items are currently in your household, regardless of who bought or owns them? The three categories above plus disposable single-use e-vapes. The same quantity bands were used.
Q5. Have you ever bought small and inexpensive electronic devices, excluding e-vapes? Yes, quite often; yes, occasionally; yes, once; no, never.
Q6. Why have you never bought a small and inexpensive electronic device? Multiple answers covering lack of need, lack of awareness, quality/durability concerns, preference for higher-quality alternatives, insufficient societal impact, someone else purchasing devices, uncertainty and other reasons.
Q7. How often do you come across small and inexpensive electronic devices? Every day; at least weekly; at least monthly; a few times a year; less often or never.
Q8. For selected fast-tech categories, did you intend to buy the item? Intended purchase; not intended.
Q9. Why did you buy the item? Multiple answers covering work or everyday use, temporary replacement, a newer version, a spare, an unneeded impulse purchase, a gift, lack of another choice and other reasons.
Q10. What generally makes you buy small and inexpensive electronic devices rather than more expensive alternatives? Multiple answers covering affordability, immediate availability, accessibility, design, recommendations, advertising, curiosity and uncertainty.
Q11. Where do you usually get these devices? Multiple answers covering brand websites, online stores and marketplaces, discount or second-hand stores, airports and stations, social media, personal contacts and other channels.
Q12. Among the devices bought during the last 12 months, how many are still personally used today? None, 1, 2–5, 6–9, 10–19, 20 or more.
Q13. What is the shortest period for which you have used one of these devices? Discarding immediately; single use; less than a week; less than a month; one to six months; six to twelve months; twelve months or more; do not know or remember.
Q14. Over the last 12 months, how many devices did you personally dispose of? Charging and lighting devices, high-tech, gadgets and lifestyle items, and disposable e-vapes; quantity bands from none to 20 or more.
Q15. What are the main reasons you stopped using these devices? Multiple answers covering not working, intended single use, obsolescence, quality, damage, becoming outdated, lack of need, uncertainty and other reasons.
Q16. What have you done with the devices you stopped using? Multiple answers covering keeping them, household waste, plastic-waste sorting, recycling centres, in-store collection, giving away or donating, selling, uncertainty and other actions.
Q17. Why did you choose these end-of-life solutions? Multiple answers covering not thinking about it, convenience, availability, knowledge, perceived responsibility, resale value, repair difficulty, others’ recommendations, uncertainty and other motivations.
Q18. Overall, how satisfied were you with the devices you used? Very dissatisfied to very satisfied.
Q19. Would you have been willing to pay more for similar items that could be used over a longer period? No; yes for some; yes for most; yes for all.
Q20. If you had to replace a broken device, what would you choose? The same inexpensive model; another small inexpensive device; a more expensive one; repair; other.
Q21a. Globally, did the devices have a shorter, similar or greater lifespan than expected?
Q21b. Were they of lower, similar or higher quality than expected?
Q22a. Compared with more expensive alternatives, were they less, similarly or more harmful to the environment?
Q22b. Compared with more expensive alternatives, did they have a shorter, similar or greater lifespan?
Q23a. Regarding buying small and inexpensive electronic devices, how favourable are you? Completely unfavourable to completely favourable.
Q23b. People important to you would… Completely disapprove to completely approve.
Q24. Compared with a year ago, how many people buy small and inexpensive electronic devices? Far fewer; fewer; the same number; more; far more.
Q25. Over the next 12 months, do you intend to buy more, fewer or the same number of small and inexpensive electronic devices? Much more; more; the same; less; much less; intend to stop.
Q26a. Consumers should buy fewer small and inexpensive electronic devices. Five-point agreement scale.
Q26b. Governments should more strictly regulate the sale of small and inexpensive electronic devices. Five-point agreement scale.
Q27. Did you know that online platforms selling electronic devices are legally required to contribute financially to electronic-waste collection and recycling? Yes; no; had heard of it but did not know the details.
Q28. Before taking this questionnaire, were you aware that these devices contain electronic parts? Yes; no.
Q29. How should you define yourself? Male; female; non-binary; other; prefer not to say.
Q30. What is your highest diploma? From no formal education to master’s degree or equivalent; prefer not to say.
Q31. What is your employment status? Unemployed; employed; self-employed; student; home-maker or caregiver; retired; other; prefer not to say.
Q32. What is your household annual income? Country-specific bands in local currency; prefer not to say.
Q33. Where do you live? Large city; suburb; small or medium-sized city; rural area or village; other; prefer not to say.
The questionnaire has been built in English, then translated in 13 languages. The translation table is available as an Excel file.
The anonymised dataset is available to participating members upon request to the authors or WEEE Forum staff.
The sample covers 12 countries, with 10,245 participants. This section describes the achieved sample without applying analytical weights.
The target sample size was reached for every participating country.
country_counts <- df |>
count(Country, name = "n") |>
arrange(desc(n)) |>
mutate(Share = n / sum(n))
country_gender_tooltips <- df |>
mutate(
Gender = recode(Gender, `98` = "No answer"),
Gender = factor(
Gender,
levels = c(
"Female",
"Male",
"Non-binary",
"Prefer not to say",
"No answer"
)
)
) |>
filter(Gender %in% c("Female", "Male")) |>
group_by(Country, Gender, .drop = FALSE) |>
summarise(
gender_n = n(),
.groups = "drop_last"
) |>
mutate(gender_pct = gender_n / sum(gender_n)) |>
filter(gender_n > 0) |>
arrange(Country, Gender) |>
summarise(
gender_tooltip = paste0(
Gender,
": ",
percent(gender_pct, accuracy = 0.1),
" (n = ",
comma(gender_n),
")",
collapse = "\n"
),
.groups = "drop"
)
country_age_tooltips <- df |>
group_by(Country) |>
summarise(
mean_age = mean(Age, na.rm = TRUE),
.groups = "drop"
)
country_counts |>
gt() |>
cols_label(Country = "Country", n = "N", Share = "%") |>
fmt_percent(columns = Share, decimals = 1) |>
grand_summary_rows(
columns = n,
fns = list(Total = ~sum(.)),
fmt = ~ fmt_integer(.)
) |>
gt_bellegarde()| Country | N | % | |
|---|---|---|---|
| France | 1505 | 14.7% | |
| Belgium | 1503 | 14.7% | |
| Romania | 1205 | 11.8% | |
| Poland | 1009 | 9.8% | |
| Ireland | 1003 | 9.8% | |
| Switzerland | 1000 | 9.8% | |
| Norway | 505 | 4.9% | |
| Greece | 504 | 4.9% | |
| Netherlands | 504 | 4.9% | |
| Portugal | 503 | 4.9% | |
| Spain | 503 | 4.9% | |
| UK | 501 | 4.9% | |
| Total | — | 10,245 | — |
p_map <- map_data("world") |>
filter(region %in% europe_context) |>
left_join(
country_counts |>
left_join(country_gender_tooltips, by = "Country") |>
left_join(country_age_tooltips, by = "Country") |>
mutate(region = Country) |>
select(
region,
n,
gender_tooltip,
mean_age
),
by = "region"
) |>
mutate(
tooltip = if_else(
!is.na(n),
paste0(
region,
"\nN = ",
comma(n),
"\n\nGender\n",
gender_tooltip,
"\n\nMean age: ",
number(mean_age, accuracy = 0.1),
" years"
),
NA_character_
)
) |>
ggplot(aes(x = long, y = lat, group = group, fill = n,
tooltip = tooltip, data_id = region)) +
geom_polygon_interactive(
colour = bg_surface,
linewidth = bg_lw_hairline
) +
coord_map("lambert", lat0 = 45, lat1 = 60,
xlim = c(-12, 35), ylim = c(34, 72)) +
scale_fill_bg_c(
palette = "seq",
name = "N",
limits = c(0, 1510),
breaks = c(0, 500, 1000, 1500),
labels = label_comma(),
) +
labs(x = NULL, y = NULL) +
theme(
axis.text = element_blank(),
panel.grid = element_blank(),
legend.position = "top"
)
girafe(
ggobj = p_map,
font_set = gdtools::font_set(),
width_svg = bg_fig_width_body,
height_svg = bg_fig_height,
options = list(
opts_hover(css = paste0("fill:", bg_accent_50, ";")),
opts_toolbar(saveaspng = FALSE),
opts_sizing(rescale = TRUE)
)
)Q1. How old are you?
Q29. How would you define yourself?
The survey included respondents across a broad age range, from 18 to 89 years old.
Women account for 50.3% of the observed sample and men for 48.7%. This corresponds to 5,134 women and 4,977 men.
gender_summary |>
ggplot(aes(x = reorder(Gender, pct), y = pct)) +
geom_col(width = bg_bar_width, fill = bg_accent) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), hjust = -0.2, size = bg_size_label / .pt) +
scale_y_continuous(labels = percent_format(), expand = expansion(mult = c(0, 0.1))) +
coord_flip() +
labs(x = NULL, y = NULL)Q31. What’s your employment status?
60.9% of respondents are employed or self-employed, 20.9% are retired and 7.2% are unemployed.
employment_summary |>
mutate(
highlight = pct == max(pct)
) |>
ggplot(
aes(
x = reorder(Employment, pct),
y = pct,
fill = highlight
)
) +
geom_col(width = bg_bar_width) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
hjust = -0.2,
size = bg_size_label / .pt
) +
scale_fill_manual(
values = c(
"TRUE" = bg_accent,
"FALSE" = bg_accent_50
),
guide = "none"
) +
scale_y_continuous(
labels = percent_format(),
expand = expansion(mult = c(0, 0.1))
) +
coord_flip() +
labs(x = NULL, y = NULL)Q30. What is your highest diploma?
Secondary school or high school was the most frequently reported highest education level (35.4%), followed by a bachelor’s degree or equivalent (28.8%). Then, 17.7% reported a master’s or doctoral degree, or equivalent qualification.
education_levels <- c(
"No formal education / Primary school",
"Secondary school / High school",
"College / Undergraduate",
"Bachelor or equivalent",
"Master or equivalent / Doctorate, Ph.D. or equivalent",
"Prefer not to say"
)
df |>
filter(!is.na(Education)) |>
count(Education, name = "n") |>
mutate(
pct = n / sum(n),
# coord_flip() displays the last factor level at the top.
Education = factor(
Education,
levels = rev(education_levels)
),
response_type = if_else(
Education == "Prefer not to say",
"Prefer not to say",
"Substantive response"
)
) |>
ggplot(
aes(
x = Education,
y = pct,
fill = response_type
)
) +
geom_col(width = bg_bar_width) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
hjust = -0.2,
size = bg_size_label / .pt
) +
scale_fill_manual(
values = c(
"Substantive response" = bg_accent,
"Prefer not to say" = bg_ink_32
),
guide = "none"
) +
scale_x_discrete(
labels = \(x) str_wrap(x, width = 35)
) +
scale_y_continuous(
labels = percent_format(),
expand = expansion(mult = c(0, 0.1))
) +
coord_flip() +
labs(x = NULL, y = NULL)Q2. Including yourself, how many people live in your household?
Q2a. How many of them are under 18 years old?
df |>
filter(!is.na(Nb_household)) |>
mutate(hh_group = case_when(
Nb_household == 1 ~ "1",
Nb_household == 2 ~ "2",
Nb_household == 3 ~ "3",
Nb_household == 4 ~ "4",
Nb_household >= 5 ~ "5+"
)) |>
group_by(hh_group) |>
summarise(
n = n(),
.groups = "drop"
) |>
mutate(pct = n / sum(n)) |>
ggplot(aes(x = hh_group, y = pct)) +
geom_col(width = bg_bar_width, fill = bg_accent) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), vjust = -0.4, size = bg_size_label / .pt) +
scale_y_continuous(labels = percent_format(), expand = expansion(mult = c(0, 0.08))) +
labs(x = "Household size", y = NULL)df |>
filter(!is.na(Nb_household_under18)) |>
mutate(has_children = if_else(
Nb_household_under18 > 0,
"With children under 18",
"No children under 18"
)) |>
group_by(has_children) |>
summarise(
n = n(),
.groups = "drop"
) |>
mutate(pct = percent(n / sum(n), accuracy = 0.1)) |>
select(has_children, n, pct) |>
gt() |>
cols_label(has_children = "", n = "N", pct = "%") |>
gt_bellegarde()| N | % | |
|---|---|---|
| No children under 18 | 4546 | 54.2% |
| With children under 18 | 3836 | 45.8% |
income_data <- df |>
transmute(
Country,
Income = coalesce(
Income_Norway,
Income_Romania,
Income_Poland,
Income_Switzerland,
Income_UK,
Income_Euro
)
)
income_ridge_data <- income_data |>
filter(
!is.na(Income),
Income != "Prefer not to say"
) |>
mutate(
income_lower = income_lower_bound(
Income
)
) |>
group_by(Country) |>
mutate(
bracket_position = assign_income_bracket_position(
Country,
income_lower
),
n_brackets = n_distinct(
income_lower
),
income_eur_equivalent = case_when(
Country == "Switzerland" &
bracket_position == n_brackets ~ 135000,
TRUE ~ 5000 +
(bracket_position - 1) * 10000
)
) |>
ungroup() |>
mutate(
Country = fct_reorder(
Country,
income_eur_equivalent,
.fun = median,
.desc = TRUE
)
)
pct_income_prefer_not <- mean(
income_data$Income == "Prefer not to say",
na.rm = TRUE
)Q32. What’s your household annual income?
Household income was collected using country-specific ranges expressed in local currencies. These ranges were designed as approximate equivalents of the same €10,000 income bands, allowing their distributions to be displayed on a shared euro-equivalent scale. Overall, 7.2% preferred not to report their household income.
Note: Each income bracket represents an approximate €10,000 interval. Because of the conversion applied to the Swiss response scale, Switzerland had one fewer bracket. The apparent gap between €120,000 and €130,000 is therefore an artefact of harmonisation, and the scale should be read as continuous across that interval.
income_ridge_data |>
ggplot(aes(
x = income_eur_equivalent,
y = Country,
group = Country,
fill = Country
)) +
ggridges::geom_density_ridges(
stat = "binline",
binwidth = 10000,
boundary = 0,
scale = 0.92,
alpha = 0.6,
colour = "#000000",
linewidth = bg_lw_hairline
) +
scale_fill_hue(
guide = "none"
) +
scale_x_continuous(
breaks = seq(0, 140000, by = 20000),
labels = function(x) {
if_else(
x == 0,
"€0",
paste0("€", x / 1000, "k")
)
},
limits = c(0, 140000),
expand = expansion(add = c(2500, 2500))
) +
scale_y_discrete(
expand = expansion(add = c(0.25, 0.75))
) +
coord_cartesian(
clip = "off"
) +
labs(
x = "Approximate annual household income equivalent (€)",
y = NULL
) +
theme(
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "none"
)location_levels <- c(
"Large city",
"Suburb of a large city",
"Small or medium sized city",
"Rural area or village",
"Other",
"Prefer not to say"
)
location_summary <- df |>
filter(!is.na(Location)) |>
count(Response = Location, name = "n") |>
mutate(
Response = factor(Response, levels = location_levels),
pct = n / sum(n)
) |>
arrange(Response)
location_pct <- setNames(
location_summary$pct,
as.character(location_summary$Response)
)Q33. Where do you live?
The sample includes respondents from a range of residential environments. Large cities account for 31.3% of respondents and small or medium-sized cities for 28.6%. A further 16.1% live in the suburbs of a large city, while 23.3% live in a rural area or village.
location_summary |>
mutate(
response_type = if_else(
Response %in% c(
"Other",
"Prefer not to say"
),
"Non-substantive response",
"Residential environment"
)
) |>
ggplot(aes(
x = pct,
y = fct_rev(Response),
fill = response_type
)) +
geom_col(width = bg_bar_width) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
hjust = -0.15,
size = bg_size_label / .pt
) +
scale_fill_manual(
values = c(
"Residential environment" = bg_accent,
"Non-substantive response" = bg_ink_32
),
guide = "none"
) +
scale_x_continuous(
labels = percent_format(),
expand = expansion(mult = c(0, 0.16))
) +
scale_y_discrete(
labels = \(x) str_wrap(x, width = 35)
) +
labs(
x = "% of respondents",
y = NULL
)The sample overview reports the respondents exactly as observed. The analyses in this report use post-stratification weights so that pooled estimates reflect the combined population aged 18–99 of the 12 participating countries, while aligning age and gender within each country. The figures below show this adjustment.
Population margins were taken from the United Nations World Population Prospects 2024, medium projection for 1 January 2026 (United Nations, Department of Economic and Social Affairs, Population Division, 2024). Age and gender distributions were calculated for the population aged 18–99 in each participating country.
weighting_age_data <- bind_rows(
df |>
transmute(
Age,
Distribution = "Observed sample",
curve_weight = 1
),
df |>
transmute(
Age,
Distribution = "Population-weighted",
curve_weight = analysis_weight
)
) |>
filter(!is.na(Age)) |>
mutate(
Distribution = factor(
Distribution,
levels = c(
"Observed sample",
"Population-weighted"
)
)
)
sample_mean_age <- mean(df$Age, na.rm = TRUE)
weighted_mean_age <- weighted_mean_safe(
df$Age,
df$analysis_weight
)
weighting_gender_summary <- df |>
mutate(
Gender = recode(Gender, `98` = "No answer")
) |>
filter(!is.na(Gender)) |>
group_by(Gender) |>
summarise(
n = n(),
weighted_n = sum(analysis_weight),
.groups = "drop"
) |>
transmute(
Gender,
`Observed sample` = n / sum(n),
`Population-weighted` = weighted_n / sum(weighted_n)
) |>
pivot_longer(
-Gender,
names_to = "Distribution",
values_to = "pct"
) |>
mutate(
Distribution = factor(
Distribution,
levels = c(
"Observed sample",
"Population-weighted"
)
)
)The adjustment changes mean age from 47.3 years in the observed sample to 49.2 years after weighting.
weighting_age_data |>
ggplot(aes(
x = Age,
weight = curve_weight,
colour = Distribution,
fill = Distribution
)) +
geom_density(
alpha = 0.16,
linewidth = bg_lw_line
) +
scale_colour_manual(
name = NULL,
values = c(
"Observed sample" = bg_ink_54,
"Population-weighted" = bg_accent
)
) +
scale_fill_manual(
name = NULL,
values = c(
"Observed sample" = bg_ink_32,
"Population-weighted" = bg_accent_50
)
) +
scale_x_continuous(
breaks = seq(18, 100, by = 10)
) +
labs(
x = "Age",
y = "Density"
) +
theme(legend.position = "top")weighting_gender_summary |>
ggplot(aes(
x = pct,
y = fct_rev(Gender),
fill = Distribution
)) +
geom_col(
position = position_dodge(width = 0.72),
width = 0.64
) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
position = position_dodge(width = 0.72),
hjust = -0.15,
size = bg_size_label / .pt
) +
scale_fill_manual(
name = NULL,
values = c(
"Observed sample" = bg_ink_32,
"Population-weighted" = bg_accent
)
) +
scale_x_continuous(
labels = percent_format(),
limits = c(0, 0.55),
expand = expansion(mult = c(0, 0.04))
) +
labs(
x = "% within each distribution",
y = NULL
) +
theme(legend.position = "top")Note: Weighting corrects the sample on observed age, gender and country-population margins. It does not remove possible coverage, panel-selection or response biases on characteristics that were not used for calibration.
completion_time_p99 <- unname(
quantile(df$qTimeMin, 0.99, na.rm = TRUE)
)
completion_time_plot_data <- df |>
filter(
!is.na(qTimeMin),
qTimeMin <= completion_time_p99
)
completion_age_breaks <- c(18, 25, 35, 45, 55, 65, Inf)
completion_age_labels <- c(
"18–24",
"25–34",
"35–44",
"45–54",
"55–64",
"65+"
)
completion_time_by_age <- completion_time_plot_data |>
mutate(
Age_group = cut(
Age,
breaks = completion_age_breaks,
labels = completion_age_labels,
right = FALSE
)
) |>
filter(!is.na(Age_group))Responses were collected through an online access panel in July 2026, over approximately one month. Country quotas were set in advance, with 1,500 participants in Belgium and France and at least 500 completed interviews in each participating country. Eligibility was restricted to adults aged 18 or over. Questionnaire routing adapted the questions to previous buyers and non-buyers and randomly selected fast-tech categories where indicated.
The questionnaire described an expected completion time of approximately 15 minutes. The observed median was 6.6 minutes; the middle 50% of sessions lasted from 4.6 to 10.1 minutes.
completion_time_plot_data |>
ggplot(aes(x = qTimeMin)) +
geom_histogram(
binwidth = 1,
boundary = 0,
fill = bg_accent,
colour = bg_surface,
linewidth = bg_lw_hairline
) +
geom_vline(
xintercept = response_time_median,
colour = bg_ink,
linewidth = bg_lw_line,
linetype = "dashed"
) +
annotate(
"text",
x = response_time_median,
y = Inf,
label = paste0(
"Median: ",
number(response_time_median, accuracy = 0.1),
" min"
),
hjust = -0.1,
vjust = 1.4,
size = bg_size_label / .pt
) +
scale_x_continuous(
breaks = pretty_breaks(8),
expand = expansion(mult = c(0, 0.02))
) +
labs(
x = "Completion time (minutes)",
y = "Respondents"
)Note: The graph excludes only the longest 1% of completion times to improve readability.
The analysis file contains 10,245 qualified completed cases. No respondent in the delivered file is younger than 18, consistent with the questionnaire’s termination rule for minors. Routing-based missing values were preserved as structurally missing rather than recoded as substantive answers.
Online-panel quality control can combine identity verification, fraud detection and survey-taking behaviour (DeSimone et al., 2015; Meade & Craig, 2012). Dynata’s published procedures specifically mention checks for speeding, straight-lining, duplicates and low-effort open-ended responses, alongside broader respondent-level quality scoring (Dynata, 2026). The delivered file contains qualified cases but not the provider’s underlying fraud scores, device movements, keystroke patterns or exclusion log; those upstream decisions cannot therefore be independently reconstructed here.
fast_response_threshold <- response_time_median / 3
fast_response_n <- sum(
df$qTimeMin < fast_response_threshold,
na.rm = TRUE
)
quality_check_summary <- tibble(
Check = c(
"Qualified completion status",
"Respondent aged under 18",
"Completion time below one-third of the median",
"Reconstructable straight-lining flag"
),
Result = c(
format_count(n_participants),
format_count(sum(df$Age < 18, na.rm = TRUE)),
paste0(
format_count(fast_response_n),
" below ",
number(fast_response_threshold, accuracy = 0.1),
" minutes"
),
"Not available"
),
Treatment = c(
"Included in the analysis file",
"None present after the eligibility filter",
"Flagged for sensitivity review; duration alone was not an automatic exclusion",
"Relied on provider controls; the questionnaire has no sufficiently long uniform response grid"
)
)| Check | Result | Treatment |
|---|---|---|
| Qualified completion status | 10,245 | Included in the analysis file |
| Respondent aged under 18 | 0 | None present after the eligibility filter |
| Completion time below one-third of the median | 124 below 2.2 minutes | Flagged for sensitivity review; duration alone was not an automatic exclusion |
| Reconstructable straight-lining flag | Not available | Relied on provider controls; the questionnaire has no sufficiently long uniform response grid |
A completion time below one-third of the sample median is used here as a transparent diagnostic convention, not as proof of a low-quality response. The 124 flagged sessions were retained in the primary analyses because survey duration depends on routing, reading speed and interruptions. Likewise, identical answers across a small number of related attitude questions are not sufficient to diagnose straight-lining: they can represent a coherent opinion.
The original export used long questionnaire labels and included panel-administration fields. The following transformations were applied:
The dynamic-norm item was centred from −2 (far fewer people) to +2 (far more people), with 0 representing the same number.
The questionnaire informed participants that their answers would be treated confidentially, analysed in aggregate and processed anonymously for research purposes. Participation was voluntary and continuing to the survey constituted consent under the wording shown to respondents.
The public analysis dataset excludes direct panel identifiers and administrative fields such as respondent UUIDs and record identifiers. Results are reported as aggregates, and low-frequency categories are interpreted cautiously. The restricted internal source remains separate from the downloadable analytical file. Data minimisation, purpose limitation and restricted access should continue to govern storage, sharing and any future linkage.
Note: Removing direct identifiers reduces disclosure risk, but cannot guarantee that the risk of identification is zero. Any public release should therefore retain only the variables necessary for reproducibility and avoid unnecessary geographic or demographic detail.
Participants received an introductory information statement explaining the general purpose of the study, its approximate duration, confidentiality and the aggregate use of results. The term fast tech was not mentioned in the introduction, to avoid priming respondents before the questionnaire provided its neutral description of the product category. Participation was voluntary, respondents could leave the survey at any time, and minors were not eligible to participate. The analytical and public-reporting process was designed to avoid identifying individuals.
No causal intervention was administered in this survey. Behavioural recommendations derived from the results should be tested transparently and should preserve individual autonomy rather than manipulate or coerce.
Bellegarde helps organisations understand and change attitudes and behaviour related to the circular economy. Our work is grounded in science but is not intended to remain confined to academic publication. We aim to support collective action and structural improvement through the best available evidence.
The following commitments guided this work:
These principles align with recognised scientific codes of conduct and international good-practice frameworks, including the OECD’s emphasis on research integrity (Organisation for Economic Co-operation and Development, 2022). Changing behaviour is not a matter of intuition: it requires evidence, proportionate methods and responsibility for the consequences of action. This is what we do.
The reports use a small number of statistical terms. They are explained below in plain language.
A correlation shows whether two measures tend to increase or decrease together. It does not show that one causes the other.
Example: respondents reporting more fast tech purchases also tend to report more devices disposed of.
Notation: Spearman’s ρ = 0.57. The positive value shows that the two measures tend to increase together. A value of 0 would indicate no monotonic association, while values closer to +1 or −1 indicate stronger associations.
correlation_example_points |>
ggplot(aes(
x = purchased_count,
y = disposed_count
)) +
geom_jitter(
width = 0.55,
height = 0.55,
alpha = 0.14,
size = 1.2,
colour = bg_accent
) +
geom_smooth(
data = correlation_example_plot_data,
method = "loess",
formula = y ~ x,
span = 0.8,
se = FALSE,
colour = bg_success,
linewidth = bg_lw_line
) +
coord_cartesian(
xlim = c(
0,
correlation_example_limits[["purchased"]]
),
ylim = c(
0,
correlation_example_limits[["disposed"]]
)
) +
labs(
x = "Estimated number purchased",
y = "Estimated number disposed of"
)Each dot represents one respondent in a random display sample. The green line summarises the trend. The counts are estimates based on the midpoints of the questionnaire’s response bands.
A p-value indicates how unusual the observed result would be if there were no association in the population.
Example: a small p-value for age and buying status means that the observed age difference would be difficult to explain by random sampling alone.
Notation: p < .001 means that a result at least this unusual would occur fewer than once in 1,000 samples if the hypothesis we have to explain the result would be false. It is not the probability that the hypothesis is true.
When many associations are tested, p-values are corrected to reduce the risk of highlighting a result coming from chance and not a real effect.
Example: the behavioural models use the Benjamini–Hochberg correction when several potential factors are compared.
Notation: q = .03 is a corrected p-value. A threshold such as q < .05 limits the expected proportion of false discoveries among the results declared significant.
The interquartile range (IQR) contains the middle half of the observations, between the 25th and 75th percentiles.
Example: the middle half of questionnaire sessions lasted from 4.6 to 10.1 minutes.
Notation: median = 6.6 min, IQR = 4.6–10.1 min.
A beta coefficient gives the direction and size of an association in a statistical model, while the other factors in that model are held constant.
Example: a positive beta coefficient for buying attitude means that a more favourable attitude is associated with a higher value of the outcome. Its numerical size depends on the outcome scale and the model used.
Notation: β = 0.25 means that a one-unit increase in the predictor is associated with a 0.25-unit increase in the outcome, when the model is linear and the other variables remain constant.
A confidence interval shows the range of values that remain reasonably compatible with the data and the statistical model.
Example: a narrow interval around an estimated buying probability gives a more precise estimate than a wide interval.
Notation: 95% CI [0.12, 0.28] gives the lower and upper limits of the interval. It does not mean that 95% of respondents lie within it.
Degrees of freedom describe how much independent information is available for a statistical test. They are not the number of respondents.
Example: a test comparing several countries needs more degrees of freedom than a simple comparison between two groups.
Notation: χ²(11) = 24.6 reports a chi-square statistic of 24.6 with 11 degrees of freedom.
A percentage point is the direct difference between two percentages.
Example: an increase from 60% to 65% is an increase of 5 percentage points, not 5%.
Notation: +5 pp means an increase of five percentage points.
Data preparation and statistical analysis were conducted in R (R Core Team, 2024), primarily using the tidyverse (Wickham et al., 2019).
Population-weighted estimation used survey (Lumley, 2004).
Behavioural models relied on nnet (Venables & Ripley, 2002), ordinal (Christensen, 2025), lavaan (Rosseel, 2012), sandwich (Zeileis, 2004) and marginaleffects (Arel-Bundock et al., 2024).
Figures were produced with ggplot2 (Wickham, 2016), ggalluvial (Brunson, 2020), ggiraph (Gohel & Skintzos, 2026) and ggridges (Wilke, 2025), while tables were produced with gt (Iannone et al., 2026).
The reports were generated with Quarto (Allaire et al., 2022).
We used OpenAI Codex (GPT-5.6 Sol) to:
The questionnaire and initial analysis plan were developed by the authors. All AI-assisted code, analyses and text were reviewed by the authors. Made with human intelligence, and actual 🧠.
Bellegarde Scientific Consulting is a behavioural science consultancy that helps organisations understand and change behaviour related to environmental issues.
Behavioural science has demonstrated its ability to explain and change human behaviour. Yet its application is too often reduced to simplistic techniques detached from scientific evidence. At a time when climate change, public-health crises, disinformation and war propaganda all hinge on behaviour, superficial approaches are no longer sufficient.
Bellegarde emerged from this gap.
We apply behavioural science rigorously and ethically, connecting real-world challenges with leading international research to deliver pragmatic, evidence-based solutions.
The bibliography includes the theoretical sources underlying the questionnaire items, the survey and statistical methods used in the reports, and the principal software packages required to reproduce the analyses.
@report{naczaj2026,
author = {Naczaj, Dimitri and Bonetto, Éric},
publisher = {Bellegarde Scientific Consulting for the WEEE Forum},
title = {Fast-Tech {Consumer} {Study}},
date = {2026},
langid = {en-GB}
}
---
title: "Survey Methodology"
subtitle: "WEEE Forum Fast-Tech Consumer Study"
description: |
Survey design, fieldwork, sample composition, weighting, data processing and ethical safeguards.
bibliography: documentation/fast-tech-references.bib
csl: documentation/apa.csl
nocite: |
@*
---
```{r}
#| label: setup
#| cache: false
source("R/fast-tech-shared.R")
```
# Study design
The WEEE Forum and its participating members commissioned a cross-sectional survey of adults living in 12 European countries. The study examines the purchase, ownership, use and end-of-life treatment of fast-tech devices, as well as purchasing intentions, perceptions, social norms, willingness to pay for durability and support for regulation.
The survey is descriptive and observational. Population calibration improves the correspondence between the achieved sample and selected demographic margins, but it does not turn associations into causal effects or fully remove online-panel coverage and self-selection bias.
## Definition of fast tech
Fast tech does not yet have a widely accepted operational definition. To avoid priming respondents or introducing the evaluative connotations associated with *fast fashion*, the questionnaire did not use the term *fast tech*. Instead, we referred to **small and inexpensive electronic devices**, with an indicative maximum cost of €10 (€15 for Switzerland) or €20. This neutral description captures the physical and market characteristics of the products without presupposing their quality, lifespan, accessibility or environmental impact. We made sure it could be translated consistently across the participating countries.
For the purposes of this study, the devices were grouped into four categories:
1. **Charging and lighting devices under €10**, described as: "*chargers, adapters, charging cables (USB-A, USB-C, lightning, micro-USB…), LED strips and light bulbs, clip-on and small desk lamps, decorative and garden lights…*"
2. **High-tech devices under €20**, described as: "*Bluetooth or wired audio devices (earphones/headphones, speakers…), power banks, wearables and fitness trackers (like smart bands and smartwatches), PC accessories (USB drives or hubs, mice, keyboards, webcams…), game controllers…*"
3. **Gadgets and lifestyle items under €20**, described as: "*mini fans and diffusers, desktop gadgets, electronic toys (handheld consoles, fidget toys, RC cars…), cooking appliances (milk frother, mini blender, cookers…), body and skin devices (mini massager, travel razor or electric toothbrush…), home devices (alarms, cameras, smart sensors, bug zapper), pet gadgets (laser toys, auto feeder or water fountain…)*"
4. **Disposable single-use e-vapes**.
The first three categories form the core definition used in the analyses of purchasing and buyer status. Disposable e-vapes were treated separately, since their use and purchasing context differ from those of the other devices. They were included in the questions on household ownership and quantities disposed of, but excluded from the principal measures of purchasing behaviour and from the buyer/non-buyer classification.
# Study material
## Questionnaire
The definitive questionnaire is available as a [PDF](https://www.bellegarde.co/clients/weee-forum/fast-tech-questionnaire/questionnaire-fast-tech.pdf). The following section provides a summary.
::: {.callout-note collapse="true" title="Show the questionnaire"}
### Household characteristics {.unnumbered}
**Q1. How old are you?** Numeric response from 18; younger respondents were terminated.
**Q2. Including yourself, how many people live in your household?** Numeric response.
**Q2a. How many of them are under 18 years old?** Numeric response.
### Buying and ownership {.unnumbered}
**Q3. How many of the following items did you personally buy over the last 12 months?** Charging and lighting devices; high-tech devices; gadgets and lifestyle items. Response bands: none, 1, 2–5, 6–9, 10–19, 20 or more.
**Q4. How many of the following items are currently in your household, regardless of who bought or owns them?** The three categories above plus disposable single-use e-vapes. The same quantity bands were used.
**Q5. Have you ever bought small and inexpensive electronic devices, excluding e-vapes?** Yes, quite often; yes, occasionally; yes, once; no, never.
**Q6. Why have you never bought a small and inexpensive electronic device?** Multiple answers covering lack of need, lack of awareness, quality/durability concerns, preference for higher-quality alternatives, insufficient societal impact, someone else purchasing devices, uncertainty and other reasons.
**Q7. How often do you come across small and inexpensive electronic devices?** Every day; at least weekly; at least monthly; a few times a year; less often or never.
**Q8. For selected fast-tech categories, did you intend to buy the item?** Intended purchase; not intended.
**Q9. Why did you buy the item?** Multiple answers covering work or everyday use, temporary replacement, a newer version, a spare, an unneeded impulse purchase, a gift, lack of another choice and other reasons.
**Q10. What generally makes you buy small and inexpensive electronic devices rather than more expensive alternatives?** Multiple answers covering affordability, immediate availability, accessibility, design, recommendations, advertising, curiosity and uncertainty.
**Q11. Where do you usually get these devices?** Multiple answers covering brand websites, online stores and marketplaces, discount or second-hand stores, airports and stations, social media, personal contacts and other channels.
### Use and end of life {.unnumbered}
**Q12. Among the devices bought during the last 12 months, how many are still personally used today?** None, 1, 2–5, 6–9, 10–19, 20 or more.
**Q13. What is the shortest period for which you have used one of these devices?** Discarding immediately; single use; less than a week; less than a month; one to six months; six to twelve months; twelve months or more; do not know or remember.
**Q14. Over the last 12 months, how many devices did you personally dispose of?** Charging and lighting devices, high-tech, gadgets and lifestyle items, and disposable e-vapes; quantity bands from none to 20 or more.
**Q15. What are the main reasons you stopped using these devices?** Multiple answers covering not working, intended single use, obsolescence, quality, damage, becoming outdated, lack of need, uncertainty and other reasons.
**Q16. What have you done with the devices you stopped using?** Multiple answers covering keeping them, household waste, plastic-waste sorting, recycling centres, in-store collection, giving away or donating, selling, uncertainty and other actions.
**Q17. Why did you choose these end-of-life solutions?** Multiple answers covering not thinking about it, convenience, availability, knowledge, perceived responsibility, resale value, repair difficulty, others’ recommendations, uncertainty and other motivations.
### Perception and willingness {.unnumbered}
**Q18. Overall, how satisfied were you with the devices you used?** Very dissatisfied to very satisfied.
**Q19. Would you have been willing to pay more for similar items that could be used over a longer period?** No; yes for some; yes for most; yes for all.
**Q20. If you had to replace a broken device, what would you choose?** The same inexpensive model; another small inexpensive device; a more expensive one; repair; other.
**Q21a. Globally, did the devices have a shorter, similar or greater lifespan than expected?**
**Q21b. Were they of lower, similar or higher quality than expected?**
**Q22a. Compared with more expensive alternatives, were they less, similarly or more harmful to the environment?**
**Q22b. Compared with more expensive alternatives, did they have a shorter, similar or greater lifespan?**
### Behaviour {.unnumbered}
**Q23a. Regarding buying small and inexpensive electronic devices, how favourable are you?** Completely unfavourable to completely favourable.
**Q23b. People important to you would…** Completely disapprove to completely approve.
**Q24. Compared with a year ago, how many people buy small and inexpensive electronic devices?** Far fewer; fewer; the same number; more; far more.
### Intention, policy and awareness {.unnumbered}
**Q25. Over the next 12 months, do you intend to buy more, fewer or the same number of small and inexpensive electronic devices?** Much more; more; the same; less; much less; intend to stop.
**Q26a. Consumers should buy fewer small and inexpensive electronic devices.** Five-point agreement scale.
**Q26b. Governments should more strictly regulate the sale of small and inexpensive electronic devices.** Five-point agreement scale.
**Q27. Did you know that online platforms selling electronic devices are legally required to contribute financially to electronic-waste collection and recycling?** Yes; no; had heard of it but did not know the details.
**Q28. Before taking this questionnaire, were you aware that these devices contain electronic parts?** Yes; no.
### Demographics {.unnumbered}
**Q29. How should you define yourself?** Male; female; non-binary; other; prefer not to say.
**Q30. What is your highest diploma?** From no formal education to master’s degree or equivalent; prefer not to say.
**Q31. What is your employment status?** Unemployed; employed; self-employed; student; home-maker or caregiver; retired; other; prefer not to say.
**Q32. What is your household annual income?** Country-specific bands in local currency; prefer not to say.
**Q33. Where do you live?** Large city; suburb; small or medium-sized city; rural area or village; other; prefer not to say.
:::
## Questionnaire translation
The questionnaire has been built in English, then translated in 13 languages.
The translation table is available as an [Excel file](https://www.bellegarde.co/clients/weee-forum/fast-tech-questionnaire/questionnaire-translation-fast-tech.xlsx).
## Dataset
The anonymised dataset is available to participating members upon request to the authors or WEEE Forum staff.
# Sample Overview
The sample covers **`r n_countries` countries**, with **`r format_count(n_participants)` participants**. This section describes the achieved sample without applying analytical weights.
## Respondents by country
The target sample size was reached for every participating country.
```{r}
#| label: tbl-sample-country
#| tbl-cap: "Number and share of respondents per country"
country_counts <- df |>
count(Country, name = "n") |>
arrange(desc(n)) |>
mutate(Share = n / sum(n))
country_gender_tooltips <- df |>
mutate(
Gender = recode(Gender, `98` = "No answer"),
Gender = factor(
Gender,
levels = c(
"Female",
"Male",
"Non-binary",
"Prefer not to say",
"No answer"
)
)
) |>
filter(Gender %in% c("Female", "Male")) |>
group_by(Country, Gender, .drop = FALSE) |>
summarise(
gender_n = n(),
.groups = "drop_last"
) |>
mutate(gender_pct = gender_n / sum(gender_n)) |>
filter(gender_n > 0) |>
arrange(Country, Gender) |>
summarise(
gender_tooltip = paste0(
Gender,
": ",
percent(gender_pct, accuracy = 0.1),
" (n = ",
comma(gender_n),
")",
collapse = "\n"
),
.groups = "drop"
)
country_age_tooltips <- df |>
group_by(Country) |>
summarise(
mean_age = mean(Age, na.rm = TRUE),
.groups = "drop"
)
country_counts |>
gt() |>
cols_label(Country = "Country", n = "N", Share = "%") |>
fmt_percent(columns = Share, decimals = 1) |>
grand_summary_rows(
columns = n,
fns = list(Total = ~sum(.)),
fmt = ~ fmt_integer(.)
) |>
gt_bellegarde()
```
```{r}
#| label: fig-map-country
#| fig-cap: "Geographic distribution of respondents"
p_map <- map_data("world") |>
filter(region %in% europe_context) |>
left_join(
country_counts |>
left_join(country_gender_tooltips, by = "Country") |>
left_join(country_age_tooltips, by = "Country") |>
mutate(region = Country) |>
select(
region,
n,
gender_tooltip,
mean_age
),
by = "region"
) |>
mutate(
tooltip = if_else(
!is.na(n),
paste0(
region,
"\nN = ",
comma(n),
"\n\nGender\n",
gender_tooltip,
"\n\nMean age: ",
number(mean_age, accuracy = 0.1),
" years"
),
NA_character_
)
) |>
ggplot(aes(x = long, y = lat, group = group, fill = n,
tooltip = tooltip, data_id = region)) +
geom_polygon_interactive(
colour = bg_surface,
linewidth = bg_lw_hairline
) +
coord_map("lambert", lat0 = 45, lat1 = 60,
xlim = c(-12, 35), ylim = c(34, 72)) +
scale_fill_bg_c(
palette = "seq",
name = "N",
limits = c(0, 1510),
breaks = c(0, 500, 1000, 1500),
labels = label_comma(),
) +
labs(x = NULL, y = NULL) +
theme(
axis.text = element_blank(),
panel.grid = element_blank(),
legend.position = "top"
)
girafe(
ggobj = p_map,
font_set = gdtools::font_set(),
width_svg = bg_fig_width_body,
height_svg = bg_fig_height,
options = list(
opts_hover(css = paste0("fill:", bg_accent_50, ";")),
opts_toolbar(saveaspng = FALSE),
opts_sizing(rescale = TRUE)
)
)
```
## Demographics
### Age and gender
::: question
**Q1.** How old are you?
**Q29.** How would you define yourself?
:::
The survey included respondents across a broad age range, from **`r headline_age_min` to `r headline_age_max` years old**.
```{r}
#| label: fig-age
#| fig-cap: "Age group distribution"
df |>
filter(!is.na(Age)) |>
ggplot(aes(x = Age)) +
geom_density(
fill = bg_accent,
colour = bg_success,
alpha = bg_alpha_mute,
linewidth = bg_lw_line
) +
scale_x_continuous(breaks = seq(18, 100, by = 10)) +
labs(x = "Age", y = "Density")
```
```{r}
#| label: gender-data
gender_summary <- df |>
mutate(Gender = na_if(Gender, "98")) |>
filter(!is.na(Gender)) |>
count(Gender, name = "n") |>
mutate(pct = n / sum(n))
gender_pct <- setNames(gender_summary$pct, gender_summary$Gender)
```
Women account for **`r format_percent(gender_pct[["Female"]])`** of the observed sample and men for **`r format_percent(gender_pct[["Male"]])`**. This corresponds to `r format_count(sum(df$Gender == "Female", na.rm = TRUE))` women and `r format_count(sum(df$Gender == "Male", na.rm = TRUE))` men.
```{r}
#| label: fig-gender
#| fig-cap: "Gender distribution"
gender_summary |>
ggplot(aes(x = reorder(Gender, pct), y = pct)) +
geom_col(width = bg_bar_width, fill = bg_accent) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), hjust = -0.2, size = bg_size_label / .pt) +
scale_y_continuous(labels = percent_format(), expand = expansion(mult = c(0, 0.1))) +
coord_flip() +
labs(x = NULL, y = NULL)
```
### Employment
```{r}
#| label: employment-data
employment_summary <- df |>
filter(
!is.na(Employment),
Employment != "Prefer not to say"
) |>
count(Employment, name = "n") |>
mutate(pct = n / sum(n))
employment_percent_by_status <- setNames(
employment_summary$pct,
employment_summary$Employment
)
```
::: question
**Q31.** What’s your employment status?
:::
**`r format_percent(sum(employment_percent_by_status[c("Employed", "Self-employed")]))` of respondents are employed or self-employed**, `r format_percent(employment_percent_by_status[["Retired"]])` are retired and `r format_percent(employment_percent_by_status[["Unemployed"]])` are unemployed.
```{r}
#| label: fig-employment
#| fig-cap: "Employment status"
employment_summary |>
mutate(
highlight = pct == max(pct)
) |>
ggplot(
aes(
x = reorder(Employment, pct),
y = pct,
fill = highlight
)
) +
geom_col(width = bg_bar_width) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
hjust = -0.2,
size = bg_size_label / .pt
) +
scale_fill_manual(
values = c(
"TRUE" = bg_accent,
"FALSE" = bg_accent_50
),
guide = "none"
) +
scale_y_continuous(
labels = percent_format(),
expand = expansion(mult = c(0, 0.1))
) +
coord_flip() +
labs(x = NULL, y = NULL)
```
### Education
::: question
**Q30.** What is your highest diploma?
:::
Secondary school or high school was the most frequently reported highest education level (`r format_percent(mean(df$Education == "Secondary school / High school", na.rm = TRUE))`), followed by a bachelor’s degree or equivalent (`r format_percent(mean(df$Education == "Bachelor or equivalent", na.rm = TRUE))`). Then, `r format_percent(mean(df$Education == "Master or equivalent / Doctorate, Ph.D. or equivalent", na.rm = TRUE))` reported a master’s or doctoral degree, or equivalent qualification.
```{r}
#| label: fig-education
#| fig-cap: "Highest education level"
education_levels <- c(
"No formal education / Primary school",
"Secondary school / High school",
"College / Undergraduate",
"Bachelor or equivalent",
"Master or equivalent / Doctorate, Ph.D. or equivalent",
"Prefer not to say"
)
df |>
filter(!is.na(Education)) |>
count(Education, name = "n") |>
mutate(
pct = n / sum(n),
# coord_flip() displays the last factor level at the top.
Education = factor(
Education,
levels = rev(education_levels)
),
response_type = if_else(
Education == "Prefer not to say",
"Prefer not to say",
"Substantive response"
)
) |>
ggplot(
aes(
x = Education,
y = pct,
fill = response_type
)
) +
geom_col(width = bg_bar_width) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
hjust = -0.2,
size = bg_size_label / .pt
) +
scale_fill_manual(
values = c(
"Substantive response" = bg_accent,
"Prefer not to say" = bg_ink_32
),
guide = "none"
) +
scale_x_discrete(
labels = \(x) str_wrap(x, width = 35)
) +
scale_y_continuous(
labels = percent_format(),
expand = expansion(mult = c(0, 0.1))
) +
coord_flip() +
labs(x = NULL, y = NULL)
```
### Household size
::: question
**Q2.** Including yourself, how many people live in your household?
**Q2a.** How many of them are under 18 years old?
:::
```{r}
#| label: fig-household
#| fig-cap: "Number of people per household"
df |>
filter(!is.na(Nb_household)) |>
mutate(hh_group = case_when(
Nb_household == 1 ~ "1",
Nb_household == 2 ~ "2",
Nb_household == 3 ~ "3",
Nb_household == 4 ~ "4",
Nb_household >= 5 ~ "5+"
)) |>
group_by(hh_group) |>
summarise(
n = n(),
.groups = "drop"
) |>
mutate(pct = n / sum(n)) |>
ggplot(aes(x = hh_group, y = pct)) +
geom_col(width = bg_bar_width, fill = bg_accent) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), vjust = -0.4, size = bg_size_label / .pt) +
scale_y_continuous(labels = percent_format(), expand = expansion(mult = c(0, 0.08))) +
labs(x = "Household size", y = NULL)
```
```{r}
#| label: tbl-household-children
#| tbl-cap: "Households with children under 18"
df |>
filter(!is.na(Nb_household_under18)) |>
mutate(has_children = if_else(
Nb_household_under18 > 0,
"With children under 18",
"No children under 18"
)) |>
group_by(has_children) |>
summarise(
n = n(),
.groups = "drop"
) |>
mutate(pct = percent(n / sum(n), accuracy = 0.1)) |>
select(has_children, n, pct) |>
gt() |>
cols_label(has_children = "", n = "N", pct = "%") |>
gt_bellegarde()
```
### Household income
```{r}
#| label: household-income-data
income_data <- df |>
transmute(
Country,
Income = coalesce(
Income_Norway,
Income_Romania,
Income_Poland,
Income_Switzerland,
Income_UK,
Income_Euro
)
)
income_ridge_data <- income_data |>
filter(
!is.na(Income),
Income != "Prefer not to say"
) |>
mutate(
income_lower = income_lower_bound(
Income
)
) |>
group_by(Country) |>
mutate(
bracket_position = assign_income_bracket_position(
Country,
income_lower
),
n_brackets = n_distinct(
income_lower
),
income_eur_equivalent = case_when(
Country == "Switzerland" &
bracket_position == n_brackets ~ 135000,
TRUE ~ 5000 +
(bracket_position - 1) * 10000
)
) |>
ungroup() |>
mutate(
Country = fct_reorder(
Country,
income_eur_equivalent,
.fun = median,
.desc = TRUE
)
)
pct_income_prefer_not <- mean(
income_data$Income == "Prefer not to say",
na.rm = TRUE
)
```
::: question
**Q32.** What’s your household annual income?
:::
Household income was collected using country-specific ranges expressed in local currencies. These ranges were designed as approximate equivalents of the same €10,000 income bands, allowing their distributions to be displayed on a shared euro-equivalent scale. Overall, `r format_percent(pct_income_prefer_not)` preferred not to report their household income.
> **Note:** Each income bracket represents an approximate €10,000 interval. Because of the conversion applied to the Swiss response scale, Switzerland had one fewer bracket. The apparent gap between €120,000 and €130,000 is therefore an artefact of harmonisation, and the scale should be read as continuous across that interval.
```{r}
#| label: fig-household-income
#| fig-cap: "Distribution of household income responses, by country and approximate euro-equivalent bracket"
#| fig-height: 7
income_ridge_data |>
ggplot(aes(
x = income_eur_equivalent,
y = Country,
group = Country,
fill = Country
)) +
ggridges::geom_density_ridges(
stat = "binline",
binwidth = 10000,
boundary = 0,
scale = 0.92,
alpha = 0.6,
colour = "#000000",
linewidth = bg_lw_hairline
) +
scale_fill_hue(
guide = "none"
) +
scale_x_continuous(
breaks = seq(0, 140000, by = 20000),
labels = function(x) {
if_else(
x == 0,
"€0",
paste0("€", x / 1000, "k")
)
},
limits = c(0, 140000),
expand = expansion(add = c(2500, 2500))
) +
scale_y_discrete(
expand = expansion(add = c(0.25, 0.75))
) +
coord_cartesian(
clip = "off"
) +
labs(
x = "Approximate annual household income equivalent (€)",
y = NULL
) +
theme(
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "none"
)
```
### Residential environment
```{r}
#| label: residential-environment-data
location_levels <- c(
"Large city",
"Suburb of a large city",
"Small or medium sized city",
"Rural area or village",
"Other",
"Prefer not to say"
)
location_summary <- df |>
filter(!is.na(Location)) |>
count(Response = Location, name = "n") |>
mutate(
Response = factor(Response, levels = location_levels),
pct = n / sum(n)
) |>
arrange(Response)
location_pct <- setNames(
location_summary$pct,
as.character(location_summary$Response)
)
```
::: question
**Q33.** Where do you live?
:::
The sample includes respondents from a range of residential environments. Large cities account for `r format_percent(location_pct[["Large city"]])` of respondents and small or medium-sized cities for `r format_percent(location_pct[["Small or medium sized city"]])`. A further `r format_percent(location_pct[["Suburb of a large city"]])` live in the suburbs of a large city, while `r format_percent(location_pct[["Rural area or village"]])` live in a rural area or village.
```{r}
#| label: fig-residential-environment
#| fig-cap: "Residential environment of respondents"
location_summary |>
mutate(
response_type = if_else(
Response %in% c(
"Other",
"Prefer not to say"
),
"Non-substantive response",
"Residential environment"
)
) |>
ggplot(aes(
x = pct,
y = fct_rev(Response),
fill = response_type
)) +
geom_col(width = bg_bar_width) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
hjust = -0.15,
size = bg_size_label / .pt
) +
scale_fill_manual(
values = c(
"Residential environment" = bg_accent,
"Non-substantive response" = bg_ink_32
),
guide = "none"
) +
scale_x_continuous(
labels = percent_format(),
expand = expansion(mult = c(0, 0.16))
) +
scale_y_discrete(
labels = \(x) str_wrap(x, width = 35)
) +
labs(
x = "% of respondents",
y = NULL
)
```
# Weighting
The sample overview reports the respondents exactly as observed. The analyses in this report use post-stratification weights so that pooled estimates reflect the combined population aged 18–99 of the 12 participating countries, while aligning age and gender within each country. The figures below show this adjustment.
Population margins were taken from the United Nations *World Population Prospects 2024*, medium projection for 1 January 2026 [@unitednations2024]. Age and gender distributions were calculated for the population aged 18–99 in each participating country.
```{r}
#| label: weighting-comparison-data
weighting_age_data <- bind_rows(
df |>
transmute(
Age,
Distribution = "Observed sample",
curve_weight = 1
),
df |>
transmute(
Age,
Distribution = "Population-weighted",
curve_weight = analysis_weight
)
) |>
filter(!is.na(Age)) |>
mutate(
Distribution = factor(
Distribution,
levels = c(
"Observed sample",
"Population-weighted"
)
)
)
sample_mean_age <- mean(df$Age, na.rm = TRUE)
weighted_mean_age <- weighted_mean_safe(
df$Age,
df$analysis_weight
)
weighting_gender_summary <- df |>
mutate(
Gender = recode(Gender, `98` = "No answer")
) |>
filter(!is.na(Gender)) |>
group_by(Gender) |>
summarise(
n = n(),
weighted_n = sum(analysis_weight),
.groups = "drop"
) |>
transmute(
Gender,
`Observed sample` = n / sum(n),
`Population-weighted` = weighted_n / sum(weighted_n)
) |>
pivot_longer(
-Gender,
names_to = "Distribution",
values_to = "pct"
) |>
mutate(
Distribution = factor(
Distribution,
levels = c(
"Observed sample",
"Population-weighted"
)
)
)
```
The adjustment changes mean age from **`r number(sample_mean_age, accuracy = 0.1)` years in the observed sample** to **`r number(weighted_mean_age, accuracy = 0.1)` years after weighting**.
```{r}
#| label: fig-weighting-age
#| fig-cap: "Observed and population-weighted age distributions"
weighting_age_data |>
ggplot(aes(
x = Age,
weight = curve_weight,
colour = Distribution,
fill = Distribution
)) +
geom_density(
alpha = 0.16,
linewidth = bg_lw_line
) +
scale_colour_manual(
name = NULL,
values = c(
"Observed sample" = bg_ink_54,
"Population-weighted" = bg_accent
)
) +
scale_fill_manual(
name = NULL,
values = c(
"Observed sample" = bg_ink_32,
"Population-weighted" = bg_accent_50
)
) +
scale_x_continuous(
breaks = seq(18, 100, by = 10)
) +
labs(
x = "Age",
y = "Density"
) +
theme(legend.position = "top")
```
```{r}
#| label: fig-weighting-gender
#| fig-cap: "Observed and population-weighted gender distributions"
weighting_gender_summary |>
ggplot(aes(
x = pct,
y = fct_rev(Gender),
fill = Distribution
)) +
geom_col(
position = position_dodge(width = 0.72),
width = 0.64
) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
position = position_dodge(width = 0.72),
hjust = -0.15,
size = bg_size_label / .pt
) +
scale_fill_manual(
name = NULL,
values = c(
"Observed sample" = bg_ink_32,
"Population-weighted" = bg_accent
)
) +
scale_x_continuous(
labels = percent_format(),
limits = c(0, 0.55),
expand = expansion(mult = c(0, 0.04))
) +
labs(
x = "% within each distribution",
y = NULL
) +
theme(legend.position = "top")
```
> **Note:** Weighting corrects the sample on observed age, gender and country-population margins. It does not remove possible coverage, panel-selection or response biases on characteristics that were not used for calibration.
------------------------------------------------------------------------
# Data gathering
## Fieldwork
```{r}
#| label: completion-time-data
completion_time_p99 <- unname(
quantile(df$qTimeMin, 0.99, na.rm = TRUE)
)
completion_time_plot_data <- df |>
filter(
!is.na(qTimeMin),
qTimeMin <= completion_time_p99
)
completion_age_breaks <- c(18, 25, 35, 45, 55, 65, Inf)
completion_age_labels <- c(
"18–24",
"25–34",
"35–44",
"45–54",
"55–64",
"65+"
)
completion_time_by_age <- completion_time_plot_data |>
mutate(
Age_group = cut(
Age,
breaks = completion_age_breaks,
labels = completion_age_labels,
right = FALSE
)
) |>
filter(!is.na(Age_group))
```
Responses were collected through an online access panel in July 2026, over approximately one month. Country quotas were set in advance, with 1,500 participants in Belgium and France and at least 500 completed interviews in each participating country. Eligibility was restricted to adults aged 18 or over. Questionnaire routing adapted the questions to previous buyers and non-buyers and randomly selected fast-tech categories where indicated.
The questionnaire described an expected completion time of approximately 15 minutes. The observed median was **`r number(response_time_median, accuracy = 0.1)` minutes**; the middle 50% of sessions lasted from **`r number(response_time_q1, accuracy = 0.1)` to `r number(response_time_q3, accuracy = 0.1)` minutes**.
```{r}
#| label: fig-completion-time
#| fig-cap: "Questionnaire completion time"
completion_time_plot_data |>
ggplot(aes(x = qTimeMin)) +
geom_histogram(
binwidth = 1,
boundary = 0,
fill = bg_accent,
colour = bg_surface,
linewidth = bg_lw_hairline
) +
geom_vline(
xintercept = response_time_median,
colour = bg_ink,
linewidth = bg_lw_line,
linetype = "dashed"
) +
annotate(
"text",
x = response_time_median,
y = Inf,
label = paste0(
"Median: ",
number(response_time_median, accuracy = 0.1),
" min"
),
hjust = -0.1,
vjust = 1.4,
size = bg_size_label / .pt
) +
scale_x_continuous(
breaks = pretty_breaks(8),
expand = expansion(mult = c(0, 0.02))
) +
labs(
x = "Completion time (minutes)",
y = "Respondents"
)
```
> **Note:** The graph excludes only the longest 1% of completion times to improve readability.
```{r}
#| label: fig-completion-time-age
#| fig-cap: "Questionnaire completion time by age group"
completion_time_by_age |>
ggplot(aes(
x = qTimeMin,
y = fct_rev(Age_group)
)) +
geom_boxplot(
width = 0.58,
fill = bg_accent_50,
colour = bg_accent,
outlier.alpha = 0.12,
outlier.size = 0.8,
linewidth = bg_lw_line
) +
labs(
x = "Completion time (minutes)",
y = "Age group"
)
```
# Data cleaning and transformation
## Eligibility and completion status
The analysis file contains **`r format_count(n_participants)` qualified completed cases**. No respondent in the delivered file is younger than 18, consistent with the questionnaire’s termination rule for minors. Routing-based missing values were preserved as structurally missing rather than recoded as substantive answers.
## Response-quality checks
Online-panel quality control can combine identity verification, fraud detection and survey-taking behaviour [@meadecraig2012; @desimone2015]. Dynata’s published procedures specifically mention checks for speeding, straight-lining, duplicates and low-effort open-ended responses, alongside broader respondent-level quality scoring [@dynata2026]. The delivered file contains qualified cases but not the provider’s underlying fraud scores, device movements, keystroke patterns or exclusion log; those upstream decisions cannot therefore be independently reconstructed here.
```{r}
#| label: response-quality-data
fast_response_threshold <- response_time_median / 3
fast_response_n <- sum(
df$qTimeMin < fast_response_threshold,
na.rm = TRUE
)
quality_check_summary <- tibble(
Check = c(
"Qualified completion status",
"Respondent aged under 18",
"Completion time below one-third of the median",
"Reconstructable straight-lining flag"
),
Result = c(
format_count(n_participants),
format_count(sum(df$Age < 18, na.rm = TRUE)),
paste0(
format_count(fast_response_n),
" below ",
number(fast_response_threshold, accuracy = 0.1),
" minutes"
),
"Not available"
),
Treatment = c(
"Included in the analysis file",
"None present after the eligibility filter",
"Flagged for sensitivity review; duration alone was not an automatic exclusion",
"Relied on provider controls; the questionnaire has no sufficiently long uniform response grid"
)
)
```
```{r}
#| label: tbl-response-quality
#| tbl-cap: "Reproducible response-quality checks in the delivered dataset"
quality_check_summary |>
gt() |>
cols_width(
Check ~ pct(31),
Result ~ pct(22),
Treatment ~ pct(47)
) |>
gt_bellegarde()
```
A completion time below one-third of the sample median is used here as a transparent diagnostic convention, not as proof of a low-quality response. The **`r format_count(fast_response_n)` flagged sessions** were retained in the primary analyses because survey duration depends on routing, reading speed and interruptions. Likewise, identical answers across a small number of related attitude questions are not sufficient to diagnose straight-lining: they can represent a coherent opinion.
## Recoding
The original export used long questionnaire labels and included panel-administration fields. The following transformations were applied:
- Analytical columns were given stable, interpretable names.
- Response labels and factor orders were standardised.
- Multiple-response items were converted into selection indicators.
- Country-specific income bands were mapped to common approximate bracket positions.
The dynamic-norm item was **centred from −2** (*far fewer people*) to **+2** (*far more people*), with 0 representing *the same number*.
# Data protection and GDPR
The questionnaire informed participants that their answers would be treated confidentially, analysed in aggregate and processed anonymously for research purposes. Participation was voluntary and continuing to the survey constituted consent under the wording shown to respondents.
The public analysis dataset excludes direct panel identifiers and administrative fields such as respondent UUIDs and record identifiers. Results are reported as aggregates, and low-frequency categories are interpreted cautiously. The restricted internal source remains separate from the downloadable analytical file. Data minimisation, purpose limitation and restricted access should continue to govern storage, sharing and any future linkage.
> **Note:** Removing direct identifiers reduces disclosure risk, but cannot guarantee that the risk of identification is zero. Any public release should therefore retain only the variables necessary for reproducibility and avoid unnecessary geographic or demographic detail.
# Ethical note
## Consent, autonomy and anonymity
Participants received an introductory information statement explaining the general purpose of the study, its approximate duration, confidentiality and the aggregate use of results. The term *fast tech* was not mentioned in the introduction, to avoid priming respondents before the questionnaire provided its neutral description of the product category. Participation was voluntary, respondents could leave the survey at any time, and minors were not eligible to participate. The analytical and public-reporting process was designed to avoid identifying individuals.
No causal intervention was administered in this survey. Behavioural recommendations derived from the results should be tested transparently and should preserve individual autonomy rather than manipulate or coerce.
## Bellegarde principles applied to this study
Bellegarde helps organisations understand and change attitudes and behaviour related to the circular economy. Our work is grounded in science but is not intended to remain confined to academic publication. We aim to support collective action and structural improvement through the best available evidence.
The following commitments guided this work:
1. **Scientific rigour.** Analytical choices are grounded in reproducible methods, documented assumptions and the strongest evidence available. Uncertainty, measurement limits and the non-causal nature of cross-sectional associations are reported rather than concealed.
2. **Impact orientation.** Analyses are organised around concrete WEEE Forum objectives and actionable environmental questions. Recommendations should be measurable, field-tested and evaluated for both intended and unintended effects.
3. **Uncompromising ethics.** Integrity, transparency and responsibility guide the choice of questions, methods and interpretations. People are treated with respect, and the work is undertaken in the public interest without imposing behaviour.
4. **Accessibility and accountability.** Methods, results and behavioural levers are made as transparent and accessible as confidentiality and data-protection constraints allow.
These principles align with recognised scientific codes of conduct and international good-practice frameworks, including the OECD’s emphasis on research integrity [@oecd2022]. Changing behaviour is not a matter of intuition: it requires evidence, proportionate methods and responsibility for the consequences of action. This is what we do.
# Understanding the statistical notation
The reports use a small number of statistical terms. They are explained below in plain language.
## Correlation
```{r}
#| label: correlation-example-data
#| include: false
correlation_purchase_midpoints <- c(
"0" = 0,
"1" = 1,
"2 to 5" = 3.5,
"6 to 9" = 7.5,
"10 to 19" = 14.5,
"20+" = 20
)
correlation_disposal_midpoints <- c(
"1" = 0,
"2" = 1,
"3" = 3.5,
"4" = 7.5,
"5" = 14.5,
"6" = 20
)
correlation_example_data <- df |>
transmute(
analysis_weight,
purchased_count = rowSums(
across(
all_of(recent_purchase_cols),
~ unname(
correlation_purchase_midpoints[as.character(.x)]
)
)
),
disposed_count = rowSums(
across(
all_of(disposed_non_vape_cols),
~ unname(
correlation_disposal_midpoints[as.character(.x)]
)
)
)
) |>
drop_na()
correlation_example_rho <- weighted_correlation_safe(
rank(correlation_example_data$purchased_count),
rank(correlation_example_data$disposed_count),
correlation_example_data$analysis_weight
)
correlation_example_limits <- c(
purchased = unname(quantile(
correlation_example_data$purchased_count,
0.98
)),
disposed = unname(quantile(
correlation_example_data$disposed_count,
0.98
))
)
correlation_example_plot_data <- correlation_example_data |>
filter(
purchased_count <= correlation_example_limits[["purchased"]],
disposed_count <= correlation_example_limits[["disposed"]]
)
set.seed(20260828)
correlation_example_points <- correlation_example_plot_data |>
slice_sample(
n = min(2500, nrow(correlation_example_plot_data))
)
```
A correlation shows whether two measures tend to increase or decrease together. It does not show that one causes the other.
**Example:** respondents reporting more fast tech purchases also tend to report more devices disposed of.
> **Notation:** Spearman’s *ρ* = `r number(correlation_example_rho, accuracy = 0.01)`. The positive value shows that the two measures tend to increase together. A value of 0 would indicate no monotonic association, while values closer to +1 or −1 indicate stronger associations.
```{r}
#| label: fig-correlation-example
#| fig-cap: "Example of a positive correlation: estimated fast tech purchases and disposal"
correlation_example_points |>
ggplot(aes(
x = purchased_count,
y = disposed_count
)) +
geom_jitter(
width = 0.55,
height = 0.55,
alpha = 0.14,
size = 1.2,
colour = bg_accent
) +
geom_smooth(
data = correlation_example_plot_data,
method = "loess",
formula = y ~ x,
span = 0.8,
se = FALSE,
colour = bg_success,
linewidth = bg_lw_line
) +
coord_cartesian(
xlim = c(
0,
correlation_example_limits[["purchased"]]
),
ylim = c(
0,
correlation_example_limits[["disposed"]]
)
) +
labs(
x = "Estimated number purchased",
y = "Estimated number disposed of"
)
```
Each dot represents one respondent in a random display sample. The green line summarises the trend. The counts are estimates based on the midpoints of the questionnaire’s response bands.
## P-value
A p-value indicates how unusual the observed result would be if there were no association in the population.
**Example:** a small p-value for age and buying status means that the observed age difference would be difficult to explain by random sampling alone.
> **Notation:** *p* < .001 means that a result at least this unusual would occur fewer than once in 1,000 samples if the hypothesis we have to explain the result would be false. It is *not* the probability that the hypothesis is true.
## Corrected p-value
When many associations are tested, p-values are corrected to reduce the risk of highlighting a result coming from chance and not a real effect.
**Example:** the behavioural models use the Benjamini–Hochberg correction when several potential factors are compared.
> **Notation:** *q* = .03 is a corrected p-value. A threshold such as *q* < .05 limits the expected proportion of false discoveries among the results declared significant.
## Interquartile range
The interquartile range (IQR) contains the middle half of the observations, between the 25th and 75th percentiles.
**Example:** the middle half of questionnaire sessions lasted from `r number(response_time_q1, accuracy = 0.1)` to `r number(response_time_q3, accuracy = 0.1)` minutes.
> **Notation:** median = `r number(response_time_median, accuracy = 0.1)` min, IQR = `r number(response_time_q1, accuracy = 0.1)`–`r number(response_time_q3, accuracy = 0.1)` min.
## Beta coefficient
A beta coefficient gives the direction and size of an association in a statistical model, while the other factors in that model are held constant.
**Example:** a positive beta coefficient for buying attitude means that a more favourable attitude is associated with a higher value of the outcome. Its numerical size depends on the outcome scale and the model used.
> **Notation:** *β* = 0.25 means that a one-unit increase in the predictor is associated with a 0.25-unit increase in the outcome, when the model is linear and the other variables remain constant.
## Confidence interval
A confidence interval shows the range of values that remain reasonably compatible with the data and the statistical model.
**Example:** a narrow interval around an estimated buying probability gives a more precise estimate than a wide interval.
> **Notation:** 95% CI [0.12, 0.28] gives the lower and upper limits of the interval. It does not mean that 95% of respondents lie within it.
## Degrees of freedom
Degrees of freedom describe how much independent information is available for a statistical test. They are not the number of respondents.
**Example:** a test comparing several countries needs more degrees of freedom than a simple comparison between two groups.
> **Notation:** χ²(11) = 24.6 reports a chi-square statistic of 24.6 with 11 degrees of freedom.
## Percentage point
A percentage point is the direct difference between two percentages.
**Example:** an increase from 60% to 65% is an increase of 5 percentage points, not 5%.
> **Notation:** +5 pp means an increase of five percentage points.
# Software and reproducibility
Data preparation and statistical analysis were conducted in R [@rcore2024], primarily using the *tidyverse* [@wickham2019tidyverse].
- Population-weighted estimation used *survey* [@lumley2004survey].
- Behavioural models relied on *nnet* [@venablesripley2002], *ordinal* [@christensen2025ordinal], *lavaan* [@rosseel2012lavaan], *sandwich* [@zeileis2004sandwich] and *marginaleffects* [@arelbundock2024marginaleffects].
- Figures were produced with *ggplot2* [@wickham2016ggplot2], *ggalluvial* [@brunson2020ggalluvial], *ggiraph* [@gohelskintzos2026ggiraph] and *ggridges* [@wilke2025ggridges], while tables were produced with *gt* [@iannone2026gt].
- The reports were generated with Quarto [@allaire2022quarto].
# Use of AI
We used OpenAI Codex (GPT-5.6 Sol) to:
- support the generation and revision of R code;
- suggest and review parts of the analytical workflow and statistical methods;
- audit the analyses and correct identified issues; and
- create dated backups before major changes.
The questionnaire and initial analysis plan were developed by the authors. All AI-assisted code, analyses and text were reviewed by the authors. Made with human intelligence, and actual 🧠.
# About the authors
Bellegarde Scientific Consulting is a behavioural science consultancy that helps organisations understand and change behaviour related to environmental issues.
Behavioural science has demonstrated its ability to explain and change human behaviour. Yet its application is too often reduced to simplistic techniques detached from scientific evidence. At a time when climate change, public-health crises, disinformation and war propaganda all hinge on behaviour, superficial approaches are no longer sufficient.
**Bellegarde emerged from this gap.**
We apply behavioural science rigorously and ethically, connecting real-world challenges with leading international research to deliver pragmatic, evidence-based solutions.
# Bibliography
The bibliography includes the theoretical sources underlying the questionnaire items, the survey and statistical methods used in the reports, and the principal software packages required to reproduce the analyses.
::: {#refs}
:::