PennController for IBEX › Forums › Support › html layout/event times/selector vs. scale/ compatibility
- This topic has 47 replies, 2 voices, and was last updated 2 years, 3 months ago by Jeremy.
-
AuthorPosts
-
December 15, 2021 at 1:51 pm #7625JeremyKeymaster
Hi,
1. You can either use
,
in place of,
, or surround your text column which contains,
s with"
s2. It keeps the same order, I don’t see an easy way of re-randomizing the order dynamically. If you’re already thinking of limiting the loop to three cycles, maybe just include the practice subsequence three times in your
Sequence
(this way the threerandomize
will generate different orders), and usejump
to directly reach the experimental trials for participants who pass, and let participants who fail continue to the next round of practice trials3. Yes, I see no technical reason why you couldn’t do that. However some organizations like the IRB have policies regarding participants’ rights to take part in an experiment and/or being compensated for it, so double-check that your setup is consistent with any policies that govern your experiment
Jeremy
March 22, 2022 at 9:40 am #7947HPIParticipantDear Jeremy,
I am back after having run some pilots and I wish to implement some modifications.
Recap:
I show to the participant a picture, then a sentence, then a question requesting to rate the sentence that describes the picture, then I load a canvas with 3 emoticons corresponding to 3 keys V (bad) N (so and so) N (good).I have a theoretical prediction on how the participant should evaluate each sentence, so in the template, there is a column “Right_Key” which contains the expected key.
So far I have managed to obtain data on the overall accuracy of responses of the participant.
I have created a trial before the experiment that sets the counter to 0, when the participant selects a key that matches the expected one, the counter adds 1.
Running the analysis of the pilot I have noticed that I would like to count and have already the counted variable on one side of the single item:
If the participant presses the expected key I would like to see a hit (1), if the participant pressed one of the other 2 keys I would like this variable to stay (0).
So I thought to add a trial before the experimental one, set an “item_score” variable to 0 when there is a success the “item_score” makes +1, I log the value, I set the value again to 0 at the end of the trial.
This is what I had:
newTrial("score_experiment_0", newVar("exp_score") // #this is the overall accuracy .global() .set(0) ) Template("stimuli.csv", variable => newTrial("experimental_trial", // .... .test.selected({ V: getImage("inappropriate"), B: getImage("infelicitous"), N: getImage("appropriate") }[variable.Right_Key]) // Increment if correct answer .success( getVar("exp_score").set(s_exp=>s_exp+1)) // ....
at the end when I log all the conditions I added
.log("Accuracy", getVar("exp_score"))
This works.
Now I want to add the second counter (the new trial is added to the sequence on top):
newTrial("score_experiment_0", newVar("exp_score") // #global accuracy .global() .set(0) ) newTrial("score_item_0", newVar("item_score") #new variable that must go 0 or 1 .global() .set(0) ) Template("stimuli.csv", variable => newTrial("experimental_trial", // ........ .test.selected({ V: getImage("inappropriate"), B: getImage("infelicitous"), N: getImage("appropriate") }[variable.Right_Key]) // Increment if correct answer .success( getVar("exp_score").set(s_exp=>s_exp+1)) .and( getVar("item_score").set(sc_i=>sc_i+1))
Here I tried to set it directly to 1
.set(sc_i=>1))
but it didn’t work, so I chose to go +1 because at trial 1 it’s a 0, it goes 1 then I want to save it and set it back to 0.getVar("item_score") .log() , // ... (getVar("item_score").set(sc_i=>0)).
It tells me that the variable “item_score” doesn’t exist, (but it does) and doesn’t compute correctly neither the item_score nor the global exp_score like this, so I guess it doesn’t compute correctly .success
Is there a way to correctly write “more than one action to do after a success?”
.success — do this; .and —- ; and —
Or after .success is it possible to have one command?
————————————————
With the same principle, I would like to have in the output the counter of how many V, or B, or N, they clicked, so after the selection, I would like to add
“if” .test.selected({ V: getImage(“inappropriate”), B: getImage(“infelicitous”), N: getImage(“appropriate”) } “N”) // Increment if the participant presses N
the variable “count_N”. (getVar(“count_N”).set(sc_i=>sc_i+1)).
But also in this case I would have to test:
-if it matches right_key then do +1 on the entire experiment;
-if it matches right_key then do +1 on the item then set it back to 0;
-if it matches “N” then do +1 on the entire experiment;
-if it matches “B” then do +1 on the entire experiment;
-if it matches “V” then do +1 on the entire experiment;after the line .test.selected
Is it possible to have multiple tests after the selection and save the values of the variables?
Thanks!
(then we would like to implement another part of the test measuring a covariate of language proficiency, and I am working on it, but first I would like to implement these changes)
Thank you for your help!
HPI
March 22, 2022 at 1:45 pm #7951JeremyKeymasterHi,
I’m not sure I’m following everything, but
and
does not mean “and do something else,” it is used to write a conjunctive test: referenceThe commands
success
andfailure
accept subsequences of commands, separated by commas just like the main sequence of commands, eg:.test.selected({ V: getImage("inappropriate"), B: getImage("infelicitous"), N: getImage("appropriate") }[variable.Right_Key]) .success( getVar("exp_score").set(s_exp=>s_exp+1) , getVar("item_score").set(sc_i=>sc_i+1) )
And yes, it is possible to have multiple
test
commands on the same element:.test.selected({ V: getImage("inappropriate"), B: getImage("infelicitous"), N: getImage("appropriate") }[variable.Right_Key]) .success( getVar("exp_score").set(s_exp=>s_exp+1) , getVar("item_score").set(sc_i=>sc_i+1) ) .test.selected(getImage("inappropriate")).success( getVar("v_score").set( v=> v+1 ) ) .test.selected(getImage("infelicitous")).success( getVar("b_score").set( v=> v+1 ) ) .test.selected(getImage("appropriate")).success( getVar("n_score").set( v=> v+1 ) )
Note that in this example I assume you have created the Var elements named v_score, b_score and n_score earlier
Feel free to share a link to your experiment with me if you’d like more assistance, so I can see the code as is, in context
Jeremy
March 28, 2022 at 7:25 am #7976HPIParticipantHi,
thank you for your help with writing several operations after a success. It worked.
The only thing that took me a while, some try-fail rounds, is the setting of a variable as a definite value (different from 0, and when calling it with “getVar”)
I wanted to mark whether each trial was correctly answered so I wanted to assign a 1 when it was correct, and a 0 when the response was not correct and I was writing
success( getVar(“score”).set( v=> 1 ). This wouldn’t work, it worked at the end writing set(1). I thought this syntax was used only in the definition of a variable. But I got it.And I thought such a counter would appear as a row in the results after the selection of the response, but I managed to log it at the end so it appears attached as a column.
So this is solved. Many thanks!
————————
I have to add after the experiment a small test for language proficiency.
This text is called C-test. There is a text like a piece I show here underneath:“It was so easy to get lost in an ancient building”
That appears to the participants with parts of the words missing:
“It was so ea__ to get lost in an anc__ buil____”
The participant has to fill the gaps.
This type of text has been already implemented on ibex in my university, but with the old Ibex syntax, and I have all my exp in the new syntax, so I would like to adjust the script in the old Ibex to add it to my script with the new syntax.
There is a function written previously that I don’t know if it exists already in Pcibex, or if I have to add it to my script, in that case, I wanted to double-check with you if it’s ok to declare it in this way.
function blank(a, b) { var sentence = b ? b : a; var n = b ? a : null; var seq = [""]; var inBlank = false; for (var i = 0; i < sentence.length; ++i) { var c = sentence.charAt(i) if (inBlank) { if (c == '_') (seq[seq.length])++; else { seq.push(c); inBlank = true; } } else { if (c != '_') seq[seq.length-1] += c else { seq.push(1); inBlank = true; } } }
Then the text is called like this:
["blank", "Form", blank(""It was so ea__ to get lost in an anc__ buil____"")],
The texts are obviously not composed just by one sentence but by many in sequence.
Questions:
Can I add the function written in this way on top of my script that uses only the new syntax? Should I rewrite the function? I am not sure I have found the documentation on the syntax in the new pcIbex.
Then I guess I have to make a template in which each text corresponds to a variable, so if it has to appear all in one screen to the participants, if I write it first in excel, each full text should stay in a single cell.
Then how should I call in the function “blank” to operate on the texts?
Thank you for your help and for answering my thousands questions!
HPI
March 28, 2022 at 9:29 am #7978JeremyKeymasterHi,
The code of the
blank
function you report seem to be missing a few lines, but if I understand its purpose correctly, you can rewrite it in a more concise way, eg:blank = sentence => Object({html: sentence.replace(/_+/g, (t,i)=>`<textarea name='blank${i}' rows=1 cols=${t.length+1} style='max-height: 1.7em'></textarea>`) });
Then you can use it the way you discuss, either creating a Form item using the standard Ibex syntax:
items = [ ["blank", "Form", blank("It was so ea__ to get lost in an anc__ buil____")] ]
Or injecting a Form controller inside a PennController trial:
newTrial("blank", newController("Form", blank("It was so ea__ to get lost in an anc__ buil____")) .log() .print() .wait() )
Jeremy
March 29, 2022 at 5:50 am #7988HPIParticipantHi,
thank you! I’ll try what you suggest.
In this way the script is composed of the practice + the main experiment, now this second part is an independent new small test.
To save my data, I use the line SendResults(“send”); after the main experiment and I save the practice and the main experiment.
Is it smarter to add this new small test before the command SendResults(“send”); and save all together, or should I save the two parts of the study in two different moments?
So I SendResults(“send”); after the main experiment, then launch the new instructions let them do the second task and save again with SendResults(“send”);
Would I be able to understand it is the same participant who did the main experiment and the c-test in this way? Would I mess around the data adding a second independent part within the same script and saving just once at the end?
Thank you!
HPIMarch 29, 2022 at 12:09 pm #7989HPIParticipantHi,
sorry for the double message, now I placed only one “send results” at the end and it seems to work.
Is it possible to combine the form with the template?
newTrial("blank", newController("Form", blank("It was so ea__ to get lost in an anc__ buil____")) .log() .print() .wait() )
Because now I get in the result something like:
blank1 = sy
blank2 = ient
blank3 = dingI am trying to code in a variable the expected answer so that I can compile the accuracy within the script.
and then log in the results output a column for each participant the expected output their output and if they match adding up the score?
I am not sure how to deal with the form in combination with the template.
Thank you a lot for your help,
HPI
March 29, 2022 at 12:45 pm #7990HPIParticipantSorry I noticed I didn’t copy the whole function:
It was like that in the previous script:
function blank(a, b) { var sentence = b ? b : a; var n = b ? a : null; var seq = [""]; var inBlank = false; for (var i = 0; i < sentence.length; ++i) { var c = sentence.charAt(i) if (inBlank) { if (c == '_') (seq[seq.length])++; else { seq.push(c); inBlank = true; } } else { if (c != '_') seq[seq.length-1] += c else { seq.push(1); inBlank = true; } } } var ihtml = ""; var bcount = 0; for (var i = 0; i < seq.length; ++i) { if (typeof(seq[i]) == "number") { ++bcount; var input = " <input type='text' name='blank-" + bcount + "' size='" + (n ? n : seq[i]) + "'></input> "; ihtml += input; } else { ihtml += $("<div>").text(seq[i])[0].innerHTML; } } var e = "<p>"; var validators = { }; var bcount = 0; for (var i = 0; i < seq.length; ++i) { if (typeof(seq[i]) == "number") { ++bcount; e += "<label class='error' for='blank-" + bcount + "'></label>" ; validators['blank-' + bcount] = function (s) { if (s && !s.match(/^\s*$/)) return true; else return "You must fill in the blank."; } } } e += "</p>" return { html: "<p>" + ihtml + "</p>" + e, validators: validators }; }
March 29, 2022 at 1:33 pm #7991JeremyKeymasterHi,
Because now I get in the result something like:
blank1 = sy
blank2 = ient
blank3 = dingThis would be what you get from the code of the
blank
function from your last message, right (egblank-1
)? Because the code of theblank
function I proposed would look something likeblank-12
,blank-36
andblank-43
(the numbers correspond to the index in the string of each first_
in a series)I am trying to code in a variable the expected answer so that I can compile the accuracy within the script.
Unless you need to show some feedback based on whether the participant answered accurately, it generally is a better idea to log raw responses and compute accuracy post data-collection. Logging raw responses is always a good idea anyway, and computing accuracy add unnecessary load during runtime
Now, assuming you always have exactly three textboxes, you could use global Var elements to store their content (by regularly inspecting the HTML elements, eg every 50ms) and log them as extra columns:
blank = sentence => Object({html: sentence.replace(/_+/g, (t,i)=>`<textarea name='blank${i}' rows=1 cols=${t.length+1} style='max-height: 1.7em'></textarea>`) }); newTrial("blank", newVar("answer1","").global(),newVar("answer2","").global(),newVar("answer3","").global() , newTimer("loop", 50).callback( getVar("answer1").set(v=>(document.querySelectorAll("textarea[name^='blank']")[0]||{value:v}).value ), getVar("answer2").set(v=>(document.querySelectorAll("textarea[name^='blank']")[1]||{value:v}).value ), getVar("answer3").set(v=>(document.querySelectorAll("textarea[name^='blank']")[2]||{value:v}).value ) , getTimer("loop").start() ).start() , newController("Form", blank("It was so ea__ to get lost in an anc__ buil____")) .print() .wait() ) .log("answer1", getVar("answer1")) .log("answer2", getVar("answer2")) .log("answer3", getVar("answer3"))
Or, if you are using
Template
and have the expected responses in columns named, say, “Correct1,” “Correct2” and “Correct3”:Template( row => newTrial("blank", newVar("answer1","").global(),newVar("answer2","").global(),newVar("answer3","").global() , newTimer("loop", 50).callback( getVar("answer1").set(v=>(document.querySelectorAll("textarea[name^='blank']")[0]||{value:v}).value ), getVar("answer2").set(v=>(document.querySelectorAll("textarea[name^='blank']")[1]||{value:v}).value ), getVar("answer3").set(v=>(document.querySelectorAll("textarea[name^='blank']")[2]||{value:v}).value ) , getTimer("loop").start() ).start() , newController("Form", blank("It was so ea__ to get lost in an anc__ buil____")) .print() .wait() , newVar("correct1").global().set(getVar("answer1")).set( v=>v==row.Correct1 ), newVar("correct2").global().set(getVar("answer2")).set( v=>v==row.Correct2 ), newVar("correct3").global().set(getVar("answer3")).set( v=>v==row.Correct3 ) ) .log("answer1", getVar("answer1")) .log("answer2", getVar("answer2")) .log("answer3", getVar("answer3")) .log("correct1", getVar("correct1")) .log("correct2", getVar("correct2")) .log("correct3", getVar("correct3")) )
Jeremy
March 30, 2022 at 8:07 am #7992HPIParticipantHi, thanks, it’s exactly the template in this way
Template( row =>
newTrial(“blank”,what I wished to do.
Now that I posted the whole function (in the old syntax) the function with the more compact syntax that you wrote doesn’t need to be modified, right?
The blank index is correct the way you wrote blank12 ecc.. but because now I have this part of the script after my task is number like blank171… I am understanding now that the item number in the output (like blank17..something here) always corresponds to the order of the input. This is very smart.
Everybody tells me to code the RT and the accuracy in the data analysis afterward, but I literally just spent the whole night fixing somebody else’s dataset that got messed up in several transformations and file separations. Then merging several datasets has a risk of misplacement, errors that I don’t want to have in my data. 😀
My philosophy is to start with a dataset that has the relevant info concerning a participant all already there 😀
So I like to attach to the data the conditions and expected answers and so on…the accuracy.
Can I impose a restriction on the blanks so that the participant cannot leave it empty, as well as a restriction that the participant cannot write something 20 characters long, I don’t know..to impose a range that the string the participant writes is between 1 and 6 characters long.
I’ll work on the script with the template,
Thanks!
HPIMarch 30, 2022 at 11:04 am #7993JeremyKeymasterHi,
Can I impose a restriction on the blanks so that the participant cannot leave it empty, as well as a restriction that the participant cannot write something 20 characters long, I don’t know..to impose a range that the string the participant writes is between 1 and 6 characters long.
You could replace the ‘continue’ link of the Form controller with a Button element, and test the content of the Var elements in its
wait
command:newController("Form", blank("It was so ea__ to get lost in an anc__ buil____")).print() , newButton("Continue").print().wait( getVar("answer1").test.is(v=>v.match(/^\w{1,6}$/)) .and( getVar("answer2").test.is(v=>v.match(/^\w{1,6}$/)) ) .and( getVar("answer3").test.is(v=>v.match(/^\w{1,6}$/)) ) .failure( newText("Please type between 1-6 characters in each box").print() ) )
Just make sure you set
continueMessage: null
on the Form controller, either as a default, or by modifying the first line ofblank
:blank = sentence => Object({continueMessage: null, html:
Jeremy
April 4, 2022 at 2:15 pm #8009HPIParticipantHi,
I just want to signal that lately, PcIbex is not updating the script even if it says it’s saving it. It did it also before, but after a couple of modifications in the script, it would save the latest versions. For 5 or 6 days it hasn’t been completely updating the script and it has been loading previously saved versions.
Also, other researchers in my university are experiencing this problem.
Is there anything we can do?
Thank you!
HPIApril 4, 2022 at 2:47 pm #8011JeremyKeymasterApril 5, 2022 at 6:43 am #8013HPIParticipantHi,
thank you for the reply, reuploading the main js file works.
I am working in this part:
Template( row => newTrial("blank", newVar("answer1","").global(),newVar("answer2","").global(),newVar("answer3","").global() , newTimer("loop", 50).callback( getVar("answer1").set(v=>(document.querySelectorAll("textarea[name^='blank']")[0]||{value:v}).value ), getVar("answer2").set(v=>(document.querySelectorAll("textarea[name^='blank']")[1]||{value:v}).value ), getVar("answer3").set(v=>(document.querySelectorAll("textarea[name^='blank']")[2]||{value:v}).value ) , getTimer("loop").start() ).start() , newController("Form", blank("It was so ea__ to get lost in an anc__ buil____")) .print() .wait() , newVar("correct1").global().set(getVar("answer1")).set( v=>v==row.Correct1 ), newVar("correct2").global().set(getVar("answer2")).set( v=>v==row.Correct2 ), newVar("correct3").global().set(getVar("answer3")).set( v=>v==row.Correct3 ) ) .log("answer1", getVar("answer1")) .log("answer2", getVar("answer2")) .log("answer3", getVar("answer3")) .log("correct1", getVar("correct1")) .log("correct2", getVar("correct2")) .log("correct3", getVar("correct")) )
I am not sure newVar(“correct1”).global().set(getVar(“answer1”)).set( v=>v==row.Correct1 ),
performs the operation that I want to do.
I placed in a template the columns with the right answers, one column per answer, and the column name are “ansa_right_blank_1”, “ansa_right_blank_2”, ecc..
The name of the text is “ansa”, so I named the trial and variables with this label
I want to do something like test.selected, so if the answer written by the participant matches correct1, a variable should mark the hit (like 1), and a variable should add + 1.
In the main experiment I have used this strategy: exp score gets the total accuracy, item score marks either 0 or 1.
.success( getVar("exp_score").set(s_exp=>s_exp+1) , getVar("item_score") .set(1) ) .failure( getVar("item_score") .set(0) )
Can I use something like .test.selected for the answers obtained with the blank function? and then add a calculation like the one above for accuracy?
newVar("correct1").global().set(getVar("answer1")).set( v=>v==row.Correct1 )
Does this write in answer1 the answer only if it matches the one in the template?the results look like this:
# 13. ansa_1. (this is answer1 in your script) # 14. ansa_2. # 15. ansa_3. # 16. ansa_right_blank_1. (this is correct1 in your script) # 17. ansa_right_blank_2. # 18. ansa_right_blank_3. # 19. Comments. 1649154382 aaa344f7a0904712475fe5f0de010eb2 PennController 41 0 ansa NULL PennController 45 _Trial_ Start 1.64915E+12 poo ggoa bbo false false false NULL 1649154382 aaa344f7a0904712475fe5f0de010eb2 PennController 41 0 ansa NULL Controller-Form Form blank170 poo 1.64915E+12 poo ggoa bbo false false false Any addtional parameters were appended as additional columns 1649154382 aaa344f7a0904712475fe5f0de010eb2 PennController 41 0 ansa NULL Controller-Form Form blank179 ggoa 1.64915E+12 poo ggoa bbo false false false Any addtional parameters were appended as additional columns 1649154382 aaa344f7a0904712475fe5f0de010eb2 PennController 41 0 ansa NULL Controller-Form Form blank186 bbo 1.64915E+12 poo ggoa bbo false false false Any addtional parameters were appended as additional columns
Thank you!
HPIApril 9, 2022 at 3:16 pm #8025HPIParticipantHi,
The original blank function i have copied paste here checked for blank spaces like this
(s && !s.match(/^\s*$/)) (if i understood correctly this checks that the box has not spaces)the one you suggested me test.is(v=>v.match(/^\w{1,6}$/))
doesn’t consider accented letters and I must allow for accented LATIN and SLAVIC letters.
If I write a string containing “Ã ” it considers the slot empty and doesn’t let me move forward in the task, and this is a bug.
Could you suggest how to improve the syntax?
I went around online to understand how to implement accented letters, but I really don’t get how to combine the syntax “any letter” “any accented letter” “1-6 characters long”.
I think I should write something like this /^[a-zA-Z\u00C0-\u017F]+$/ but I get errors, I don’t understand the correct way to write this here.
I was trying for a solution like “doesn’t match empty” as in the original function, but I don’t understand to negate .match
I managed to set the counter of the previous message.
Thank you for your help,
HPI
-
AuthorPosts
- You must be logged in to reply to this topic.