Translating pseudorandom function to PsychoJS

I created an experiment in Builder where I want my stimuli to be presented in pseudorandom order, e.g. without presenting more than 3 stimuli in the same condition in a row. I added a function called pseudoRand() in a Begin Experiment code component, adapted from here: Pseudo-randomization of stimuli trials in a loop

The following function works locally using PsychoPy:

import random

def pseudoRand(excelFile):

    # Load trials
    trials=data.importConditions(excelFile)
    conditionMet = False
    # Pseudo-randomize trials
    while conditionMet == False:
        random.shuffle(trials)
        conditions = []
        for row in range(len(trials)):
            conditions.append(trials[row]["stim_type"])  #Name of the conditions type column
        # Condition for pseudo-randomization: No consecutive same type
        for row in range(len(trials) - 3):
            if trials[row]["stim_type"] == trials[row + 1]["stim_type"] and trials[row]["stim_type"] == trials[row + 2]["stim_type"] and trials[row]["stim_type"] == trials[row + 3]["stim_type"]:
                conditionMet = False
                break
            elif trials[row]["question_num"] == trials[row + 1]["question_num"] and trials[row]["question_num"] == trials[row + 2]["question_num"] and trials[row]["question_num"] == trials[row + 3]["question_num"]:
                conditionMet = False
                break
            elif trials[row]["target_num"] == trials[row + 1]["target_num"] and trials[row]["target_num"] == trials[row + 2]["target_num"] and trials[row]["target_num"] == trials[row + 3]["target_num"]:
                conditionMet = False
                break
            elif trials[row]["target_phoneme"] == trials[row + 1]["target_phoneme"] and trials[row]["target_phoneme"] == trials[row + 2]["target_phoneme"] and trials[row]["target_phoneme"] == trials[row + 3]["target_phoneme"]:
                conditionMet = False
                break
            else:
                conditionMet = True
    # Save the final order of the trials
    customOrder = []
    for row in range(len(trials)):
        customOrder.append(trials[row]["stim_order"]) #Name of the original index column
    return customOrder

The function creates new random orders until all conditions are met (no more than 3 trials in each of the conditions of interest, namely “stim_type”, “question_num”, “target_num” and “target_phoneme”). I then call the function in the “Selected rows” property of my trial loop as:

$pseudoRand('List1_stim_data.csv')

To upload the experiment to Pavlovia, I edited the function in PsychoJS as follows:

shuffle = util.shuffle;
pseudoRand = function(excelFile) {
    var conditionMet, conditions, customOrder, trials;
    trials = data.importConditions(excelFile);
    conditionMet = false;
    while ((conditionMet === false)) {
        shuffle(trials);
        conditions = [];
        for (var row, _pj_c = 0, _pj_a = util.range(trials.length), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
            row = _pj_a[_pj_c];
            conditions.push(trials[row]["stim_type"]);
        }
        for (var row, _pj_c = 0, _pj_a = util.range((trials.length - 3)), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
            row = _pj_a[_pj_c];
            if ((((trials[row]["stim_type"] === trials[(row + 1)]["stim_type"]) && (trials[row]["stim_type"] === trials[(row + 2)]["stim_type"])) && (trials[row]["stim_type"] === trials[(row + 3)]["stim_type"]))) {
                conditionMet = false;
                break;
            } else {
                if ((((trials[row]["question_num"] === trials[(row + 1)]["question_num"]) && (trials[row]["question_num"] === trials[(row + 2)]["question_num"])) && (trials[row]["question_num"] === trials[(row + 3)]["question_num"]))) {
                    conditionMet = false;
                    break;
                } else {
                    if ((((trials[row]["target_num"] === trials[(row + 1)]["target_num"]) && (trials[row]["target_num"] === trials[(row + 2)]["target_num"])) && (trials[row]["target_num"] === trials[(row + 3)]["target_num"]))) {
                        conditionMet = false;
                        break;
                    } else {
                        if ((((trials[row]["target_phoneme"] === trials[(row + 1)]["target_phoneme"]) && (trials[row]["target_phoneme"] === trials[(row + 2)]["target_phoneme"])) && (trials[row]["target_phoneme"] === trials[(row + 3)]["target_phoneme"]))) {
                            conditionMet = false;
                            break;
                        } else {
                            conditionMet = true;
                        }
                    }
                }
            }
        }
    }
    customOrder = [];
    for (var row, _pj_c = 0, _pj_a = util.range(trials.length), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
        row = _pj_a[_pj_c];
        customOrder.push(trials[row]["stim_order"]);
    }
    return customOrder;
}

This first led to an error as “data.importConditions()” is not supported by PsychoJS. Following Access Condition File variables online experiement - #2 by hellenjingyuan I edited:

trials = data.importConditions(excelFile);

to:

trials = excelFile;

However I now get this error:

TypeError: Cannot assign to read only property '18' of string 'List1_stim_data.csv'

I’m not familiar with JavaScript so I’m not sure where the issue is in the code (e.g., whether the indexing is wrong). Thanks in advance for the help!

Hi! I’m wondering what you mean when you say you get

TypeError: Cannot assign to read only property '18' of string 'List1_stim_data.csv'

as an error. It seems like the code somewhere is trying to do: 'List1_stim_data.csv'['18'] which doesn’t make sense because it’s a string.

Is data, in your trials = data.importConditions(excelFile); defined anywhere else in your javascript?

My guess is that is not – or that data is not the right kind of object.

myData = new TrialHandler({
psychoJS: psychoJS,
nReps: 1, method: TrialHandler.Method.SEQUENTIAL,
extraInfo: expInfo, originPath: undefined,
trialList: 'conditions.xlsx',
seed: undefined, name: 'myData'});

SOLUTION FOUND!

Thanks so much for your help! You’re right, I was making some mistakes when importing the conditions file. I went through some more debugging and I think I’ve actually managed to successfully translate the function to JS! I’m copying the working version here in case it’s useful to anyone:

shuffle = util.shuffle;
range = function (size, startAt = 0) {
    return [...Array(size).keys()].map(i => i + startAt);
}


pseudoRand = function(excelFile) {

    var conditionMet, conditions, customOrder, trials;
    
    trials = new TrialHandler({
    psychoJS: psychoJS,
    nReps: 1, method: TrialHandler.Method.SEQUENTIAL,
    extraInfo: expInfo, originPath: undefined,
    trialList: excelFile,
    seed: undefined, name: 'trials'});

    conditionMet = false;
    while ((conditionMet === false)) {
        shuffle(trials.trialList);
        conditions = [];
        for (var row, _pj_c = 0, _pj_a = range(trials.trialList.length), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
            row = _pj_a[_pj_c];
            conditions.push(trials.trialList[row]["stim_type"]);
        }
        for (var row, _pj_c = 0, _pj_a = range((trials.trialList.length - 3)), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
            row = _pj_a[_pj_c];
            if ((((trials.trialList[row]["stim_type"] === trials.trialList[(row + 1)]["stim_type"]) && (trials.trialList[row]["stim_type"] === trials.trialList[(row + 2)]["stim_type"])) && (trials.trialList[row]["stim_type"] === trials.trialList[(row + 3)]["stim_type"]))) {
                conditionMet = false;
                break;
            } else {
                if ((((trials.trialList[row]["question_num"] === trials.trialList[(row + 1)]["question_num"]) && (trials.trialList[row]["question_num"] === trials.trialList[(row + 2)]["question_num"])) && (trials.trialList[row]["question_num"] === trials.trialList[(row + 3)]["question_num"]))) {
                    conditionMet = false;
                    break;
                } else {
                    if ((((trials.trialList[row]["target_num"] === trials.trialList[(row + 1)]["target_num"]) && (trials.trialList[row]["target_num"] === trials.trialList[(row + 2)]["target_num"])) && (trials.trialList[row]["target_num"] === trials.trialList[(row + 3)]["target_num"]))) {
                        conditionMet = false;
                        break;
                    } else {
                        if ((((trials.trialList[row]["target_phoneme"] === trials.trialList[(row + 1)]["target_phoneme"]) && (trials.trialList[row]["target_phoneme"] === trials.trialList[(row + 2)]["target_phoneme"])) && (trials.trialList[row]["target_phoneme"] === trials.trialList[(row + 3)]["target_phoneme"]))) {
                            conditionMet = false;
                            break;
                        } else {
                            conditionMet = true;
                        }
                    }
                }
            }
        }
    }
    customOrder = [];
    for (var row, _pj_c = 0, _pj_a = range(trials.trialList.length), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
        row = _pj_a[_pj_c];
        customOrder.push(trials.trialList[row]["stim_order"]);
    }
    return customOrder;
}

2 Likes