psychopy.org | Reference | Downloads | Github

Randomise stimuli list without surrounding stimuli appearing consecutively

I’m using Psychopy version 1.84.2 on Windows.

What are you trying to achieve?:

I have created a task where participants are presented with a range of stimulus images (84) repeated 8 times. This is done via a loop accessing stimuli randomly from an excel list. I want the stimuli to be presented randomly, without the previous or next stimulus on on the excel list appearing subsequently (e.g., Stim_2 can’t followed by Stim_1 or Stim_3).

I’ve seen some threads discussing randomisation without repeated stimuli, but not without repeats from the neighbouring stimuli on a list.
It would also be great if there was a solution that could be updated to increase the number of neighbouring list stimuli that are not put in a row, as I may need to make it so the two surrounding list stimuli are not presented subsequently (e.g., Stim_3 cannot be followed by Stim_1, Stim_2, Stim_4, or Stim_5).

Here are some photos of the experiment set up in case it is useful.


Thanks so much!

Hi Paige,

Here is a suggestion that adapts @dvbridges solution to a related problem. The suggestion assumes that you have a list of numbers that you randomise.

import random

while 1:
    choices = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    shuffledList = []

    previous = ""

    while choices:

        tmpList = choices
        
        # if we've already found one element
        if previous:
            #print(previous)
            # find all list elements that are not direct neighbours of the previous element
            tmpList = [x for x in tmpList if x != previous-1 and x != previous+1]
            #print(tmpList)
        
        # there are no elements left that are not direct neighbours
        if not tmpList:
            print("\nno valid solution")
            break 
        
        # choose a random element from the reduced list
        newEl = random.choice(tmpList)
        
        # append this element to the new list
        shuffledList.append(newEl)
        
        # remove the chosen element from *choices* (not tmpList!)
        choices.remove(newEl)
        
        # remember the element for the next iteration
        previous = newEl
        
    # if choices is empty (i.e., all elements were successfully moved to shuffledList)
    if not choices:
        print(shuffledList)
        break

To apply the result to your Excel input file, you could follow these instructions. Note that you would then need to choose sequential as loopType. If you wanted to, you could create a separate input file for each participant.

Hope this helps.

Jan

Hi Jan,

Thanks a lot for your suggestion.

I am unsure how to apply this code to my task, and which part of the instructions you linked is relevant to me. Do you mean I should number my stimuli from 1-84 in a new excel column? Then would I replace the number sequence in the first “choices = […]” line of code to reflect the new excel column?

Hi Paige,

You could use the following code to create ten different sequences and write these to files called 1.txt to 10.txt:

import random

count = 1

while count < 11:

    # numbers 1 to 84
    choices = list(range(1, 85))

    shuffledList = []

    previous = ""

    while choices:

        tmpList = choices
        
        # if we've already found one element
        if previous:
            #print(previous)
            # find all list elements that are not direct neighbours of the previous element
            tmpList = [x for x in tmpList if x != previous-1 and x != previous+1]
            #print(tmpList)
        
        # there are no elements left that are not direct neighbours
        if not tmpList:
            print("\nno valid solution")
            break 
        
        # choose a random element from the reduced list
        newEl = random.choice(tmpList)
        
        # append this element to the new list
        shuffledList.append(newEl)
        
        # remove the chosen element from *choices* (not tmpList!)
        choices.remove(newEl)
        
        # remember the element for the next iteration
        previous = newEl
        
    # if choices is empty (i.e., all elements were successfully moved to shuffledList)
    if not choices:
        
        myFile = "{}.txt".format(count)
        
        with open(myFile, 'w') as file_handler:
            for item in shuffledList:
                file_handler.write("{}\n".format(item))
        
        count = count + 1

You would then need 10 copies of your input file, each with an extra column with just the stimulus numbers ordered from 1 to 84 (as you suspected). In terms of then getting the stimuli in the right order, see the attached example in Excel:

  • Column A is the original order
  • Column B uses the formula from the link, e.g. =MATCH(A1,C:C,FALSE)
  • Column C is the order produced by the script (which you would need to copy and paste into your Excel file)
  • You would then need to select columns A and B and sort these according to column B
  • The resulting order in column A should match that in column C

If you wanted to, you could instead use pandas and a dataframe, which would remove the need for manually copying/pasting/sorting, but would be more involved.

Jan

example.xlsx (8.6 KB)

Thanks so much Jan!

While it takes a couple of steps this seems to do the trick perfectly and fits with my basic knowledge of coding :slight_smile:

If I needed to change things so that I didn’t have any of the two neighbouring list items on each side occur subsequently (4 total), how would I alter the code?

Hi Paige,

I’ve modified the code so that you can now specify a minimum gap between items (where 1 means a gap of 1 [the original behaviour], 2 a gap of 2, and so on).

import random

count = 1

minGap = 2

while count < 11:

    # numbers 1 to 84
    choices = list(range(1, 85))

    shuffledList = []

    previous = ""

    while choices:

        tmpList = choices
        
        # if we've already found one element
        if previous:
            #print(previous)
            # find all list elements that are not within minGap of the previous element
            tmpList = [x for x in tmpList if x < previous-minGap or x > previous+minGap]
            #print(tmpList)
        
        # there are no elements left that are not within minGap of the previous element
        if not tmpList:
            print("\nno valid solution")
            break 
        
        # choose a random element from the reduced list
        newEl = random.choice(tmpList)
        
        # append this element to the new list
        shuffledList.append(newEl)
        
        # remove the chosen element from *choices* (not tmpList!)
        choices.remove(newEl)
        
        # remember the element for the next iteration
        previous = newEl
        
    # if choices is empty (i.e., all elements were successfully moved to shuffledList)
    if not choices:
        
        myFile = "{}.txt".format(count)
        
        with open(myFile, 'w') as file_handler:
            for item in shuffledList:
                file_handler.write("{}\n".format(item))
        
        count = count + 1

Jan

Hi Jan,

That works perfectly. Thanks so much for all your help!