Logging Results for Multiple Attempts

PennController for IBEX Forums Support Logging Results for Multiple Attempts

Viewing 6 posts - 1 through 6 (of 6 total)
  • Author
    Posts
  • #5992
    penn_baby_lab
    Participant

    Hi Jeremy,

    We’re creating an experiment with a training/test phase before the actual task. We want to set a threshold of 50% accuracy on this preliminary test, so that if the participant scores below 50%, they’ll have to repeat the training. The problem we’re having is that PennController logs only the results of the first pass and not for any subsequent attempts. Is there a way to log results for all re-takes of a test?

    Thanks!

    Anna

    #5993
    Jeremy
    Keymaster

    Hi Anna,

    How do you handle training repetition? Is it all part of one big newTrial that you reset if accuracy is low?

    Jeremy

    #5994
    penn_baby_lab
    Participant

    Here’s an excerpt of the code:

    const launch = (num, audio, flapChooseT, notFlapChooseT, status) => [
        newAudio("flapAud" + num, audio)
            .play()
            .wait()
        ,
        newText("prompt" + num, "This was...<br><br>")
            .settings.center()
            .print()
        ,
        newButton("flap" + num, "a flap")
            .print()
            .center()
            .once()
            .log()
            .callback(getText("response" + num).text(flapChooseT), (status == "a flap" ? getVar("FlapScore").set(v => v + 1) : getVar("FlapScore").set(v => v + 0)), getButton("notFlap" + num).disable(), getButton("moveOn" + num).visible())
        ,
        newText("spacer" + num, "<br>")
            .print()
        ,
        newButton("notFlap" + num, "not a flap")
            .print()
            .center()
            .once()
            .log()
            .callback(getText("response" + num).text(notFlapChooseT), (status == "not a flap" ? getVar("FlapScore").set(v => v + 1) : getVar("FlapScore").set(v => v + 0)), getButton("flap" + num).disable(), getButton("moveOn" + num).visible())
        ,
        newText("response" + num, "<p></p>")
            .print()
        ,
        newButton("moveOn" + num, "Next")
            .hidden()
            .print()
            .wait()
        ,
        getText("prompt" + num).remove(),
        getText("spacer" + num).remove(),
        getButton("flap" + num).remove(),
        getButton("notFlap" + num).remove(),
        getText("response" + num).remove(),
        getButton("moveOn" + num).remove(),
    ]
    
    newTrial("flapTest",
        newButton("launch").callback(
            ...launch("1", "adequateT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("7", "producingT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            ...launch("2", "bitterT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("3", "daddyT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("8", "traditionT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            ...launch("4", "mottoT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("5", "italicsT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            ...launch("6", "planetaryT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            getButton("carryOn").click(),
            getButton("finally").click()
        ).click()
        ,
        newButton("carryOn", "Next")
            .print()
            .hidden()
            .wait()
        ,
        newText("evaluation", "Your number of correct answers: ").after(newText("").text(getVar("FlapScore")))
            .print()
        ,
        getVar("FlapScore")
            .test.is(0)
            .success(newText("nope1", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(), newButton("toNext1", "Continue").print().wait(), getText("nope1").remove(), getButton("toNext1").remove(), getText("evaluation").remove(), getButton("launch").click())
            .failure(
                getVar("FlapScore").test.is(1)
                .success(newText("nope2", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(), newButton("toNext2", "Continue").print().wait(), getText("nope2").remove(), getButton("toNext2").remove(), getText("evaluation").remove(), getButton("launch").click())
                .failure(
                    getVar("FlapScore").test.is(2)
                    .success(newText("nope3", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(), newButton("toNext3", "Continue").print().wait(), getText("nope3").remove(), getButton("toNext3").remove(), getText("evaluation").remove(), getButton("launch").click())
                    .failure(
                        getVar("FlapScore").test.is(3)
                        .success(newText("nope4", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(), newButton("toNext4", "Continue").print().wait(), getText("nope4").remove(), getButton("toNext4").remove(), getText("evaluation").remove(), getButton("launch").click())
                        .failure(
                            getVar("FlapScore").test.is(4)
                            .success(newText("nope5", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(), newButton("toNext5", "Continue").print().wait(), getText("nope5").remove(), getButton("toNext5").remove(), getText("evaluation").remove(), getButton("launch").click())
                            .failure(newText("<p>Good job! When you're ready, press the button below to proceed.</p>").print(), newButton("Continue").print().wait(), getButton("finally").click())
                        )
                    )
                )
            )
        ,
        newButton("finally")
            .wait()
    )

    We’d like to know how to log the results of the launch processes when getButton(“launch”).click() is executed near the end of the code snippet (like on line 67), i.e., how to log the button clicks in lines 10-26 the first time the launch button (line 44) is “clicked” as well as the second time. Right now it’s only logging the first round.

    #5995
    Jeremy
    Keymaster

    The problem is coming from the newButton commands being rerun the second time, which effectively erases the history of clicks from the element. I’ll have to work on that for future releases, but in the meantime here is a revised version of your script that should work:

    const launch = (num, audio, flapChooseT, notFlapChooseT, status) => [
        newAudio("flapAud" + num, audio)
            .play()
            .wait()
        ,
        newText("prompt" + num, "This was...<br><br>")
            .settings.center()
            .print()
        ,
        getButton("carryOn")
            .test.clicked()
            .success( 
              getButton("flap"+num).enable().print(),
              getText("spacer"+num).print(),
              getButton("notFlap"+num).enable().print() 
            )
            .failure(
              newButton("flap" + num, "a flap")
                .print()
                .center()
                .log()
                .callback(
                  getText("response" + num).text(flapChooseT),
                  getVar("FlapScore").set(v => v + Number(status=="a flap")),
                  getButton("flap" + num).disable(),
                  getButton("notFlap" + num).disable(),
                  getButton("moveOn" + num).visible()
                )
              ,
              newText("spacer" + num, "<br>").print()
              ,
              newButton("notFlap" + num, "not a flap")
                .print()
                .center()
                .log()
                .callback(
                  getText("response" + num).text(notFlapChooseT),
                  getVar("FlapScore").set(v => v + Number(status == "not a flap")),
                  getButton("flap" + num).disable(),
                  getButton("notFlap" + num).disable(),
                  getButton("moveOn" + num).visible()
                )
            )
        ,
        newText("response" + num, "<p></p>")
            .print()
        ,
        newButton("moveOn" + num, "Next")
            .hidden()
            .print()
            .wait()
        ,
        getText("prompt" + num).remove(),
        getText("spacer" + num).remove(),
        getButton("flap" + num).remove(),
        getButton("notFlap" + num).remove(),
        getText("response" + num).remove(),
        getButton("moveOn" + num).remove(),
    ]
    
    newTrial("flapTest",
        newButton("carryOn").log()
        ,
        newButton("launch").callback(
            ...launch("1", "adequateT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("7", "producingT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            ...launch("2", "bitterT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("3", "daddyT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("8", "traditionT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            ...launch("4", "mottoT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap"),
            ...launch("5", "italicsT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            ...launch("6", "planetaryT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap"),
            getButton("carryOn").click()
        ).click()
        ,
        getButton("carryOn").wait()
        ,
        newText("evaluation", "Your number of correct answers: ").after(newText("").text(getVar("FlapScore")))
            .print()
        ,
        getVar("FlapScore")
            .test.is(v=>v>=5)
            .success(
                newText("<p>Good job! When you're ready, press the button below to proceed.</p>").print(), 
                newButton("Continue").print().wait()
            )
            .failure(
                newText("nope1", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(),
                newButton("toNext1", "Continue").print().wait(), 
                getText("nope1").remove(), 
                getButton("toNext1").remove(), 
                getText("evaluation").remove(), 
                getButton("launch").click(),
                getButton("carryOn").wait()
            )
    )

    You’ll notice that I changed a few other things:

    • I used a function in the Var’s test, which allowed me to concisely apply a “greater than” test
    • I created the carryOn button first and got rid of “finally”
    • I test in launch whether carryOn was clicked before, so I don’t recreate the buttons (which would erase their click history) the second time around
    • I also got rid of once on the buttons because you’ll need to have them clickable again if you repeat the training phase

    One note about the results file: you’ll get only one line for buttons that were never clicked, with “Never” as their timestamp, and one or two lines for the other buttons depending on how often they were clicked. Unfortunately the results lines don’t seem to appear in chronological order (despite my attempt at coding that feature, another thing I’ll need to fix for the next release) so you’ll have to reorder them when running your analyses, using the timestamps. I logged the carryOn button to make it easier to identify first-round vs second-round clicks by comparing timestamps.

    Let me know if you have questions

    Jeremy

    #5997
    penn_baby_lab
    Participant

    This is great, thank you!

    Given the structure of the code, is there any way to randomize the order of the calls to launch within the newButton(“launch”).callback()?

    #6000
    Jeremy
    Keymaster

    Hi Anna,

    There is, but things are getting a little messy. You’ll need to execute some plain javascript code just before repeating the calls to truly randomize their order. This is precisely what the newFunction element is for. Here’s a possible implementation:

    newTrial("flapTest",
        newFunction("init", function(){ 
            this.progress = -1; 
            this.indices = [... new Array(8)].map((v,i)=>i).sort(v=>Math.random()-0.5);
        }).call()
        ,
        newFunction("getNextIndex", function(){
            this.progress++;
            if (this.progress>=this.indices.length) return "stop";
            return this.indices[this.progress];
        })
        ,
        newButton("carryOn").log()
        ,
        newButton("launch").callback(
            newVar("whichIndex").set( getFunction("getNextIndex").call() )
            .test.is(0).success( ...launch("1", "adequateT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap") )
            .test.is(1).success( ...launch("2", "bitterT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap") )
            .test.is(2).success( ...launch("3", "daddyT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap") )
            .test.is(3).success( ...launch("4", "mottoT.mp3", "<p>Correct!</p>", "<p>Incorrect.</p>", "a flap") )
            .test.is(4).success( ...launch("5", "italicsT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap") )
            .test.is(5).success( ...launch("6", "planetaryT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap") )
            .test.is(6).success( ...launch("7", "producingT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap") )
            .test.is(7).success( ...launch("8", "traditionT.mp3", "<p>Incorrect.</p>", "<p>Correct!</p>", "not a flap") )
            .test.is("stop").success( getButton("carryOn").click() )
            .testNot.is("stop").success( getButton("launch").click() )
        ).click()
        ,
        getButton("carryOn").wait()
        ,
        newText("evaluation", "Your number of correct answers: ").after(newText("").text(getVar("FlapScore")))
            .print()
        ,
        getVar("FlapScore")
            .test.is(v=>v>=5)
            .success(
                newText("<p>Good job! When you're ready, press the button below to proceed.</p>").print(), 
                newButton("Continue").print().wait()
            )
            .failure(
                newText("nope1", "<p>Given your score, we would like you to take the quiz again. Press the button below to proceed.</p>").print(),
                newButton("toNext1", "Continue").print().wait(), 
                getFunction("init").call(),
                getText("nope1").remove(), 
                getButton("toNext1").remove(), 
                getText("evaluation").remove(), 
                getButton("launch").click(),
                getButton("carryOn").wait()
            )
    )

    The init function is what takes care of randomly ordering the indices, and (re)setting the pointer (this.progress) to the first index in the random list (this.indices). The getNextIndex function, which is called whenever the launch button is clicked (manually or by simulation) will increment the pointer and return the next index in the list. By assigning that value to a Var element, we can test it and run the corresponding launch sequence. If the indices have been exhausted, the value is "stop", in which case we simulate a click on carryOn, otherwise we simulate a new click on launch which will run the next sequence from the randomized list of indices.

    Note the line getFunction("init").call() just before repeating the training, so as to reinitialize the pointer and re-randomize the list of indices

    Let me know if you have questions

    Jeremy

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