Randomize without repeating stimuli

If this template helps then use it. If not then just delete and start from scratch.

OS (e.g. Win10): Win10
PsychoPy version (e.g. 1.84.x): v2021.1.2
Standard Standalone? (y/n) If not then what?: y
What are you trying to achieve?:

  • I have 40 different stimuli
  • I would like to split those into two groups of 20 stimuli
  • For Session 001, I want to randomly present the first 20 stimuli from the first group and Session 002, randomly presents 20 stimuli from the other group. I have it coded and everything but it repeats some stimuli which I do not want.

What did you try to make it work?:

import random, xlrd

#randomize the SEED
random.seed()

#stimulus file
infile = 'FragStimuli.xlsx'

#num of study items
num_study = 20

(Begin Experiment)
#access the stim file
inbook = xlrd.open_workbook(infile)
insheet = inbook.sheet_by_index(0)

#arrays for stimuli
word = []

#for randomizing words
random_order = list(range(num_study))

#read the stimuli from sheet (all columns)
for rowx in range(num_study+1):
    row = insheet.row_values(rowx)
#save word to word array
    word.append(row[0])

random.shuffle(num_study)

#array for final stimuli
word_frag = word

for i in range(num_study):
    word_frag.append(word[i])


(Begin Experiment in the experiment loop)
#list of order to show study items
study_order = word_frag

#randomize order of study list
random.shuffle(study_order)

#current study trial
study_trial = 0

(Begin Routine in loop)
#assign study item
WordTing = study_order[study_trial]

#current study item counter
study_trial = study_trial + 1

By session you mean at a separate time? Locally the easiest way would be to save a personalised word list during session 1 of the words to be used in session 2

How would I do that?

Alternatively, instead of setting a random seed (which isn’t necessary, as there will be a new seed each time unless you set it otherwise), create a fixed one for each subject. e.g. in the information dialog at the start of the experiment, enter a seed value which is unique for each subject. Then the randomisation will be the same across the two sessions for that subject. In the first session, use the first 20 stimuli, and in the second, use the second lot of 20.

NB don’t use the random package from Python’s standard library. Use numpy's random package instead. That way, the seed you set will apply to all relevant PsychoPy functions as well.

e.g. this is what all Builder scripts make available:

import numpy as np  # whole numpy lib is available, prepend 'np.'
from numpy.random import random, randint, normal, shuffle, choice as randchoice

So similarly, if you have an experiment info dialog field called ‘seed’, you could do:

np.random.seed(expInfo['seed'])

Then shuffle your list. Present the first half in session 1, and the second half in session 2. Probably easiest to also enter the session number in the exp. info dialog.

2 Likes

I’d recommend you go with @Michael ‘s suggestion. In session 1 use continueRoutine=False to skip second 20 trials and in session 2 skip the first 20.

1 Like

Thank you Michael - I’m sorry but I’m new with PsychoPy so I am just a little confused: I want to get my stimulus file from an excel sheet and just have it take 20 words from there, randomized but not repeated. How would I go about that and what would be my seed value?

Hi wakecarter, thanks so much - I already have a session 1 and a session 2 set for the order in which my tests are going to be administered, so I just want my first 20 to be presented in session 1 and the last 20 to be presented in session 2, randomized. Where would I put Routine = False?

Any integer. It just needs to be unique per subject. That means that each subject will get a different random order but the “random” order will be the same across sessions for a given subject. If you’re not comfortable with programming, then just use a spreadsheet to create a list of random numbers between say 1 and 1000 000. Record those in a table so you can enter in each person’s value.

In the loop dialog, there is a box for a seed value that is specific to that loop. So you could simply put this in that field:

expInfo['seed']

Then when the loop runs again for a subject, it will use the same “random” order. The only thing you need to do then is select the first 20 rows on the first session and the second twenty rows on the next one. So insert a code component and in the “begin experiment” tab, put something like this:

if expInfo['session'] == 1:
    selected_rows = [0, 20]
else:
    selected_rows = [20, 40]

# record that for checking in the data file:
thisExp.addData('selected_rows', selected_rows)

then just put this in the selected rows field:

$selected_rows
1 Like

This is what I have in my Begin Routine at the beginning of my experiment:

` >#word lists - contains all words
totalwords = []
#list that contains study words
studywords = []

#randomize words
random_order = list(range(num_total))

#iterates all words in list
for rowx in range(1, num_total+1):
#row containing all values in row
row = insheet.row_values(rowx)
#store 0th place value in total word list
totalwords.append(row[0])

#shuffles the words
random.shuffle(random_order)

for i in range(num_fragment):
studywords.append(totalwords[random_order[i]])

if expInfo[‘seed’] == 1:
selected_rows = [0, 20]
else:
selected_rows = [20, 40]

record that for checking in the data file:

thisExp.addData(‘selected_rows’, selected_rows)`

Is that okay?

Unless I’ve misunderstood something from your initial description, everything other than the code I suggested can be deleted. i.e. you should just need this:

if expInfo['session'] == 1:
    selected_rows = [0, 20]
else:
    selected_rows = [20, 40]

# record that for checking in the data file:
thisExp.addData('selected_rows', selected_rows)

Now maybe your original code was doing something else but I can’t really interpret it. I’m just going off your original description, which is a bit ambiguous.

Note there was a typo before, and this:

if expInfo['seed'] == 1:

should have been:

if expInfo['session'] == 1:

assuming you have a field called ‘session’ in the info dialog.

1 Like

I also have another line of code that changes the order of presentation of the experiments itself:

if expInfo['session'] == '001':
    hugeloop_num = 1
    hugeloop2_num = 0
elif expInfo ['session'] == '002':
    hugeloop2_num = 1
    hugeloop_num = 0
else:
    errormessage = 'Incorrect session! Try 001 or 002.'
    error_num = 1
    hugeloop_num = 0
    hugeloop2_num = 0

Would I put that code over here or should I keep it as its own? Also, do I still keep the code that imports my excel sheet’s stimuli/words?

#importing randomization stuff
import numpy as np
import xlrd

#more importations
from numpy.random import random, randint, normal, shuffle, choice as randchoice


#stimulus file
infile = 'FragStimuli.xlsx'
inbook = xlrd.open_workbook(infile)
insheet = inbook.sheet_by_index(0)

#num of study and total items
num_total = 40
num_fragment = 20

You can put that code in the same “begin experiment” tab.

No, a builder loop imports an Excel file for you automatically. Just navigate to the file using the “conditions” field.

All we need to do is control the rows used in each session. And that’s where I put you wrong, because a Builder loop selects the rows before applying the randomisation. So the code I gave above would mean that every subject got the first 20 rows in the first session, although the order would be randomised. So instead, scrap the code I gave above and use this:

np.random.seed(expInfo['seed']) # fixed order per subject
row_numbers = list(range(40))
shuffle(row_numbers)

if expInfo['session'] == '001':
    selected_rows = row_numbers[0:20]
else:
    selected_rows = row_numbers[20:40]

and once again just put $selected_rows in the selected rows field of your loop stimulus. Delete whatever was in the “seed” field, as that is now being handled in code.

1 Like

I did that and I received this error upon running the code:

np.random.seed(expInfo[‘seed’]) # fixed order per subject
KeyError: ‘seed’

Should that have said session instead?

  • Please use ``` before and after all code, or indent individual code lines with four spaces. Otherwise, we can’t read the code formatted properly - e.g. above it looks like curly quotes got into your code.

No.

Do you have a field called that in your experiment info dialog, and have a number in it?

1 Like

oooooooh sorry I had no idea how to do it! & Where would I find my experiment info dialogue? This is what I have in relation to expinfo:

if expInfo['session'] == '001':
    hugeloop_num = 1
    hugeloop2_num = 0
elif expInfo ['session'] == '002':
    hugeloop2_num = 1
    hugeloop_num = 0
else:
    errormessage = 'Incorrect session! Try 001 or 002.'
    error_num = 1
    hugeloop_num = 0
    hugeloop2_num = 0

np.random.seed(expInfo['seed']) # fixed order per subject
row_numbers = list(range(40))
shuffle(row_numbers)

#define words to be presented per session
first_session_rows = row_numbers[0:20]
second_session_rows = row_numbers[21:40]

#sets the words to be presented per session
if expInfo['session'] == '001':
    selected_rows = first_session_rows
elif expInfo['session'] == '002':
    selected_rows = second_session_rows
else:
    errormessage = 'Incorrect session! Try 001 or 002.'
    error_num = 1
    hugeloop_num = 0
    hugeloop2_num = 0```

Click the “Experiment settings” icon in the toolbar, and add a new field called seed (by clicking a + button).

1 Like
    np.random.seed(expInfo['seed']) # fixed order per subject
  File "mtrand.pyx", line 244, in numpy.random.mtrand.RandomState.seed
  File "_mt19937.pyx", line 166, in numpy.random._mt19937.MT19937._legacy_seeding
  File "_mt19937.pyx", line 186, in numpy.random._mt19937.MT19937._legacy_seeding
TypeError: Cannot cast array from dtype('<U1') to dtype('int64') according to the rule 'safe'

Okay so this is the new error after doing so. There is now also a prompt to enter a seed number when I run my code - does this mean that when a new seed number is put in, it’ll be different each time?

As above, you are responsible for this. You need to maintain a list of integers, one per subject, and enter one at the start of the session. This is how you ensure constant randomisation across sessions for each subject (by using the same seed for each session), while ensuring different randomisation for each subject (by using a different seed for each subject).

We want PsychoPy to interpret what you entered as an integer, but it seems to be read as something else for some reason.

Please add this to the beginning of the code:

print('seed:')
print(expInfo['seed'])
print(type(expInfo['seed']))

and let me know what it produces.

1 Like

So I would just need to keep a note of this for myself, rather than put in more code or anything, right?

print(seed)
NameError: name 'seed' is not defined

Sorry, that was a typo. Should have been expInfo['seed'] instead of just seed (post has been edited above accordingly).

1 Like