--- title: "Race Models" description: "A step-by-step race model workflow in EMC2 using the LBA" author: "Niek Stevenson" output: rmarkdown::html_document bibliography: refs.bib vignette: > %\VignetteIndexEntry{"Race Models"} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} rm(list = ls()) library(EMC2) set.seed(11) ``` ## Introduction This vignette shows a single-subject race-model workflow in *EMC2* using the Linear Ballistic Accumulator (LBA). In *EMC2*, the Racing Diffusion Model (RDM) and Lognormal Race Model (LNR) are also race models and use a similar design logic. Here we use a simulated Stroop task to illustrate how to set this up. The steps are: 1. Specify a race-model design with multiple responses and stimulus identities 2. Inspect sampled parameters and their mapping to design cells 3. Simulate data 4. Define priors and create an `emc` object 5. Fit the model 6. Summarize posterior estimates and run posterior predictive checks For parameter details and transformations, see `?LBA`. ## 1. Specify a Race-Model Design We define a three-choice Stroop task with stimulus identity `S` (`red`, `green`, `blue`) and matching response levels. In race models, *EMC2* internally builds an accumulator factor `lR` (one accumulator per response option). We define `matchfun` to indicate whether the stimulus identity matches each accumulator, which creates the latent match factor `lM`. By adding `S` to the drift rate we account for differences in color processing. Similarly, by adding `B ~ LR` we account for a-priori preferences for responding certain colors. ```{r} matchfun <- function(d) d$S == d$lR # "Average/difference" coding for the TRUE/FALSE lM factor ADmat <- matrix(c(-1/2, 1/2), ncol = 1, dimnames = list(NULL, "d")) design_lba <- design( factors = list(subjects = 1, S = c("red", "green", "blue")), Rlevels = c("red", "green", "blue"), matchfun = matchfun, formula = list(v ~ lM + S, B ~ lR, A ~ 1, t0 ~ 1, sv ~ 1), contrasts = list(v = list(lM = ADmat)), constants = c(sv = log(1)), model = LBA ) ``` `design()` combines model and experimental structure into an `emc.design` object. ## 2. Inspect Parameters and Mapping `sampled_pars()` returns the free parameters implied by the design. ```{r} sampled_pars(design_lba) ``` `mapped_pars()` shows how those parameters map back to design cells. Here: - `v_lMd` captures match-vs-mismatch drift differences - `v_Sgreen` and `v_Sblue` capture stimulus-identity shifts in drift - `B_lRgreen` and `B_lRblue` capture response-specific threshold bias ```{r} mapped_pars(design_lba) ``` In this example we simulate data, but you can replace this with your empirical data. We first define true parameter values on the transformed scale and inspect the numeric mapping: ```{r} p_vector <- sampled_pars(design_lba) p_vector[] <- c( v = 1.4, v_lMd = 1.8, v_Sgreen = 0.15, v_Sblue = -0.1, B = log(0.7), B_lRgreen = 0.1, B_lRblue = -0.1, A = log(0.3), t0 = log(0.25) ) mapped_pars(design_lba, p_vector) ``` To visualize the implied design-level behavior: ```{r, message=FALSE, fig.alt = "Design-level LBA trajectories for three stimulus identities"} plot_design(design_lba, p_vector = p_vector, factors = list(v = "S", B = "lR"), plot_factor = "lR", layout = c(1,3)) ``` ## 3. Simulate Data `make_data()` simulates trial-level responses and response times from the specified design and parameter values. ```{r, results = "hide"} dat <- make_data(parameters = p_vector, design = design_lba, n_trials = 80) ``` A quick check of the simulated defective densities by stimulus identity: ```{r, fig.alt = "Defective density plots for three-choice race model simulated data"} plot_density(dat, factors = "S", layout = c(1,3)) ``` ## 4. Set Prior and Build EMC Object `prior()` defines prior settings for parameters in this design. As always, be mindful that some parameters are represented on transformed scales, see `?LBA`. ```{r, results = "hide"} prior_lba <- prior( design = design_lba, type = "single", pmean = c( v = 1.2, v_lMd = 1.5, v_Sgreen = 0, v_Sblue = 0, B = log(0.8), B_lRgreen = 0, B_lRblue = 0, A = log(0.25), t0 = log(0.2) ), psd = c( v = .5, v_lMd = .6, v_Sgreen = .4, v_Sblue = .4, B = .25, B_lRgreen = .25, B_lRblue = .25, A = .25, t0 = .15 ) ) ``` Inspecting implied priors is a useful sanity check: ```{r, fig.alt = "Prior densities for three-choice LBA example"} plot(prior_lba, N = 1e3) ``` Next we construct the `emc` object. `make_emc()` combines data, design, and prior into the object expected by `fit()`. ```{r, results = "hide"} emc <- make_emc(dat, design_lba, prior_list = prior_lba, type = "single") ``` ## 5. Fit The following call is how you can fit this model and save intermediate output: ```{r, eval = FALSE} emc <- fit(emc, fileName = "data/race-models.RData") ``` ```{r, include = FALSE} load("data/race-models.RData") ``` ## 6. Summarize and Check Model Fit `summary()` reports quantiles, `Rhat`, and ESS for estimated parameters: ```{r} summary(emc) ``` `plot_pars()` compares posterior densities with the generating values: ```{r, fig.alt = "Posterior parameter densities against true values for three-choice LBA", fig.height = 9} plot_pars(emc, true_pars = p_vector, use_prior_lim = FALSE) ``` Finally, we generate posterior predictive datasets and compare CDFs: ```{r, results = "hide"} pp <- predict(emc) ``` ```{r, fig.alt = "Posterior predictive defective CDF by stimulus identity in three-choice LBA"} plot_cdf(dat, pp, factors = "S", layout = c(1,3)) ``` This same race-model workflow extends naturally to the other race models `RDM` and `LNR`; the core steps (`design()` -> `prior()` -> `make_emc()` -> `fit()` -> predictive checks) remain the same.