Switching between multiple tasks

PennController for IBEX Forums Support Switching between multiple tasks

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
    Posts
  • #8140
    vlanglois
    Participant

    Hello,

    I’m coding up an experiment that requires participants to switch between a flanker task and a sentence task (see demo for details). I have successfully programmed both in, but I want the trials for each task intermixed within the experiment.
    For example, the following combinations could be:
    Trial 1&2: Flanker task followed by sentence task
    Trial 3&4: Sentence task followed by sentence task
    Trial 5&6: Flanker task followed by flanker task
    Trial 7&8: Sentence task followed by flanker task

    I thought about incorporating one design template for each of these four combinations, but I probably would run into issues in regards to order (whether that is randomizing or pseudo-randomizing by hand the different combinations). What would be the best way to intermix these trials, if there is a way?

    Here is the code that I have for the two tasks. Currently, the flanker task always follows the sentence task. https://farm.pcibex.net/r/RgVUnM/

    Thank you!

    #8144
    Jeremy
    Keymaster

    Hello,

    In the demo that you shared, there is a single newTrial which includes both task types. It is my understanding from what you said (that two tasks of the same type can follow each other) that you want to decouple the two, ie. not have them inside a single newTrial command. That would also mean no longer associating specific sentences with specific sets of flanker images the way your table is currently set up. The most convenient way to start on that would be to use two different CSV tables for each task type

    Then you can create different newTrials inside different Template commands, giving different labels to trials of each task type. Once you have such labeled trials, you can define a custom predicate function to shuffle your trials as you’d like, for example:

    const popAelseB = (a,b) => a.pop()||b.pop()
    function LatinMix(a, b) {
        this.args = [a,b];
    
        this.run = function(arrays) {
            assert(arrays.length == 2, "Wrong number of arguments (or bad argument) to LatinMix");
            let as = arrays[0];
            let bs = arrays[1];
            let newArray = [];
            let i = 0;
            while (as.length || bs.length){
                newArray.push( [
                    i%2 ? popAelseB(as,bs) : popAelseB(bs,as),
                    (i+1)%4<2 ? popAelseB(as,bs) : popAelseB(bs,as)
                ] );
                i++;
            }
            fisherYates(newArray);
            return newArray.flat();
        }
    }
    function latinMix(a, b) { return new LatinMix(a, b); }

    You can use the function in Sequence like this:

    Sequence( latinMix(randomize("sentence"),randomize("flanker")) )
    
    Template("flanker.csv", row => newTrial("flanker", /* ... */ ) )
    Template("sentence.csv", row => newTrial("sentence", /* ... */ ) )

    Jeremy

    #8146
    vlanglois
    Participant

    Thanks Jeremy! If I wanted to control which flanker task image precedes the sentence task, would this be hard to incorporate? In this case, would I have to keep the template with both tasks in it?

    E.g.

    Sequence( latinMix(randomize("flanker-sentence"), randomize("sentence"),randomize("flanker")) )
    
    Template("flanker.csv", row => newTrial("flanker", /* ... */ ) )
    Template("sentence.csv", row => newTrial("sentence", /* ... */ ) )
    Template("flanker-sentence.csv", row => newTrial("flanker-sentence", /* ... */ ) )
    #8147
    Jeremy
    Keymaster

    Hi,

    I thought you wanted the sentence vs flanker tasks to be totally independent, because you stated that half of your items need to form sentence-sentence or flanker-flanker subsequences. I’m not sure how you would achieve that with a table where each line has references for both a sentence and a flanker task: it would seem to me that such a table would systematically pair a sentence task with a flanker task, regardless of whether the former precedes or follows the latter, but that it wouldn’t really allow for sentence-sentence or flanker-flanker pairs. Or am I missing something?

    The latinMix function I proposed accepts only two arguments (see assert(arrays.length == 2, "Wrong number of arguments (or bad argument) to LatinMix");) so you will get an error if you try Sequence( latinMix(randomize("flanker-sentence"), randomize("sentence"),randomize("flanker")) ). I also don’t know what output exactly you would expect to get from that, since in your first message you listed four types of pairs resulting from crossing two types of tasks, and this is exactly what latinMix does

    To reiterate, I don’t know how one could modify the code I suggested, or come up with another implementation, because I don’t know how to determine the sentence-sentence and flanker-flanker pairs if you need to systematically associate one sentence with one flanker task (ie. how would you decide which two sentences or which flanker images to associate to form pairs?)

    Note that if all you want really are sentence-flanker and flanker-sentence pairs, then once you put all those pairs one after the other in a sequence of trials, you will for example get a general sequence like sentence-flanker-flanker-sentence-flanker-sentence-sentence-flanker-etc., which does technically contains both sentence-sentence and flanker-flanker subsequences, because sometimes you’ll have a pair that starts with one task type following another pair that ends with that same task type. If that’s what you’re after, then all you need to do is alternate which bit of code comes first in your existing newTrial, eg:

    // Flanker task
    flanker = row => [
      newTimer("RT",1000).start()
      ,
      newCanvas("FlankerCanvas", 1800, 900)
        .add(675,400, newImage(row.FlankerImage))
        .scaling("page")
        .print("center at 50vw","middle at 50vh")
      ,
      newKey("flankerFJ", "FJ")
        .log("first")
        .callback(getTimer("RT").stop())
      ,
      getTimer("RT").wait()
      ,
      getKey("flankerFJ").disable()
      ,
      getCanvas("FlankerCanvas").remove()
    ]
    // Sentence task
    sentence = row => [
      newAudio("audio", row.AudioFile).play()
      ,
      defaultImage.size(225, 225)
      ,
      newImage("TL", row.TL)
      ,
      newImage("TR", row.TR)
      ,
      newImage("BL", row.BL)
      ,  
      newImage("BR", row.BR)
      ,
      newCanvas("visualWorld", 1800, 900)
        .add(    "left at 50px" ,     "top at 50px" , getImage("TL"))
        .add(    "left at 50px" , "bottom at 850px" , getImage("BL"))
        .add( "right at 1700px" ,     "top at 50px" , getImage("TR"))
        .add( "right at 1700px" , "bottom at 850px" , getImage("BR"))
        .scaling("page")
        .print("center at 50vw","middle at 50vh")
      ,
      getAudio("audio").wait("first")
      ,
      newSelector().add(getImage("TL"), getImage("BL"), getImage("TR"), getImage("BR"))
        .wait()
        .log()
      ,
      getCanvas("visualWorld").remove()
      ,
      newText("comp-question", row.Question)
        .cssContainer({"font-size": "160%", "color": "black"})
        .center()
        .print()
      ,
      newText("yesno", "<p>YES &nbsp; &nbsp; &nbsp; NO</p>")
        .cssContainer({"font-size": "160%", "color": "black"})
        .center()
        .print()
      ,
      newKey("SentenceFJ", "FJ").log("first").wait().disable()
      ,
      getText("comp-question").remove()
      ,
      getText("yesno").remove()
    ]
    
    ABorBAs = []    // Browse the table once to fill ABorBAs with alternating 0s and 1s
    Template("flanker-sentence.csv", row => newTrial("__dummy__", ABorBAs.push(ABorBAs.length%2)) )
    Template("flanker-sentence.csv", row =>
      newTrial( "test" ,
        fisherYates(ABorBAs)    // Shuffle ABorBAs
        ,
        ABorBA = ABorBAs.pop()  // Pick the last entry from ABorBAs
        ,
        newCanvas("FixationCanvas", 1800, 900)
          .add(675,400, newImage("Fixation.png"))
          .scaling("page")
          .print("center at 50vw","middle at 50vh")
        ,
        newTimer("wait", 1000).start().wait()
        ,
        clear()
        ,
        ...(ABorBA?flanker(row):sentence(row))  // Flanker if 1, sentence otherwise
        ,
        getTimer("wait").start().wait()
        ,
        getCanvas("FixationCanvas").print("center at 50vw","middle at 50vh")
        ,
        getTimer("wait").start().wait()
        ,
        clear()
        ,
        ...(ABorBA?sentence(row):flanker(row))  // Sentence if 1, flanker otherwise
      )
      .log("flankerFirst", ABorBA)
      .setOption("hideProgressBar",true)
    )
    
    Sequence( randomize("test") )

    Jeremy

    #8148
    vlanglois
    Participant

    Sorry for the confusion! I was hoping that I could control which flanker image (i.e. the direction of the middle arrow) would precede the sentence task, but I didn’t mention this in the original post. I believe that I can do that with a template that combines the two tasks, and I also think your second solution in your recent post should work, which will probably be the way to go.

    For the latinMix function, does this mean there is no way to modify it to intermix between three different tasks?

    Thanks for helping me out with everything!

    #8149
    Jeremy
    Keymaster

    There is something that still confuses me when you say “which flanker image (i.e. the direction of the middle arrow) would precede the sentence task,” because the flanker image does not (immediately) precede any sentence in a flanker-flanker pair, and the sentence is not (immediately) preceded by any flanker image in a sentence-sentence pair. You say that my second solution should work, so I presume that you do not want sentence-sentence or flanker-flanker pairs in the end?

    The latinMix function takes two sets of labeled trials, say “A” and “B,” and outputs a sequence of pairs of trials evenly distributed over A-B, B-A, A-A and B-B. For example, if you have four A trials and four B trials, you could get A(1)-B(4)-A(3)-A(2)-B(1)-A(4)-B(3)-B(2). I hard-coded the function to output such a crossing from strictly two labels, but it could in theory be modified to accommodate any number of labels. For example, with three labels A, B and C, a generalized function would output an even distribution of triples of trials over A-B-C, A-C-B, B-A-C, B-C-A, C-A-B, C-B-A, A-A-A, A-A-B, A-A-C, A-B-B, A-C-C, B-A-A, B-B-A, B-B-B, B-B-C, B-B-B, C-A-A, C-B-B, C-C-A, C-C-B and C-C-C. Is that what you want?

    Jeremy

    #8150
    vlanglois
    Participant

    Right, only for the flanker-sentence combinations would the preceding flanker image matter. This would be the only combination that matters since it is made up of only experimental items. Then, the rest are fillers. Therefore, I think to make things simpler, it would be easier to have the latinMix function take three labels, flanker-sentence, flanker, and sentence. It would also cover the remaining combinations, though sometimes there might be 3 of the same tasks in a row, which shouldn’t be a problem.

    Thank you again! Sorry I didn’t explain this experiment very well from the beginning.

    Val

    #8151
    Jeremy
    Keymaster

    So let me summarize and try to clarify the idea to make sure we’re on the same page

    If you were to use a generalized version of latinMix with three labels (say, flanker-sentence, flanker and sentence) you would need to create three types of trials: one type containing only the sentence task (labeled sentence), one containing only the flanker task (labeled flanker) and one containing both the flanker and then the sentence tasks (labeled flanker-sentence). You say that only the latter would correspond to experimental items with a preset flanker-sentence association. This suggests to me that for those (flanker-sentence, mixed-type trials) you would need a table where each line has references for both the sentence and the flanker tasks, whereas for the former two (sentence and flanker, homogeneous-type trials) you would need two tables, one that only contains references for the reference task and one that only contains references for the flanker task

    Now, say you have 21 rows in the flanker-sentence table, 21 rows in the sentence table and 21 rows in the flanker table. You would get a total of 42 iterations of the flanker task in your final experiment (half from the mixed trials labeled flanker-sentence and half from the homogeneous trials labeled flanker) and a total of 42 iterations of the sentence task (same logic). I use those numbers because the number of combinations that a generalized version of latinMix would output for three labels is 21. Some of the triplets would contain only three task iterations (mixed or not) but some would contain four iterations, some five and one would even contain six (the one that draws exclusively from flanker-sentence, namely flanker-sentence-flanker-sentence-flanker-sentence). Obviously you would end up with an over-representation of the flanker-sentence subsequence, because we started with 21 rows in each table but one of them is used to generate flanker-sentence while the other two generate only flanker or only sentence. As you can see this all becomes very complicated very quickly

    Now that I have a better idea of what you want, I do not think that using any version of latinMix will make your life simpler. I think what would help is determine how many experimental items you will have, and how many of those will use each type of flanker image (as I understand it, you have four possible flanker images). Then, you would need to determine how many iterations of each task you want in the final experiment, and whether/how you want to balance your design. For example, if you have 8 experimental trials (2 where the sentence is preceded by LL.png, 2 by LR.png, 2 by RL.png and 2 by RR.png) do you also want to have 8 corresponding filler trials? That would give you 16 trials where the sentence is preceded by a flanker task. Would you want to then have another 16 filler trials where the sentence is *followed* by a flanker image (4 with LL.png, 4 with LR.png, 4 with RL.png and 4 with RR.png)? Finally, how many more independent iterations of each task type do you want to mix in? And do you want to license sequences that would end the experiment on a flanker task?

    The situation would be a little simpler (although not much) if, instead of randomly generating the fillers during runtime, you defined a pseudo-randomized set of filler items in your tables to compensate your experimental items, as you would simply reference all the sentences and images by hand and the newTrials would be pretty generic

    Re-using the flanker and sentence functions I defined in my previous message (and assuming a ‘yes’ answer where it applies to all the questions I raise above) you could write a single function to generate each type of trials without having to repeat whole pieces of code, eg:

    trialOfType = (type,row) => newTrial(type,
        newCanvas("FixationCanvas", 1800, 900)
          .add(675,400, newImage("Fixation.png"))
          .scaling("page")
          .print("center at 50vw","middle at 50vh")
        ,
        newTimer("wait", 1000).start().wait()
        ,
        clear()
        ,
        ...( type=="flanker-sentence"||type=="flanker" ? flanker(row) : sentence(row) )
        ,
        ...( type.match('-') ? [
          getTimer("wait").start().wait()
          ,
          getCanvas("FixationCanvas").print("center at 50vw","middle at 50vh")
          ,
          getTimer("wait").start().wait()
          ,
          clear()
          ,
          ...( type=="flanker-sentence" ? sentence(row) : flanker(row) )
        ] : [ null ] )
    )
    .setOption("hideProgressBar",true)
    
    Template("flanker-sentence_experimental.csv", row => trialOfType("flanker-sentence", row))
    Template("flanker-sentence_filler.csv", row => trialOfType("flanker-sentence", row))
    Template("sentence-flanker.csv", row => trialOfType("sentence-flanker", row))
    Template("flanker.csv", row => trialOfType("flanker", row))
    Template("sentence.csv", row => trialOfType("sentence", row))
    
    Sequence( rshuffle("flanker-sentence", "sentence-flanker", "flanker", "sentence") )

    Jeremy

Viewing 8 posts - 1 through 8 (of 8 total)
  • You must be logged in to reply to this topic.