--- title: "Diffusion Decision Model (DDM)" description: "A step-by-step DDM workflow in EMC2" author: "Niek Stevenson" output: rmarkdown::html_document bibliography: refs.bib vignette: > %\VignetteIndexEntry{"Diffusion Decision Model (DDM)"} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} rm(list = ls()) library(EMC2) set.seed(1) ``` ## Introduction This vignette shows a single-subject DDM workflow in *EMC2*: 1. Specify a DDM design with factors, covariates, and parameter formulas 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 details on DDM parameters, constraints, and transformations, see `?DDM`. ## 1. Specify a DDM Design We set up a lexical decision design with one factor (`Lex`: Non-Word vs Word), one covariate (`Freq`), and two response levels. Drift rate `v` depends on lexicality and frequency. EMC2 takes the levels of the response (`R`) factor to determine what the lower and upper boundary is for the DDM. In this case, lower is associated with non-word responding, and upper with word responding. Other parameters are consistent across cells. For more info see `?DDM` ```{r} LexMat <- cbind(d = c(-1, 1)) design_lex <- design( factors = list(Lex = c("Non-Word", "Word"), subjects = 1), covariates = "Freq", Rlevels = c("Non-Word", "Word"), formula = list(v ~ Lex + Freq, a ~ 1, t0 ~ 1, Z ~ 1, sv ~ 1, s ~ Lex), contrasts = list(Lex = LexMat), constants = c(s = log(1)), model = DDM ) ``` `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_lex) ``` `mapped_pars()` shows how those parameters map back to model cells. Note that the v parameter is a drift bias parameter, positive values favor word responding and negative values non-word responding. The `v_Lexd` parameter captures general sensitivity to lexical evidence. ```{r} mapped_pars(design_lex) ``` In this example we'll simulate some data, but you can of course use your own real data! We define true parameter values (on the transformed scale) and inspect the numeric mapping: ```{r} p_vector <- sampled_pars(design_lex) p_vector[] <- c(.1, 1.5, .2, log(1.1), log(.3), qnorm(.55), log(.3), .2) mapped_pars(design_lex, p_vector) ``` To see more details on the parameters and their scales see `?DDM`. We can also visualize the implied design-level behavior: ```{r, message=FALSE, fig.alt = "Design-level DDM trajectories for lexical decision"} plot_design(design_lex, p_vector = p_vector, factors = list(v = "Lex")) ``` ## 3. Simulate Data `make_data()` simulates trial-level responses and response times from the design and parameter values. ```{r, results = "hide"} dat <- make_data(parameters = p_vector, design = design_lex, n_trials = 100) ``` See the error that lexicality is randomly imputed? Let's fix that with some more realistic values. Frequency is only defined for `Word` stimuli, and typically skewed. ```{r} word_frequency <- rgamma(sum(dat$Lex == "Word"), shape = 5, rate = .1) # To make it more normally distributed we log-transform word_frequency <- log(word_frequency) # And scale it so that meaning of the intercept remains the mean drift word_frequency <- as.numeric(scale(word_frequency)) frequency <- numeric(nrow(dat)) frequency[dat$Lex == "Word"] <- word_frequency # Now we feed it to `make_data()` dat <- make_data( parameters = p_vector, design = design_lex, n_trials = 100, covariates = list(Freq = frequency) ) ``` `plot_density()` gives a quick check of the simulated data: ```{r, fig.alt = "Defective density plots for lexical decision simulated data"} plot_density(dat, factors = "Lex") ``` ## 4. Set Prior and Build EMC Object `prior()` defines prior settings for the parameters in the chosen design. Be mindful of the transformations on the parameters when setting priors! Again see `?DDM` ```{r, results = "hide"} prior_lex <- prior( design = design_lex, type = "single", pmean = c( v = 0, v_Lexd = 2, v_Freq = 0, a = log(1), t0 = log(.25), Z = qnorm(.5), sv = log(.3), s_Lexd = 0 ), psd = c( v = 1, v_Lexd = 1, v_Freq = .5, a = .2, t0 = .15, Z = .25, sv = .5, s_Lexd = .2 ) ) ``` Inspecting the implied prior is helpful to check prior settings. ```{r, fig.alt = "Prior densities for DDM lexical example"} plot(prior_lex, 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_lex, prior_list = prior_lex, 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/DDM.RData") ``` ```{r, include = FALSE} load("data/DDM.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"} 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 lexicality"} plot_cdf(dat, pp, factors = "Lex") ``` ```{r, fig.alt = "Posterior predictive defective CDF by frequency", fig.height = 6} plot_cdf(dat, pp, factors = "Freq") ``` In this example, the frequency plot is less intuitive because non-words have frequency fixed at zero. Consequently the second quantile is dominated by non-word responding