Experimental Economics: Data Workflow

Lecture 3: Fundamental components of an experiment

Author

Matteo Ploner

Published

April 11, 2023

1 Types of experiments

1.1 Individual and group experiments

  • We can identify two main types of experiments:
    • Individual experiments: each participant plays the game alone
      • The payoffs are computed based on the choices of the participant
        • Surveys are a special case of individual experiments without payoffs associated to actions
    • Group experiments: each participant plays the game with other participants
      • The payoffs are computed based on the choices of the participant and the choices of the other participants

1.2 Individual experiments

  • Each player chooses independently
    • Payoffs are defined by parameters and by her own choice
  • Each choice is statistically independent from the others

1.3 Group experiments

  • Each player chooses independently
    • Payoffs are defined by parameters and by her own choice and the choices of the other players

2 Individual experiments

2.1 Multiple Price List

  1. we investigate individuals’ risk attituted

2.2 Risk attitudes

  • A Multiple Price List format
  • For each pair of options the participants they should choose A or B
    • Option A is safer than corresponding Option B
    • The attractiveness of Option B in terms of relative expected payoffs increases when scrolling down to the bottom of the table
  • A risk-neutral decision maker should switch from A to B at \(5^{th}\) choice
    • Choosing A at \(10^{th}\) choice is dominated for all preference types
  • One row is randomly selected and outcomes paid

2.3 Screens

3 Code

3.1 __init__.py (models)

  • Import module random
    • Needed for payoff computation
  • Define session constants
    • Outcomes of lotteries in MPL
  • Group and Subsession classes are empty
    • Typical of one-shot individual decision making
  • Grouping is not defined

import random

class Constants(BaseConstants):
    name_in_url = 'MPL'
    players_per_group = None
    num_rounds = 1
    # these are the lottery payoffs, f1 and f2 refer to lottery A and f3 and f4 to lottery B
    A_h = 2.00
    A_l = 1.60
    B_h = 3.85
    B_l = 0.10

class Group(BaseGroup):
    pass

class Subsession(BaseSubsession):
    pass

3.2 __init__.py (models) (ii)

  • In Player class we define the “templates” for data collection
    • MPL table
      • One variable for each row
class Player(BasePlayer):

    # This is for main choices, each variable is one row in the choice table MPL
   # This is for main choices, each variable is one row in the choice table MPL
    HL_1 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_2 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_3 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_4 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_5 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_6 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_7 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_8 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_9 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )
    HL_10 = models.CharField(
        choices=['A', 'B'], 
        widget=widgets.RadioSelectHorizontal
    )

    # This is needed for the instructions
    HL = models.PositiveIntegerField(
      blank=True,
      choices=['A','B'],
      widget=widgets.RadioSelectHorizontal)

    # These variables are collected in the final questionnaire
    sex = models.StringField(
    widget=widgets.RadioSelectHorizontal(),
    choices=['Male', 'Female']
    )

    age = models.IntegerField(
      choices = range(18,60,1)
      )

    comment = models.TextField(
      label="Your comment here:"
      )

    like = models.IntegerField(
      choices=[1,2,3,4,5],
      widget=widgets.RadioSelectHorizontal
      )

3.3 __init.py__ (models) (iii)

  • In Player class we compute the payoffs
    • Typical of individual decision making
def set_payoff_HL(player: Player):
    # *******************************************
    # select random row and random outcome
    # *******************************************
    player.row = random.randint(1, 10)
    # select one row randomly for payment (from module random)
    player.drawn = random.randint(1, 10)
    # select the number x that defines the outcome of the lottery (if x<=p, outcome is left A_h or B_h, otherwise A_l or A_h)
    # *******************************************
    # select choices in correspondence to random row
    # *******************************************
    choices = [
    player.HL_1,
    player.HL_2,
    player.HL_3,
    player.HL_4,
    player.HL_5,
    player.HL_6,
    player.HL_7,
    player.HL_8,
    player.HL_9,
    player.HL_10
    ]
    # create a list with all choices of the player (see self)
    player.choice = choices[player.row - 1]
    # select from the list the choice in correspondence to the randomly drawn row (notice the offset)
    # *******************************************
    # Compute here the payoffs
    # *******************************************
    if player.drawn <= player.row:
        # if the random number is smaller equal than the random row
        if player.choice == "A":  
            # if the choice was A
            player.payoff = Constants.A_h
            # because HL_row is the same as p in the MPL
        else:
            # if the choice was B
            player.payoff = Constants.B_h
    else:
        # if the random number is larger than the random row
        if player.choice == "A":  # A
            # if the choice was A
            player.payoff= Constants.A_l
            # because HL_row is the same as p in the MPL
        else:
            player.payoff= Constants.B_l
    # write the payoff to player.payoff

3.4 __init.py__ (pages)

  • In the instructions we have a simulation of choice protocol
    • HL (see models)
  • In the main choice page we need to import a form for each row of the MPL table
  • We also need the outcomes, retrieved from constants
    • Important to “declare” the variables to display with vars_for_template()
class Instructions(Page):
    form_model = 'player'
    form_fields = ['HL'] # the demo MPL

class PageHL(Page):
# which forms are needed from class player
    form_model = 'player'
    form_fields = [
      'HL_1',
      'HL_2',
      'HL_3',
      'HL_4',
      'HL_5',
      'HL_6',
      'HL_7',
      'HL_8',
      'HL_9',
      'HL_10'
      ] # all 10 options

    # values that are to be displayed (dictionary)
    @staticmethod
    def vars_for_template(player: Player):
        # retrieve values from constants and store them in a dictionary
        return {
          'A_h': Constants.A_h, 
          'A_l': Constants.A_l, 
          'B_h': Constants.B_h, 
          'B_l': Constants.B_l
        }

3.5 __init.py__ (pages) (ii)

  • Before moving to the next page, we compute payoffs
    • See method set_payoff_HL() from models.py
      • This way we compute payoffs only once and not when browser is refreshed
class PageHL(Page):

"""
[...]
"""
    # before moving to next page, compute payoffs (avoids that with refreshing payoffs are recomputed again)
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        # built-in method
        set_payoff_HL(player)  # see in models in Player class

3.6 __init.py__ (pages) (iii)

  • Display outcomes
    • declare them with vars_for_template()
      • retrieve values from participant.vars and “store” them in a dictionary
class OutcomeHL(Page):
    # values needed to inform subjects about the actual outcome
    @staticmethod
    def vars_for_template(player: Player):
        # retrieve values from participant.vars and store them in a dictionary
        return {
            'row': player.row,  # randomly chosen row
            'value': player.drawn,  # randomly chosen value to define outcome
            'choice': player.choice,  # actual choice
            'p_A_1': player.row,
            'p_A_2': 10 - player.row,
            'p_B_1': player.row,
            'p_B_2': 10 - player.row
        }

3.7 __init.py__ (pages) (iv)

  • Manage the sequence of pages

# the coreography of pages
page_sequence = [
                    Instructions,
                    PageHL,
                    OutcomeHL
]

4 Templates

4.1 Instructions.html

  • Style elements
    • size of radio buttons

{{ extends "global/Page.html" }}
{{ load staticfiles otree }}

{{ block styles }}
   <style>
      input[type=radio] {
        transform: scale(1.1);
        margin: 12px -10px 0px -30px;
      }
   </style>
{{ endblock }}

4.2 Instructions.html (ii)

  • Main body and demo of MPL
    • In a bs container

{{ block content }}
<h1>Instructions</h1>

<div class="container border" style="font-size:12pt">
  <h2> Part 1 </h2>

      <p>In the first part of the experiment you are going to choose between couples of lotteries. </p>

      <p>The following is an example of the decision setting you are facing</p>
      <table class="table">
        <thead>
          <tr>
            <th scope="col">#</th>
            <th scope="col" colspan="2" style="text-align:center">A</th>
            <th scope="col" ></th>
            <th scope="col" colspan="2"  style="text-align:center">B</th>
            <th scope="col"></th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th scope="row">1</th>
            <td > p/10 of €A<sub>1</sub>  </td>
            <td> (1-p)/10 of €A<sub>2</sub></td>
            <td style="font-weight: bold">{{ form.HL }}</td>
            <td>p/10 of €B<sub>1</sub></td>
            <td >(1-p)/10 of €B<sub>2</sub> </td>
          </tr>
        </tbody>
        </table>

<p>You must choose between lottery A and lottery B, with lottery A delivering €A<sub>1</sub> with probability p/10 and €A<sub>2</sub> with probability (1-p)/10.</p>
<p>Similarly, lottery B delivers €B<sub>1</sub> with probability p/10 and €B<sub>2</sub> with probability (1-p)/10.</p> <p>You will face 10 choices between A and B, with p changing across choices.</p>
<p>All your earnings are virtual, no cash is going to be paid to you. However, choose as if the monetary stakes were real.</p>

</div>

4.3 Instructions.html

  • The button to leave the page
    • Put it to the right with a container

"""
[...]
"""

<div class="container" style="font-size:18pt">
  <div class="row">
    &nbsp;
  </div>
  <div class="row" style="padding-left:135px;">
    <div class="col-md-10">

    </div>
    <div class="col-md-2">
      <button name="btn_submit" value="True" class="btn btn-primary btn-large">
         <span style="font-size:14pt">Continue</span>
     </button>
    </div>
  </div>
</div>

{{ endblock }}

4.4 PageHL.html

  • Choices are collected in a table

<table class="table">
<thead>
  <tr>
    <th scope="col">#</th>
    <th scope="col" colspan="2" style="text-align:center">A</th>
    <th scope="col" ></th>
    <th scope="col" colspan="2"  style="text-align:center">B</th>
    <th scope="col"></th>
  </tr>
</thead>
<tbody>
<!-- Row 1 -->
  <tr>
    <th scope="row">1</th>
    <td > 1/10 of €{{A_h}}  </td>
    <td> 9/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_1 }}</td>
    <td>1/10 of €{{B_h}}</td>
    <td >9/10 of €{{B_l}} </td>
  </tr>
<!-- Row 2 -->
  <tr>
    <th scope="row">2</th>
    <td>2/10 of €{{A_h}} </td>
    <td> 8/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_2 }}</td>
    <td>2/10 of €{{B_h}}</td>
    <td>8/10 of €{{B_l}} </td>
  </tr>
<!-- Row 3 -->
  <tr>
    <th scope="row">3</th>
    <td>3/10 of €{{A_h}} </td>
    <td> 7/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_3 }}</td>
    <td>3/10 of €{{B_h}}</td>
    <td>7/10 of €{{B_l}} </td>
  </tr>
<!-- Row 4 -->
  <tr>
    <th scope="row">4</th>
    <td>4/10 of €{{A_h}} </td>
    <td> 6/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_4 }}</td>
    <td>4/10 of €{{B_h}}</td>
    <td>6/10 of €{{B_l}} </td>
  </tr>
<!-- Row 5 -->
  <tr>
    <th scope="row">5</th>
    <td>5/10 of €{{A_h}} </td>
    <td> 5/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_5 }}</td>
    <td>5/10 of €{{B_h}}</td>
    <td>5/10 of €{{B_l}} </td>
  </tr>
<!-- Row 6 -->
  <tr>
    <th scope="row">6</th>
    <td>6/10 of €{{A_h}} </td>
    <td> 4/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_6 }}</td>
    <td>6/10 of €{{B_h}}</td>
    <td>4/10 of €{{B_l}} </td>
  </tr>
<!-- Row 7 -->
  <tr>
    <th scope="row">7</th>
    <td>7/10 of €{{A_h}} </td>
    <td> 3/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_7 }}</td>
    <td>7/10 of €{{B_h}}</td>
    <td>3/10 of €{{B_l}} </td>
  </tr>
<!-- Row 8 -->
  <tr>
    <th scope="row">8</th>
    <td>8/10 of €{{A_h}} </td>
    <td> 2/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_8 }}</td>
    <td>8/10 of €{{B_h}}</td>
    <td>2/10 of €{{B_l}} </td>
  </tr>
<!-- Row 9 -->
  <tr>
    <th scope="row">9</th>
    <td>9/10 of €{{A_h}} </td>
    <td> 1/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_9 }}</td>
    <td>9/10 of €{{B_h}}</td>
    <td>1/10 of €{{B_l}} </td>
  </tr>
<!-- Row 10 -->
  <tr>
    <th scope="row">10</th>
    <td>10/10 of €{{A_h}} </td>
    <td> 0/10 of €{{A_l}}</td>
    <td style="font-weight: bold">{{ form.HL_10 }}</td>
    <td>10/10 of €{{B_h}}</td>
    <td>0/10 of €{{B_l}} </td>
  </tr>

</tbody>
</table>

4.5 OutcomeHL.html


<h1> Results </h1>

<div class="container border" style="font-size:14pt">
        <p> <b>Row #{{row}}</b> was randomly selected for payment.</p>

        <table class="table">
            <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col" colspan="2" style="text-align:center">A</th>
                    <th scope="col" ></th>
                    <th scope="col" colspan="2"  style="text-align:center">B</th>
                    <th scope="col"></th>
                </tr>
            </thead>
        <tr>
            <th scope="row">{{row}}</th>
            <td>{{p_A_1}}/10 of €{{Constants.A_h}} </td>
            <td> {{p_A_2}}/10 of €{{Constants.A_l}}</td>
        <td></td>
            <td>{{p_B_1}}/10 of €{{Constants.B_h}}</td>
            <td>{{p_B_2}}/10 of €{{Constants.B_l}} </td>
        </tr>

        </table>
<!-- The lottery chosen -->
        <p>You chose Lottery <b>{{choice}}</b></p>

<p> The randomly drawn value that defines the outcome of the lottery is {{value}}.</p>

<p> Thus, you earn <b>{{player.payoff}}</b></p>
</div>
  • The randomly chosen row is displayed {row}
  • They learn about their payoff

5 Group experiments

5.1 Matching, roles, values

  • How many repetitions of an interaction are implemented?
    • One shot
    • Repeated
  • When repeated, how are people matched?
    • Partner matching
    • Random partner matching
  • Which roles do they have in the interaction?
    • Symmetric roles
    • Asymmetric roles
  • Which values are associated to sujects?
    • Unconditional
    • Conditional

5.2 A working example

  • Consider 8 subjects that are matched in groups of two

  • One subject in each group is of type BLUE and the other of type GREEN

5.3 Repeated interaction

  • When the interaction is repeated we can have 4 possible combinations of type ad matching
Matching
Constant Varying

Type
Constant CT/CM CT/VM
Varying VT/CM VT/VM
  • Changes in roles and matching can be explicitly modeled by design or be random
    • In the code provided below we randomize
      • Safe and common approach

5.4 Constant Type and Constant Matching (CT/CM)

  • Round 1

  • Round 2

  • Individuals are matched together for the entire experiment and keep the same role across repetitions

5.5 Varying Type and Constant Matching (VT/CM)

  • Round 1

  • Round 2

  • Individuals are matched together for the entire experiment but do not keep the same role across repetitions

5.6 Constant Type and Varying Matching (CT/VM)

  • Round 1

  • Round 2

  • Individuals are not matched together for the entire experiment but keep the same role across repetitions

5.7 Varying Type and Varying Matching (VT/VM)

  • Round 1

  • Round 2

  • Individuals are not matched together for the entire experiment and do not keep the same role across repetitions

5.8 Values

  • Participants are generally endowed with some values (attributes)
    • Unconditionally the same for all participants
      • e.g., endowment in the dictator game
    • Conditional upon some characteristic of the participant
      • e.g., efficiency factors the are related to previous performance in a task

5.9 Values: example

  • Green Players get an endowment of 0 Euro
  • Blue players get an endowment of 10 Euro

6 oTree code for roles, matching, and value assignment

6.1 __init__.py

  • Roles and matching are governed by the file __init__.py
    • Section models
    • This file manages the “structure” of your experiment
      • The “database”
  • 4 alternative protocols are presented here
    • Constant type and constant matching (CT/CM)
    • Varying type and constant matching (VT/CM)
    • Constant type and varying matching (CT/VM)
    • Varying type and varying matching (VT/VM)

7 Constant type and constant matching (CT/CM)

7.1 CT/CM

  • This is the code in __init__.py

class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = "Constant Type and Constant Matching (CT/CM)"

class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    if subsession.round_number == 1:  # this way we get a fixed role across repetitions
        subsession.group_randomly()
        print(subsession.get_group_matrix())
        for g in subsession.get_groups():
            for p in g.get_players():
                if p.id_in_group % 2 == 0:
                    p.type = 'BLUE'
                    p.value = cu(10) # assign the corresponding value
                else:
                    p.type = 'GREEN'
                    p.value = cu(0)# assign the corresponding value
    else:
        subsession.group_like_round(1)
        for g in subsession.get_groups():
            for p in g.get_players():
                p.type = p.in_round(subsession.round_number-1).type
                p.value = p.in_round(subsession.round_number-1).value

class Group(BaseGroup):
    pass

# needed to store values 

class Player(BasePlayer):
    type = models.StringField()
    id_oth = models.IntegerField()
    type_oth = models.StringField()
    value = models.CurrencyField()
    value_oth = models.CurrencyField()
  • This is the assignment to groups and roles we get (from the POW of Player 1)




7.2 CT/CM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 10
    # how many repetitions (important for matching!)
    matching = "Constant Type and Constant Matching (CT/CM)"
    # name of the matching protocol

class Subsession(BaseSubsession):
    pass

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
#***************************************************************************
# START code to generate matching and roles in Round 1
#***************************************************************************
    if subsession.round_number == 1:
    # the following code is executed only id round is == 1
    # round_number -> is a built-in function that gives the current round number
        subsession.group_randomly()
        # group_randomly() -> built-in function that  shuffles players randomly
        for g in subsession.get_groups():
        # get_groups() -> returns a list of all the groups in the subsession.
        # loop through the groups in the subsession
            for p in g.get_players():
            # get_players() -> returns a list of all the players in the subsession.
            # loop through the players in the subsession (p is a player)
                if p.id_in_group % 2 == 0:
                # id_in_group -> player's attribute (unique identifier)
                # if the id is even (via modulo operator)
                    p.type = 'BLUE'
                    # the participant is assigned to type "BLUE"
                    # type is "initialized" in class player as a string
                    p.value = cu(10)
                    # the blues are assigned an endowment of 10 points
                    # value is "initialized" in class player as currency
                else:
                    # if the participant id is odd
                    p.participant.vars['type'] = 'GREEN'
                    # the participant is assigned to type "GREEN"
                    p.value = cu(0)
                    # the greens are assigned an endowment of 0 points
                    # value is "initialized" in class player as currency

#***************************************************************************
# END code to generate matching and roles in Round 1
#***************************************************************************
#***************************************************************************
# START code to generate matching and types in round >1
#***************************************************************************

        else:
          # if round is not round 1 (see the indenting)
            subsession.group_like_round(1)
            # perform matching like in round 1 (partner matching)
            for g in subsession.get_groups():
                for p in g.get_players():
                    p.type = p.in_round(subsession.round_number-1).type
                    p.value= p.in_round(subsession.round_number-1).value
#***************************************************************************
# END code to generate matching and roles
#***************************************************************************


class Player(BasePlayer):
    type = models.StringField() # this is a string variable that will be filled with player's type (see above)
    value = models.CurrencyField() # this is a currency variable that will be filled with player's endowment (see above)

class Group(BaseGroup):
    pass

8 Varying type and constant matching (VT/CM)

8.1 VT/CM


class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = " Varying Type and Constant Matching (VT/CM)"

import random
class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    if subsession.round_number == 1: # this way we get a fixed role across repetitions
        subsession.group_randomly()# built-in function
        print(subsession.get_group_matrix())
        rdm=random.randint(0, 1)
        print(rdm)
        for g in subsession.get_groups():
            for p in g.get_players():
                if rdm==1: #this way we randomize role accordin to id in group
                    if p.id_in_group % 2 == 0:
                        p.type = 'BLUE'
                        p.value = cu(10)# assign the corresponding value
                    else:
                        p.type = 'GREEN'
                        p.value = cu(0)   # assign the corresponding value
                else:
                    if p.id_in_group % 2 == 0:
                        p.type = 'GREEN'
                        p.value = cu(0)
                    else:
                        p.type = 'BLUE'
                        p.value = cu(10)
    else:
        subsession.group_like_round(1)
        rdm=random.randint(0, 1)
        print(rdm)
        for g in subsession.get_groups():
            for p in g.get_players():
                if rdm==1: #this way we randomize role accordin to id in group
                    if p.id_in_group % 2 == 0:
                        p.type = 'BLUE'
                        p.value = cu(10)
                    else:
                        p.type = 'GREEN'
                        p.value = cu(0)
                else:
                    if p.id_in_group % 2 == 0:
                        p.type = 'GREEN'
                        p.value = cu(0)
                    else:
                        p.type = 'BLUE'
                        p.value = cu(10)
        print(subsession.get_group_matrix())

class Player(BasePlayer):
    type = models.StringField() # this is a string variable that will be filled with player's type (see above)
    value = models.CurrencyField() # this is a currency variable that will be filled with player's endowment (see above)

class Group(BaseGroup):
    pass
  • This is the assignment to groups and roles we get (from the POW of Player 1)





8.2 VT/CM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 10
    # how many repetitions (important for matching!)
    matching = " Varying Type and Constant Matching (VT/CM)"
    # matching protocol

import random
# import module random
class Subsession(BaseSubsession):

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
#***************************************************************************
# START code to generate matching and types in round 1
#***************************************************************************
    if subsession.round_number == 1:
    # the following code is executed only id round is == 1
    # round_number -> is a built-in function that gives the current round number
        subsession.group_randomly()# built-in function
        # group_randomly() -> built-in function that  shuffles players randomly
        rdm=random.randint(0, 1)
        # assign a random value, either 0 or 1, to variable rdm
        for g in subsession.get_groups():
        #get_groups() -> returns a list of all the groups in the subsession.
            # loop through the groups in the subsession
            for p in g.get_players():
            # get_players() -> returns a list of all the players in the subsession.
            # loop through the players in the subsession (p is a player)
#***************************************************************************
# matching and types when random is 1 - > even = BLUE, odd= GREEN
#***************************************************************************
                if rdm==1:
                # the following code is executed if rdm is 1
                    if p.id_in_group % 2 == 0:
                    # id_in_group -> player's attribute (unique identifier)
                    # if the id is even (via modulo operator)
                        p.type = 'BLUE'
                        # the participant is assigned to type "BLUE"
                        p.value = c(10)# assign the corresponding value
                        # the participant is assigned the corresponding endowment
                        else:
                        # if the participant id is odd
                        p.type = 'GREEN'
                        # the participant is assigned to type "GREEN"
                        p.value = c(0)# assign the corresponding value
                        # the participant is assigned the corresponding endowment

#***************************************************************************
# matching and types when random is 1 - > even = GREEN, odd= BLUE
#***************************************************************************
                else:
                # the following code is executed if rdm is 0
                        if p.id_in_group % 2 == 0:
                        # see comment above
                            p.type = 'GREEN'
                            # see comment above
                            p.value = c(0)# assign the corresponding value
                        # the participant is assigned the corresponding endowment
                        else:
                            p.type = 'BLUE'
                            # see comment above
                            p.value = c(10)# assign the corresponding value
                        # the participant is assigned the corresponding endowment
#***************************************************************************
# END code to generate matching and types in round 1
#***************************************************************************
#***************************************************************************
# START code to generate matching and types in round 1
#***************************************************************************
    else:
    # if round is not round 1 (see the indenting)
        subsession.group_like_round(1)
        # perform matching like in round 1 (partner matching)
        rdm=random.randint(0, 1)
        # here we run the same code as in round 1 to randomly generate types
        for g in subsession.get_groups():
            for p in g.get_players():
                if rdm==1:
                    if p.id_in_group % 2 == 0:
                        p.type = 'BLUE'
                    else:
                        p.type = 'GREEN'
                else:
                    if p.id_in_group % 2 == 0:
                        p.type = 'GREEN'
                    else:
                        p.type = 'BLUE'
#***************************************************************************
# END code to generate matching and types
#***************************************************************************

class Group(BaseGroup):
    pass

# needed to store values

class Player(BasePlayer):
    type = models.StringField()
    value = models.CurrencyField()

9 Constant Type and Varying Matching (CT/VM)

9.1 CT/VM

class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = "Constant Type and Varying Matching (CT/VM)"

class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    subsession.group_randomly(fixed_id_in_group=True)# built-in function
    print(subsession.get_group_matrix())
    for g in subsession.get_groups():
        for p in g.get_players():
            if p.id_in_group % 2 == 0:
                p.type = 'BLUE'
                p.value = cu(10)
            else:
                p.type = 'GREEN'
                p.value = cu(0)

#To assign values in each round, the blue get 10 and the green get 0
    for p in subsession.get_players():
        if p.type == 'BLUE':
            p.value = cu(10)
        else:
            p.value = cu(0)

class Player(BasePlayer):
    type = models.StringField()

class Group(BaseGroup):
    pass
  • This is the assignment to groups and roles we get (from the POW of Player 1)





9.2 CT/VM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 10
    # how many repetitions (important for matching!)
    matching = "Varying Type and Varying Matching (VT/VM)"
    # name of matching protocol

class Subsession(BaseSubsession):
    pass

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
    subsession.group_randomly(fixed_id_in_group=True)
    #group_randomly(fixed_id_in_group=True) -> built-in function that  shuffles players randomly but keeps the id constant
    for g in subsession.get_groups():
    # get_groups() -> returns a list of all the groups in the subsession.
    # loop through the groups in the subsession
        for p in g.get_players():
        # get_players() -> returns a list of all the players in the subsession.
        # loop through the players in the subsession (p is a player)
            if p.id_in_group % 2 == 0:
                # id_in_group -> player's attribute (unique identifier)
                # if the id is even (via modulo operator)
                p.type = 'BLUE'
                # the participant is assigned to type "BLUE"
                p.value = c(10)
                # the corresponding endowment
            else:
            # if the participant id is odd
                p.type = 'GREEN'
                # the participant is assigned to type "GREEN"
                p.value = c(0)
                # the corresponding endowment

class Player(BasePlayer):
type = models.StringField()
value = models.CurrencyField()

class Group(BaseGroup):
pass

10 Varying Type and Varying Matching (VT/VM)

10.1 VT/VM

class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = "Varying Type and Varying Matching (VT/VM)"

class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    subsession.group_randomly()  # built-in function
    rdm = random.randint(0, 1)
    print(rdm)
    for g in subsession.get_groups():
        for p in g.get_players():
            if rdm == 1:  # this way we randomize role according to id in group
                if p.id_in_group % 2 == 0:
                    p.type = 'BLUE'
                    p.value = cu(10)
                else:
                    p.type = 'GREEN'
                    p.value = cu(0)
            else:
                if p.id_in_group % 2 == 0:
                    p.type = 'GREEN'
                    p.value = cu(0)
                else:
                    p.type = 'BLUE'
                    p.value = cu(10)
    print(subsession.get_group_matrix())

class Player(BasePlayer):
    type = models.StringField()
    value = models.CurrencyField()

class Group(BaseGroup):
    pass




10.2 VT/VM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 2
    # how many repetitions (important for matching!)
    matching = "Varying Type and Varying Matching (VT/VM)"
    # matching protocol

import random
class Subsession(BaseSubsession):
    pass

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
    subsession.group_randomly()
    #group_randomly() -> built-in function that  shuffles players
    rdm=random.randint(0, 1)
    # assign a random value, either 0 or 1, to variable rdm
    for g in subsession.get_groups():
    #get_groups() -> returns a list of all the groups in the subsession.
    # loop through the groups in the subsession
        for p in g.get_players():
        # get_players() -> returns a list of all the players in the subsession.
        # loop through the players in the subsession (p is a player)
#***************************************************************************
# matching and types when random is 1 - > even = BLUE, odd= GREEN
#***************************************************************************
            if rdm==1:
            # the following code is executed if rdm is 1
                if p.id_in_group % 2 == 0:
                # id_in_group -> player's attribute (unique identifier)
                # if the id is even (via modulo operator)
                    p.type = 'BLUE'
                    # the participant is assigned to type "BLUE"
                    p.value = c(10)
                else:
                # if the participant id is odd
                    p.type = 'GREEN'
                    # the participant is assigned to type "GREEN"
                    p.value = c(0)
#***************************************************************************
# matching and types when random is 1 - > even = GREEN, odd= BLUE
#***************************************************************************
            else:
            # the following code is executed if rdm is 0
                if p.id_in_group % 2 == 0:
                # see comment above
                    p.type = 'GREEN'
                    # see comment above
                    p.value = c(0)
                else:
                # see comment above
                    p.type = 'BLUE'
                    # see comment above
                    p.value = c(10)
#***************************************************************************
# END code to generate matching and types
#***************************************************************************

class Group(BaseGroup):
    pass

class Player(BasePlayer):
    type = models.StringField()
    value = models.CurrencyField()
# needed to store values 

11 A Public Goods Game

11.1 Setting

  • Voluntary Contribution game

  • Participants can decide how much contribute to a “public” project out of their endowment
  • Participants are in a group of N subjects (usually 4)
    • What is contributed is multiplied by an efficiency factor \(1/N < \alpha < 1\)
    • What is not contributed is kept in a private account
  • Private incentives are to contribute nothing to the public account
    • But, contributions are efficient
      • \(\Rightarrow\) social dilemma

11.2 Parameters

  • The interaction is repeated 10x in a partner fashion
    • Total earnings are given by the sum of earnings in each stage
  • Groups of 3
    • Matched together for the entire experiment (partner)
  • The efficiency factor \(\alpha=2/3\)
  • The initial endowment is E=100
    • The individual payoff function is
      • \(\Pi_i=E-c_i+\alpha \sum_j^N c_j\)
        • where, \(j\) are the members of \(i\)’s group (\(i\) include)
        • \(\sum_j^N c_j\) is the size of the public project
  • Choices are in integer steps
    • \(c_i \in \{0, 100\}\)

12 App

12.1 Screens

12.2 __inity__ (models): Constants

  • Set here a few important constants
    • Players per group
    • Number of rounds
    • Individual endowment
    • Multiplier for the public project
      • \(\alpha=\frac{2=multiplier}{3=group~members}\)
class Constants(BaseConstants):
    name_in_url = 'PGG'
    players_per_group = 3
    num_rounds = 3
    endowment = cu(100)
    multiplier = 2

12.3 __init.py__ (models): Matching

  • A function that defines matching with reference to class Subsession
    • No need to define roles in PGG
  • Partner matching
    • Random first round and then as first round
def creating_session(subsession: Subsession):
    if subsession.round_number == 1:  # this way we get a fixed role across repetitions
        subsession.group_randomly()
    else:
        subsession.group_like_round(1)

12.4 __init.py__ (models): Compute payoffs

  • A function that defines matching with reference to class Group
    • Typical of strategic interaction, while in individual decision making they are defined in Player
class Group(BaseGroup):
    total_choices = models.CurrencyField() # for VC is contributions , for CP is withdrawals
    individual_share = models.CurrencyField() #the share from public project
    total_earnings = models.CurrencyField() # share from public project + private account

def set_payoffs(group: Group):
# 1) compute earnings from the public project
    players = group.get_players()
    # retrieve players in the subsession
    choices = [p.choice for p in players]
    # store their choices in a list. For VC they are contributions
    group.total_choices = sum(choices)
    # sum the total choices (gives the size of the public project)
    group.total_earnings = group.total_choices * Constants.multiplier
    # size of the public project
    group.individual_share = (group.total_earnings / Constants.players_per_group)
    # the individual share from the public project
    for p in players:
        p.payoff =  Constants.endowment-p.choice + group.individual_share #
        # compute individual payoffs (private + public account)
        # each player has already a payoff value 

12.5 __init.py__ (models): Compute final payoff

  • Final payoff is the sum of payoffs in all rounds

[...]      

    #compute final payoff (as sum of all payoffs)
    if group.round_number == Constants.num_rounds:
        for p in players:
            hist = p.in_all_rounds() #retrieve previous choices
            p.payoff_final = sum([g.payoff for g in hist])

12.6 __init.py__ (models): Player’s variables

  • Variables for players

[...]      

class Player(BasePlayer):
    choice = models.CurrencyField(min=0,max=Constants.endowment)#
    # for CP is contributions
    treatment=models.CharField()
    # copy the treatment, mainly for data analysis
    payoff_final=models.CurrencyField()
    # to store final payoff

12.7 __init.py__ (pages):

  • We have 5 pages
  • page_sequence = [Instructions, Contribute, ResultsWaitPage, Results, FinalResults]
    • Instructions(Page)
      • Displayed only in round 1
    • Contribute(Page)
    • ResultsWaitPage(WaitPage)
      • WaitPage → oTree waits until all players in the group have arrived at that point in the sequence, and then all players are allowed to proceed
        • Important that we have all data before computing the payoffs!
    • Results(Page)
    • FinalResults(Page)
      • Displayed only in last round

page_sequence = [Instructions, Contribute, ResultsWaitPage, Results, FinalResults]

12.8 Intructions(Page)

class Instructions(Page):
    @staticmethod
    def is_displayed(player: Player):
    # define this method to return True if the page should be show
        return player.round_number == 1
        # show the page only in round 1

    @staticmethod
    def vars_for_template(player: Player):
    # to pass variables to the template
        return{
        'common_proj': Constants.endowment*Constants.players_per_group
        }
        # dictionary with variables needed in the template

12.9 Contribute(Page)

  • Contribute
    • Collect own contribution to the public project
class Contribute(Page):
    form_model = 'player'
    form_fields = ['choice']
    # the player will fill out the field choice and it will be saved in player model (see models)
    @staticmethod
    def vars_for_template(player: Player):
        return {
            'round_number': player.round_number,
            'endowment': Constants.endowment,
        }

12.10 ResultsWaitPage(WaitPage)

  • Need to “gather” all members of the group
    • When all players arrive you apply the set_payoffs method (see above)
  • If you do not use a waitpage you will not be able to get all chocies in a group!
class ResultsWaitPage(WaitPage):
    #If you have a WaitPage in your sequence of pages, then oTree waits until all players in the group have arrived at that point in the sequence, and then all players are allowed to proceed.
    after_all_players_arrive = 'set_payoffs'
    # see method 'set_payoffs' in class Group in models.py
    # payoffs are computed here

12.11 Results(Page)

  • Display results of current round
  • Display history of choices
    • A dynamic table
class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        hist = player.in_previous_rounds()
        # for the dynamic history graph
        history_contrib = [g.group.total_choices for g in hist]
        history_contrib = safe_json(history_contrib)
        # history data
        data_hist = [
            [g.choice for g in hist],
            [g.group.individual_share for g in hist],
            [g.payoff for g in hist]
        ]
        # organize them for printing in a list
        table_hist = []
        for j in range(0, (player.round_number-1)):  # start from 
            t = []
            t.append(j+1)
            for i in data_hist:
                t.append(i[j])
            table_hist.append(t)

        print(table_hist)

        amount_kept = Constants.endowment - player.choice

        return {
            'history_contrib': history_contrib,
            'round_number': player.round_number,
            'kept': amount_kept,
            'table_hist': table_hist # historical values to be printed
        }
  • In Round 3, table_hist will look like this

  • [[1, 44.00cu, 66.00cu, 122.00cu], [2, 0.00cu, 36.67cu, 136.67cu]]

    • [[Round, Own Choice, Share from Group, Total Payoff][…]]

12.12 FinalResults(Page)

  • Final results
    • Cumulative payment

class FinalResults(Page):
    @staticmethod
    # display this only in the last round
    def is_displayed(player: Player):
        return player.round_number == Constants.num_rounds

    @staticmethod
    def vars_for_template(player: Player):
        return {'payoff_final': player.payoff_final}  #
        }

13 Templates

13.1 Instructions

{{ extends "global/Page.html" }}
{{ load staticfiles otree }}

<!-- Title -->
{{ block title }}
Instructions 
{{ endblock }}

<!-- Main body -->

{{ block content }}

<div class="container p-3 my-3 border" style="font-size:12pt">

<h2> The interaction</h2>

<p>In this study, you will be in a randomly formed group of {{ Constants.players_per_group }} participants. </p>
<p>Each participant in the group is given {{ Constants.endowment }}. </p>
<p>Each participant in the group decides how much she or he is going to contribute to a common project.</p>
<p>Contributions could be any integer between 0 to {{ Constants.endowment }}.</p>

<h2> Your payoff </h2>
<p> The earnings from the project are calculated as follows:
<ul>
  <li>
  The contributions of all {{ Constants.players_per_group }} participants are added up.</li>
  <li>
    The sum of contributions is multiplied by a factor of {{ Constants.multiplier }}: these are the <i>total returns from the project</i>.
  </li>
  <li>
    <i>Total returns from the project</i> are then evenly split among all {{ Constants.players_per_group }}  participants: : these are <i>your earnings from the project</i>.
  </li>
</ul>
<p><i>Your payoff</i> is given by your earnings from the project, plus the amount you did not contribute.</p>

</div>


<!-- Continue Button -->

<div class="container" style="font-size:14pt">
  <div class="row">
    &nbsp;
  </div>
  <div class="row" style="padding-left:135px;">
    <div class="col-md-10">

    </div>
    <div class="col-md-2">
      <button name="btn_submit" value="True" class="btn btn-primary btn-large">
         <span style="font-size:14pt">Continue</span>
     </button>
    </div>
  </div>
</div>

{{ endblock }}

13.2 Choices


{{ extends "global/Page.html" }}
{{ load staticfiles otree }}



{{ block title }}
    <h1> Contribute (Round {{ round_number }} of {{Constants.num_rounds}})<h1>
{{ endblock }}

<!-- They are remembered in which round they are and how many left -->

{{ block content }}


<div class="container p-3 my-3 border" style="font-size:12pt">

{{ formfield player.choice label="How much do you contribute to the common project?" }}

<!-- Here we collect the contribution choice -->

<small>An integer number between 0 and {{endowment}}</small>

</div>

<!-- Comtinue button -->

<div class="container" style="font-size:18pt">
  <div class="row">
    &nbsp;
  </div>
  <div class="row" style="padding-left:135px;">
    <div class="col-md-10">

    </div>
    <div class="col-md-2">
      <button name="btn_submit" value="True" class="btn btn-primary btn-large">
         <span style="font-size:14pt">Continue</span>
     </button>
    </div>
  </div>
</div>

13.3 Results: round feedback


{{ extends "global/Page.html" }}
{{ load staticfiles otree }}

{{ block title }}
    Results ( Round {{ round_number }} of {{Constants.num_rounds}})
{{ endblock }}

{{ block content }}

<!-- the results are fully disclose din a table -->
<div class="container p-3 my-3 border" style="font-size:12pt">
    <div class="row">
        <div class="col-sm-5">
    <table class="table-condensed" style="width:500px; margin-top:20px;">
        <tr><td>You contributed:</td><td>{{ player.choice }}</td></tr>
        <tr><td>Other participants contributed:</td><td></td></tr>
        <!-- get the other players in the group and put them in a table -->
         <!-- the variables here a re taken directly from the class they belong to-->
        {{ for p in player.get_others_in_group }}
            <tr><td></td><td>{{ p.choice }}</td></tr>
        {{ endfor }}
        <tr><td>Total contribution:</td><td>{{ group.total_choices }}</td></tr>
                <tr>
                    <td>Total earnings from the project:</td>
                    <td>{{ group.total_earnings }}</td>
                </tr>
        <tr><td colspan="2"><hr/></td></tr>
        <tr><td>Your earnings from the project:</td><td>{{ group.individual_share }}</td></tr>
        <tr>
            <td>You kept:</td>
            <td>{{kept}} </td>
            </tr>
        <tr><td colspan="2"><hr/></td></tr>
        <tr>
            <td>Thus in total you earned:</td>
            <td><span style="font-weight:bold">{{ player.payoff }}</span></td>
        </tr>
    </table>
</div>
</div>

13.4 Results: history


{{ if player.round_number > 1 }}
<div class="container border" style="font-size:12pt">
    <h3>History</h3>
    <table class="table">
        <thead>
            <tr>
                <th scope="col">Round</th>
                <th scope="col">Your contribution</th>
                <th scope="col">Your earnings from the project</th>
                <th scope="col">Your total earnings</th>
            </tr>
        </thead>

        {{ for j in table_hist }}
        <tr>
            {{ for i in j }}
            <td>{{i}} </td>
            {{ endfor }}
        </tr>
        {{ endfor }}

    </table>


<!-- GRAPH -->
<div class="col-sm-12">
    <div id="container" style="width:100%; height:300px;"> </div>
</div>
<!-- END GRAPH -->

</div>
{{ endif }}



<!-- END HISTORY -->

<div class="container" style="font-size:18pt">
  <div class="row">
    &nbsp;
  </div>
  <div class="row" style="padding-left:135px;">
    <div class="col-md-10">
    </div>
    <div class="col-md-2">
      <button name="btn_submit" value="True" class="btn btn-primary btn-large">
         <span style="font-size:14pt">Continue</span>
     </button>
    </div>
  </div>
</div>


{{ endif }}

13.5 Final Results

{ extends "global/Page.html" }}
{{ load staticfiles otree }}

{{ block title }}
Final Results
{{ endblock }}

{{ block content }}

<div class="container p-3 my-3 border" style="font-size:12pt">


Your total earnings in the game are {{payoff_final}}

<div class="container" style="font-size:18pt">
  <div class="row">
    &nbsp;
  </div>
  <div class="row" style="padding-left:135px;">
    <div class="col-md-10">

    </div>
    <div class="col-md-2">
      <button name="btn_submit" value="True" class="btn btn-primary btn-large">
         <span style="font-size:14pt">Continue</span>
     </button>
    </div>
  </div>
</div>


{{ endblock }}

14 Appendix

14.1 Assignment 1

  • Reasonably easy
    • Add a third option in each row: “Indifferent”
      • The choice should look like
  • Easy
    • Allow people to “switch” only once from A to B
      • Avoid mistakes

14.2 Assignment 2

  • Use app groups_roles

    1. Easy
    • Implement a constant type and constant matching (CT/CM) with value assignment as follows
      • All types get 10 Euros
        • Green types get additional 2 Euros
        • Blue types are taken away 2 Euros
    1. Less easy
    • Implement the following matching protocol
      • 8 periods
      • First 4 periods varying type and varying matching (random) (VT/VM)
      • Last 4 periods constant types constant matching (CT/CM)

14.3 Assignment 3

  • Use app PGG

      1. Easy

        • One in the group randomly gets an endowment of 80; the others of 100
    1. Quite easy
    • Instead of cumulative payment, pay only one round randomly

      1. Difficult
      • Implement a Common Pool version of the game
        • i.e., participants need to decide in each round how much to take from the public project of size \(E \times N\)
          • where \(E=100\) is the maximum each player can take and N is the number of players in the groups
            • what is left in the public project after the decision to take is multiplied by 2 and equally distributed among participants.

14.4 oTree code

  • The oTree app of this lecture:

14.4.1 MPL

14.4.2 Group and roles

14.4.3 PGG

14.5 An alternative approach to MPL

  • The PageHL.html template can be written in a more compact form with for

    • Main table

<table class="table">
  <thead>
    <tr>
      <th scope="col" colspan="1">Lottery</th> 
      <th scope="col" colspan="2" style="text-align:center">A</th>
      <th scope="col" colspan="1" style="text-align:center"></th>     
      <th scope="col" colspan="2" style="text-align:center">B</th>
      <th scope="col" colspan="2" style="text-align:center">La mia scelta</th>
    </tr>
  </thead>

  {{ for i in Lott }}
  <tr>
      {{ for j in i }}
        <td style="text-align:center">
        {{ j }}
        </td>
      {{ endfor }}
      <td style="text-align:center">
        &nbsp; &nbsp;  A<input type="radio" id="HL_{{forloop.counter}}" name="HL_{{forloop.counter}}" value=1>
      </td>
      <td style="text-align:center">
       &nbsp;&nbsp;  B <input type="radio" id="HL_{{forloop.counter}}" name="HL_{{forloop.counter}}" value=2>
      </td>
  </tr>
  {{ endfor }}
</table>

14.6 An alternative approach to MPL (ii)

In pages.py create a list of choices - Each choice is a list itself

class PageHL_2(Page):
    form_model = 'player'
    form_fields = ['HL_1','HL_2','HL_3','HL_4','HL_5','HL_6','HL_7','HL_8','HL_9','HL_10'] # all 10 options

    def vars_for_template(self):
        Lotteries = []
        for i in range(1,11):
            Lotteries.append([i,str(i)+"/10 of €"+str(Constants.f1), str(10-i)+"/10 of €"+str(Constants.f2),"", str(i)+"/10 of €"+str(Constants.f3), str(10-i)+"/10 of €"+str(Constants.f4)])
        return{
        'Lott': Lotteries
        }

14.7 References

References

Holt, Charles A, and Susan K Laury. 2002. “Risk Aversion and Incentive Effects.” American Economic Review 92 (5): 1644–55.