Randomising types of trials so the same stimuli do not repeat one after the other

Hi,

I am trying to programme a dot-probe task which includes trials with the same stimuli (but 4 different conditions) in the same block. I have mostly programmed it on builder view, however I am at the stage where I am trying to insert some code to help with randomising the order of trials, but stopping the same stimuli repeating after one another.

I want to insert some code so that the same stimuli do not repeat after one another, but all the trials (24) eventually play in one block (i.e. 4 repetitions). I want this to be randomised, but have a balanced number of each condition number (1 - 4), and each type (0-5) in each cycle. Hope this makes sense so far! Here is a screenshot of my excel conditions file - highlighting (in red) the 4 conditions for each stimuli type.

I have tried inserting some code which I modifed based on this thread: Randomly select every 4 row in condition file
However, I think I am missing code which instructs psychopy to repeat the cycle so all conditions for each type eventually play (i.e. there are 6 types and 4 trials so there should be a total of 24 trials). If anyone could help, that would be appreciated. I am very new to psychopy/ coding so apologies if I haven’t explained this very well - please let me know if I need to go into more depth!

See below for the code I have inserted so far:

#beginexperiment
import pandas as pd

dat = pd.read_excel("./Conditions.xlsx")
conditionType = [1,2,3,4] # a list for each type of condition
shuffle(conditionType)

nTrials = list(range(0, dat[‘Type’].max()+1)) # Takes max value from Type column and creates a list of trial numbers
shuffle(nTrials)

#beginroutine
trialSet = dat[dat[‘Type’] == nTrials[trials.thisN]] # Uses trialhandler to index random trial number from list

if len(conditionType):
useCondition = conditionType.pop() # Get condition from shuffled list and remove index from list
else:
conditionType = [1,2,3,4] # If list is empty, create a new one.
shuffle(conditionType)
useCondition = conditionType.pop()

thisTrial = trialSet[trialSet[‘condition’]==useCondition]

Thanks very much!

I need a little clarification on what you’re trying to do. Is the idea that you want a random order, but you don’t want the same “voice” file to play twice in a row? So, for example, you never want one of the top four lines to be selected after another of the top four lines, but you do want the order to be otherwise fully random for 24 trials?

Also, a question I feel I should ask up front, are you planning to run this online on Pavlovia? If so, the solution will look nothing like what you have now. PsychoPy’s ability to convert Python code components to JavaScript can’t support importing external libraries like pandas.

If you’re just trying to run this locally on your computer, the easiest solution will be a nested loop structure in builder. You’ll need a trial to sit in between each cycle of 24, but it can even be a blank trial with the code component you currently have under “begin experiment” under “begin routine”. The outer loop repeats four times, and then you put another loop around the test trial itself that repeats 24 times.

If you want to do this online, that’s still probably going to be the general outline of the solution, but you’ll need to figure out some JS code that can do this.

Yes, that is correct. I don’t want the same voice file to play twice in a row. Other than that, everything else should be random. This experiment will not be run online, so I am just trying to get it working on a computer.

Thanks for your solution, although I am slightly confused. I have added in two loops like this (if this is what you mean):

Within the blank trial I have added the code component. Loop one (trials) repeats 6 times and the outer loop (trials_3) repeats 4 times (So overall totalling 24 trials in one block). This works so the same voice files do not repeat twice in a row. However, when looking at the output file, I can see that the order of Type/ voice trials is the same on every repeat. See picture below. Is there a way of ensuring the voice trials are presented in a random order for every repeat?

output

Also if possible, I would like every trial/ row of the excel file to play only once in a block. For example, if stimuli type 1, condition 2 has played once, it shouldn’t be played again. This will ensure all four conditions for each stimuli type will play once in a block. This isn’t the case at the moment as as you can see from the below output, stimuli type 3 condition 1 played twice.

output2

Thank you for your help!

Oh, I misunderstood slightly. I was thinking you would have trials_2 as you have it now, but trials would only loop across the dotprobetrial 24 times after the condition order is established in the blank trial. trials_3 would be redundant in my version, but your solution might work better in some ways. The reason that it’s the same order of stimuli on every cycle is that nTrials is only shuffled once, where you want it to be shuffled after every six trials, but honestly I’m not entirely sure how because your two-loop structure seems like it should avoid that problem.

However, now that I think about it, making the types random on every cycle will also introduce the risk of the end of one cycle sharing stimuli with the start of the next. As for that trial/row playing twice, I think I know why it’s happening but it’s hard to avoid when you are juggling two different types of randomization that aren’t well-connected to each other.

Thinking about it more, it’s going to take something fairly complex to satisfy all of your randomization requirements, to the point where it might even be easier to pre-construct a bunch of random orders in separate files and cycle through those files, rather than trying to procedurally randomize each time. That said, I think there is a way to do it. Here’s the basic outline of it:

  1. Make separate lists for each stimulus type, i.e., six different lists of four items each. You could either implement this by having six separate files, or you could do this manually after reading the files in.
  2. Cycle through those six lists, popping one thing from each into the final list, and then shuffle each set of six, making note of the first and last one in the shuffled order.
  3. If this is not the first set of six, compare the first one to the last one of the previous set of six, and if they are the same, reshuffle.

and you would need to do all of this for every block of 24 trials. This would pretty much all happen in the “begin routine” code for you blank trial, with the risk that the last trial from one set of 24 would be the same as the first trial from the next set of 24. The overall structure of the experiment would be trials_2 is your primary outer loop, repeating for however many blocks you want total, and “trials” only goes around dotprobetrial with 24 repetitions, after the blank trial code element has established the trial order for that block.

Here’s a rough concept of what the randomization would look like, but note that this is just to show you the kind of nested randomization you would need. This isn’t connected to the condition file, you’ll need to figure out how to implement it in the right way.

#Begin routine
datStructure = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]] # A list of six lists (one per type), each with four items (conditions).
blockOrder = [] # This will hold a set of pairs in the format [type, condition]. 
for i in range (0,4):
    # Ultimately we are cycling through these six things four times.
    subOrder = [] # This will hold the order for each set of six.
    for j in range(0, len(datStructure)):
        shuffle(datStructure[j])
        subOrder.append([j,datStructure[j].pop()]) # pulls one of the conditions at random w/out replacement
    shuffle(subOrder)
     #subOrder now contains one instance of each type, with a randomly chosen condition for each, in random order.
    if len(blockOrder) > 0:
       while subOrder[0][0] == blockOrder[-1][0]: # Just comparing the Type element of the start of this set of six and the end of the last set of six, to make sure the same type is not repeated.
            shuffle(subOrder) # If it is repeated, re-randomize until it isn't.
    for k in range(0, len(subOrder)): # Once that's all done, add these trials to the final trial list.
        blockOrder.append(subOrder[k])

This is just doing it with lists of numbers rather than actual full conditions, but the output of this should be a list that has 24 ‘trials’ (in this case pairs in the form [type, condition]), each one unique (so no repeats of the same trial type with the same condition), and no consecutive types (because within each set of six they are all distinct, and it makes sure that the first element of the next set doesn’t match the last element of the last set).

This isn’t the exact code you should use because it doesn’t connect to the condition file, but it gives you an idea of the kind of structure for the randomization you’d want to do for each block of trials. You just need to figure out a way to line it up with the actual conditions and feed it to trialhandler.

1 Like

Hi,

Thank you for this! I have now created six different lists for each of the four items in different excel files (titled: StimType0.xlsx, StimType1.xlsx… StimType5.xlsx).

This code seems useful, but due to being an absolute novice, I have no idea how to connect these condition files into the code.

Do I need to use some of the same code as I started with?
e.g.
import pandas as pd

dat = pd.read_excel("./StimType0.xlsx, ./StimType1.xlsx… etc)

Then once the files are imported, how do I link the condition collumn (1, 2, 3, 4) with the code you suggested?

Sorry, this is all very new and confusing for me!

I had to think about this for a bit. Pandas is both helping and hindering here. It’s not well-equipped for the very particular kind of hierarchical pseudo-randomization we want, so I think we’re actually better off doing all the randomization with the exact code I’ve shown you and only going to pandas when we need to reference the trial itself. So, here’s my recommendation:

  1. Put everything back in one condition file, we’re basically only going to use it as a reference rather than manipulating it.

  2. For blanktrial, use the exact begin routine code you see in my previous post.

  3. For dotprobetrial, in begin routine, now we bring in some of your original code, but instead of trying to pick a trial with pandas, we just use the blockOrder array we created to find the correct trial in the list.

#Put this in "begin experiment"
import pandas as pd
dat = pd.read_excel("./Conditions.xlsx")

# Put this in begin routine for dotprobetrial
trialSet = dat[dat['Type']==blockOrder[trials.thisN][0]] # Get all the entries of the type stored in the current row of blockOrder
thisTrial = trialSet[trialSet['condition']==blockOrder[trials.thisN][1]] # Get the subset of those that has the right condition as well

Basically, we’re just finding the row that has the same type/condition combination as the appropriate row in blockOrder. That means that the It’s a slightly kludge way to do it, but probably the simplest. Technically we could probably do this in one line:

thisTrial = dat[dat['Type'] == blockOrder[trials.thisN][0] && dat['condition']==blockOrder[trials.thisN][1]] 

This works perfectly! Thank you very much for your quick responses and help.
It has also helped me learn a little about coding too so much appreciated.