# Simulate the example ESM dataset used throughout the primer.
#
# Design: n_id participants x n_day days x n_beep stratified-random prompts
# per day between 09:00 and 22:30. Stress and negative affect (NA) follow
# within-day autoregressive processes; each person has their own mean stress,
# NA level, stress reactivity (stronger with higher neuroticism) and inertia.
# Compliance is lower at the first/last prompt and when stressed, and ~3% of
# answered prompts are careless (all items set to 50).
#
# With the defaults (seed = 2026) this reproduces the numbers quoted in the text.
# Changing the design arguments changes the random stream, so all results change.
simulate_esm <- function(n_id = 100, n_day = 14, n_beep = 6, seed = 2026,
start_date = "2026-03-02", tz = "Europe/Amsterdam") {
stopifnot(requireNamespace("dplyr", quietly = TRUE))
set.seed(seed)
block_len <- floor((22.5 - 9) * 60 / n_beep) # minutes per sampling block
persons <- dplyr::tibble(
id = 1:n_id,
neuro = rnorm(n_id), # trait neuroticism (z)
mu_stress = rnorm(n_id, 40, 10), # typical stress (0-100)
u0 = rnorm(n_id, 0, 8), # NA intercept deviation
u1 = rnorm(n_id, 0, 0.12), # stress-slope deviation
phi = pmin(pmax(rnorm(n_id, 0.30, 0.15), -0.2), 0.8), # NA inertia
p_resp = rnorm(n_id, 0, 0.8) # response propensity (logit)
)
item_u <- matrix(rnorm(n_id * 4, 0, 4), n_id, 4) # person-specific item offsets
clamp <- function(x) pmin(pmax(round(x), 0), 100)
esm <- expand.grid(id = 1:n_id, day = 1:n_day, beep = 1:n_beep) |>
dplyr::arrange(id, day, beep) |>
dplyr::left_join(persons, by = "id") |>
dplyr::mutate(
# stratified random schedule: one prompt at a random minute inside each
# block (the last 30 minutes of a block are excluded)
sched_min = (9 * 60 + (beep - 1) * block_len) +
sample(0:(block_len - 30), dplyr::n(), replace = TRUE),
date = as.Date(start_date) + day - 1,
scheduled = as.POSIXct(paste(date, "00:00"), tz = tz) + sched_min * 60
) |>
dplyr::group_by(id, day) |>
dplyr::mutate(
stress = mu_stress + as.numeric(stats::filter(rnorm(dplyr::n(), 0, 12), 0.3,
method = "recursive")),
na_true = 25 + u0 + 4 * neuro + 0.5 * (mu_stress - 40) +
(0.30 + u1 + 0.08 * neuro) * (stress - mu_stress) +
as.numeric(stats::filter(rnorm(dplyr::n(), 0, 8), dplyr::first(phi),
method = "recursive"))
) |>
dplyr::ungroup() |>
dplyr::mutate(
stress = clamp(stress),
na_true = clamp(na_true),
# four negative-affect items (0-100 sliders) = true score + item noise
na_upset = clamp(na_true + item_u[id, 1] + rnorm(dplyr::n(), 0, 8)),
na_anxious = clamp(na_true + item_u[id, 2] + rnorm(dplyr::n(), 0, 8)),
na_sad = clamp(na_true + item_u[id, 3] + rnorm(dplyr::n(), 0, 8)),
na_irritated = clamp(na_true + item_u[id, 4] + rnorm(dplyr::n(), 0, 8)),
# compliance: lower for the first and last prompt, lower when stressed
responded = rbinom(dplyr::n(), 1,
plogis(1.4 + p_resp - 0.4 * (beep %in% c(1, n_beep)) -
0.01 * (stress - 40))) == 1,
started = dplyr::if_else(responded,
scheduled + pmin(rexp(dplyr::n(), 1 / 180), 900), NA),
careless = responded & runif(dplyr::n()) < 0.03,
completed = started + dplyr::if_else(careless, runif(dplyr::n(), 4, 8),
runif(dplyr::n(), 20, 60))
)
items <- c("na_upset", "na_anxious", "na_sad", "na_irritated", "stress")
esm |>
dplyr::mutate(
dplyr::across(dplyr::all_of(items), \(x) dplyr::if_else(careless, 50, x)),
dplyr::across(dplyr::all_of(items), \(x) dplyr::if_else(responded, x, NA))
) |>
dplyr::select(id, day, beep, scheduled, started, completed, neuro,
stress, na_upset, na_anxious, na_sad, na_irritated)
}Appendix A — Simulated dataset
All examples use data generated by simulate_esm() in R/simulate_esm.R: 100 participants, 14 days, and 6 stratified-random prompts per day between 09:00 and 22:30.
- Stress and negative affect follow within-day autoregressive processes.
- Each person has their own mean stress, negative-affect level, stress reactivity (which increases with neuroticism) and inertia.
- Compliance is lower at the first and last prompt of the day and when people are stressed.
- About 3% of answered prompts are careless (every item set to 50).
With the default seed, the function reproduces every result in Chapter 5 and Chapter 6. Change the arguments to explore other designs, for example simulate_esm(n_id = 60, n_day = 7).
A.1 Computing environment
R version 4.3.3 (2024-02-29)
dplyr 1.1.4
lme4 1.1.35.1
lmerTest 3.1.3
nlme 3.1.164
lavaan 0.6.17
ggplot2 3.4.4