psychopy.org | Reference | Downloads | Github

Help with while loop to check for repeats in pseudo-randomization

I have 3 types of stimuli (4 shapes, 8 letters, 8 numbers) and 128 trials and I want to ensure no exact stimulus repeats. We first attempted to generate the entire list of 128 stimuli and force randomization of the list until there were no repeats, but this seemed to run indefinitely. I then changed to a random without replacement strategy, where each stimulus list is shuffled and then an item is selected with pop and the list is replenished once all items have been selected. This worked pretty well, but still needs to check for repeats. I have the code snippet for one list below. I keep getting repeats in the list despite the while loop check. Is there an error in the code? Thanks!

import numpy as np
stimCounts=128;
shapeStims=[]
prevShape=-1
for trials in range(0, stimCounts):
    if trials in range(0,stimCounts,4):
        shapelist = ['square','diamond','hexagon','star']
        np.random.shuffle(shapelist)
    checkforreps = True
    while checkforreps:
        currshape=shapelist[-1]
        if currshape == prevshape:
            np.random.shuffle(shapelist)
            break
        else:
            checkforreps = False
    currshape=shapelist.pop(-1)
    prevshape=currshape
    shapeStims.append(currshape)

I think you’re going about this from the wrong direction. It would be simpler to come up with the list of all 256 possible combinations (i.e. 4 shapes × 8 letters × 8 numbers) deterministically.

Then simply randomly sample 128 entries without replacement from that list, in one step. You will be guaranteed to have no repeats, there is no need for while loops, and the code will always take the same amount of time to run.

Make lists of the three stimulus types:

shapes = ['square', 'diamond' ,'hexagon', 'star']
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
numbers = list(range(8))

Now we need to get a list for each of length 256 so they can be aligned, but also make them cycle at different rates, so that corresponding entries perfectly balance. i.e. The shapes list will cycle quickly (i.e. neighbouring entries differ), so we need to change the rate to be slow for numbers and intermediate for letters:

# cycle as quickly as possible:
shapes = shapes * 64 

# cycle as slowly as possible (successive rows of 32 identical values):
numbers = numbers * 32
numbers.sort() 

# intermediate cycling, successive rows of 4 identical values:
letters = letters * 4
letters.sort()
letters = letters * 8

Now zip the three lists together, into a list of 256 tuples, each containing a unique triplet of values:

conditions = list(zip(shapes, letters, numbers))

Now dictionaries tend to be more useful than tuples, as you can refer to the values by name, so lets convert to a list of 256 dictionaries, using a list comprehension:

trials = [{'shape': condition[0], 
           'letter': condition[1], 
           'number': condition[2]} for condition in conditions)]

Now you have all 256 combinations, you can either take a random sample of 128 entries from it (without replacement), or (as can be simpler), shuffle it and just take the first 128 entries:

np.random.shuffle(trials)
selection = trials[0:128]
1 Like

That’s very helpful, thanks!