psychopy.org | Reference | Downloads | Github

Creating a For Loop

Hi there,

I am a graduate student trying to use PsychoPy to create an N400 EEG study. To do so, sentences with various expectation failing properties are presented one word at a time with a short static period in between them.

Thus, I am trying to code a loop that essentially goes:
Load random sentence from excel file
For every word in this sentence:
list word for 250 ms, wait 300 ms
If last word, wait for 1200 ms.

Loop for X trials.

In theory it sounds like it shouldn’t be too difficult to code in python, but I can’t make heads or tails of it.

Thanks!

If you created this experiment in the graphical Builder interface, then everything here could be achieved without any code, except a for a snippet of four lines to achieve the conditional duration of the last wait period.

Happy to show how to do that, but don’t really have the time to walk through creating the entire rest of the experiment in code.

Hi Michael,

It would be great if you could lend a hand with setting that up in the builder interface.

Thanks!

Start with this tutorial on the Stroop task, which will teach you the basics of putting text stimuli on the screen, randomised from an Excel or .csv file:

Once you have got the basics implemented (of displaying the randomised words), insert a second text stimulus component on your main trial routine. One will be used to display the word for this trial, and will have a fixed start time of 0 and a fixed stop time of 0.25.

The second text stimulus will be blank and just used to control the variable wait period. Give it a fixed start time of 0.25 and a duration of $wait_time.

i.e. the $ symbol indicates to Builder not to take the value literally, but treat it as a Python expression (in this case, the variable name wait_time). To control that variable, insert a code component on the routine (from the “custom” component panel). In the “begin routine” tab, insert code something like this:

if the_name_of_your_loop.nRemaining == 0:
    wait_time = 1.200
else:
    wait_time = 0.300

Make sure that the code component is above the text stimulus, so that the wait_time variable is updated before the text stimulus needs to refer to it. You can change the order of components by right-clicking on their icons.

Apologies, that took four lines of custom code, not the initially-promised two.

1 Like

Hi Michael,

Thank you for going through all this routine for me.

Unfortunately, this isn’t entirely what I am trying to achieve. I have csv cells with between 4 and 14 words in them. I need each word to appear analogously, but within the same cell.

Something more like this type of programming logic (see above).

Thanks again!

Sorry, I missed that you are presenting sentences word-by-word, rather than independent individual words. You will probably get some good pointers by searching this forum for “self-paced reading”.

Thanks for the new keywords, Michael.

I have been searching through the forums and found one that seems like it can be molded to work:

Masked self-paced reading with time limit (code inside Builder) (available here).

The issue that I am at is that I am trying to modify the part of the code that reads:
def replaceWithX(textList, currentWordNumber):
"
xSentence = ‘’
for index, word in enumerate(textList): # cycle through the words and their index numbers
if index != currentWordNumber:
xSentence = xSentence + ‘x’ * len(word) + ’ ’ # add a string of x characters
else:
xSentence = xSentence + word # except for the current word

return xSentence # yields the manipulated sentence

"

With this current set up, it creates frames where the missing words are "X"s, as opposed to invisible (what I am trying to achieve). Ideally, only one word analogously will appear; without any visible "X"s.

I’ll update if I figure it out.

It turns out if you simply delete the “x”, but leave the ‘’ in place - it fixes this issue.

Thanks!

A new issue, however, has arisen from this change.

When the sentence is completed, the loop does not end. I have a contingent set up for " $statement.status==FINISHED " however, the status is never set to finished.

Is there a way to write something that goes:
"
if currentWordNumber = max(wordNumber):
status==FINISHED
else:
continue
"
?

Thanks in advance!

It is a little difficult to give suggestions here, as I don’t know the details of what you’ve implemented. But I’m going to guess that you have the sentence in a column in your conditions file, called, say, sentence.

Then you need two nested loops, one (the outer one) linked to your conditions file, which will run once per row of that file (i.e. once per sentence). The inner one will run once per word. The inner loop will run once per word.

The (slightly) tricky thing is telling this inner loop how many times to run. What you can do is count how many words there are in the sentence, and put that in the nReps field of the inner loop, like this (where we split the sentence into its constituent words and count how many there are in the resulting list):

len(sentence.split())

You don’t need to use most of the code in those previous posts for this task, as you don’t need to do any masking. Instead, we can simply split the sentence into words and just display each one on the each iteration of the inner loop. So insert a code competent, and in the “Begin routine” tab, put:

if your_inner_loop_name.thisN == 0: # only on the first iteration
    sentence_list = sentence.split()
    # this breaks your sentence's single string of characters into a list of individual
    # words, e.g. 'The quick brown fox.' becomes ['The', 'quick', 'brown', 'fox.']

Then in the “text” field of your text stimulus, put this:

$sentence_list[your_inner_loop_name.thisN]

i.e. on each iteration of the inner loop, this will show the corresponding word from the sentence. No other code should be necessary, except for the suggestion above about making the wait period longer on the last iteration.

Hi Michael,

I hate to sound too inept, but your most recent comment furthered my confusion.

The goal is to accomplish these goals:

  1. Draw random sentence from a .xlsx file, column named “policy”
  2. Split the sentence into individual words
  3. Count the number of words (ranging from 4 to 14)
  4. Display each word as the sole stimuli for 0.5 seconds
  5. Once the last word is done, set the status to finished so a review question can be asked about the sentence
  6. Restart the loop

In my mind, in order to achieve this we need to link the routine to the conditions .xlsx file (step 1).
Then in the code, have " sentenceList = sentence.split() " (step 2).
Then follow that in the code with " len(sentenceList()) " (step 3).
Then create a function that displays the words analogously:
"
def replaceWithX(textList, currentWordNumber):

xSentence = ''
for index, word in enumerate(textList): # cycle through the words and their index numbers
    if index != currentWordNumber:
        xSentence = xSentence + '' * len(word) + ' ' # add a string of x characters
    else:
        xSentence = xSentence + word # except for the current word
return xSentence 

" (step 3.1)
Set the statement to the function " statement.text = replaceWithX(sentenceList, wordNumber) " (step 3.2)

Now in each frame code:

Set a timer and run function
"
elapsed_time = last_keypress_timer.getTime()

if elapsed_time >= 0.5:
last_keypress_timer.reset() # reset the clock
wordNumber = wordNumber + 1
" (step 4)

Then check if last word is entered "
if sentenceList.thisN > len(sentenceList())
status == finished
" (step 5)

Then set a loop for X trials (step 6).

I am not sure what I am missing, both logically and writing wise.

Thanks

My last post above should achieve all of that. Just use the variable name policy in place of sentence.

You don’t need set any status manually. What you would do is just insert a separate routine to ask the question, which comes after the word-displaying routine. The inner loop would just surround the word display routine. The outer loop would surround both routines. i.e. the first routine will run once per word because it is contained within the inner loop, and the second will run only once per sentence, as it is contained only by the outer loop. No custom code required at all.

Forget all about this function. It is designed for masking words sequentially, which you don’t need. Simply use the arrangement above, of putting the expression $sentence_list[your_inner_loop_name.thisN] in your text component’s text field, set to update “every repeat”.

I can’t make out most of the rest of the post due to the inconsistent formatting (please use triple backticks to surround code, so that it is readable).

But in the first instance, I’d suggest you actually just attempt to implement the simplified scheme above and come back with any specific problems. The best route to understanding is by implementing code, rather than thinking about it.

Hi there! This thread is amazing and it helped me a lot. I was able to get the experiment to work offline - however it gives me an error when I run it online - ReferenceError: len is not defined. It probably needs to be converted to JS - do you have any idea how to do this? I have been looking around on the fora but haven’t found anything…

Thank you in advance!

I’m not a JavaScript person so can’t help very much. But in essence, you need to provide a JavaScript equivalent to each snippet of custom Python code in your code components. e.g. select “Both” in the code type menu in your code components and a new pane will open up that allows you to type in JavaScript while still seeing the Python that needs to be translated.

You can also select “Auto -> JS”. This will do its best to translate your Python to JavaScript for you. It isn’t perfect, but it usually gets you a big part of the way there.

1 Like

Thanks Michael! I’ve translated the code from

len(sentence.split())

to

SentenceAfter.split(" ").length;

But it is not going well. It doesn’t recognises the code and crashes online and offline. I’m gonna keep working on it. Thanks anyway!