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.

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).

# 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)
}

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