How to use a spreadsheet

Previous page: Sample experiment: Priming Design
Next page: Data Collection & Data Analyses

On this page, we will see how to use PennController‘s functions to generate multiple template-based trials from a spreadsheet.

Preparation of the table

On the previous page, we ended up defining four items: 2 picture and 2 rating trials. Both items in each pair had exactly the same format, with only a few parameters varying from one item to the other.

Rather than manually copying the same code over and over, editing the variable parts each time (a method which is prone to fatal typos!) we will create a spreadsheet that associates each item with its variables, and use it to automatically take care of the copying for us by feeding a template.

Rating trials table

For the moment, we will only deal with the rating trials, to get a sense of how PennController handles the spreadsheet-template interface.

Using your favorite spreadsheet editor, create a table for the rating trials like this one:

Sentence
To me the color green is…
To me the color purple is…

Link to the spreadsheet

Adding a table to the Ibex project

Save your spreadsheet as a CSV file (e.g. design.csv) and upload it to the folder chunk_includes of your Ibex project.

Generating trials with PennController.Template

We will now use the PennController.Template function to generate Ibex items from the content of the spreadsheet. This is how we use it:

PennController.ResetPrefix(null);

PennController.Template(
    // 'row' will successively point to each row of the table (feel free to use another name)
    row => PennController(
        // row.Sentence will iteratively take the value of the column 'Sentence' for each row
        newText( "sentence" , row.Sentence )
        ,
        newScale("judgment",    "cold", "cool", "lukewarm", "warm", "hot")
            .settings.labelsPosition("top")
            .settings.before( getText("sentence") )
            .settings.size("auto")
            .print()
            .wait()   
    )
);

As you can see, the template’s structure is identical to the rating items we defined before, but we replaced the content of the Text element with row.Sentence. This is how it works: Template goes through each row of the spreadsheet, fills row (or whatever name you use before => PennController() with the values of the cells for the row it is currently inspecting, and generates an item using the template. Since our spreadsheet has two rows, it will use the template twice to generate two items, with row.Sentence successively taking its value from the Sentence cell of the first and then the second row.

Extending the table

We could add as many rows to the spreadsheet in order to generate more rating trials, but now we would like to also generate picture trials using the spreadsheet.

Picture trials

We need to add three more columns to the spreadsheet for the picture trials, one (Type) to distinguish the rating rows from the picture rows, and two which will point to the images of each patch for a given trial. Let’s name these two last columns Color1 and Color2 (since we won’t use those columns for the rating trials, we’ll fill them with NAs for the rating rows). Now we can add new rows to the spreadsheet and use the columns to define picture-trial-specific parameters.

Type Sentence Color1 Color2
rating To me the color green is… NA NA
rating To me the color purple is… NA NA
picture Which patch is greener? green1.png green2.png
picture Which patch is purpler? purple1.png purple2.png

Link to the spreadsheet

Updating the table from your Ibex project

Now that you have updated your spreadsheet, you also need to update the table you previously updated to your Ibex project. Once again, save your spreadsheet as a CSV file and go to your Ibex project. Under chunk_includes, locate the file you uploaded before and click on upload new version to its right, then select the new CSV file from your computer: your table is now up to date.

One spreadsheet, Two templates

We now need to define a new template based on the structure of the picture trials we scripted before, so we will call Template a second time. But since we are using a single table for two different types of trial, we need to assign the rating rows to the rating template, and the picture rows to the picture template. We can do so by referring to the table in each call to Template and by filtering the rows by looking at the text in their Type column.

Since your project only contains one table so far (your Ibex project has only one CSV file under chunk_includes) you can refer to it using the special keyword PennController.defaultTable, and filter its rows using the method .filter onto it, as follows:

// Show the picture trials first (though we generate them second)
PennController.Sequence( randomize("picture") , randomize("rating") );
PennController.ResetPrefix(null);
// We don't give the full URL in the spreadsheet
PennController.AddHost("http://files.lab.florianschwarz.net/ibexfiles/PennController/SampleTrials/");

// As before, we use Template to define a template
// But now, we also specify that we want to use a subset of the table:
// this template only uses the rows where Type is 'rating'
PennController.Template( PennController.defaultTable.filter("Type","rating") ,
    row => PennController( "rating" ,
        newText( "sentence" , row.Sentence )
        ,
        newScale("judgment",    "cold", "cool", "lukewarm", "warm", "hot")
            .settings.labelsPosition("top")
            .settings.before( getText("sentence") )
            .settings.size("auto")
            .print()
            .wait()
    )
);

// We use Template a second time to define a template for the 'picture' trials
// This template only uses the rows where Type is 'picture'
PennController.Template( PennController.defaultTable.filter("Type","picture") ,
    row => PennController( "picture" ,
        defaultImage
            .settings.size(200, 200)
        ,
        newText("test sentence", row.Sentence)
            .print()
        ,
        newCanvas("patches", 500, 200)
            .settings.add(   0, 0, newImage("color1", row.Color1) )
            .settings.add( 300, 0, newImage("color2", row.Color2) )
            .print()
        ,
        newSelector("patch")
            .settings.add( getImage("color1") , getImage("color2") )
            .wait()
    )
);

Under the hood, this code generates exactly the same script from the page before: it generates 4 items in the native Ibex format for us. This way, we can manipulate the order of presentation of the items in the shuffleSequence variable the same way we did before.

Within-item manipulation

Let’s say we want each of our picture trials to come in two versions: one version with the comparative as before, and one version with a less comparative instead. That is, each item now comes in two variants. We have to reflect this in our spreadsheet (we focus on the picture rows for the moment, throwing in two extra colors just for the fun of it!):

Type Group Sentence Color1 Color2
picture More Which patch is greener? green1.png green2.png
picture Less Which patch is less green? green1.png green2.png
picture More Which patch is purpler? purple1.png purple2.png
picture Less Which patch is less purple? purple1.png purple2.png
picture More Which patch is beiger? beige1.png beige2.png
picture Less Which patch is less beige? beige1.png beige2.png
picture More Which patch is oranger? orange1.png orange2.png
picture Less Which patch is less orange? orange1.png orange2.png

We inserted one column, Group, and duplicated each picture row to reflect our group-design manipulation. The Group column defines different groups of participants: for any given participant, the template will generate items using either only the More rows, or only the Less rows.

Alternatively, we could have chosen to show one group of participants half the items in one variant, and half the items in the other variants—the other group of participants would see the complimentary half-half distribution. Let’s make one group group A and the other group group B. We simply cycle through the items by defining the Group column as shown below: participants from group A will see green and beige with the more comparative and purple and orange with the less comparative, participants from group B will see green and beige with the less comparative and purple and orange with the more comparative.

Type Group Sentence Color1 Color2
picture A Which patch is greener? green1.png green2.png
picture B Which patch is less green? green1.png green2.png
picture B Which patch is purpler? purple1.png purple2.png
picture A Which patch is less purple? purple1.png purple2.png
picture A Which patch is beiger? beige1.png beige2.png
picture B Which patch is less beige? beige1.png beige2.png
picture B Which patch is oranger? orange1.png orange2.png
picture A Which patch is less orange? orange1.png orange2.png

Whichever manipulation you end up going with, PennController.Template automatically detects the presence of a Group column and generates group-specific trials accordingly, so you can keep the script from above exactly as it is.

Before you update your file though, you have to also duplicate the rating rows to match the Group assignations. Since we only want our manipulation to affect the picture items, we simply create duplicates of the rating rows where only the value in Group changes: this way, rating trials will appear the same for participants from either group. Below is a spreadsheet reflecting the former, non-latin-square group design:

Type Group Sentence Color1 Color2
rating More To me the color green is… NA NA
rating Less To me the color green is… NA NA
rating More To me the color purple is… NA NA
rating Less To me the color purple is… NA NA
rating More To me the color beige is… NA NA
rating Less To me the color beige is… NA NA
rating More To me the color orange is… NA NA
rating Less To me the color orange is… NA NA
picture More Which patch is greener? green1.png green2.png
picture Less Which patch is less green? green1.png green2.png
picture More Which patch is purpler? purple1.png purple2.png
picture Less Which patch is less purple? purple1.png purple2.png
picture More Which patch is beiger? beige1.png beige2.png
picture Less Which patch is less beige? beige1.png beige2.png
picture More Which patch is oranger? orange1.png orange2.png
picture Less Which patch is less orange? orange1.png orange2.png

Link to the spreadsheet

Github

In case you got lost on the way, you can sync the branch spreadsheet (remember that syncing with this GitHub repository will erase whatever currently is in your Ibex project) of https://github.com/PennController/Tutorial.git

Assigning groups to participants

Ibex provides different methods to run the experiment in each group. If you simply click the link of your experiment, Ibex will use its internal counter to choose a group. By default, the counter is increased each time a participant complete the experiment. In your case, since your design defines two different groups, clicking the link will assign you to a different group depending on whether an even or an odd number of participants already completed the task. Feel free to try it yourself, completing and not completing the task at times, to see the effect.

If you want more control over group assignment, you can directly modify the URL of the experiment, by replacing the experiment.html part at the end with server.py?withsquare=N where N is a number used to override the internal counter’s value. In this case, using server.py?withsquare=0 (even) or server.py?withsquare=1 (odd) will attribute different groups. This is helpful when you send the URL around for data collection but want to control which group your participants end up in.

Using two tables [OPTIONAL]

Please note that the next pages will build on the one-table script above: you will have to adapt those scripts accordingly if you want to use the two-table solution.

If you don’t like having to introduce NAs in your spreadsheet and duplicate rows for invariant items, you can split your spreadsheet in two: one spreadsheet for the rating items, and one spreadsheet for the picture items.

Rating

We can now keep only the Sentence column from this spreadsheet, falling back on the one-column format that we started with.

Sentence
To me the color green is…
To me the color purple is…
To me the color beige is…
To me the color orange is…

Link to the spreadsheet

Picture

This spreadsheet keeps all but the Type column and only contains the picture rows.

Group Sentence Color1 Color2
More Which patch is greener? green1.png green2.png
Less Which patch is less green? green1.png green2.png
More Which patch is purpler? purple1.png purple2.png
Less Which patch is less purple? purple1.png purple2.png
More Which patch is beiger? beige1.png beige2.png
Less Which patch is less beige? beige1.png beige2.png
More Which patch is oranger? orange1.png orange2.png
Less Which patch is less orange? orange1.png orange2.png

Link to the spreadsheet

In your Ibex project

Just repeat the procedure from the section Adding a table to the Ibex project (make sure you upload both CSV files as separate files—i.e. do not click upload new version after you upload the first of the two CSV files).

Summary

So now you should have more than one CSV file under chunk_includes. PennController.defaultTable points to the table in the file whose name comes first in the alpha-numerical order. In your case, you have two templates in your script that you each want to use with a specific CSV file. So instead of using PennController.defaultTable, you will use PennController.GetTable( "filename.csv" ), replacing filename.csv with the filename of the appropriate CSV file for its respective template.

Your script should look like this (with possibly different names instead of rating-table.csv and picture-table.csv for your tables—you may also have felt free to choose to use a different name for the row variable):

PennController.Sequence( randomize("picture") , randomize("rating") );
PennController.ResetPrefix(null);
PennController.AddHost("http://files.lab.florianschwarz.net/ibexfiles/PennController/SampleTrials/");

// No need to filter: rating-table.csv only contains what we need for this template
PennController.Template( PennController.GetTable("ratingfull.csv") ,
    row => PennController( "rating" ,
        newText( "green" , row.Sentence )
        ,
        newScale("judgment",    "cold", "cool", "lukewarm", "warm", "hot")
            .settings.labelsPosition("top")
            .settings.before( getText("green") )
            .settings.size("auto")
            .print()
            .wait()
    )
);

// No need to filter: rating-picture.csv only contains what we need for this template
PennController.Template( PennController.GetTable("picturefull.csv") ,
    row => PennController( "picture" ,
        defaultImage
            .settings.size(200, 200)
        ,
        newText("test sentence", row.Sentence)
            .print()
        ,
        newCanvas("patches", 500, 200)
            .settings.add(   0, 0, newImage("color1", row.Color1) )
            .settings.add( 300, 0, newImage("color2", row.Color2) )
            .print()
        ,
        newSelector("patch")
            .settings.add( getImage("color1") , getImage("color2") )
            .wait()
    )
);

As you may have noticed, your mixed-item-type table is no longer useful, so feel free to delete its file from your Ibex project if you feel so inclined.

Note on PennController.defaultTable and PennController.GetTable

When we introduced Template, we did not refer to any table. We did not need to do so, because we only used one table and one template. Under the hood, when you do not specify which table you want to use, Template uses the table referred to by PennController.defaultTable. This keyword always picks the table whose name comes first in the alpha-numerical order: if you only have one CSV file, it will necessarily refer to its table, but if you have at least two CSV files, say rating.csv and picture.csv, PennController.defaultTable will refer to picture.csv, because p comes before r in the alpha-numerical order.


Next page: Data Collection & Data Analyses