oTree Crash Course

Session V - oTree Introduction & Individual Experiments

Ali Seyhun Saral (Institute for Advanced Study in Toulouse)

IMPRS Be Smart Summer School

2023-08-08

Check oTree in action:

experiment.lol

oTree Introduction

About oTree

  • oTree is a platform/software to run lab/online experiments.
  • Participants interact with each other using their browsers
  • oTree runs on a web server:
    • It runs on your computer for development
    • It may run on a physical server
    • It may run on a cloud server

About oTree (2)

  • It essentially works as a “web-app”
  • oTree handles necessary components like database, pages, urls, …
  • Defines a specialized data structure for our needs
    • Players
    • Groups
    • Session

Managing your experiment

link

Ways to build your experiment

  • Using a text editor

  • oTree studio (https://www.otreehub.com/)

What do you need?

  • A Python 3 installation
  • A text editor/IDE to develop Python
    • Visual Studio Code (preferred)
    • PyCharm
    • RStudio
    • Notepad++
    • Even notepad
  • A command-line interface

Components of an oTree Experiment

Virtual Environments in Python

  • A virtual environment is essentially a directory that contains:
    • Python installation
    • A number of additional packages
  • They provide isolated python development enviromens with different packages and dependencies.

oTree Development Installation Structure

(my suggestion)

ot (or your project name)
├── oTree
│   ├── app1
│   ├── app2
│   ├── ...
└── venv
  • ot: A container folder
  • oTree: oTree software (project folder)
  • venv: Virtual Environment

oTree Installation

  1. Create the parent folder ot
mkdir ot
  1. Go to the folder
cd ot
  1. Create a virtual enviroment
python3 -m venv venv
  1. Activate the virtual environment
# MacOS or Linux
source venv/bin/activate
# Windows
venv\Scripts\activate

oTree Installation

  1. Install otree Package
pip install otree
  1. Create an oTree project
otree startproject oTree
  1. Go to oTree folder
cd oTree
  1. Check if oTree is can be startd
otree devserver
~/ot/oTree> otree devserver
Open your browser to http://localhost:8000/
To quit the server, press Control+C.

Command Line Mini Cheatsheet

Command Windows MacOS/Linux
print current location pwd cd
list files here dir ls
go to directory cd NAME cd NAME
create directory mkdir NAME mkdir NAME
delete file del filename rm filename
activate virtual env. PATH\Scripts\activate source PATH\bin\activate
deactivate virtual env. deactivate deactivate
run oTree server (dev.) otree devserver otree devserver
create app otree startapp APPNAME otree startapp APPNAME

oTree Interface

  • Demo: A quick way to test experiments
  • Sessions: To create and manage sessions
  • Rooms: A tool to run experiments on specific links with specific participants
  • Data: To download data (all together)
  • Server check: Shows a summery of server configuration

What is an app?

What is an app (cont’d)?

  • App is the basic unit of an experiment.
  • Each experiment should consist at least one app.
  • Each app has its own:
    • Pages
    • Fields/data structure for participant
    • Other components like images, csv files etc.

What is an app?

  • Experiments may contain more than one app

Create blank app and register it

  • Make sure you have your virtual environment activated

  • Make sure you are in the oTree (project) folder

  • Create the app

    otree startapp APPNAME
  • In our case we will use the name my_survey:

    otree startapp my_survey

Register the app in the project

  • Go to settings.py

  • Modify SESSION_CONFIGS as below

    SESSION_CONFIGS = [
      dict(
        name='my_survey',
        app_sequence=['my_survey'],
        num_demo_participants=10,
      ),
    ]

oTree File Structure

  • settings.py: oTree settings file
  • my_survey folder: the app in our oTree project. We apps on our own.
    • __init__.py: Backend components (pages and models)
    • *.html: Frontend components (templates)

How __init__.py looks like?

from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'newapp'
    players_per_group = None
    num_rounds = 1

## MODELS
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]

oTree Structure

Individual Decisions: Building a survey

Survey

  • The simplest experiment is a survey experiment

  • We ask some questions and collect the answers

  • Usually we only need only Player and Constants classes

Let create a survey

  • We would like to ask following questions:
    • Name (name)
    • Age (age)
    • Continent you live in (continent)
    • Enjoyment level (1-5) (enjoyment)
    • Further Comments (comments)

Things to do to create a survey (very briefly)

(Assume we created the project and the app)

  1. Create data structure (models in __init__.py)
  2. Create page classes (pages in __init__.py)
  3. Create HTML *templates for each page (in HTML files)
  4. Add the pages to page_sequence (in __init__.py)

Things to do to create a survey (more detailed)

  • Step 1 - Implement your data structure by adding fields to Player model (in __init__.py in app folder)
    • Every question you ask should be a field (if not more than one)
  • Step 2 - Create Page classes (in __init__.py in app folder)
    • Each page is represented by a class
    • This class manages the page logic, and calculations
    • Define which field you want to ask in a form in each page
  • Step 3 - Create HTML templates for each page (in HTML files in app folder)
    • These templates are rendered by the page classes
  • Step 4 - Add the pages to page_sequence (in __init__.py in app folder)
    • This is the order of the pages in the experiment

Creating Fields in our models

Variable Field Type
name StringField
age IntegerField
continent StringField
enyjoyment IntegerField
comments LongStringField
  • Each field will be defined under Player class

Fields

  • Fields create the data structure to record the data. They can be thought as “columns” in a spreadsheet.
  • They are placed in Player or Group classes in __init__.py
  • You can create a field for form input, or to save the data without an explicit input

Fields (cont’d)

Field Name What for? Example
StringField Short text, Categories department = Models.StingField()
IntegerField Integer (whole numbers) age = Models.IntegerField()
FloatField Decimals percentage = Models.FloatField()
BooleanField True or False is_dictator = Models.BooleanField()
CurrencyField Numbers in currency format earned_stage1 = Models.CurrencyField()
LongStringField Long test diary_entry = Models.LongStringField()

Some field options

  • label: The label of the field
name = models.StringField(label = “What is your name”)
  • min and max: The minimum and maximum values for the field
age = models.IntegerField(min = 18, max = 100)
  • blank: Empty field allowed or not
comments = models.LongStringField(blank = True)
  • initial: Default value for the field
age = models.IntegerField(default = 18)
  • choices: Multiple choices for the field
mood = models.StringField(choices = ['happy', 'sad', 'neutral'])

Creating Pages

Page Description
Survey Survey questions
Results Feedback. Thanks etc
  • Each page will have a
    • Class in __init__.py
    • They should be added to page_sequence list
    • Will have a html template in the same folder

Creating Templates

  • We will modify the default template
{{ block title }}
    TITLE HERE
{{ endblock }}

{{ block content }}
CONTENT HERE
    {{ formfields }}   <--- This generates input fields automatically

{{ next_button }}      <--- This creates a next button
{{ endblock }}

Template Syntax

  • Templates use HTML codes, and oTree template items (indicated by {{ }}).

  • Indentation is not important in templates.

  • oTree has two default blocks:{{ block title }}, and {{ block content }}

  • You can reach variables of the player with {{ player.variablename }}

  • You can have conditional content by {{ if CONDITION }}

Conditional Content in Templates

  {{ if CONDITION}}
  ...
  {{ endif }}



{{ if CONDITION}}
...
{{ else }}
...
{{ endif }}

Conditional Content in Templates: Example

{{ if player.age < 18 }}
  You are too young to participate
{{ else }}
  You are old enough to participate
{{ endif }}

Individual Decisions: Building an interactive experiment

Risky Choice

  • Now we will build a simple interactive experiment.

  • We will ask participants two options:

    • Take the safe option (and get 5 points for sure)
    • Take the risky option (and get 10 points with 50% chance, and 0 points with 50% chance)

Pages

Page Description
Choice Choice between risky and safe options
Results Feedback. Thanks etc

Data Structure

Variable Description
wants_risky Whether the player wants the risky option or not
is_lucky If the player won the lottery or not

Data Structure (cont’d)

Variable Place Type
wants_risky Player class BooleanField()
is_lucky Player class BooleanField
RISKY_PAYOFF C class (Constants) integer
SAFE_PAYOFF C class (Constants) integer

Implementation

Let’s implement those at first. We will deal later with the payoffs.

Setting payoffs

  • It is common to define a function to set payoffs.
  • We can call it whatever we want but set_payoffs is a common name.
  • This function will be written in the top level (as opposed to a method of a class)
  • It should take player object as input and set the payoffs of the player.
  • We should call this function at some point.

Modifying player fields

  • We can modify the fields of the player object by using player.fieldname
  • We should make sure we give the right type of input to the field
  • oTree already has a built in attribute player.payoff which we can use to set the payoff of the player
  • It is a currency field, but we can also give it an integer value (it will be converted to currency automatically)

Setting payoffs

def set_payoffs(player):
    if player.wants_risky_choice:
        player.is_lucky = random.choice([True, False])

        if player.is_lucky:
            player.payoff = C.RISKY_PAYOFF
        else:
            player.payoff = 0
    else:
        player.payoff = C.SAFE_PAYOFF

Triggering the payoff function

  • We need to call the set_payoffs function at some point.

  • One option is to call it at the end of the Choice page.

  • We can do this by using the before_next_page() function (method).

  • This can defined in the Choice class. If it is defined, it will be called before the next page is loaded.

  • It takes two arguments: player and timeout_happened

class Choice(Page):
    form_model = 'player'
    form_fields = ['wants_risky_choice']

    def before_next_page(player, timeout_happened):
        do_something()

Finishing the template Results.html

Remember that we can conditionally show participants different content.

{{ if CONDITION}}
...
{{ else }}
...
{{ endif }}

Conditionally showing content

{{ if player.wants_risky_choice }}
You chose the risky option
{{ else }}
  You chose the safe option
{{ endif }}