psychopy.org | Reference | Downloads | Github

How do I make selective no consecutive trials?

Hi, I’m trying to make a standard stop signal task with Builder view.
But the thinig is, unlike normal stop signal task, there should be no consecutive ‘stop’ trials in my experiments. Consecutive go trials are fine to happen.

This is my condition file. So the signal column really matters. The number of total trials is 150.
I want to have fullrandom (or pseudo-random) list of trials without any consecutive signal trials.
The original experiment used condition file with loop(not necessary since I can easily make excel file with 150 rows), but it seems bit annoying for builder to make more advanced trail randomization.

I tried to follow this link here: https://discourse.psychopy.org/t/randomisation-without-consecutive-presentations/8089

stimulus_list = ['v', 'b', 'n', 'm'] * 3
shuffle(stimulus_list)

# make sure the same target doesn't appear on consecutive trials
double_stim = False
list_ok = False

while list_ok == False:
    for i in range(len(stimulus_list) - 1):
        # check for equal neighbours:
        if stimulus_list[i] == stimulus_list[i + 1]:
            double_stim = True  # D'oh!
            break  # can stop checking on this run
    if double_stim == True:
        shuffle(stimulus_list)  # try a new order
        double_stim = False
    else:
        list_ok = True  # Yay! The loop won't run again.

print(stimulus_list)

Code here looks simple except for the meaning of variable ‘stimulus_list’.
The comment says I can add the code in the Begin Experiment tap,
but because I don’t know what stimulus_list means I cannot manipulate it.

expInfo = {‘participant’: ‘YWH’, ‘session’: ‘001’, ‘initialSSD’: ‘0.200’}

organize them with the trial handler

trials = data.TrialHandler(nReps=1, method=‘random’,
extraInfo=expInfo, originPath=-1,
trialList=data.importConditions(‘realTrialConditions.xlsx’),
seed=None, name=‘realTrials’)

listOk = False
double = 0

while not listOk:
for thisTrial in trials:
print(str(trials.thisTrial[‘signal’]) + ’ ’ + str(trials.thisIndex))

    if (trials.thisIndex % 5 == 4):
        double += 1
    else:
        double = 0

if double == 2:
    trials = data.TrialHandler(nReps=1, method='random', 
    extraInfo=expInfo, originPath=-1,
    trialList=data.importConditions('realTrialConditions.xlsx'),
    seed=None, name='realTrials')
    
    double = 0
else:
    listOk = True

print(‘end’)

This is the code I tried to manage in Coder view format. I have no idea… what I should do to ‘shuffle’ the order of the trials variable? Only if anyone can provide good advice for it, things will be solved easily I guess. (I’m not familiar with the interface in the forum here, so don’t consider about indents because there’s no problem about it in my program!)

Thank you for your help! I’ll attach the program I built.

blocksSST.xlsx (9.7 KB) goOnlyConditions.xlsx (10.7 KB) realTrialConditions.xlsx (13.7 KB) SST_Finger.psyexp (27.9 KB) SST_Finger.py (30.8 KB)

Hi @AkaiRed, to get non-consecutive trials based on the signal variable, add the following to the “Begin Experiment” tab in a code component.

import pandas as pd
import random


while 1:
    choices = pd.read_excel("realTrialConditions.xlsx")
    choices = pd.DataFrame.to_dict(choices.T)
    shuffle = []
    last = ""
    
    while choices:
        remaining = choices 
        if last:
            remaining = [choices[x] for x in choices if last['signal'] == 0 or choices[x]['signal'] != last['signal']]
            remaining = pd.DataFrame(remaining)
            remaining = pd.DataFrame.to_dict(remaining.T)
        if not remaining:
            #no valid solution
            break 
        newElement = random.choice(list(remaining))
        last = remaining[newElement]
        shuffle.append(remaining[newElement])
        for each in choices:
            if choices[each]['index'] == remaining[newElement]['index']:
                del choices[each]
                break
    if not choices:
        pd.DataFrame(shuffle).to_excel("newCond.xlsx")
        break

The output is attached. Note, for this to work you have to add a new column called index, which are sequential values. See attached.realTrialConditions.xlsx (14.2 KB) newCond.xlsx (9.8 KB)

The “newCond.xlsx” conditions file that has been created can be used in your loops later in the experiment.

Thank you dvbridge!

I’ve been working for this all day and you saved me! At first the code didn’t work, there were still consecutive signals in the data file, but as I change the loop setting from ‘random’ to ‘sequential’ it all went good! (Sounds reasonable because we already made an random list with constraint, randomizing again will only make things worse.)

My work is done, rest is just my curiousity. I understood most of your code, but this choice (or remaining) is bit tricky to me. The type of choice variable is a dictionary, right? What would the code below mean?

remaining = [choices[x] for x in choices if last['signal'] == 0 or choices[x]['signal'] != last['signal']]
...
for each in choices:
            if choices[each]['index'] == remaining[newElement]['index']:
                del choices[each]
                break

I get the logical idea you used to avoid consecutive terms, but is dictionary callable? It would be appreciated if you let me know the type of those x, each, choices. Oh wait, whether you comment about it or not I’ll appreciate from the bottom of my heart lol. Thanks!

Hi @AkaiRed, I am glad it is working. Dictionaries can be indexed using keys (strings) - see docs

The first line:
remaining = [choices[x] for x in choices if last['signal'] == 0 or choices[x]['signal'] != last['signal']]
This loops through the remaining choices/trials left in the original dictionary created from the conditions file. It creates a list of trials to be selected at random. If the last trial selected was a stop condition (signal == 1) it only allows go conditions to be added to the trial list (signal == 0).

newElement = random.choice(list(remaining))
last = remaining[newElement]
shuffle.append(remaining[newElement])
for each in choices:
        if choices[each]['index'] == remaining[newElement]['index']:
            del choices[each]
            break 

This block first randomly selects a new trial from the allowed trial list, adds it to the shuffle list (a container of the randomised trials) and then deletes that entry (remaining[newElement]) from the choices dict by matching the index values that you entered in the conditions spreadsheet.

I hope that makes sense. Add some print statements throughout to see these things in action.

Thanks dvbridge, for the beautifully explained code. It helped me a lot!

Hi @dvbridges,

Any chance you have an idea for how to do the same thing with two variables? I mean the conditional loop for not to have consecutive trials by considering two variables.I would be really glad if you share any ideas.

Thanks