Blocks of Pseudo-Random Stimulus Positions Independent from Outer Loop (Reversal Learning)

OS (e.g. Win10): Win10
PsychoPy version (e.g. 1.84.x): 2021.1.3
Standard Standalone? (y/n) Yes

Hello! This forum is so helpful and I’ve learned so much by reading others’ questions here as I’m new to PsychoPy and python/coding in general. There’s one issue that I haven’t been able to deal with. I’ve been reading through a lot of past posts to try to troubleshoot this but haven’t found a similar issue.

I am making a reversal learning touchscreen task for dogs in which 2 stimuli are displayed and the “correct” (rewarded) stimulus changes such that the other (previously unrewarded) stimulus is rewarded after the dog reaches criteria (at least 9 correct of the last 10 consecutive trials), and so on, until 50 trials have been completed (regardless of the number of reversals that were completed). I got that part worked out just fine in a preliminary version of the task. In this initial version of the task, I had just used a 2-row conditions file set to loopType random to change the stimulus position each trial to either the Blue shape on the left and the Yellow shape on the right, or the Yellow shape on the left and the Blue on the right.

The “correct stimulus” is defined by an outer loop called reversals, which is a 2-row conditions file set to loopType sequential such that when the code tells it the trials have reached criteria, it will switch to the opposite stimulus being correct/rewarded. The inner/nested loop is the trials loop which contains the trials. Trials consist of presentation of clickable/touchable stimuli, a feedback routine where code will later be inserted to operate an automatic feeder/dispense a treat when the correct stimulus is touched, and a timeout routine that only occurs if the touched stimulus was incorrect. (I realized I can probably easily combine the feedback and timeout routine but haven’t done so yet).

Here’s the issue I’m having:

The PI working on the touchscreen task actually wants pseudo-random blocks for the stimulus position. The goals for this are:
• The correct stimulus is in the same place (left or right) no more than 3 times in a row (to minimize the chance of the dogs picking up on a pattern in the position of the correct stimulus)
• The correct stimulus is on the left vs right equally often within 10 trials (i.e. each block of 10 trials should have 5 where the Blue shape is on the right and 5 where the Yellow shape is on the right)
• Each block of 10 trials has a certain unique order of stimulus positions and that “block of stimulus positions” is not repeated.

The files for the blocks contain these columns: block (the block #, 1 through 12, largely irrelevant but in testing phases is helping me confirm that the random sorting is working and realize when used as a conditions file it is not behaving as I expected) and blueX, blueY, yellowX, and yellowY which are used as coordinates for the stimuli and are set to update on each trial. (The position block conditions I defined do not need to “know” which stimulus is correct since they just deal with the positions of the Yellow and Blue stimulus, and they all start with a flip of position, contain no more than 3 sequential rows in the same stimuli positions in the middle, and end with no more than 2 sequential rows in with the same stimuli positions. So when put back-to-back there’s no way for the position blocks to have more than 3 correct in a row on the same side).

What seems to be giving me trouble in trying to get this to work is that the blocks of stimulus positions should be independent from the reversals. A dog can take more than 10 trials to reach the criteria for a reversal, and will likely reach criteria on a trial number that is not a multiple of 10. We would like a conditions file that is a sequence of “position blocks” that runs continuously through all 50 trials without restarting. I think I’m missing something about how loops work or where to insert code or a routine to tell the trials loop which rows it should use. If my description isn’t clear I’ve attached a simplified example with my goal vs how the experiment is currently behaving as an Excel file of fake data for 40 trials. It demonstrates what I want the trials to be like (position blocks and reversal independent of each other) and the errors I’ve been running into. psychopy_exampledata40trials.xlsx (15.2 KB)

Here’s what I’ve done:

I’ve tried several things. Right now I have code that runs at the beginning of the experiment to randomizes the order of the filenames of 12 possible pre-defined pseudo-random position blocks in a list (block1.xlsx, block2.xlsx, block3.xlsx, etc) and then writes an excel file called “conditions.xlsx” that merges/concatenates the first 5 blocks in the randomized list into one file. “Conditions.xlsx” is written only one time during the experiment but overwrites itself each time the experiment is started again. The randomizing and rewriting of the conditions file at the start of the experiment works fine. That file, “conditions.xlsx,” is set as the conditions file for the trials loop. The conditions file conditions.xlsx (made of 5 randomly ordered blocks, where the rows within the blocks remain in sequential order) is set to loopType sequential because we still want to keep the pseudo-random blocks in order, not randomize the whole conditions file. I’m having a hard time getting it to continue sequentially through the conditions file continuously, and I think this has to do with outer loops, like the reversal loop, combined with the fact that the trials loop runs however many times it needs to until criteria are met. The outer loops seem to reset the trials loop’s “progress” through its conditions file and until the most recent thing I tried, it was starting back at row 0 when a reversal occurred. The slightly similar examples I’ve seen on the forum have a finite end, a certain fixed number of trials, and don’t have trials like these that are meant to keep going until certain criteria are reached.

I have somewhat lost track of the different things I’ve tried in many drafts of the experiment trying to get this to work, but I think I had the same issue when I did not combine the position blocks into one conditions file but instead had code specifying which conditions file, block1.xlsx, block2.xlsx, etc to use on each trials loop. I’ve also tried not concatenating the Excel files and instead putting in code to use the first block_.xlsx file in the randomized list when the blockCount ==1, the second in the list when the blockCount==2, etc. I’ve also done the same thing but with trialCount instead, e.g. if 0 <= trialCount <= 10: use the 1st conditions file in the list, allConds[0], if 11 <= trialCount <= 20: use the second conditions file in the list, allConds[1].

The most recent thing I’ve tried results in it essentially cycling through the same 10 rows of the conditions file in sequence continuously until a reversal happens, and then moving to the next 10 rows of the conditions file (but at least it is not starting over from the top of the conditions file. The code for this is in the “block” routine, at the beginning of the routine it calculates which block it is on (blockCount += 1 each time the routine runs to count the blocks), and additional lines of code create code that defines which rows of the conditions file to use. The variable condRows is then put in the selected rows field for the trials loop as $condRows (with the conditions file set to conditions.xlsx). But if I’m interpreting this correctly, this is happening because it is only checking/calculating the block number when reversals occur because the block routine is (by necessity?) outside of the trials loop but within the reversals loop.

It seems like PsychoPy can’t use a variable within from within the trial routine to define which rows of the conditions file it should use (for example, using trialCount when trialCount += 1 every time the trial routine runs so it keeps a running count of the total number of trials that have happened). Basically on each trial I just want it to run the row # of the stimulus positions conditions file that is equal to “trialCount - 1” (since my trialCount variable starts at 1, not 0). When I try to define which rows to use with code that is within the trial routine I get a condRows not defined error. Am I correct in thinking that because the trialCount is defined on each round of the trial routine it cannot use that information from within the trials loop to define the conditions for the trials loop? (It didn’t work to say when trialCount is between 1-10, use rows 0-9 of the conditions file sequentially, if the trialCount is between 11-20, use rows 10-19 of the conditions file, etc). Is there a way to tell the trials loop to continue from where it left off in the sequential conditions loop regardless of reversals? Or is there some other way to go about accomplishing this? Am I thinking about this in the completely wrong way?

This is the code that is in the Begin Routine tab of the ‘block’ routine currently. I’m sure it’s sloppy as I’m very new to this.

selectRows = ((blockCount - 1)*10)
selectRows2 = (blockCount*10)
condRows = str(selectRows) + ":" + str(selectRows2)

That makes “select rows” 0:10 when the blockCount is 1, 10:20 when the blockCount is 2, etc.

I’ve also tried defining a version of the selected rows that doesn’t have an endpoint, where condRows2 = str(selectRows) + “:”
That is probably the closest I’ve gotten but is still not quite what we are trying for (see the Excel file for explanation).

Essentially I think I understand why the variations I’ve tried seem to not be accomplishing what I’m after, but I don’t know how to set up code/loops/etc so that it does run through the conditions file sequentially and independently of the reversals.

Thanks in advance for your time and assistance!

The cleanest way to do this is to continue the same loop with other variables (e.g. reversals) changing based on the loop number.

selectRows = ((blockCount - 1)10)
selectRows2 = (blockCount
condRows = str(selectRows) + “:” + str(selectRows2)

This should work to select rows.


Sorry, I’m not sure what you mean by continuing the same loop with other variables (e.g. reversals changing based on the loop number). Can you clarify? Does this mean reversals etc should be controlled entirely in code instead of with a loop and the only loop should be the trials loop?

Yes, that code snippet does work to select rows, but the problem is that it cycles through those same 10 rows (e.g. rows 0-10) sequentially for the entire time it is on one “reversal” and only changes to the next positions block (e.g. rows 10-20) from the conditions file when trial criteria are met and a reversal happens. I think I understand why it’s behaving that way and I don’t think it’s an “error,” the code seems to be doing what it is “supposed to” under that circumstance. But I’m hoping to find a way to have the trials loop continue straight through the conditions file sequentially regardless of what reversal # it’s on, running all rows in order and each row of the conditions file once.

These are the various things I’ve run into while trying to accomplish that (shown in the images below):

  1. It is progressing to the next positions block in the conditions file after 10 trials (10 rows of the first “positions block”), but resetting to the top of the conditions file when a new Reversal starts. I think this is what happens when I don’t define any blocks or put anything in the selected rows field but give the trials loop the whole entire conditions file to use. See the column PosBlockError1
  2. It’s changing to the next “positions block” in the conditions file, but only when the reversals occur (so it’s staying on, for example, “positions block #8” for 15 trials when it took 15 trials to reach the criteria for reversal). This is what is happening when $condRows is used in the selected rows field, which makes sense since condRows is only defining 10 rows from the conditions file on each “block” so it keeps looping through those same 10 over and over until a reversal occurs, then swithces to the next “positions block.” See the column PosBlockError2
  3. Using condRows2 as the selected rows (has a beginning defined based on the blockCount but not a defined end). It makes perfect sense why this is continuing on to subsequent rows of the conditions file during one reversal, but on the next reversal it checks the blockCount and repeats the same for 10 trials, even if some trials already occurred with that block. But I can’t figure out how to set up code/loops/etc so that it will behave the way I’m hoping for. See the column PosBlockError3.

Here are the visual depictions of what’s happening (in these examples assume the randomly generated order of the stimulus position blocks is 8, 6, 3, 1, 10:

In this depiction of the issues the red text shows where the position block from the conditions file continues past where I want it to stop, changes early, or changes back to the block that was randomly sorted to the top of the conditions file.