psychopy.org | Reference | Downloads | Github

Controlling trials number per condition

I have a very general question.

Is it possible to control the number of trials per condition within the same loop?

Let’s suppose I have 2 different conditions (A, B).

Condition A is composed of 7 different possible stimuli;
Condition B of 30 possible stimuli.

On the one hand, I want to present all the 7 stimuli of Condition A, but only 3 of Condition B, within the same loop and in a random manner. So, 10 trials in total per loop, 70% from Condition A, 30% from Condition B.

So far I haven’t figured out a way to do it. The only thing I was thinking about was to repeat the trials in the excel file where the loop takes the conditions from. In this case, the excel file with the 7 stimuli would then be repeated 10 times to have 70 stimuli and aggregated to the 30 stimuli of Condition B. So you have the 70 vs. 30% already in the excel.

However, while this solution might work in some cases, there should be a more effective way to do this. Do you have any idea?

This is an example file of the two conditions together (here 3 each).
Question_randomization.xlsx (5.0 KB)
What the trial looks like is this a colored cross appears, there is a break, then a second colored cross appears. After that the participants have to make 4 presses.

@GioP, you can do this with a nested loop structure and a code component:

image

loopA and loopB present blocks of trials only once (nReps = 1). nLoops nReps determines how many times loopA and loopB are presented. loopA will show all 7 trials, but loopB will only present 3 of however many trials if you add a code component to the trialB routine with the following in the beginRoutine tab:

if loopB.thisTrialN == 2:
    currentLoop.finished = True

Example:

controllingN.psyexp (10.4 KB)
condA.xlsx (7.8 KB)
condB.xlsx (8.1 KB)

1 Like

Thank you very much for your reply.

I am not able to open the file since I am working with version 1.85.3. However, I implemented what you wrote, I think in the appropriate manner. While your solution definitely helps in keeping the number of trials as wanted, it still does not help with randomization.

In this way, it will always present first the 7 of Trial A and then the 3 of Trial B, right? But what if want to randomize the order? And not just the order of the loops (first A and then B vs. first B and then A), but the trials themselves. E.g., A B A A A B A B A A, B A A A B A B A A A and so forth and so on.

If you are prepared to do all of the balancing of the proportions of A and B in a single conditions file, as in your original post, then it can become quite straightforward. Just have a single loop around both routines, connected to the conditions file. Let’s say you have a column in that file called trial_type, which will contain values of either A or B.

Then insert a code component into each of the two routines (from the “custom” component panel), and in the “begin routine” tab of each, put something like this in trialA:

if trial_type != 'A':
    continueRoutine = False

and in trialB:

if trial_type != 'B':
    continueRoutine = False

This way, only one of the routines will run on each iteration of the loop, in the 70:30 proportion as controlled by your conditions file.

1 Like

Hi Michael,

Thanks for the suggestion. I still do not understand why but it seems to work.

Still, I would think there is a problem in balancing in the excel file. Let´s suppose a concrete experimental case. I have 20 trials A and 80 trials B (possible filler trials). I want to present all trials A but only a portion of trials B, since it is only filler trials.

I can balance in the excel. But then I would need to incrementally increase the repetitions of trials A within the loop. I increase trials A to 20*6 and leave Trials B to 80, this way I have 200 trials, 40% Trials B, 60% Trials A. However, for many time-related issues, it might be inconvenient to have a loop containing already 6 times your experimental conditions.

If 20 conditions last 5 minutes, I can have an experiment of 45 minutes (+ filler trials), by having 7 loops. But if I have to use multiple of 6, the first loop will last 30 minutes (+ fillers) and so the second loop. However, I would still have a time limit of 45 minutes imposed by the experimental setting. So I cannot run a second loop.

Would it be possible to do something like this, maintaining the unbalanced excel file? Something where you keep track of trials A and B and when trials B reach a certain desired amount (e.g, in this example “3”), trials B stop to appear. I put this in the Begin Experiment:

Trial_A = 0
Trial_B = 0

And this in the Begin Routine, having a single loop with one excel file with the column trial_type:

if trial_type == 'A':
    Trial_A =+ 1
elif trial_type == "B":
    Trial_B =+ 1

if trial_type == "B" and Trial_B >= 3:
    continueRoutine = False

It is not working, but would it be possible to reach something like this?


UPDATE

I just realized a silly mistake in the code I sent you in the order of “=” and “+”.

Now the code looks like this. Begin Experiment is the same. Begin Routine:

if trials.thisTrialN == 0: #restart the value of Trial_B at the beginning of each loop
    Trial_B= 0

if trial_type == "B" and Trial_B == 3:
    continueRoutine = False
elif trial_type == "B":
    Trial_B +=1 

Now it seems to work pretty fine. Does it have any cons doing it like this?

Probably wise not to accept what some random person tells you on the internet until you know exactly what is going on… :wink: But it seems that you are moving beyond this solution anyway, which is good.

I haven’t read through this in enough detail to see if it will do precisely what you want, but you seem to be on the right track. The best approach will be to test, test, test to ensure you get the results you want. This would include going through the resulting data output files and checking that the proportion of trials is exactly what you want.

One other suggestion: you might also need to terminate the loop itself when you meet your goal number of trials, if that occurs before the maximum number of trials that the loop would otherwise produce (corresponding to the number of rows in your conditions file). I couldn’t really see if that will be required for you, but if for example, you want to end the loop when the expected number of A trials and maximum number of B trials has occurred, then you can do this if required:

trials.finished = True

Just to make your code more readable, I’d suggest doing it this way:

if trial_type == 'B':
    Trial_B +=1
    if Trial_B >= 3:
        continueRoutine = False

Otherwise, on first reading, it looks like you will skip the routine only when it is exactly the third trial, which works, but isn’t very intuitive to read. Although the continued counting might seem pointless here, it might also be useful information to know how many B trials have been skipped.

Sorry if I update this old thread. I have one slight doubt.

At the moment, I have the following code in Each Routine of every routine in the trial loop:

if trial_type == "filler" and n_filler > n_max_filler:
    continueRoutine = False
    thisExp.addData('filler_del', "1") #1 = filler deleted
else:
    thisExp.addData('filler_del', "0")

So, if the number of filler trials exceeds the value of n_max_filler the filler trial skips. This is working, with one issue that I don’t know if it is affecting the experiment in any way.

Let’s say I have 11 possible filler trials but I only display 1 in my experiment. In the result .csv file I can see that 1 has been loaded and shown and 10 have not been shown but they have been “loaded” anyway.

fillers

Is it affecting the experiment (frame, timing, anything) the fact that 10 filler trials are not shown but loaded and stopped? In my actual experiment those 10 trials are 500, quite a high number.

I have just tried with a higher number of filler trials (=170) and it seems to me that there is an evident delay.


UPDATE

I just tried to make one thing. I have two files one with the experimental trials and one with the filler trials. At the beginning of the experiment I can create a new dataframe concatenating all the experimental trials and 5 random filler trials.

df_filler = pd.read_excel('GroupFiller' + expInfo['group'] + '.xlsx') #reads the dataframe from the file containing filler trials
df_filler_2 = df_filler.sample(n=5) #creates a new dataframe with 5 random filler trials

df_experimental_trials =  pd.read_excel('GroupTrials' + expInfo['group'] + '.xlsx') #reads the dataframe with the experimental trials

frames = [df_filler_2, df_experimental_trials]
experimental_df = pd.concat(frames) #concatenates the two dataframes: filler and experimental trials

experimental_df.to_excel('Group' + expInfo['group'] + '.xlsx', index=False) #creates a new excel from the concatenated dataframe

Then in the loop I can just read the trials from the last concatenated file. The problem here is that every time the experiment would need to go back and forth, deleting and adding rows for each participant and for each loop (n=6).

It seems to work, without the delay I was experiencing before. Could this solution have any drawbacks?

If anyone is having the same issue, this here might be a solution.

Adapt this code at Begin Experiment

import pandas as pd

n_dataset_int = 0
n_dataset = str(n_dataset_int)

df_all_possible_trials = pd.read_excel('Group' + expInfo['group'] + n_dataset + '.xlsx') # this is my dataset with all possible trials (500 trials)

### you might skip this part ####
#out of it I will extract all the types of filler I have (fill_rule, sub_rule, fill_spec)
#extract filler trials
df_filler_rule = df_all_possible_trials.loc[df_all_possible_trials['conditions'] == "fill_rule"]
df_filler_sub_rule = df_all_possible_trials.loc[df_all_possible_trials['conditions'] == "fill_sub_rule"]
df_filler_spec = df_all_possible_trials.loc[df_all_possible_trials['conditions'] == "fill_spec"]

#extract the experimental trials - all of them
df_experimental = df_all_possible_trials.loc[df_all_possible_trials['trial_type'] == "experimental"]


#### creating the files ####
for iterations in range(0,12): #my experiment has 12 trial loops
  
    n_dataset_int += 1 #this keeps track of the number of iterations
    n_dataset = str(n_dataset_int) 

    #sample two random filler trials per condition and create a new dataframe out of these
    df_filler_rule = df_filler_rule.sample(n=2)
    df_filler_sub_rule = df_filler_sub_rule.sample(n=2)
    df_filler_spec = df_filler_spec.sample(n=2)

    # concat all the dataframes
    frames = [df_filler_rule, df_filler_sub_rule, df_filler_spec, df_experimental]
    df_experiment_concat = pd.concat(frames, ignore_index=True)
    df_experiment = df_experiment_concat.sample(n=38) #38 is the total number of trials
    df_experiment.reset_index(drop=True, inplace=True)
    
    #max color1expl cannot be identical in 3 consecutive trials
    #max color2expl cannot be identical in 3 consecutive trials
    #trials cannot be filler in 2 consecutive trials

    randomized = False
    
    while not randomized: #thise while loop will make everytime a randomization of the rows in the dataframe
        experimental_df_2 = df_experiment.sample(frac=1).reset_index(drop=True) # where experimental_df_2 is the original file read in
        for i in range(0, len(experimental_df_2)):
            try:
                if i == len(experimental_df_2) - 1:
                    randomized = True
                elif i != len(experimental_df_2) - 1:
                    continue
                else:
                    break    
            except:
                pass
            
    #export the excel file
    experimental_df_2.to_excel('Group' + expInfo['group'] + n_dataset + '.xlsx', index=False) #creates a new                                  

Now, what this code will do is to create 12 files with random order of trials, in the same folder as the original file you give in input. The names are gonna be, eg., GroupA1, GroupA2, GroupA3 … GroupA12.

The final thing to do is to put in the loop Conditions something like this: $'Group' + expInfo['group'] + x + '.xlsx'.

And you need some code to update x in the experiment as well at the end of the loop. So every time it will get the trials from the new file.

The advantage to what I was doing before is that the files are created in advance, this should reduce any delay in the experiment.

If two participants belong to the same A group, files will be overwritten automatically. Nothing has to be done manually.