Skip to contents

Introduction to Across-Trial Parameter Variability

In the basic Diffusion Decision Model (DDM), core parameters like drift rate (v), starting point (z), and non-decision time (ter) are often assumed to be constant across all trials within an experimental condition. However, empirical evidence and theoretical considerations suggest that these parameters can fluctuate from one trial to the next. Incorporating this across-trial parameter variability makes the DDM more realistic and often improves its ability to fit complex patterns in behavioral data.

Why is Variability Important?

  • More Realistic RT Distributions: Variability, especially in drift rate (sv) and non-decision time (st0), helps capture the full shape of empirical RT distributions, including their characteristic skewness and the spread of the leading edge.

  • Explaining Error RTs: The relationship between correct and error RTs (e.g., errors being sometimes faster or slower than corrects) is often better explained when considering variability in drift rate (sv) and starting point (sz).

  • Accounting for Fluctuations: Human cognitive processes are inherently noisy and subject to fluctuations in attention, motivation, and processing efficiency. Parameter variability provides a way to model these fluctuations.

This vignette demonstrates how to simulate the DDM with across-trial variability using the functions in R/03_ddm_simulator_variable.R.

Simulating DDM with Parameter Variability

We have two key functions for this:

  • simulate_diffusion_trial_variable(): Simulates a single DDM trial where v, z, and ter are sampled from distributions.

  • simulate_diffusion_experiment_variable(): Simulates multiple trials using simulate_diffusion_trial_variable().

Variability Parameters:

  • sv (Drift Rate Variability): Standard deviation of a Normal distribution from which the trial-specific drift rate (v_trial) is sampled: v_trial ~ Normal(mean_v, sv).

  • sz (Starting Point Variability): Range of a Uniform distribution for z_trial: z_trial ~ Uniform(mean_z - sz/2, mean_z + sz/2). The sampled z_trial is clipped to stay within (0, a).

  • st0 (Non-Decision Time Variability): Range of a Uniform distribution for ter_trial: ter_trial ~ Uniform(mean_ter - st0/2, mean_ter + st0/2). The sampled ter_trial is clipped to be non-negative.

If a variability parameter (e.g., sv) is set to 0, the corresponding model parameter (v_trial) will take its mean value (mean_v) for all trials.

Example: Simulating an Experiment with Variability

Let’s simulate an experiment with variability in all three parameters (v, z, ter).

set.seed(303) # For reproducibility
n_var_trials <- 2000 # Sufficient trials for distribution shapes

# Parameters for the variable DDM simulation
var_params <- list(
  mean_v = 0.1,
  a = 1.0,
  mean_z = 0.5,    # Mean starting point (a/2 for unbiased)
  s = 0.3,         # Within-trial noise (constant)
  mean_ter = 0.1,  # Mean non-decision time
  sv = 0.2,        # SD of drift rate variability
  sz = 0.2,        # Range of starting point variability (z from 0.4 to 0.6)
  st0 = 0.05,      # Range of non-decision time variability
  dt = 0.01
)

ddm_data_variable <- simulate_diffusion_experiment_variable(
  n_trials = n_var_trials,
  mean_v = var_params$mean_v,
  a = var_params$a,
  mean_z = var_params$mean_z,
  s = var_params$s,
  mean_ter = var_params$mean_ter,
  sv = var_params$sv,
  sz = var_params$sz,
  st0 = var_params$st0,
  dt = var_params$dt
)

knitr::kable(head(ddm_data_variable), caption = "First 6 trials from DDM with parameter variability.")
First 6 trials from DDM with parameter variability.
trial choice rt decision_time v_trial z_trial ter_trial
1 1 4.741999 4.63 0.0006122 0.5219631 0.1119988
2 1 1.538572 1.42 0.0774412 0.4739782 0.1185719
3 1 0.864889 0.78 -0.1528199 0.5746136 0.0848890
4 0 1.214346 1.12 -0.1148261 0.4718828 0.0943463
5 1 1.162694 1.08 0.1351808 0.5701450 0.0826937
6 0 1.468756 1.39 0.0335632 0.5451873 0.0787557
summary(ddm_data_variable[, c("choice", "rt", "v_trial", "z_trial", "ter_trial")])
##      choice             rt            v_trial            z_trial      
##  Min.   :0.0000   Min.   :0.2164   Min.   :-0.56052   Min.   :0.4000  
##  1st Qu.:0.0000   1st Qu.:1.0149   1st Qu.:-0.03097   1st Qu.:0.4505  
##  Median :1.0000   Median :1.6323   Median : 0.10460   Median :0.5027  
##  Mean   :0.6703   Mean   :1.9051   Mean   : 0.10364   Mean   :0.5012  
##  3rd Qu.:1.0000   3rd Qu.:2.5858   3rd Qu.: 0.23130   3rd Qu.:0.5506  
##  Max.   :1.0000   Max.   :5.1133   Max.   : 0.76949   Max.   :0.5999  
##  NA's   :171      NA's   :171                                         
##    ter_trial      
##  Min.   :0.07500  
##  1st Qu.:0.08680  
##  Median :0.09994  
##  Mean   :0.09971  
##  3rd Qu.:0.11227  
##  Max.   :0.12499  
## 

The output data frame now includes v_trial, z_trial, and ter_trial, showing the specific parameter values used for each simulated trial.

Visualizing Sampled Trial Parameters

We can plot histograms of the sampled parameters to verify they are drawn from the intended distributions.

# Reshape data for easier plotting with ggplot2 (optional, could do separate plots)
params_long <- ddm_data_variable %>%
  select(v_trial, z_trial, ter_trial) %>%
  pivot_longer(cols = everything(), names_to = "parameter", values_to = "value")

ggplot(params_long, aes(x = value)) +
  geom_histogram(aes(y = after_stat(density)),  # Plot density for better comparison of shapes
                 fill = "cornflowerblue", color = "black", alpha = 0.7, bins = 30) +
  geom_density(color = "red", linewidth = 1) +
  facet_wrap(~parameter, scales = "free") + # Separate plot for each parameter
  labs(title = "Distributions of Sampled Trial-Specific Parameters", x = "Parameter Value", y = "Density") +
  theme_bw()

Histograms of sampled trial parameters showing distributions of drift rate, starting point, and non-decision time across trials

  • v_trial should approximate a Normal distribution.

  • z_trial and ter_trial should approximate Uniform distributions over their respective ranges (after accounting for any clipping).

Impact of Parameter Variability on RT Distributions

Let’s compare the RT distributions generated by a DDM with across-trial parameter variability to one without it (i.e., a “standard” DDM where v, z, and ter are fixed across trials).

# Parameters for comparison to highlight skewness
n_comp_trials <- 5000 # Increase number of trials

# Standard DDM parameters
std_v <- 0.1
std_a <- 1.0
std_z <- 0.5
std_s <- 0.25
std_ter <- 0.15

# Variable DDM parameters (same means, but with variability)
var_mean_v <- std_v
var_a <- std_a
var_mean_z <- std_z
var_s <- std_s
var_mean_ter <- std_ter
var_sv <- 0.20  # <<--- KEY: Increased sv for more pronounced skew
var_sz <- 0.1   # Moderate sz
var_st0 <- 0.05  # Moderate st0

# Simulate Standard DDM
set.seed(305) # New seed for new simulation
ddm_data_standard_skew <- simulate_diffusion_experiment(
  n_trials = n_comp_trials,
  v = std_v,
  a = std_a,
  z = std_z,
  s = std_s,
  ter = std_ter,
  dt = 0.01 # Assuming dt is handled in your function, else add it
)

# Simulate DDM with Variability
set.seed(305) # Use the same seed for the variable part for closer comparison
ddm_data_variable_skew <- simulate_diffusion_experiment_variable(
  n_trials = n_comp_trials,
  mean_v = var_mean_v,
  a = var_a,
  mean_z = var_mean_z,
  s = var_s,
  mean_ter = var_mean_ter,
  sv = var_sv,
  sz = var_sz,
  st0 = var_st0,
  dt = 0.01 # Assuming dt is handled in your function
)

# Add a 'model_type' column
ddm_data_standard_skew$model_type <- "Standard DDM"
ddm_data_variable_skew$model_type <- "DDM with Variability"

# Combine data
combined_plot_data_skew <- bind_rows(
  ddm_data_standard_skew %>% select(rt, choice, model_type),
  ddm_data_variable_skew %>% select(rt, choice, model_type)
) %>%
  filter(!is.na(rt)) %>%
  mutate(choice_label = factor(choice, levels = c(0,1), labels = c("Choice 0 (e.g., Error)", "Choice 1 (e.g., Correct)")))

# Calculate Mean and Median to numerically check skewness
summary_stats_skew <- combined_plot_data_skew %>%
  group_by(model_type, choice_label) %>%
  summarise(
    N = n(),
    Mean_RT = mean(rt, na.rm = TRUE),
    Median_RT = median(rt, na.rm = TRUE),
    SD_RT = sd(rt, na.rm = TRUE),
    Skewness_coeff = if(N > 2) moments::skewness(rt, na.rm=TRUE) else NA # Requires 'moments' package
  ) %>% ungroup()

print("Summary Statistics for RTs (Mean > Median indicates right skew):")
## [1] "Summary Statistics for RTs (Mean > Median indicates right skew):"
knitr::kable(summary_stats_skew, digits = 3)
model_type choice_label N Mean_RT Median_RT SD_RT Skewness_coeff
DDM with Variability Choice 0 (e.g., Error) 1350 2.364 2.150 1.166 0.580
DDM with Variability Choice 1 (e.g., Correct) 2914 2.064 1.783 1.122 0.835
Standard DDM Choice 0 (e.g., Error) 648 2.510 2.370 1.173 0.370
Standard DDM Choice 1 (e.g., Correct) 3318 2.506 2.310 1.167 0.404
# Plot density distributions
# Adjust xlim to see the tails better
rt_upper_quantile_for_plot <- quantile(combined_plot_data_skew$rt, 0.99, na.rm = TRUE) # Show 99% of data

p_rt_comparison_skew <- ggplot(combined_plot_data_skew, aes(x = rt, fill = model_type)) +
  geom_density(alpha = 0.6, linewidth = 0.7) + # Removed color=model_type for clearer fill
  facet_grid(model_type ~ choice_label) + # Facet by model_type rows, choice_label columns
  labs(
    title = "Comparison of RT Distributions: Impact of Parameter Variability",
    subtitle = paste("sv =", var_sv, ", sz =", var_sz, ", st0 =", var_st0, "for Variable DDM"),
    x = "Reaction Time (s)",
    y = "Density",
    fill = "Model Type"
  ) +
  coord_cartesian(xlim = c(0, rt_upper_quantile_for_plot * 1.1)) + # Zoom appropriately
  theme_bw(base_size = 11) + # Using theme_bw for clearer facets
  theme(legend.position = "top",
        strip.text = element_text(size = 9, face="bold"), # Make facet labels clearer
        plot.subtitle = element_text(size=9))

print(p_rt_comparison_skew)

Density plots comparing reaction time distributions between standard DDM and DDM with parameter variability, showing how variability affects distribution shape and skewness

Observations from the Comparison:

  • Skewness: The DDM with parameter variability (especially sv) typically produces RT distributions that are more positively skewed (longer right tail) than the standard DDM.

  • Leading Edge: st0 (variability in non-decision time) will tend to spread out the leading edge (fastest RTs) of the distribution.

  • Error RTs: The relationship between correct RTs and error RTs can change significantly. For example, sv can lead to “slow errors” (errors are slower than corrects) if, on some trials, the drift rate is small or even slightly in the wrong direction. sz can contribute to “fast errors” if the starting point happens to be close to the incorrect boundary.

  • Overall Fit: For real data, models incorporating these variabilities often provide a substantially better quantitative fit.

Exploring Individual Variability Components

To better understand how each source of across-trial variability affects the model’s predictions, we can simulate scenarios where only one variability parameter is non-zero at a time. We will compare these to our ddm_data_standard_skew (simulated earlier with no across-trial variability) as a baseline.

Parameters for these focused simulations:
(We’ll use the same mean parameters as in the previous comparison for consistency, and a common number of trials.)

# These should match the parameters used for ddm_data_standard_skew
# Or redefine them if this section is run independently
n_iso_trials <- 5000 # Number of trials for each isolated simulation

# Base mean parameters (same as previous comparison)
base_mean_v <- 0.1
base_a <- 1.0
base_mean_z <- 0.5
base_s <- 0.2
base_mean_ter <- 0.15
base_dt <- 0.01

# Magnitude of variability to explore for each component
iso_sv_val <- 0.40
iso_sz_val <- 0.40 # Range for z: mean_z +/- 0.1
iso_st0_val <- 0.3 # Range for ter: mean_ter +/- 0.05

# Re-simulate standard DDM if not available or to ensure consistency with n_iso_trials
set.seed(305) # Match seed from previous standard sim if desired
ddm_data_standard_iso <- simulate_diffusion_experiment(
  n_trials = n_iso_trials,
  v = base_mean_v, a = base_a, z = base_mean_z,
  s = base_s, ter = base_mean_ter, dt = base_dt
)
ddm_data_standard_iso$model_type <- "Standard DDM (No Variability)"

1. Effect of Drift Rate Variability (sv) Only

Drift rate variability (sv) is often the most impactful, particularly on the overall skew of RT distributions and the characteristics of error responses.

set.seed(401) # New seed for this specific simulation
ddm_data_sv_only <- simulate_diffusion_experiment_variable(
  n_trials = n_iso_trials,
  mean_v = base_mean_v, a = base_a, mean_z = base_mean_z,
  s = base_s, mean_ter = base_mean_ter, dt = base_dt,
  sv = iso_sv_val,  # Only sv is non-zero
  sz = 0,
  st0 = 0
)
ddm_data_sv_only$model_type <- paste0("Only sv = ", iso_sv_val)

# Combine with standard for plotting
combined_sv_data <- bind_rows(
  ddm_data_standard_iso %>% select(rt, choice, model_type),
  ddm_data_sv_only %>% select(rt, choice, model_type)
) %>%
  filter(!is.na(rt)) %>%
  mutate(choice_label = factor(choice, levels = c(0,1), labels = c("Choice 0", "Choice 1")))

# Plot
p_sv_effect <- ggplot(combined_sv_data, aes(x = rt, fill = model_type)) +
  geom_density(alpha = 0.6) +
  facet_grid(model_type ~ choice_label) +
  labs(
    title = "Impact of Drift Rate Variability (sv) Only",
    subtitle = paste("Comparing Standard DDM to DDM with sv =", iso_sv_val),
    x = "Reaction Time (s)", y = "Density", fill = "Model Type"
  ) +
  coord_cartesian(xlim = c(0, quantile(combined_sv_data$rt, 0.99, na.rm = TRUE) * 1.1)) +
  theme_bw(base_size = 10) +
  theme(legend.position = "top", strip.text = element_text(size = 8, face="bold"))
print(p_sv_effect)

Density plots showing the specific effect of drift rate variability (sv) on reaction time distributions, comparing standard DDM to DDM with sv only

# Summary Stats
summary_sv <- combined_sv_data %>% group_by(model_type, choice_label) %>%
  summarise(N=n(), Mean_RT=mean(rt), Median_RT=median(rt), .groups="drop")
knitr::kable(summary_sv, caption = "RT Summary: sv effect", digits=3)
RT Summary: sv effect
model_type choice_label N Mean_RT Median_RT
Only sv = 0.4 Choice 0 1720 1.957 1.62
Only sv = 0.4 Choice 1 2684 1.723 1.39
Standard DDM (No Variability) Choice 0 245 2.857 2.74
Standard DDM (No Variability) Choice 1 3198 2.810 2.68

Expected Observation: With sv > 0, the RT distributions (especially for the choice favored by mean_v) should become more right-skewed compared to the standard DDM. You might also see more “slow errors” if sv is large enough for v_trial to sometimes be near zero or negative.

2. Effect of Starting Point Variability (sz) Only

Starting point variability (sz) primarily influences the probability of fast errors and can slightly affect the overall RT distribution shape.

set.seed(402)
ddm_data_sz_only <- simulate_diffusion_experiment_variable(
  n_trials = n_iso_trials,
  mean_v = base_mean_v, a = base_a, mean_z = base_mean_z,
  s = base_s, mean_ter = base_mean_ter, dt = base_dt,
  sv = 0,
  sz = iso_sz_val,  # Only sz is non-zero
  st0 = 0
)
ddm_data_sz_only$model_type <- paste0("Only sz = ", iso_sz_val)

combined_sz_data <- bind_rows(
  ddm_data_standard_iso %>% select(rt, choice, model_type),
  ddm_data_sz_only %>% select(rt, choice, model_type)
) %>%
  filter(!is.na(rt)) %>%
  mutate(choice_label = factor(choice, levels = c(0,1), labels = c("Choice 0", "Choice 1")))

p_sz_effect <- ggplot(combined_sz_data, aes(x = rt, fill = model_type)) +
  geom_density(alpha = 0.6) +
  facet_grid(model_type ~ choice_label) +
  labs(
    title = "Impact of Starting Point Variability (sz) Only",
    subtitle = paste("Comparing Standard DDM to DDM with sz =", iso_sz_val),
    x = "Reaction Time (s)", y = "Density", fill = "Model Type"
  ) +
  coord_cartesian(xlim = c(0, quantile(combined_sz_data$rt, 0.99, na.rm = TRUE) * 1.1)) +
  theme_bw(base_size = 10) +
  theme(legend.position = "top", strip.text = element_text(size = 8, face="bold"))
print(p_sz_effect)

Density plots showing the specific effect of starting point variability (sz) on reaction time distributions, comparing standard DDM to DDM with sz only

summary_sz <- combined_sz_data %>% group_by(model_type, choice_label) %>%
  summarise(N=n(), Mean_RT=mean(rt), Median_RT=median(rt), .groups="drop")
knitr::kable(summary_sz, caption = "RT Summary: sz effect", digits=3)
RT Summary: sz effect
model_type choice_label N Mean_RT Median_RT
Only sz = 0.4 Choice 0 330 2.440 2.275
Only sz = 0.4 Choice 1 3137 2.738 2.640
Standard DDM (No Variability) Choice 0 245 2.857 2.740
Standard DDM (No Variability) Choice 1 3198 2.810 2.680

Expected Observation: sz > 0 can lead to a slight increase in the variance of RTs. More notably, if mean_v strongly favors one response, sz can increase the likelihood of fast errors for the other response (if z_trial happens to start closer to that incorrect boundary). The overall skew might not change as dramatically as with sv.

3. Effect of Non-Decision Time Variability (st0) Only

Non-decision time variability (st0) primarily affects the spread of the leading edge (fastest part) of the RT distribution.

set.seed(403)
ddm_data_st0_only <- simulate_diffusion_experiment_variable(
  n_trials = n_iso_trials,
  mean_v = base_mean_v, a = base_a, mean_z = base_mean_z,
  s = base_s, mean_ter = base_mean_ter, dt = base_dt,
  sv = 0,
  sz = 0,
  st0 = iso_st0_val  # Only st0 is non-zero
)
ddm_data_st0_only$model_type <- paste0("Only st0 = ", iso_st0_val)

combined_st0_data <- bind_rows(
  ddm_data_standard_iso %>% select(rt, choice, model_type),
  ddm_data_st0_only %>% select(rt, choice, model_type)
) %>%
  filter(!is.na(rt)) %>%
  mutate(choice_label = factor(choice, levels = c(0,1), labels = c("Choice 0", "Choice 1")))

p_st0_effect <- ggplot(combined_st0_data, aes(x = rt, fill = model_type)) +
  geom_density(alpha = 0.6) +
  facet_grid(model_type ~ choice_label, scales = "free_y") + # Facet by model_type and choice
  labs(
    title = "Impact of Non-Decision Time Variability (st0) Only",
    subtitle = paste("Comparing Standard DDM to DDM with st0 =", iso_st0_val),
    x = "Reaction Time (s)", y = "Density", fill = "Model Type"
  ) +
  coord_cartesian(xlim = c(0, quantile(combined_st0_data$rt, 0.99, na.rm = TRUE) * 1.1)) +
  theme_bw(base_size = 10) +
  theme(legend.position = "top", strip.text = element_text(size = 8, face="bold"))
print(p_st0_effect)

Density plots showing the specific effect of non-decision time variability (st0) on reaction time distributions, comparing standard DDM to DDM with st0 only

summary_st0 <- combined_st0_data %>% group_by(model_type, choice_label) %>%
  summarise(N=n(), Mean_RT=mean(rt), Median_RT=median(rt), .groups="drop")
knitr::kable(summary_st0, caption = "RT Summary: st0 effect", digits=3)
RT Summary: st0 effect
model_type choice_label N Mean_RT Median_RT
Only st0 = 0.3 Choice 0 264 2.740 2.541
Only st0 = 0.3 Choice 1 3188 2.790 2.684
Standard DDM (No Variability) Choice 0 245 2.857 2.740
Standard DDM (No Variability) Choice 1 3198 2.810 2.680

Expected Observation: With st0 > 0, the RT distributions should look “smeared out” more uniformly, especially at the fast end. The minimum RT will be effectively mean_ter - st0/2 (plus minimum decision time), and the overall distribution will be a convolution of the decision time distribution and the uniform ter distribution. It tends to make the entire distribution wider without necessarily increasing skew as much as sv does.

Interactively Exploring Parameters with the Shiny App

Interactively Exploring Parameters with the Shiny App

Conclusion

Across-trial parameter variability is a critical extension to the basic DDM. By allowing drift rate, starting point, and non-decision time to fluctuate from trial to trial, the model can more accurately capture the richness and complexity of human decision-making behavior, particularly the full shape of RT distributions and the patterns of error responses.

The functions simulate_diffusion_trial_variable() and simulate_diffusion_experiment_variable() in your R scripts provide the tools to explore these more nuanced aspects of the DDM.