Get the previous row of a column

PennController for IBEX Forums Support Get the previous row of a column

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author
    Posts
  • #10640
    Larissa_Cury
    Participant

    Hello, everyone! I’m creating an experiment which has the following rationale: “Display a string on the screen from the “sequence” column and play the corresponding audio. Afterwards, you’ll press either S or K to indicate your answer. The strings become bigger as the experiment continues. The experiment finishes when you get the 2 strings of the same size wrong”. R is easier for me, so I made this logic there, simulating the experiment:

    The items are in the “Sequence” column, and I’ve storaged their lentgh in the “lenNumeric” column. I’ve also uploaded a wide version with the items separated by “_”, if that’s necessary.

    
    library(tidyverse)
    library(stringr)
    
    ### Start at 0 - no errors 
    
    errorNum <- 0 
    
    for (i in 1:6) { ## Note: this is not zero based such as Js
      
      df <- data
      
      print(str_glue('Item number: {i} ; Sequence: {df$sequence[i]}')) ## simulate newText()
      
      ## simulate answer
      
      answer = readline(prompt = "Answer: ")  
      
      # ### store current length 
      
      lenCurr <- df$lenNumeric[i]    
      
      if (answer == 's') {
        
        print('correct')
        
        errorNum <- 0 ## erase error count 
        
      } else if (answer == 'k') {
        
        ### store and count mistakes:
        
        errorNum <- errorNum + 1 ## increase by one 
        
      if (errorNum == 2) {  ## errorNum == 2 equals 'two mistakes in sequence'
          
           if(lenCurr == df$lenNumeric[i - 1]) { ## if lenCurr equals previous Length
            
            print('stop experiment')
             
            break ## break loop (experiment finishes)
            
           } else if (lenCurr != df$lenNumeric[i - 1]) {
            
             errorNum <- 0 ## erase error count 
             
          }
          
        }
      
      }
      
    }
    

    My idea was:

    1) “Create an error counter *errorNum* which starts at 0 outside of the loop so that it doesn’t get erased whenever we enter the loop”
    2) “Store the current length of the *sequence* from the *lenNumeric* column so that we can compare this current lentgh with the previous lentgh afterwards”
    3) “If the answer is right (=== ‘s’), then simply move on, but make sure to erase the error count in case a participant get 2 sequential errors, but with different lengths (which is allowed)
    4) Ok, now if the answer is wrong, then we first increase the error count by 1 errorNum <- errorNum + 1
    5) Great. Now, if we get to mistakes in sequence, then errorNum == 2
    6) Good. Now we have to test if the current lentgh is the same of the previous length to break the loop (aka, finish experiment), so we do:
    lenCurr == df$lenNumeric[i - 1]
    7) Otherwise, lenCurr != df$lenNumeric[i - 1] , we just erase the counter again

    This seems to be working just fine in R. I tried to convert this logic to Pcibex, but I’m struggling with that…I couldn’t write the logit to compare the curr length to the previous length

    The experiment with the data is here: https://farm.pcibex.net/r/uuULrc/ — my attempt is there too

    I guess my biggest problem is to try to associate a loop with a Pcibex trial.

    #10642
    Larissa_Cury
    Participant

    We have to erase the counter if the answer is right in case a participant get 1 error followed by a correct answer (which is allowed), not in case we have two sequential errors, I wrote it wrong above.

    My attempt in Pcibex was this, but I got stuck…

    
    // CREATE an error count outside "loop" so that it doesn't gets erased whenever a trial starts 
    let errorNum = 0;
    
    Template("newStimuliShort.csv", row =>
      newTrial("listOfNumbers",
      defaultText
      .cssContainer({
               padding: '0.5em',
              'text-align': 'center',
              "justify-content": 'center',
              "align-items": 'center' ,
              'font-size': '50px',
            })
            .center()
            .print()
    ,
        newMediaRecorder("recorder", "audio").log().record()
      //  ,
      //  newTimer("recording", 60000).log().start()
       ,
        newText("words", row.sequence) // Display sequences 
      ,
        newAudio("audio", row.audio)  // play corresponding audio (which is in Portuguese) 
                .play()
        ,
        getAudio("audio").wait("first") // wait audio finishes
        ,
        newAudio("beep", "beep.mp3") 
                .play()
        ,
        getAudio("beep").wait("first") // wait audio finishes
        ,
        newVar("currLen").set(row.lenNumeric) // get current i length of "sequence" 
        ,
         newKey("NEXT", "SK")
                .wait()
                .log()
        ,
         getKey("NEXT")
         .disable() // make sure we only have 1 answer - participant can only answer once
        ,
         getKey("NEXT")
         .test.pressed("S")
          .success(                          
          newText("success", 'yey'),   // if the answer is right, 
          getVar("errorNum").set(0))   // make sure to erase the error count in case a participant get 1 error followed by a correct answer 
        .failure(
            
        newVar("errorCount").set( errorNum + 1)  // Increase error count by 1 ans store it in a variable called 'errorCount' 
        ,
    
        // I would need sth here to store the errors so that k + k generates errorCount = K , then I guess I could use .testIs() to see if errorCount === 2. If yes, then 
                  we should check if the current length equals the last length and break the experiment in case it's TRUE. Otherwise just erase the error count again and the 
              experiment continues
              
      // newText("lastLen", row.lenNumeric).map((w,i) => <code>${w[i - 1]}</code>) // get the last length ---- COULDN'T DO IT!! 
        
    //    ,
        newText("failure")
        //    .text( getVar("errorCount") ) // I was displaying just to see if it was working
            .text(getVar("errorCount")) // I was displaying just to see if it was working
            .cssContainer({"font-size":"30px", "text-align":"center", "margin-top":"255px","font-family":"Comic Sans MS", "color":"red","white-space":"nowrap"})
    
    )
    ,
        newTimer("wait-success",100) //timer for the success or failure messenge
        .start()
        .wait()
    ,
        getText("success")
        .remove()
    ,
        getText("failure")
        .remove()
        // ,
        // getTimer("recording").wait()
        ,
        getMediaRecorder("recorder").stop()
     )
      );
    

    PS: I tried to use 0 and 1 instead of letters for the keypress, but I couldn’t do it. I guess it was interpreting 0 and 1 as strings, not numbers, how would I overcome that as well ?

    • This reply was modified 9 months ago by Larissa_Cury. Reason: forgot to put block code
    #10645
    Jeremy
    Keymaster

    Hi Larissa,

    A few comments:

    • getVar("errorNum").set(0): this won’t work, because you never create a Var element named “errorNum”
    • newVar("errorCount").set( errorNum + 1): this will always set the value to 1, because you set the javascript variable errorNum to 0 at the top of your script and never update it
    • newText("lastLen", row.lenNumeric).map((w,i) => `${w[i - 1]}`): you are using .map on the closing parenthesis of newText() but there is no command called map on Text elements. It wouldn’t make sense to call .map on row.lenNumeric either, since row.lenNumeric is not an Array (which is the type of object on which the map method is defined)

    I have created a project here to illustrate how to implement the logic you describe (I didn’t include any audio playback or recording). What allows one to share information from one trial to the next is the Var element’s .global command

    Jeremy

    #10653
    Larissa_Cury
    Participant

    Thank you very much, Jeremy! as always! I’ll sleep on it and give you some feedback!

    #10742
    Larissa_Cury
    Participant

    Hi, @Jeremy! It’s working properly, thank you VERY much, as always!

    See, why can I set any trial before the logic? For example, I’m trying to add a button to make it fo fullscreen, but nothing happens before the logic ?

    
    Sequence(
        "fullscreen",
        // Lengths go from 1 through 6 \\ 1 to 9 -> zero based = 0-8 (?)
        ...[...Array(9)].map((v,i)=>randomize("sequence-"+parseInt(i+1)))
        ,
       "end"
    );
    
    newTrial('fullscreen',
    newButton("wait","openFull")
      .center()
      .print()
    ,
     fullscreen()
    );
    
    
    • This reply was modified 7 months, 2 weeks ago by Larissa_Cury. Reason: correct code
    #10744
    Larissa_Cury
    Participant

    Hi, @Jeremy ! I could fiz the previous issue, I wasn’t adding a .wait(). However, the getTEXT() bellow do not show to be working: https://farm.pcibex.net/r/QIOhcO/

    Note: they are working in another experiment just fine, I don’t know why they aren’t here:

    
    // 👉 Instructions trial 
    newTrial("instructions",
    defaultText
            .center()
            .print()
    ,
    newCanvas("my-canvas", 950,625) //950, 625
          .add(180,160, getText("welcome-researcher-msg"))
          .add(150,210, getText("type-ID-msg"))
          .add(225,240, getTextInput("inputID"))
          .add(320,320, getButton("wait"))
          .add(270,350, getText("failure"))
          .cssContainer({
            "padding": "1,5em",
            "background-color": '#FFFCF1',
            "border-radius": '25px',
            "border": '2px solid #73AD21'
          })
            .center()
            .print()
    ,
    newText("welcome-researcher-msg", "Welcome, researcher!")
    .cssContainer({
       // "border": "5px solid black",
      //  "background-color": "yellow",
      //  "color": "black",
        "font-size":"50px",
        "white-space":"nowrap"
    })
    ,
    newText("type-ID-msg", "<br>Please, type <b style=color:red;>your participant's ID</b> below and click on 'START EXPERIMENT'.</br>")
        ,
        newTextInput("inputID", "")
            .center()
            .cssContainer({
                "margin":"2em",  // Add a em margin around this element
                "height":"35px",
                "border":"#FFFCF1",
                "border-radius": "4px",
            })
                 
            .print()
    ,
    newButton("wait","START EXPERIMENT 👉")
      .cssContainer({
      "border": "none",
      "font-size": "40px",
      "padding": "0.5rem 1rem",
      "border-radius": "0.7rem",
     // "color": "white",
      "background-color": "green",
      "cursor": "pointer",
    })
      .center()
      .print()
      
    // Only validate a click on Start when inputID has been filled
            .wait( getTextInput("inputID").testNot.text("") 
            .failure(newText("failure", "Please, type your participant's ID above 👆")
            .cssContainer({
                "margin-top": "1em", 
                "color": "red"})
             .center()
             .print()
             ))
    ,
    // Store the text from inputID into the Var element
        getVar("ID").set( getTextInput("inputID") )
    );
    
    the texts are appearing below the canvas, I don't know why..Why is that?
    
    #10747
    Jeremy
    Keymaster

    Hi Larissa,

    There are two issues about getText in the code from your message (the same points apply to the “wait” button): first, you use getText("failure") before you even create it, which might cause a reference error for PennController. Second, you print() a Text element named “failure” (which you create at the same time, with newText) upon a click with no text in the input box: calling print() with empty parentheses always has the effect of appending the content of the element (if any) below the most recently print()-ed element; so in your case, the text will appear below the (also just print()-ed) button

    What you want is something like this (ignoring CSS for simplicity):

    newCanvas("my-canvas", 950,625) //950, 625
      .add(180,160, getText("welcome-researcher-msg"))
      .add(150,210, getText("type-ID-msg"))
      .add(225,240, getTextInput("inputID"))
      .add(320,320, newButton("wait", "START EXPERIMENT 👉"))
      .add(270,350, newText("failure","Please, type your participant's ID above 👆").hidden() )
    // ...
    getButton("wait")
      .wait( 
        getTextInput("inputID").testNot.text("") 
        .failure( getText("failure").visible() )
      )
    

    Regarding the fullscreen issue, you are not waiting for the button so the browser tries to go fullscreen as soon as the experiment starts, which most browsers won’t allow for security/user-experience concerns

    Jeremy

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