Multi-trial experiments using templates
Most experiments consist of multiple trials, typically varying levels of a factor, e.g., whether or not the inflectional morpheme -s is present or not in our example experiment. So the last step in designing our experiment is to create an entire set of trials.
We could copy and paste the PennController(...) defining our picture-selection trial, making changes along the way to display other sentences and using different audio and image files for the different experimental items. But then we would need to edit every copy whenever we want to change small aspects of the trial structure, e.g., adding a pause of some number of milliseconds at the beginning of each trial. This is cumbersome and seems unnecessary, since the various trials will usually have the exact same structure, apart from some key ingredients.
A more sensible approach is to define a template for the general trial structure and to identify the variable bits in your PennController(...), which can then be supplied from a table.
In our case, there are four strings that will change on a trial-by-trial basis:
- "2fishRoundTank.mp3" in
- "The fish swim in a tank which is perfectly round" in
newText("The fish swim in a tank which is perfectly round"),
- "2fishRoundTank.png" in
- "1fishSquareTank.png" in
Everything else will be constant from one trial to the other.So we can respectively replace these bits with variable.AudioFile, variable.Description, variable.PluralImageFile and variable.SingularImageFile, and embed PennController(...) within PennController.Template( variable => ... ) to transform it into a template:
PennController.Template( variable => PennController( newAudio("description", variable.AudioFile) .play() , newText(variable.Description) .unfold(2600) , newImage("two", variable.PluralImageFile) .settings.size(200,200) , newImage("one", variable.SingularImageFile) .settings.size(200,200) , newCanvas(450,200) .settings.add( 0 , 0 , getImage("two") ) .settings.add( 250 , 0 , getImage("one") ) .print() , newSelector() .settings.add( getImage("two") , getImage("one") ) .settings.keys( "F" , "J" ) .settings.log() .wait() , getAudio("description") .wait("first") ) .log( "ID" , getVar("ID") ) )
Save your script and test your experiment. You should now see a series of 4 picture-selection trials. The values for the variables for these trials are supplied by a table, whose structure we turn to next.
Importing the resources at the beginning of this tutorial from github uploaded various files (such as the image and audio files we already used) to the Resources section of the main project page. This also included a file named fulldesign.csv. This file is a table created using a spreadsheet editor and saved in a comma-separated-value (CSV) format (i.e., same format as the results file). Below is a rendering of the table:
|1fishSquareTank.mp3||The fish swims in a tank which is perfectly square||2fishRoundTank.png||1fishSquareTank.png||fish||A||-s||2600|
|2fishRoundTank.mp3||The fish swim in a tank which is perfectly round||2fishRoundTank.png||1fishSquareTank.png||fish||B||No-s||2600|
|1deerDenseWood.mp3||The deer runs in a wood which is extremely dense||2deerSparseWood.png||1deerDenseWood.png||deer||B||-s||2500|
|2deerSparseWood.mp3||The deer run in a wood which is extremely sparse||2deerSparseWood.png||1deerDenseWood.png||deer||A||No-s||2500|
|1sheepRedPen.mp3||The sheep roams in a pen which is strikingly red||2sheepBluePen.png||1sheepRedPen.png||sheep||A||-s||2200|
|2sheepBluePen.mp3||The sheep roam in a pen which is strikingly blue||2sheepBluePen.png||1sheepRedPen.png||sheep||B||No-s||2200|
|1mooseNewPark.mp3||The moose walks in a park which is visibly new||2mooseOldPark.png||1mooseNewPark.png||moose||B||-s||2200|
|2mooseOldPark.mp3||The moose walks in a park which is visibly old||2mooseOldPark.png||1mooseNewPark.png||moose||A||No-s||2200|
The template we created looks for columns with corresponding names in the CSV table based on the specified variables, such as variable.AudioFile, (e.g., AudioFile, Description, PluralImageFile and SingularImageFile) and successively uses the values in each row to generate as many trials.
There are two additional columns not corresponding to variables in our template: Group and Ending.
We commonly want a given participant to only see a subset of the various versions of our table, and the Group column controls this: PennController automatically detects it and will generate trials only using the rows with one value in Group, in our case either the A or the B rows every other time the experiment runs (for more on how the group value is chosen on a given run of the experiment, see the full documentation). As a result, even though the table contains 8 rows besides the header row, when you test the experiment you only see 4 trials, either generated from rows 2, 5, 6 and 9 (highlighted in the table above) or generated from rows 3, 4, 7 and 8.
The Ending column specifies the property of our sentences that we care about, i.e., the manipulation of our independent variable, here whether or not the verb ends in the inflectional morpheme -s.
Tracking trial details
In order to keep track of all the details for a given trial, we need the lines of our results file to indicate things like what item they correspond to, what condition (presence vs absence of -s) they correspond to and what group (A vs B) they correspond to. In other words, we want to report the values of the table’s Item, Ending and Group columns to the lines in our results file. We simply use the same .log command we used before, referring to the table’s columns using the variable. method:
PennController.Template( variable => PennController( newAudio("description", variable.AudioFile) .play() , newText(variable.Description) .unfold(2600) , newImage("two", variable.PluralImageFile) .settings.size(200,200) , newImage("one", variable.SingularImageFile) .settings.size(200,200) , newCanvas(450,200) .settings.add( 0 , 0 , getImage("two") ) .settings.add( 250 , 0 , getImage("one") ) .print() , newSelector() .settings.add( getImage("two") , getImage("one") ) .settings.keys( "F" , "J" ) .settings.log() .wait() , getAudio("description") .wait("first") ) .log( "ID" , getVar("ID") ) .log( "Item" , variable.Item ) .log( "Ending" , variable.Ending ) .log( "Group" , variable.Group ) )
Save and take the experiment, and you’ll see all the relevant information listed in the lines for each trial in the results file.
Next: Timers & Randomization