This vignette shows how to add triggers to a model. A trigger is a named absence-probability parameter. All members in one trigger call share the same absence draw. Separate trigger calls create independent absence draws.
##
## Attaching package: 'AccumulatR'
## The following object is masked from 'package:stats':
##
## simulate
Define the model
We use two lognormal accumulators and one shared trigger. The trigger
parameter is named shared_trigger.
model <- race_spec() |>
add_accumulator("go1", "lognormal") |>
add_accumulator("go2", "lognormal") |>
add_outcome("R1", "go1") |>
add_outcome("R2", "go2") |>
add_trigger("shared_trigger", members = c("go1", "go2")) |>
set_parameters(separate = list(m = TRUE, s = TRUE)) |>
finalize_model()
true_params <- c(
go1.m = log(0.30),
go1.s = 0.18,
go2.m = log(0.35),
go2.s = 0.18,
shared_trigger = 0.15
)The trigger probability is supplied in the same parameter vector as the timing parameters.
Simulate data
Failed trigger trials appear as missing responses and missing response times.
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(
trials = sim$trials,
R = factor(sim$R),
rt = sim$rt,
stringsAsFactors = FALSE
)
table(data_df$R, useNA = "ifany")##
## R1 R2 <NA>
## 924 361 215
Evaluate the likelihood
We prepare the data, build a model context, and evaluate the shared-trigger model under the true parameter values.
prepared <- prepare_data(model, data_df)
ctx <- make_context(model)
params_df_true <- build_param_matrix(
model,
true_params,
n_trials = n_trials
)
ll_true <- as.numeric(log_likelihood(ctx, prepared, params_df_true))
ll_true## [1] 785.4287
To see why trigger grouping matters, compare that with independent trigger absence draws. This uses two separate trigger calls, one for each accumulator.
model_independent <- race_spec() |>
add_accumulator("go1", "lognormal") |>
add_accumulator("go2", "lognormal") |>
add_outcome("R1", "go1") |>
add_outcome("R2", "go2") |>
add_trigger("go1_trigger", members = "go1") |>
add_trigger("go2_trigger", members = "go2") |>
set_parameters(
separate = list(m = TRUE, s = TRUE),
share = list(shared_trigger = c("go1_trigger", "go2_trigger"))
) |>
finalize_model()
independent_prepared <- prepare_data(model_independent, data_df)
independent_ctx <- make_context(model_independent)
params_df_independent <- build_param_matrix(
model_independent,
true_params,
n_trials = n_trials
)
ll_independent <- as.numeric(log_likelihood(
independent_ctx,
independent_prepared,
params_df_independent
))
ll_independent## [1] 513.7229
The same syntax can also be used when only one accumulator has a trigger.
model_single_q <- race_spec() |>
add_accumulator("go1", "lognormal") |>
add_accumulator("go2", "lognormal") |>
add_outcome("R1", "go1") |>
add_outcome("R2", "go2") |>
add_trigger("go1_trigger", members = "go1") |>
finalize_model()Estimate parameters with optim()
We estimate go1.m, go1.s,
go2.m, and go2.s, while keeping the trigger
probability fixed in the parameter vector passed to the likelihood.
neg_loglik <- function(theta) {
est <- true_params
est["go1.m"] <- theta[["go1.m"]]
est["go1.s"] <- exp(theta[["log_go1.s"]])
est["go2.m"] <- theta[["go2.m"]]
est["go2.s"] <- exp(theta[["log_go2.s"]])
params_df <- build_param_matrix(
model,
est,
n_trials = n_trials
)
ll <- log_likelihood(ctx, prepared, params_df)
-as.numeric(ll)
}
start <- c(
go1.m = log(0.25),
log_go1.s = log(0.12),
go2.m = log(0.42),
log_go2.s = log(0.12)
)
fit <- optim(
start,
neg_loglik,
method = "Nelder-Mead",
control = list(maxit = 4000, reltol = 1e-9)
)
fit_params <- c(
go1.m = fit$par[["go1.m"]],
go1.s = exp(fit$par[["log_go1.s"]]),
go2.m = fit$par[["go2.m"]],
go2.s = exp(fit$par[["log_go2.s"]])
)
target <- true_params[c("go1.m", "go1.s", "go2.m", "go2.s")]
data.frame(true = target, recovered = fit_params, miss = abs(target - fit_params))Use one trigger call when several accumulators share an absence
event. Use separate trigger calls when absence draws are independent;
group those trigger parameters with set_parameters() if
they should have the same probability.
