Search code examples
rfor-loopsimulationrandom-walk

Simulating a discrete approximation to a random walk in R with multiple conditions


I am building a simulator for a task that has two conditions. On each trial, the condition is supposed to go back and forth between the two levels of nCond and the respective index of the other variables. For example, trial 1 should have the stimulus "R", a pi value of .75, and a difficulty diff that is "Easy." Trial two should have the stim "L", pi of .65, and diff of "Hard", the trial three should go back to mirror trial 1 with slightly different end RTs.

What is instead happening is every trial has a condition of 2, stimulus of "L", and the same pi/mean_a/a_s/etc. I'm sure it's likely a minor issue with the second loop concerning the mycond index, but I can't quite figure out how to fix it.

Would greatly appreciate any help!

#Set a seed
set.seed(2024)


#Variable declaration-------------
nConds <- 2     #Number of conditions
stim <- c("R", "L")       #Stimulus labels
pi <- c(.75, .65)         #Probability of step (+1) for random walk; 1-pi will be the probability of stepping -1
diff <- c("Easy", "Hard")  #Difficulty label for the condition
mean_a <- c(40, 40)       #Mean upper bound for each condition
a_s <- c(0, 0)            #Variability in upper bound for each condition
zp=c(.5, .5)              #Multiplier of a for starting point for condition (Z = a*zp)
zp_s=c(0, 0)              #Variability for starting point multiplier
Nsubs=1                   #Number of subjects
Trials_per_cond=100       #Number of trials per condition
timeout=500               #Timeout parameter for the random walk


#Create an empty dataframe for storing responses
mydata=data.frame("Sub"=rep(1:1, each=100), "Trial"=1:100, "RESPONSE"=NA, "RT"=NA)


for (myperson in unique(Nsubs)){
  
  for (mycond in 1:nConds){
    
    condpi=pi[mycond]
    cond_a=mean_a[mycond]
    cond_a_s=a_s[mycond]
    cond_zp=zp[mycond]
    cond_zp_s=zp_s[mycond]
    cond_stim=stim[mycond]
    cond_diff=diff[mycond]
    
    for (i in 1:Trials_per_cond){
      
      mytrial=i
    
     trial_a=rnorm(1, mean=cond_a, sd=cond_a_s)
     trial_Z=rnorm(1, mean=trial_a*cond_zp, sd=cond_zp_s)
     trial_Pi=rnorm(1, mean=condpi, sd=0)
    
     #Begin setup for random walk
     time=0
     curpointer=trial_Z
  
     #Conduct random walk
     while (time < timeout){
       
       time=time+1
       curpointer=curpointer+sample(c(-1, 1), size=1, prob = c(1-trial_Pi, trial_Pi))

       if (curpointer >= trial_a) { 
        myresponse="1"
        break
          }
    
       else if (curpointer <= 0){
       myresponse="0"
       break
       }
    
    }
  
     myRT=abs(time)

  #Save the parameters to the respective trial in the dataframe
  mydata[mydata$Sub==myperson & mydata$Trial==mytrial,"RESPONSE"]=myresponse
  mydata[mydata$Sub==myperson & mydata$Trial==mytrial, "RT"]=myRT
  mydata[mydata$Sub==myperson & mydata$Trial==mytrial, "Condition"]=mycond
  mydata[mydata$Sub==myperson & mydata$Trial==mytrial, "Stimulus"]=cond_stim
  
  }
 }
}

Right now the end table looks something like this:

mydata

|sub|Trial|RESPONSE|RT  |Condition|Stimulus|
|1  |1    | 1      |100 |  2      |  L     |
|1  |2    | 1      |130 |  2      |  L     |
|1  |3    | 1      |98  |  2      |  L     |
.....
|1  |98   | 1      |65  |  2      |  L     |
|1  |99   | 1      |120 |  2      |  L     |
|1  |100  | 1      |100 |  2      |  L     |

It should look like this:


mydata

|sub|Trial|RESPONSE|RT  |Condition|Stimulus|
|1  |1    | 1      |102 |    1    |  R     |
|1  |2    | 1      |111 |    2    |  L     |
|1  |3    | 1      |65  |    1    |  R     |
.....
|1  |98   | 1      |98  |    1    |  R     |
|1  |99   | 1      |120 |    2    |  L     |
|1  |100  | 1      |100 |    1    |  R     |


Solution

  • Essentially, your innermost loop overwrites the row since both mycond iterators share same Sub and Trial conditions. Consider adding Condition as a present column in empty data frame and also in logical condition:

    #Create an empty dataframe for storing responses
    mydata = data.frame(
      "Sub"=rep(1:1, each=100), "Trial"=1:100, "Condition"=rep(1:2, 50)
    )
    ...
    
    #Save the parameters to the respective trial in the dataframe
    row_filter <- mydata$Sub==myperson & mydata$Trial==mytrial & mydata$Condition==mycond
    
    mydata[row_filter, "RESPONSE"] = myresponse
    mydata[row_filter, "RT"] = myRT
    mydata[row_filter, "Stimulus"] = cond_stim
    

    By the way, consider a more functional form of assigning values iteratively. Since you are running elementwise across rows, use mapply and avoid nested for loops:

    run_walk <- function(mytrial, mycond) {
        condpi=pi[mycond]
        cond_a=mean_a[mycond]
        cond_a_s=a_s[mycond]
        cond_zp=zp[mycond]
        cond_zp_s=zp_s[mycond]
        cond_stim=stim[mycond]
        cond_diff=diff[mycond]
        
        trial_a=rnorm(1, mean=cond_a, sd=cond_a_s)
        trial_Z=rnorm(1, mean=trial_a*cond_zp, sd=cond_zp_s)
        trial_Pi=rnorm(1, mean=condpi, sd=0)
        
        #Begin setup for random walk
        time=0
        curpointer=trial_Z
      
        #Conduct random walk
        while (time < timeout){
           time=time+1
           curpointer=curpointer+sample(c(-1, 1), size=1, prob = c(1-trial_Pi, trial_Pi))
    
           if (curpointer >= trial_a) { 
               myresponse="1"
               break
           }
           else if (curpointer <= 0){
               myresponse="0"
               break
           }
        }
      
        return(
            c(RESPONSE=myresponse, RT=abs(time), STIMULIS=cond_stim)
        )
    }
    
    # CREATE EMPTY DATA FRAME
    mydata2 <- data.frame(
        Sub = rep(1:1, each=100), Trial = 1:100, Condition = rep(1:2, 50)
    )
    
    # ASSIGN TRANSPOSED MATRIX VALUES TO COLUMNS
    mydata2[,c("RESPONSE", "RT", "STIMULIS")] <- with(
        mydata2,
        t(mapply(run_walk, Trial, Condition))
    ) 
    
    # CONVERT SOME COLUMNS TO INTEGER FROM FULL CHARACTER MATRIX 
    mydata2 <- transform(
        mydata2,
        RESPONSE = as.integer(RESPONSE),
        RT = as.integer(RT)
    )
    

    Online Demo