Refer to a string array that is smaller that the trial array in the same loop?

URL of experiment: https://pavlovia.org/run/roberto.petrosino/maskedpriming/html/

In my experiment, subjects are asked to perform a typical visual priming experiment. I want subject to take a break after half of the stimuli are presented. In addition, since this is going to be online, I want to ask them to answer an open question. The way I envisioned this is the following:

in which breakTrial will contain a textComponent saying “you can take a break now.” and breakTrial2 will contains three different textComponents:

  1. prompt_breakTrial2: the actual question subjects need to answer. The questions will be drawn from the column $prompt of the condition file warm_up.csv that the loop loopTrial is linked to.
  2. question_breakTrial2: a simple textComponent saying “What would you do?”
  3. answer_breakTrial2: an input field textComponent coded as shown here.

Both routines breakTrial and breakTrial2 are coded so that they pop up only after half of the stimuli have been presented. I am facing a couple of issues I can’t quite seem to figure out, and I really hope any of you could help.

  1. For some reason the question_breakTrial2 textComponent appears immediately after the first stimulus and remains there throughout the entire routine flow.
  2. The prompt_breakTrial2 textComponent, which is set to select one of the questions presented in the warm_up.csv conditions file, never shows up for some reason. This might be due to the fact that the column $prompt of the conditions file contains only three questions (whereas the number of trials are 10). I fear that the program may randomly choose a row of the $prompt column (thus it may possibly choose an empty row).

I would really appreciate any sort of help/suggestion!

These are component-level questions, yet the only detail we have access to here is at the routine-level (i.e. the flow panel screenshot). So we can’t give any real suggestions without knowing what is happening at the level of the individual components (such as screenshots of their dialog box settings).

Hi @Michael,

thank you for responding! The reason why I did not post any other detail is that I am working directly on the script, since a lot of the settings I need cannot be implemented through the Builder. I posted the URL of the experiment as a way to show what is happening and also a way for JS experts to access the script. Sorry if any of this was unclear. I wish I could copy or attach the script (hopefully the URL will work), but I am not allowed to do either.

Thank you!

Sorry, I missed the presence of that link. And am certainly not a JS expert, so sorry, not much else to contribute…

No worries. Hopefully tomorrow morning I will find a nice present from the JS geeks of the forum :wink:

Is anybody able to help? I think the post may be a little be confusing, so I am focusing on just one question (I am also changing the name of the topic accordingly).

I guess the main problem is that I would need the prompt_breakTrial2 textComponent to randomly select a question out of a smaller set as compared to the set of trials presented in the fmaskTrial - primeTrial - targetTrial flow. If I create another variable in the condition file attached in loopTrial with the questions to select from, it may be the case that the empty rows will be selected, which is something that must be avoided. So, the issue is:

How to have a textComponent select one among a given set of strings, which has not the same number of the trials?

I did found a way to do this in python, that is defining the strings (in my case, the questions) as part of an array, shuffle them and then tell the textComponent to choose onw of them:

# Begin experiment
questions = ["What is your name?", "How old are you?", "What is your best friend's name?"]
shuffle(questions)

#Begin routine
$questions[loop.thisTrialN]

But I am just not sure the shuffle command is available in PsychoJS. Is anybody aware of it? or, Does anybody have a different/easier solution to the problem?

Hi @rob-linguistics13, there is no shuffle command in JS, but you can use one I have found:

/**
 * Shuffles array in place.
 * @param {Array} a items An array containing the items.
 */
function shuffle(a) {
    var j, x, i;
    for (i = a.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        x = a[i];
        a[i] = a[j];
        a[j] = x;
    }
    return a;
}

shuffle(questions)  // shuffles questions in place, as with Python

Thank you, @dvbridges! Can I also ask you where should I put this? At the beginning of the script? Anywhere specific?

If you are only using it in one place, just paste the function within the function using it e.g., a BeginRoutine function.

1 Like

Sorry - follow-up question on this command:

#Begin routine
$questions[loop.thisTrialN]

Now an error pops up saying

prompt_breakTrial2.setText(prompts[loopTrial.thisN])
IndexError: list index out of range

I think it is because the index of the total trials completed so far (thisN) is bigger than the index of my questions array. On the other hand, if I just put $questions, it will just print all of the elements of the questions array. How can I make sure that questions will be shuffled at every repeat and a different of the element of the array is picked at every break?

Yes, that’s right. You could try using prompts.pop() to take the last item of the list, then you will only ever get each item once, because it is removed from the list. However, then you have to worry about what happens if you call pop() when the list is empty, so you will need to read about catching errors if that happens e.g., using try/catch statements. For that, see Mozilla help.

1 Like

Thanks. This seems advanced for a rookie like me - is there any easier solution available that would do what I want?

Its not so bad, its about the same level as you have been coding so far. Try this instead, using conditionals:

//You have your list, that you shuffle
prompts = ['q1', 'q2', 'q3'];
shuffle(prompts);

// Then, you want to take one of the questions for your text component
// But only if there are questions in list - i.e., list length > 0
if (prompts.length > 0) {
  let newQuestion = prompts.pop();
  prompt_breakTrial2.setText(newQuestion);
// What if there are not anymore questions?
// Perhaps make a generic question
} else {
  prompt_breakTrial2.setText("Are you enjoying the task?")
}

Thank you, @dvbridges.

I was actually trying to do something along similar lines but much simpler, i.e.:

# python, but it would be roughly the same in JS
breakCounter = 0 #set a counter

if loopTrial.thisTrialN not in [2,5,7]:
    continueRoutine = False
else:
    breakCounter = breakCounter + 1 # when the break routine pops up, update the counter

prompt = prompts[breakCounter] # pick the question from the list having the same index as the counter
prompt_breakTrial2.setText(prompt)

However, this makes the same prompt as prompt_breakTrial2 textComponent for all the breaks, regardless of breakCounter. As far as I understand, your code something similar, although it has the additional else statement. The thing is, I need to make sure that the questions listed in prompts, no matter how many, will never repeat at each break.

Any ideas?

One problem with that snippet of code is that you are setting the counter to 0 every time you run this code, so it will always be zero or one. If breakCounter is defined at the beginning of the experiment (“Begin Experiment” tab), then it should be ok. ACtually, with my example using pop(), the list of questions needs to be defined in the experimentInit function, or if you were using a code component, in the “Begin Experiment” tab. To know what is really going on, I will need to see the actual code working at your URL rather than an example snippet.

To debug your code online, you can use the developers tools - Press F12 with the task open in a browser, go to sources tab and select your JS code file, and put a break point in where you want to stop your experiment, then you can use the console tab to evaluate the values of each variable at that break point in the experiment - here are some useful guidelines on doing this in Chrome https://javascript.info/debugging-chrome .

A quick update on using the shuffle function. If you define the function like the following at the beginning of your experiment, it will have a global scope and can be called from anywhere after its creation. This is because we assign an anonymous shuffle function to the variable name shuffle, which PsychoPy then gives global scope when the code is written. Note, if you are not using Builder to create your script, you will have to define the shuffle variable outside of the function it is in e.g., var shuffle;

/**
 * Shuffles array in place.
 * @param {Array} a items An array containing the items.
 */
shuffle = function(a) {
    var j, x, i;
    for (i = a.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        x = a[i];
        a[i] = a[j];
        a[j] = x;
    }
    return a;
};

shuffle(questions)  // shuffles questions in place, as with Python

Yes! That was really dumb of me. Thank you. One more thing is that at the beginning of the experiment breakCounter should be set to -1 since python indexation starts from 0.

Makes sense. But this will have the same effect as I use the previous version of the function at the beginning of the breakTrial2 routine, right?

Now everything seems to work as I wanted. Many thanks for all of your help, @dvbridges!

1 Like

I just noticed that sometimes the same question will pop up in more than one break, but I do not understand why.

#Begin experiment
breakTrial2Counter = -1;

shuffle = function(a) {
    var j, x, i;
    for (i = a.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        x = a[i];
        a[i] = a[j];
        a[j] = x;
    }
    return a;
};

#Begin breakTrial2 routine
shuffle(prompts) // shuffle question prompts
breakTrial2Time = true;
  
if ( ![2,5,7].includes(loopTrial.thisTrialN) ){
      breakTrial2Time = false; // take a break after the first half of the stimuli has been presented
  } else { 
      breakTrial2Counter += 1;  // increase the counter at every routine
      prompt = prompts[breakTrial2Counter]; // select the prompt corresponding to the index of the current break
      prompt_breakTrial2.setText(prompt); // print it as text Component
      answer_breakTrial2.text = ''; } //answer input field

I have tried to define the prompt selection within the if-statement to make sure that the questions in prompts gets selected depending on the value assigned to breakTrial2Counter. I do not understand why this is happening. I have also tried to use the debugged as you, @dvbridges, suggested, but, as far as I understood, the breakTrial2Counter increases as expected (from 0 to 2), but it seems that the command

prompt = prompts[breakTrial2Counter]

does not seem to get the right index.

I stand corrected. The problem was that I had put shuffle(prompts) within the breakTrial2RoutineBegin loop, so the prompts were shuffled at each loop repeat. Moving shuffle(prompts) to the beginning of the experiment ensured that the prompts array was shuffle only once at the beginning of the experiment and the order of the question was constant throughout the experiment.

1 Like