Randomizing File Choice Across Two Blocks

First, I apologize if this has an easy solution or if I miss something in my explanation, I am new to psychopy and coding.

I have an experiment built using builder that has two word exposure blocks (audio files are played based on two conditions file), with informational text blocks occurring between the two testing blocks. Currently, I have it set up so that the two blocks use separate condition’s files and separate words (the “A” block always has the “A” set of words and the “B” block always has the “B” set of words).

What I would like to do is make it so that the A and B block pick from one condition file (or even two if it achieves the same result) that contains all of the words that will be played in the two exposure phases, but only choose half of the words for one block (A), and then the other half would be played during the next block (B). Essentially, I want the words presented in A and B to be completely randomized, meaning that across both blocks it is random which words play during each run of the experiment.

Is this possible in Psychopy? I have tried looking through different topics and posts but I haven’t been able to find one that is specifically about my question.

Thank you!

Spencer

It isn’t provided for out-of-the-box by Builder, but it would be possible to achieve in Builder by inserting some custom code components to get around its limitations of having a separate conditions file for each loop. i.e. what you need is a single list of words that can be used across both loops.

What that code would do:

  • at the beginning of the experiment:
    • read in a .csv file just containing a single column of all of your words and put that into a list.
    • shuffle(that_list) (to give you your randomisation).
  • at the beginning of each routine (whether in block A or B):
    • current_word = that_list.pop() to get the next element of the list into a variable to use as the word for this trial.
    • the_loop_name.addData(current_word) to save the selected word for this trial in the data file (needs to be done manually as you aren’t having this done automatically for you via the loop’s conditions file).
    • use current_word as required, e.g. in a text stimulus.

You insert a code component from the “custom” component panel.

Hi Michael,

Thank you very much for your detailed response. So with this coding would the words that first appear in block A not appear in block B, or would it just be random order from the entire list for both blocks? I am trying to have any words that appear in block A to then not appear in block B, for the words that appear in block A and B to be a random selection from one list each time. So there can’t be any overlap of words in A and B.

Spencer

Correct. Let’s say that Block A and Block B are 30 trials each. So you initially read in a list of 60 words and randomise its order. Block A gets the first 30 words, Block B the second 30 words. The words are presented from the list sequentially, so there can be no duplicates, but the order of the list was randomised, giving you the best of both worlds.

Perfect, I was able to go in and add code to the routine and it works exactly how I want it to now, thank you! But now I notice another problem, so for the routine I have, it is a simple exposure discrimination task, they have to decide if a word is an animal or not, and are then in a separate routine, block C, are tested on the non-animal words. The issue is that I want an equal amount of animal and non-animal words in block A and B (i.e. 10 animal words and 20 non-animal in A and B). Is there something I can tag the words with in the conditions file and some code that will cause this?

You can create two .csv files, one for the 20 animal words, and one for 40 non-animal words. They would look like this:

word    type
cat     animal
dog     animal
gnu     animal
etc
word    type
rock    non-animal
stick   non-animal
leaf    non-animal
etc

Read each one in with a DictReader: https://docs.python.org/3/library/csv.html#csv.DictReader

This imports the file into a list of dictionaries, where each dictionary contains both the name and its associated type, e.g. the list would look like this:

[{'word':'cat', 'type':'animal'}, {'word':'dog', 'type':'animal'}, etc]

So lets say you have two lists, one called animals and one called non_animals, then just go through the same sort of list manipulation as before:

# randomise words across trials and blocks:
shuffle(animals)
shuffle(non_animals)

A_words = animals[0:10] + non_animals[0:20]
shuffle(A_words) # randomise across types, within block

B_words = animals[10:20] + non_animals[20:40]
shuffle(B_words) # randomise across types, within block

all_words = A_words + B_words # optional?

So now to use the word, e.g. in a text stimulus, you need to extract it from the dictionary for this trial, e.g:

trial_details = all_words.pop() # the values for this trial
current_word = trial_details['word']
current_type = trial_details['type']

your_loop_name.addData('word', current_word) # record in the data file
your_loop_name.addData('type', current_type)

Thank you again, you have been very helpful!

So now I am having an issue with the shuffle function after I made the changes, it gives me an error message when I get to the routine

shuffle(Animal_Exposure)
NameError: name ‘Animal_Exposure’ is not defined

“Animal_Exposure” is the name of the csv file I created for all of the animal words. If I have looptype set to random in the loop for the trials do I still need to shuffle?

Spencer

In this case, you are handling the randomisation yourself manually, in code. The whole point of using this custom code is that Builder’s loop functionality can’t provide for this sort of customised design. This is also why we have to do this:

your_loop_name.addData('word', current_word)

because the loop doesn’t know anything about the variables that you have created in your own code, so you need to tell it manually to save the values in the data file on each trial.

So your loop shouldn’t be connected to the .csv file containing your words at all. If you have other variables that you want the loop to control (e.g. stimulus colours, or ITIs, etc), then sure connect it to a .csv containing that information. But if you don’t, then the loop won’t be connected to any csv file at all. Just tell it to run with an nReps value of 30 to get the right number of trials.

Okay, so I was able to get the shuffle function to work correctly. I currently have the following code in the “Begin Experiment Section” of my custom code component (which is located in the first exposure loop)


with open('Animal_Exposure.csv') as csvfile:
    reader=csv.DictReader(csvfile)
    animals=list(reader)
with open('Non_Animal_Exposure.csv') as csvfile:
    reader=csv.DictReader(csvfile)
    non_animals=list(reader)
shuffle(animals)
shuffle(non_animals)```



The experiment starts without error, so I believe this code is all correct. For the begin routine section, this is what I have. The problem is that when it gets to the first exposure loop, which is after a small practice loop, there is a text routine in between, it will only play the last word that was played during the practice over and over again and won’t move onto the other list. “Animal_Not_1” is the name of the routine for the first exposure, “animals” and “non_animals” are the two lists, “sounds” is the header for the sound file names on the conditions file, and “Animal_or_Non” is the type header. “Animal_Not_1_Loop” is the name of the first exposure loop.

Animal_Not_1=animals[0:9]+non_animals[0:17]
shuffle(Animal_Not_1)
all_words= animals+non_animals
trial_details = all_words.pop() # the values for this trial

current_word = trial_details['sounds']
current_type = trial_details['Animal_or_Non']

Animal_Not_1_Loop.addData('sounds', current_word) # record in the data file
Animal_Not_1_Loop.addData('Animal_or_Non', current_type)

You need to consider when the code in each tab will run. The “begin experiment” tab is for code that should run only once per session, before anything else happens. The “begin routine” tab contains code that runs before every trial.

So in this case it looks like you are repeating things that should happen only once. In this case, you’ll be re-creating the all_words list on every trial, which defeats the purpose of working through it one entry at a time.

e.g.

all_words = animals + non_animals
trial_details = all_words.pop()

So here you are re-creating the list on every trial, which means you are extracting the same word from it each time, which is why the displayed word seems to be constant. Instead, you should just construct your list only once, then .pop() from it on every routine, which will give you a fresh word on each trial.

Lastly, you need to follow the logic of what is being constructed. Currently, your list all_words is simply the concatenation of the animals and non-animals list, without the careful randomisation of types across blocks as suggested above.

Thank you for that explanation and also for your patience with me.

I think I have discovered what the primary issue is, I went through and made the changes you suggested in regards to where each piece of code should be (begin experiment or begin routine) and was able to confirm that the creation of lists was working correctly by running a few different print() functions at the end of the “Begin Experiment” section of code. I went through and disabled the Practice routine that comes before the first exposure and realized that the issue was that for each trial the audio file for the word was not being played. After disabling the practice component (which still used a conditions file) I would either get this error

Animal_Not_Words.setSound(sounds)
NameError: name 'sounds' is not defined

with “Animal_Not_Words” being the name of my audio component for that routine and “sounds” being the header of the .csv with the list of sound files/words and types (two columns).

If I disable the audio component for that routine, the routine runs but nothing happens/no audio.

My begin routine part of the code now contains this only, I define trial_details in “Begin Experiment”.

current_word = trial_details['sounds']
current_type = trial_details['Animal_or_Non']
Animal_Not_1_Loop.addData('word', current_word) # record in the data file
Animal_Not_1_Loop.addData('type', current_type)

It seems that the primary issue is getting the routine/trials to play the audio files from the list, since I believe the list is being generated correctly, the list looks something like this:

[OrderedDict([('sounds', 'Sounds\\Investigate.wav'), (Animal_or_Non', 'Non_Animal')]), [OrderedDict([('sounds', 'Sounds\\Horse.wav'), (Animal_or_Non', 'Animal')])

Thank you again for your patience with my questions!

Sorry, this is the first mention of sound, so I don’t know what your requirements are here. Can you hold my hand through this a bit? i.e. what sounds needs to be played on each trial, and how are they related to the word selected for each trial?

Oops! I apologize if it wasn’t clear that they were audio files of words playing.

So the experiment consists of only audio words, no visual/text based words.

There are four main “blocks”, listeners are making “one or the other” decisions via keyboard about what category each word falls into.

Block 1, practice block, listener hears 2 animal words and 2 non-animal words.

Text block in between.

Block 2, exposure block 1, listener hears around 20 non-animal words and around 15 animal words, needs to decide animal or not.

Block 3, exposure block 2, same thing as exposure 1 but we make a change to equipment outside of psychopy during a routine that is just text of “take a break” which occurs after block 2 is finished.

*words in block 2 & 3 cannot overlap, and there must be 20 non-animal and 15 animal in each.

Text block in between.

Block 4, test block, listeners are asked if they remember hearing words from block 2 and 3, 50/50 new and old words. *This block is all set and works fine.

Once again I apologize if I wasn’t clear about the words be audio based!

OK, so it looks like you are putting the variable name $sounds in the sound field of your stimulus. But Builder has no idea what that variable refers to: remember, you have been manually reading in that file and processing it. i.e. the file isn’t connected to a loop, so Builder doesn’t know anything about what it contains. So you simply need to replace $sounds with $current_word, which is the variable that we have manually created to represent the current sound file for each trial.

Make sure that your code component is above the sound component, so it gets to refer to that latest value of current_word.

Michael, you are the absolute greatest! That did it, I’ll have to do some testing just to make sure but I ran through the whole thing and it seems like it’s doing exactly what I want now.

Thank you again for your patience with me!