Experimental Economics: Data Workflow
Lecture 2: Introduction to oTree
1 Design
1.1 Smart design
 
16 animali, Enzo Mari, 1957
2 Computerized experiments
2.1 Advantages of computerized experiments
- Computerized experiments present several advantages over paper&pencil experiments
- Live interaction
 - Dynamic interfaces
 - Codified data
 - Wide audience
- Web-based experiments
- Prolific, Mechanical Turk
 
 
 - Web-based experiments
 
 - Specialized softwares are available
- z-Tree (Fischbacher 2007) is the de-facto standard for lab experiments
- Runs locally on Win OS
 - See my online lecture notes if interested
 
 - oTree (Chen, Schonger, and Wickens 2016) is the current choice of most
- Platform-independent, web-based
 
 
 - z-Tree (Fischbacher 2007) is the de-facto standard for lab experiments
 - Here, we focus on oTree
 
2.2 About oTree
- oTree is a framework based on Python to run controlled experiments
- Games
- e.g., Public Goods Games (PGG)
 
 - Individual decision making
- e.g., Risk elicitation tasks
 
 - Surveys and tests
- e.g., Raven test
 
 
 - Games
 - Support by the community
 - oTree is open-source,
- Licensed under an adaptation of the MIT license.
- Cite it in the paper when you use it
 
 
 - Licensed under an adaptation of the MIT license.
 
2.3 Code
- Programming language of oTree is Python 
- Popular object-oriented programming language
 - Developed in early 90’s by Guido Van Rossum
 
 - OTree’s user interface is based on HTML5 
- Supported by modern browser and rich in functionalities
- Can be enriched with
- css
 - javascript
 - bootstrap
 - …
 
 
 - Can be enriched with
 
 - Supported by modern browser and rich in functionalities
 - All the components of oTree are free and open-source
 
2.4 Functioning
- The basic setup consists in
- An app (experiment) written within oTree
 - A server computer
 
- Cloud server, local PC …
 
- Subjects’ devices with a browser
 
- PC, Laptop, Tablet, Mobile Phone …
 
 - oTree creates a session on the server and generates links for all participants
 - Participants click on the links and are sent to a personal page
- They submit their answers, which are collected by the server
 - The experimenter can check the progress on the server
 
 
3 oTree app
3.1 Conceptual overview
| Session | |||||
|---|---|---|---|---|---|
| Subsession | Subsession | ||||
| Page | Page | Page | Page | Page | Page | 
- Sessions
- Participants take part in a series of tasks
 
 - Subsessions
- Sections of a session
- EXAMPLE: a PGG is subsession 1 and a questionnaire is subsession 2
 - Repetitions of the same task are performed over distinct subsessions (periods)
 
 
 - Sections of a session
 - Page
- Sections of a subsession
- EXAMPLE: the PGG is made of 4 pages (instructions, …) and the questionnaire of 2 pages
 
 
 - Sections of a subsession
 - Groups
- Each subsession can be divided into groups of players
- Groups can be shuffled between subsessions
 
 
 - Each subsession can be divided into groups of players
 
3.2 Object hierarchy
- oTree’s entities are organized with the following hierarchy
 
- A session is a series of subsessions
- A subsession contains multiple groups
- A group contains multiple players
- Each player proceeds through multiple pages
 
 
 - A group contains multiple players
 
 - A subsession contains multiple groups
 - Example:
- 18 players take part in an experimental session made of 5 rounds and are matched in groups of 3 players
- 5 subsessions
 - 6 groups
 - 18 players
 
 
 - 18 players take part in an experimental session made of 5 rounds and are matched in groups of 3 players
 
3.3 Object hierarchy (ii)
- You can access any higher-up object from a lower object
 
4 Your first app
4.1 Create the projects folder
- Choose a convenient location
- e.g., inside DOCUMENTS
 
 - Create the projects folder with the following terminal command
- Your appes will be developed here
 
 
otree startproject oTree_proj- You will be asked: Include sample games? (y or n):
- I suggest y
 
 - Now move into your folder
 
cd oTree_proj- In the folder you will find some demo app and other files like setting.py
 - The apps that in setting.py are contained in SESSION_CONFIGS = [] can be run locally with the command
 
otree devserver4.2 Create an app
- To create an app named hello_world move to the oTree folder
 
cd oTree- and create the app
 
otree startapp hello_world- Move to the folder my_first_app
- You will find the following files
- Python 
__init__.py
 - HTML 
- MyPage.html
 - Results.html
 
 
 - Python 
 
 - You will find the following files
 
4.3 __init__.py
- Contains Python classes (see introductory tutorial)
 - Can be ideally divided into two parts
- models
- refers to main oTree’s entities
- Constants
 - Subsession
 - Group
 - Player
 
 - A model is basically a database
 - Specify columns and their nature
- Integers, strings, …
 
 
 - refers to main oTree’s entities
 - pages
- refers to “screens” seen by participants
 
 
 - models
 - In older versions of oTree (<5) you could actually find models.py and pages.py in place of init
 
4.4 __init__.py: Content
from otree.api import *
doc = """
Your app description
"""
class C(BaseConstants):
    NAME_IN_URL = 'hello_world'
    PLAYERS_PER_GROUP = None
    NUM_ROUNDS = 1
class Subsession(BaseSubsession):
    pass
class Group(BaseGroup):
    pass
class Player(BasePlayer):
    pass
# PAGES
class MyPage(Page):
    pass
class ResultsWaitPage(WaitPage):
    pass
class Results(Page):
    pass
page_sequence = [MyPage, ResultsWaitPage, Results]- In page_sequence you control the sequence of pages
 
4.5 Templates
- These are the pages that are displayed to participants
- html files that contain informations and forms
- forms are used to collect data
 
 - A default MyPage.html is created
- {{ formfields }} will display the forms of the page
- see 
__init__.py 
 - see 
 - {{ next_button }} will display a button to continue
 
 - {{ formfields }} will display the forms of the page
 
 - html files that contain informations and forms
 - Main content within {{ block content }} — {{ endblock }}
 - The HTML can rely on “fancy” stuff
- Javascript
 - Bootstrap framework
 - CSS
 - …
 
 
{{ block title }}
    Page title
{{ endblock }}
{{ block content }}
    {{ formfields }}
    {{ next_button }}
{{ endblock }}4.6 Modify MyPage
Add the following title “Hello World!”
Add a smiley
😜
{{ block title }}
    Hello World!
{{ endblock }}
{{ block content }}
    {{ formfields }}
    {{ next_button }}
<p style="font-size:200px">😜</p>
    {{ formfields }}
    {{ next_button }}
{{ endblock }}5 Forms
5.1 Submitting information
- Each page in oTree can contain a form
- The player fills with some value and then submits it
- Cardinal values
- Integer, Float
 
 - Ordinal values
- Integer, Categorical
 
 - Text
- Strings of numbers and letters
 
 
 - Cardinal values
 
 - The player fills with some value and then submits it
 - Several formats to collect values
- Open fiels
 - Buttons
 - Radio buttons
 - Dropdown lists
 - …
 
 
5.2 Basic structure of forms
- First create fields in *__init__py*, models section
 - As an example, if you want to collect name and age
 
class Player(BasePlayer):
    name = models.StringField(label="Your name:")
    age = models.IntegerField(label="Your age:")- Then in *__init__py*, pages section, you will create a class
 
class Anag(Page):
    form_model = 'player'
    form_fields = ['name', 'age'] # saved as player.name, player.age- Finally, in the template Anag.html the form will be displayed with
 
{{ formfields }}- To display the forms separately
 
{{ formfields player.age }}6 Input formats
6.1 Examples
- In the following examples the participant receives a random number
 - The participant is asked to report features of the number
- Several input interfaces are presented
- Radio
 - Button
 - Checker
 - Dropdown
 - Radio Sequence
 - Text
 - Value
 - Tabular
 - Sliders
 
 
 - Several input interfaces are presented
 
6.2 Radio
__init.py__(Models)
input_radio = models.CharField(
    choices=['Odd', 'Even'],
    widget=widgets.RadioSelect)__init.py__(Pages)
class Radio(Page):
    form_model = 'player'
    form_fields = ['input_radio']- Template
 
{{ block content }}
{{ formfield player.input_radio label="The number is:"}}
  {{ next_button }}
{{ endblock }}6.4 Checker
__init.py__(Models)
class Player(BasePlayer):
  input_checker = models.CharField(
    choices=[
            ["Even", 'Yes'],
            ["Odd", 'No']
            ],
    widget=widgets.RadioSelectHorizontal,
    label="Is the number even?")__init.py__(Pages)
class Checker(Page):
    form_model = 'player'
    form_fields = ['input_checker']- Template
 
{{ block content }}
{{ formfield player.input_checker }}
    {{ next_button }}
{{ endblock }}6.5 Dropdown
__init.py__(Models)
class Player(BasePlayer):
    input_dropdown = models.IntegerField(
    choices=[1, 2, 3, 4, 5, 6, 7, 8, 9])__init.py__(Pages)
class Dropdown(Page):
    form_model = 'player'
    form_fields = ['input_dropdown']- Template
 
{{ block content }}
{{ formfield player.input_dropdown label="The number is:"}}
    {{ next_button }}
{{ endblock }}6.6 Radiosequence
__init.py__(Models)
class Player(BasePlayer):
    input_radiosequence = models.IntegerField(
        choices=[1, 2, 3, 4, 5, 6, 7, 8, 9],
        widget=widgets.RadioSelectHorizontal
        )__init.py__(Pages)
class RadioSequence(Page):
    form_model = 'player'
    form_fields = ['input_radiosequence']- Template
 
{{ block content }}
{{ formfield player.input_radiosequence label="The number is:"}}
    {{ next_button }}
{{ endblock }}6.7 Text
__init.py__(Models)
class Player(BasePlayer):
    input_text = models.CharField()__init.py__(Pages)
class Text(Page):
    form_model = 'player'
    form_fields = ['input_text']- Template
 
{{ block content }}
{{ formfield player.input_text label="The number is:"}}
    {{ next_button }}
{{ endblock }}6.8 Value
__init.py__(Models)
class Player(BasePlayer):
    input_value = models.IntegerField(min=1, max=9)__init.py__(Pages)
class Value(Page):
    form_model = 'player'
    form_fields = ['input_value']- Template
 
{{ block content }}
{{ formfield player.input_value label="The number is:"}}
    {{ next_button }}
{{ endblock }}6.9 Tabular (i)
__init.py__(Models)
class Player(BasePlayer):
    tab_1 = models.BooleanField(blank=True)
    tab_2 = models.BooleanField(blank=True)
    tab_3 = models.BooleanField(blank=True)
    tab_4 = models.BooleanField(blank=True)
    tab_5 = models.BooleanField(blank=True)
    tab_6 = models.BooleanField(blank=True)
    tab_7 = models.BooleanField(blank=True)
    tab_8 = models.BooleanField(blank=True)
    tab_9 = models.BooleanField(blank=True)__init.py__(Pages)
class Tabular(Page):
    form_model = 'player'
    form_fields = ['tab_1','tab_2','tab_3','tab_4','tab_5','tab_6','tab_7','tab_8','tab_9']6.10 Tabular (ii)
Template
- raw HTML, bs table
 
{{ block content }}
  <table class="table table-bordered">
    <tbody>
      <tr>
          <td><button name="tab_1" value="True" class="btn btn-outline-primary" btn-lg"> 1 </button></td>
          <td> <button name="tab_2" value="True" class="btn btn-outline-primary" btn-lg">2</button></td>
          <td> <button name="tab_3" value="True" class="btn btn-outline-primary" btn-lg">3</button></td>
        </tr>
        <tr>
          <td><button name="tab_4" value="True" class="btn btn-outline-primary" btn-lg">4</button></td>
          <td><button name="tab_5" value="True" class="btn btn-outline-primary" btn-lg">5</button></td>
          <td><button name="tab_6" value="True" class="btn btn-outline-primary" btn-lg">6</button></td>
      </tr>
      <tr>
        <td ><button name="tab_7" value="True" class="btn btn-outline-primary" btn-lg">7</button></td>
        <td><button name="tab_8" value="True" class="btn btn-outline-primary" btn-lg">8</button></td>
        <td><button name="tab_9" value="True" class="btn btn-outline-primary" btn-lg">9</button></td>
<!--    <td><button name="tab_8" value="True" class="btn btn-outline-primary" btn-lg">8</button></td> -->
      </tr>
    </tbody>
  </table>
{{ endblock }}6.11 Slider (simple)
__init.py__(Models)
input_slider = models.IntegerField(min=1, max=9)__init.py__(Pages)
class Slider(Page):
    form_model = 'player'
    form_fields = ['input_slider']- Template
 
<div class="input-group">
    <div class="input-group-prepend">
        <span class="input-group-text">Disagree</span>
    </div>
    <input type="range" name="input_slider" min="1" max="9" step="1">
    <div class="input-group-append">
        <span class="input-group-text">Agree</span>
    </div>
</div> 
{{ form.input_slider.errors }}6.12 Sliders (boosted)
- Contributed by Max Grossmann
- relies on css styling + javascript code
 
 
- Template
 
{{ block content }}
{{ formfield_errors 'slider_1' }}
{{ formfield_errors 'slider_2' }}
<div id="slider1_here"></div>
<br>
<div id="slider2_here"></div>
{{ next_button }}
{{ endblock }}- Javascript 
- load mgslider.js from *_static*
 
 
{{ block scripts }}
<!-- to display the value in the confirmation-->
<script src="{{ static 'mgslider.js' }}"></script>
<script>
    $(document).ready(function (event) {
        slider1 = new mgslider("slider_1", 0, 9, 1);
        slider1.print(document.getElementById("slider1_here"));
        slider2 = new mgslider("slider_2", 0, 9, .1);
        slider2.print(document.getElementById("slider2_here"));
    });
</script>
{{ endblock }}- css
 
{{ block styles }}
<style>
    .mgslider-wrapper {
        border-spacing: 10px;
    }
    .mgslider-limit {
        width: 10%;
        min-width: 75px;
        height: 40px;
        margin: 100px;
        text-align: center;
        background: #eee;
        border: 1px solid #888;
    }
    .mgslider-limit,
    .mgslider-value {
        font-variant-numeric: tabular-nums;
    }
    .mgslider-before {
        height: 16px;
        width: 100%;
        background: #1e5bff;
    }
</style>
{{ endblock }}
{{ endblock }}7 Implement a simple survey
7.1 Steps to implement the survey
- Implement a simple survey to collect the age of course participants
 
- Develop the oTree code
__init__.py- templates
 
 - Test our code locally
 - Transfer our code to a server (running oTree)
 - Send links to participants (you)
 - Fill in the survey
 - Collect outcomes and analyze them
 
7.2 __init__.py: models
from otree.api import *
author = 'MP'
doc = """
A simple app to collect the age of respondent
"""
class C(BaseConstants):
    NAME_IN_URL = 'my_first_survey'
    PLAYERS_PER_GROUP = None
    NUM_ROUNDS = 1
class Subsession(BaseSubsession):
    pass
class Group(BaseGroup):
    pass
class Player(BasePlayer):
    age = models.IntegerField(choices=range(18, 99, 1))- Create the field 
agein class player- The input will be an integer spanning 18-99
 
 
7.3 __init__.py: pages
class CollectAge(Page):
    form_model = 'player'
    form_fields = ['age']
class Results(Page):
  pass
        
page_sequence = [CollectAge, Results]- In page 
CollectAgewe insert a form for class player- The field is 
age- Must be the same name we used in models.py
ageand not, as an example,Age
 
 - Must be the same name we used in models.py
 
 - The field is 
 - In page 
Resultswe give a feedbackyour_agewill be printed on the screen
 page_sequencedefines the sequence of your pages- All your pages are a class that must be defined here
 
7.4 Develop the oTree code: templates
- Templates are the .html files
 
- CollectAge
 
{{ block title }}
    Insert your age here
{{ endblock }}
{{ block content }}
{{ formfield player.age }}
    {{ next_button }}
{{ endblock }}{{ formfield player.age }}is where the information is input- Variable 
agein the class player 
- Variable 
 - Aesthetics elements
- Title
 - Body
 - Next button
 
 
7.5 Develop the oTree code: templates (ii)
- Results
 
{{ block title }}
    Your age
{{ endblock }}
{{ block content }}
Your age is {{player.age}}. Thank you for answering!
    {{ next_button }}
{{ endblock }}{{ player.age }}is passed gathered from class player- The right name must be put within 
{{ }}brackets 
- The right name must be put within 
 
7.6 Test our code locally
See also the video tutorial!
Move to the folder in which your oTree is installed
- Usually 
cd ~/oTree - Add your new app to the `settings.py file
 
- Usually 
 
SESSION_CONFIGS = [
    dict(
        name='my_first_survey',
        display_name='my_first_survey',
        num_demo_participants=4,
        app_sequence=['my_first_survey'],
    )
]- In 
app_sequencethe exact name of the apps should be given - Give command
 
otree devserver7.7 Test our code locally (ii)
- Open a browser and insert 
http://localhost:8000/ 
- Click on the name of your app
 
- Click on the session-wide link and try it
 
8 Appendix
8.1 Assignment
- Create the 
my_first_surveyapp- Add the field 
gender- Categorical variable with values: 
Male,Female,Non-binary,Prefer not to answer 
 - Categorical variable with values: 
 - Collect gender both with
- Radio buttons
 - Dropdown list
 
 
 - Add the field 
 
8.2 References
References
Chen, Daniel L, Martin Schonger, and Chris Wickens. 2016. “oTree—an Open-Source Platform for Laboratory, Online, and Field Experiments.” Journal of Behavioral and Experimental Finance 9: 88–97.
Fischbacher, Urs. 2007. “Z-Tree: Zurich Toolbox for Ready-Made Economic Experiments.” Experimental Economics 10 (2): 171–78.
Holzmeister, Felix. 2017. “oTree: Ready-Made Apps for Risk Preference Elicitation Methods.” Journal of Behavioral and Experimental Finance 16: 33–38.