Experimental Economics: Data Workflow
Lecture 3: Fundamental components of an experiment
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
- The payoffs are computed based on the choices of the participant
- 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
- Individual experiments: each participant plays the game alone
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
we investigate individuals’ risk attituted
Multiple Price List (MPL) questionnaire (Holt and Laury 2002)
- Trade-off between lotteries
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):
= 'MPL'
name_in_url = None
players_per_group = 1
num_rounds # these are the lottery payoffs, f1 and f2 refer to lottery A and f3 and f4 to lottery B
= 2.00
A_h = 1.60
A_l = 3.85
B_h = 0.10
B_l
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
- MPL table
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
= models.CharField(
HL_1 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_2 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_3 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_4 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_5 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_6 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_7 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_8 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_9 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)= models.CharField(
HL_10 =['A', 'B'],
choices=widgets.RadioSelectHorizontal
widget
)
# This is needed for the instructions
= models.PositiveIntegerField(
HL =True,
blank=['A','B'],
choices=widgets.RadioSelectHorizontal)
widget
# These variables are collected in the final questionnaire
= models.StringField(
sex =widgets.RadioSelectHorizontal(),
widget=['Male', 'Female']
choices
)
= models.IntegerField(
age = range(18,60,1)
choices
)
= models.TextField(
comment ="Your comment here:"
label
)
= models.IntegerField(
like =[1,2,3,4,5],
choices=widgets.RadioSelectHorizontal
widget )
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
# *******************************************
= random.randint(1, 10)
player.row # select one row randomly for payment (from module random)
= random.randint(1, 10)
player.drawn # 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)
= choices[player.row - 1]
player.choice # 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
= Constants.A_h
player.payoff # because HL_row is the same as p in the MPL
else:
# if the choice was B
= Constants.B_h
player.payoff else:
# if the random number is larger than the random row
if player.choice == "A": # A
# if the choice was A
= Constants.A_l
player.payoff# because HL_row is the same as p in the MPL
else:
= Constants.B_l
player.payoff# 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()
- Important to “declare” the variables to display with
class Instructions(Page):
= 'player'
form_model = ['HL'] # the demo MPL
form_fields
class PageHL(Page):
# which forms are needed from class player
= 'player'
form_model = [
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
- See method set_payoff_HL() from models.py
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
# see in models in Player class set_payoff_HL(player)
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
- declare them with vars_for_template()
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>
[type=radio] {
inputtransform: 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">
</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
- In the code provided below we randomize
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
- Unconditionally the same for all participants
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):
= 'groups_roles'
name_in_url = 2
players_per_group = 10
num_rounds = "Constant Type and Constant Matching (CT/CM)"
matching
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:
type = 'BLUE'
p.= cu(10) # assign the corresponding value
p.value else:
type = 'GREEN'
p.= cu(0)# assign the corresponding value
p.value else:
1)
subsession.group_like_round(for g in subsession.get_groups():
for p in g.get_players():
type = p.in_round(subsession.round_number-1).type
p.= p.in_round(subsession.round_number-1).value
p.value
class Group(BaseGroup):
pass
# needed to store values
class Player(BasePlayer):
type = models.StringField()
= models.IntegerField()
id_oth = models.StringField()
type_oth = models.CurrencyField()
value = models.CurrencyField() value_oth
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
= 'groups_roles'
name_in_url # label that appears in browser
= 2
players_per_group # how many players in each group (important for matching!)
= 10
num_rounds # how many repetitions (important for matching!)
= "Constant Type and Constant Matching (CT/CM)"
matching # 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)
type = 'BLUE'
p.# the participant is assigned to type "BLUE"
# type is "initialized" in class player as a string
= cu(10)
p.value # the blues are assigned an endowment of 10 points
# value is "initialized" in class player as currency
else:
# if the participant id is odd
vars['type'] = 'GREEN'
p.participant.# the participant is assigned to type "GREEN"
= cu(0)
p.value # 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)
1)
subsession.group_like_round(# perform matching like in round 1 (partner matching)
for g in subsession.get_groups():
for p in g.get_players():
type = p.in_round(subsession.round_number-1).type
p.= p.in_round(subsession.round_number-1).value
p.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)
= models.CurrencyField() # this is a currency variable that will be filled with player's endowment (see above)
value
class Group(BaseGroup):
pass
8 Varying type and constant matching (VT/CM)
8.1 VT/CM
class Constants(BaseConstants):
= 'groups_roles'
name_in_url = 2
players_per_group = 10
num_rounds = " Varying Type and Constant Matching (VT/CM)"
matching
import random
class Subsession(BaseSubsession):
pass
def creating_session(subsession):
if subsession.round_number == 1: # this way we get a fixed role across repetitions
# built-in function
subsession.group_randomly()print(subsession.get_group_matrix())
=random.randint(0, 1)
rdmprint(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:
type = 'BLUE'
p.= cu(10)# assign the corresponding value
p.value else:
type = 'GREEN'
p.= cu(0) # assign the corresponding value
p.value else:
if p.id_in_group % 2 == 0:
type = 'GREEN'
p.= cu(0)
p.value else:
type = 'BLUE'
p.= cu(10)
p.value else:
1)
subsession.group_like_round(=random.randint(0, 1)
rdmprint(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:
type = 'BLUE'
p.= cu(10)
p.value else:
type = 'GREEN'
p.= cu(0)
p.value else:
if p.id_in_group % 2 == 0:
type = 'GREEN'
p.= cu(0)
p.value else:
type = 'BLUE'
p.= cu(10)
p.value 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)
= models.CurrencyField() # this is a currency variable that will be filled with player's endowment (see above)
value
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
= 'groups_roles'
name_in_url # label that appears in browser
= 2
players_per_group # how many players in each group (important for matching!)
= 10
num_rounds # how many repetitions (important for matching!)
= " Varying Type and Constant Matching (VT/CM)"
matching # 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
# built-in function
subsession.group_randomly()# group_randomly() -> built-in function that shuffles players randomly
=random.randint(0, 1)
rdm# 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)
type = 'BLUE'
p.# the participant is assigned to type "BLUE"
= c(10)# assign the corresponding value
p.value # the participant is assigned the corresponding endowment
else:
# if the participant id is odd
type = 'GREEN'
p.# the participant is assigned to type "GREEN"
= c(0)# assign the corresponding value
p.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
type = 'GREEN'
p.# see comment above
= c(0)# assign the corresponding value
p.value # the participant is assigned the corresponding endowment
else:
type = 'BLUE'
p.# see comment above
= c(10)# assign the corresponding value
p.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)
1)
subsession.group_like_round(# perform matching like in round 1 (partner matching)
=random.randint(0, 1)
rdm# 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:
type = 'BLUE'
p.else:
type = 'GREEN'
p.else:
if p.id_in_group % 2 == 0:
type = 'GREEN'
p.else:
type = 'BLUE'
p.#***************************************************************************
# END code to generate matching and types
#***************************************************************************
class Group(BaseGroup):
pass
# needed to store values
class Player(BasePlayer):
type = models.StringField()
= models.CurrencyField() value
9 Constant Type and Varying Matching (CT/VM)
9.1 CT/VM
class Constants(BaseConstants):
= 'groups_roles'
name_in_url = 2
players_per_group = 10
num_rounds = "Constant Type and Varying Matching (CT/VM)"
matching
class Subsession(BaseSubsession):
pass
def creating_session(subsession):
=True)# built-in function
subsession.group_randomly(fixed_id_in_groupprint(subsession.get_group_matrix())
for g in subsession.get_groups():
for p in g.get_players():
if p.id_in_group % 2 == 0:
type = 'BLUE'
p.= cu(10)
p.value else:
type = 'GREEN'
p.= cu(0)
p.value
#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':
= cu(10)
p.value else:
= cu(0)
p.value
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
= 'groups_roles'
name_in_url # label that appears in browser
= 2
players_per_group # how many players in each group (important for matching!)
= 10
num_rounds # how many repetitions (important for matching!)
= "Varying Type and Varying Matching (VT/VM)"
matching # 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
=True)
subsession.group_randomly(fixed_id_in_group#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)
type = 'BLUE'
p.# the participant is assigned to type "BLUE"
= c(10)
p.value # the corresponding endowment
else:
# if the participant id is odd
type = 'GREEN'
p.# the participant is assigned to type "GREEN"
= c(0)
p.value # the corresponding endowment
class Player(BasePlayer):
type = models.StringField()
= models.CurrencyField()
value
class Group(BaseGroup):
pass
10 Varying Type and Varying Matching (VT/VM)
10.1 VT/VM
class Constants(BaseConstants):
= 'groups_roles'
name_in_url = 2
players_per_group = 10
num_rounds = "Varying Type and Varying Matching (VT/VM)"
matching
class Subsession(BaseSubsession):
pass
def creating_session(subsession):
# built-in function
subsession.group_randomly() = random.randint(0, 1)
rdm 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:
type = 'BLUE'
p.= cu(10)
p.value else:
type = 'GREEN'
p.= cu(0)
p.value else:
if p.id_in_group % 2 == 0:
type = 'GREEN'
p.= cu(0)
p.value else:
type = 'BLUE'
p.= cu(10)
p.value print(subsession.get_group_matrix())
class Player(BasePlayer):
type = models.StringField()
= models.CurrencyField()
value
class Group(BaseGroup):
pass
…
10.2 VT/VM: commented code
class Constants(BaseConstants):
# define here the constants of the session
= 'groups_roles'
name_in_url # label that appears in browser
= 2
players_per_group # how many players in each group (important for matching!)
= 2
num_rounds # how many repetitions (important for matching!)
= "Varying Type and Varying Matching (VT/VM)"
matching # 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
=random.randint(0, 1)
rdm# 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)
type = 'BLUE'
p.# the participant is assigned to type "BLUE"
= c(10)
p.value else:
# if the participant id is odd
type = 'GREEN'
p.# the participant is assigned to type "GREEN"
= c(0)
p.value #***************************************************************************
# 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
type = 'GREEN'
p.# see comment above
= c(0)
p.value else:
# see comment above
type = 'BLUE'
p.# see comment above
= c(10)
p.value #***************************************************************************
# END code to generate matching and types
#***************************************************************************
class Group(BaseGroup):
pass
class Player(BasePlayer):
type = models.StringField()
= models.CurrencyField()
value # 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
- But, contributions are efficient
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
- \(\Pi_i=E-c_i+\alpha \sum_j^N c_j\)
- The individual payoff function is
- 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):
= 'PGG'
name_in_url = 3
players_per_group = 3
num_rounds = cu(100)
endowment = 2 multiplier
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:
1) subsession.group_like_round(
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):
= models.CurrencyField() # for VC is contributions , for CP is withdrawals
total_choices = models.CurrencyField() #the share from public project
individual_share = models.CurrencyField() # share from public project + private account
total_earnings
def set_payoffs(group: Group):
# 1) compute earnings from the public project
= group.get_players()
players # retrieve players in the subsession
= [p.choice for p in players]
choices # store their choices in a list. For VC they are contributions
= sum(choices)
group.total_choices # sum the total choices (gives the size of the public project)
= group.total_choices * Constants.multiplier
group.total_earnings # size of the public project
= (group.total_earnings / Constants.players_per_group)
group.individual_share # the individual share from the public project
for p in players:
= Constants.endowment-p.choice + group.individual_share #
p.payoff # 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:
= p.in_all_rounds() #retrieve previous choices
hist = sum([g.payoff for g in hist]) p.payoff_final
12.6 __init.py__
(models): Player’s variables
- Variables for players
[...]
class Player(BasePlayer):
= models.CurrencyField(min=0,max=Constants.endowment)#
choice # for CP is contributions
=models.CharField()
treatment# copy the treatment, mainly for data analysis
=models.CurrencyField()
payoff_final# 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!
- 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
- Results(Page)
- FinalResults(Page)
- Displayed only in last round
- Instructions(Page)
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):
= 'player'
form_model = ['choice']
form_fields # 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.
= 'set_payoffs'
after_all_players_arrive # 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):
= player.in_previous_rounds()
hist # for the dynamic history graph
= [g.group.total_choices for g in hist]
history_contrib = safe_json(history_contrib)
history_contrib # history data
= [
data_hist for g in hist],
[g.choice for g in hist],
[g.group.individual_share for g in hist]
[g.payoff
]# organize them for printing in a list
= []
table_hist for j in range(0, (player.round_number-1)): # start from
= []
t +1)
t.append(jfor i in data_hist:
t.append(i[j])
table_hist.append(t)
print(table_hist)
= Constants.endowment - player.choice
amount_kept
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>
</li>
The contributions of all {{ Constants.players_per_group }} participants are added up.<li>
<i>total returns from the project</i>.
The sum of contributions is multiplied by a factor of {{ Constants.multiplier }}: these are the </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">
</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">
</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">
</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">
</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
- The choice should look like
- Add a third option in each row: “Indifferent”
- Easy
- Allow people to “switch” only once from A to B
- Avoid mistakes
- Allow people to “switch” only once from A to B
14.2 Assignment 2
Use app groups_roles
- 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
- All types get 10 Euros
- 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
Easy
- One in the group randomly gets an endowment of 80; the others of 100
- Quite easy
Instead of cumulative payment, pay only one round randomly
- 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.
- where \(E=100\) is the maximum each player can take and N is the number of players in the groups
- i.e., participants need to decide in each round how much to take from the public project of size \(E \times N\)
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 withfor
- 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">
A<input type="radio" id="HL_{{forloop.counter}}" name="HL_{{forloop.counter}}" value=1>
</td>
<td style="text-align:center">
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):
= 'player'
form_model = ['HL_1','HL_2','HL_3','HL_4','HL_5','HL_6','HL_7','HL_8','HL_9','HL_10'] # all 10 options
form_fields
def vars_for_template(self):
= []
Lotteries for i in range(1,11):
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)])
Lotteries.append([i,return{
'Lott': Lotteries
}