This example is designed to give you a better feel for the importance of evidence accumulaton to a the threshold in a DDM and its effects on decision-making. It is written using R Markdown. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.
You can download the code file used to create this page at http://decisionneurolab.com/resources/Intro_to_DDM/exercises/threshold.Rmd.
Author: Cendri Hutcherson
Date last modified: May 1, 2019
I’m assuming that you’ve already read the introductory material describing the basic notion of sequential accumulation to bound. Here, we’ll play around with a function that computes a choice and an RT using these two principles.
Let’s start by writing a simple version of this function in R.
ddm = function(driftrate = .08, threshold = .15, noise = .1, precision = .001){
# function ddm:
# driftrate: average strength of the evidence
# threshold: the choice-determining threshold
# noise: standard deviation of the evidence, by convention set to .1
# precision: the size of the time-step for accumulating evidence (default = .001s)
# 1. start at time t = 1
t = 1
# 2. start with evidence at 0
accumulatedEvidence = 0
# 3. use a while loop that will break when the evidence passes +/- the threshold
while(abs(accumulatedEvidence[t]) < threshold){
t = t+1
# 4. take a noisy sample, scaled for the temporal precision with which we are estimating
noisySample = driftrate*precision + rnorm(1, mean = 0, sd = noise*sqrt(precision))
# 5. add it to the previous evidence
accumulatedEvidence[t] = accumulatedEvidence[t - 1] + noisySample
}
# 6. return a data structure with the choice, the RT (in s), and the observed accumulated evidence
choiceInfo = list(choice = sign(accumulatedEvidence[t]), rt = t*precision, drift = accumulatedEvidence)
}
Above is a very simple function to compute a DDM. It takes as input 4 arguments: the desired drift rate, the threshold, the noise, and the temporal step size used to calculate the total evidence in a single sample.
Let’s take this baby out for a stroll and see what it does!
aChoice = ddm() # compute information about a single choice, with the default options
# what was the choice?
aChoice$choice
## [1] 1
# how long did it take?
aChoice$rt
## [1] 1.99
# what did the accumulation of evidence look like?
plot(aChoice$drift, type = 'l', col = 'blue', xlab = 'Time (in ms)', ylim = c(-.15, .15), ylab = "Accumulated Evidence")
# draw in the thresholds and the zero line
segments(c(0,0),c(.15, -.15), c(1000*aChoice$rt+50,1000*aChoice$rt+50), c(.15,-.15), lwd = 2)
segments(0,0,1000*aChoice$rt+50,0, lty = 2)
Well, that looks like some accumulation of evidence right there, doesn’t it? You can see that the accumulated evidence should cross one of the thresholds right at the point where the rt indicated.
What would happen if we ran another instantiation of the DDM, with exactly the same parameters?
anotherChoice = ddm()
# what was the choice?
anotherChoice$choice
## [1] -1
# how long did it take?
anotherChoice$rt
## [1] 1.54
# what did the accumulation of evidence look like?
plot(anotherChoice$drift, type = 'l', col = 'blue', xlab = 'Time (in ms)', ylim = c(-.15, .15),
ylab = "Accumulated Evidence")
# draw in the thresholds and the zero line
segments(c(0,0),c(.15, -.15), c(1000*anotherChoice$rt+50,1000*anotherChoice$rt+50), c(.15,-.15), lwd = 2)
segments(0,0,1000*anotherChoice$rt+50,0, lty = 2)
As you can see, due to the randomness of the noise, no two accumulation trajectories ever look precisely the same.
Given that no two DDM realizations ever look exactly the same, one might ask “Are there some regularities that we can use to describe the resulting choices and RTs?” Well, let’s take a peek. To do this, we’ll create a function to simulate and plot the frequency of choices at different RTs. Then, we’ll simulate with the default parameters, and graph the resulting choices and RTs in a histogram.
simAndPlot = function(driftrate = .08, threshold = .15, nSim = 1000){
# initialize vectors to hold the simulated choices and RTs
Choices = RTs = vector(mode = 'numeric', length = nSim)
# run the simulations and save the data
for(sim in 1:nSim){
temp = ddm(driftrate = driftrate, threshold = threshold)
Choices[sim] = temp$choice
RTs[sim] = temp$rt
}
# Plot the data!
# 1. Get the frequencies of YES and NO responses in specific RT bins, determined by the maximum RT
maxRT = ceiling(max(RTs))
rtBins = seq(0,maxRT, length = 40)
freqYes = hist(RTs[Choices == 1], breaks = rtBins, plot = FALSE)$counts
freqNo = hist(RTs[Choices == -1], breaks = rtBins, plot = FALSE)$counts
# 2. plot the frequencies
par(mfrow = c(2,1), mar = c(0,4,5,1) + .2)
yMax = ceiling(max(c(freqYes, freqNo)/50))*50
hist(RTs[Choices == 1], breaks = rtBins, ylim = c(0,yMax), main = '', xaxt = 'n'
, col = 'red')
par(mar = c(4,4,1,1) + .2)
hist(RTs[Choices == -1], breaks = rtBins, ylim = c(yMax,0), main = '', xlab = 'RT'
, col = 'blue')
# 3. Add descriptive text to the plot
text(maxRT * .5, yMax * .4, paste('% YES: ', format(mean(Choices == 1), digits = 3)), adj = 0)
text(maxRT * .5, yMax * .6, paste('Ave. YES RT: ', format(mean(RTs[Choices == 1]), digits = 3)), adj = 0)
text(maxRT * .5, yMax * .8, paste('Ave. NO RT: ', format(mean(RTs[Choices == -1]), digits = 3)), adj = 0)
}
# Now let's use our function!
simAndPlot(nSim = 1000)
What you can see is that, given a drift rate of +.08 and threshold of .15, YES responses are much more frequent than NO responses (~90% vs. 10%), and the average RT tends to be about 1.5 seconds or so.
Let’s examine what happens if we double the drift rate (to .16). First, think about what you predict will happen to the distribution of the choices and RTs. Ok, let’s test your intuition.
simAndPlot(driftrate = .16)
Was it what you predicted?
Let’s see what happens if the evidence is on average negative rather than positive.
simAndPlot(driftrate = -.08, threshold = .15)
Let’s say that instead of changing the drift rate, we changed the treshold. We’ll try a couple of examples here. Note how changing the threshold changes the likelihood of a yes response, and the associated RTs.
simAndPlot(driftrate = .08, threshold = .05)
simAndPlot(driftrate = .08, threshold = .1)
simAndPlot(driftrate = .08, threshold = .2)