Skip to contents

This vignette shows how to define a response from a pool of accumulators. Here response A is produced by a 2-of-3 pool: the response is observed once any two of A1, A2, and A3 have finished. Response B is generated by a single competing accumulator.

## 
## Attaching package: 'AccumulatR'
## The following object is masked from 'package:stats':
## 
##     simulate

Define the model We use three lognormal accumulators feeding a pooled response A, plus one lognormal accumulator feeding response B.

model <- race_spec() |>
  add_accumulator("A1", "lognormal") |>
  add_accumulator("A2", "lognormal") |>
  add_accumulator("A3", "lognormal") |>
  add_accumulator("B", "lognormal") |>
  add_pool("A_pool", c("A1", "A2", "A3"), k = 2L) |>
  add_outcome("A", "A_pool") |>
  add_outcome("B", "B") |>
  finalize_model()

true_params <- c(
  A1.m = log(0.28),
  A1.s = 0.16,
  A2.m = log(0.28),
  A2.s = 0.16,
  A3.m = log(0.28),
  A3.s = 0.16,
  B.m = log(0.28),
  B.s = 0.18
)

Simulate data We simulate a response and response time for each trial.

set.seed(123456)

n_trials <- 1500
params_df <- build_param_matrix(model, true_params, n_trials = n_trials)

sim <- simulate(model, params_df)

data_df <- data.frame(
  trial = sim$trial,
  R = factor(sim$R),
  rt = sim$rt,
  stringsAsFactors = FALSE
)

table(data_df$R)
## 
##   A   B 
## 751 749

When response A is observed, its response time is the finishing time of the second finisher of the pool.

Estimate parameters with optim() We estimate a shared location and spread for the three pool members, plus the location and spread of accumulator B. The spread parameters are optimized on the log scale.

prepared <- prepare_data(model, data_df)
ctx <- make_context(model)
neg_loglik <- function(theta) {
  est <- true_params
  est[c("A1.m", "A2.m", "A3.m")] <- theta[["A_pool.m"]]
  est[c("A1.s", "A2.s", "A3.s")] <- exp(theta[["log_A_pool.s"]])
  est["B.m"] <- theta[["B.m"]]
  est["B.s"] <- exp(theta[["log_B.s"]])
  params_df <- build_param_matrix(
    model,
    est,
    trial_df = prepared
  )
  ll <- log_likelihood(ctx, prepared, params_df)
  -as.numeric(ll)
}

start <- c(
  A_pool.m = log(0.24),
  log_A_pool.s = log(0.12),
  B.m = log(0.34),
  log_B.s = log(0.12)
)

set.seed(123456)
fit <- optim(
  start,
  neg_loglik,
  method = "Nelder-Mead",
  control = list(maxit = 4000, reltol = 1e-9)
)
fit_params <- c(
  A_pool.m = fit$par[["A_pool.m"]],
  A_pool.s = exp(fit$par[["log_A_pool.s"]]),
  B.m = fit$par[["B.m"]],
  B.s = exp(fit$par[["log_B.s"]])
)
target <- c(
  A_pool.m = true_params[["A1.m"]],
  A_pool.s = true_params[["A1.s"]],
  B.m = true_params[["B.m"]],
  B.s = true_params[["B.s"]]
)

data.frame(true = target, recovered = fit_params, miss = abs(target - fit_params))
##               true  recovered         miss
## A_pool.m -1.272966 -1.2749227 0.0019570346
## A_pool.s  0.160000  0.1605954 0.0005954359
## B.m      -1.272966 -1.2731653 0.0001996564
## B.s       0.180000  0.1768839 0.0031160588

The code path is the same as in a basic race model. The difference is that the observed response A now depends on a pooled completion rule rather than a single accumulator.