The purpose of running an experiment is to record data – what treatments are in your experiment, what games were played in those treatments, what the results were, what actions the participants took, etc.
pTree stores your data in database tables (SQL). For example, let’s say you are programming an ultimatum game, where in each 2-person match, one participant makes a monetary offer (say, 0-100 cents), and another participant either rejects or accepts the offer. You will want your “Match” table to look something like this:
Match ID Amount offered Offer accepted 1 50 TRUE 2 25 FALSE 3 50 TRUE 4 0 FALSE 5 60 TRUE
In order to end up with a table like this this, you need to define a Django model, which is a Python class that defines a database table. You define what fields (columns) are in the table, what their data types are, and so on. When you run your experiment, the SQL tables will get automatically generated, and each time users visit, new rows will get added to the tables.
Here is what the model might look like for the above “Match” table:
class Match(ptree.models.BaseModel):
amount_offered = models.IntegerField()
offer_accepted = models.BooleanField()
This class will be placed in your app’s models.py file.
Every pTree app needs the following 4 models:
They are related to each other as follows:
A Participant is part of a Match, which is part of a Treatment, which is part of an Experiment.
Furthermore, there are usually multiple Participant objects in a Match, multiple Match objects in a Treatment, and multiple Treatment objects in an Experiment, meaning that your objects would look something like this:
pTree models are Django models with some extra fields and capabilities. To be able to define a model, you need to read the Django documentation on models and understand:
In your models.py file, you will find pre-generated model classes (Participant, Match, Treatment, and Experiment) that inherit from pTree’s built-in models (BaseParticipant, BaseMatch, BaseTreatment, and BaseExperiment).
The base models already define many fields and methods that will come in handy for you; they are documented below.
What you need to do in your models is:
A Participant is a person who participates in a Match. For example, a Dictator Game match has 2 Participant objects.
A match can contain only 1 Participant if there is no interaction between Participant objects. For example, a game that is simply a survey.
Participant classes should inherit from ptree.models.participants.BaseParticipant, which gives you the following fields and methods:
The Match this Participant is a part of.
the ordinal position in which a participant joined a game. Starts at 0.
whether the participant is finished playing (i.e. has seen the redemption code page).
A Match is a particular instance of a game being played, and holds the results of that instance, i.e. what the score was, who got paid what.
So, “Match” is used in the sense of “boxing match”, in the sense that it is an event that occurs where the game is played.
Example of a Match: “dictator game between participants Alice & Bob, where Alice gave $0.50”
Match classes should inherit from ptree.models.participants.BaseMatch, which gives you the following fields and methods.
The Treatment this Match is part of.
Returns the Participant objects in this match.
Whether the match is full, i.e.:
return len(self.participants()) >= self.treatment.participants_per_match
Whether the match is completed (i.e. is_finished() is True for each participant).
Whether the game is ready for another participant to be added.
If it’s a non-sequential game (you do not have to wait for one participant to finish before the next one joins), you can use this to assign participants until the game is full:
return not self.is_full()
A Treatment is the definition of what everyone in the treatment group has to do.
Example of a treatment: ‘dictator game with stakes of $1, where participants have to chat with each other first’
A treatment is defined before the experiment starts. Results of a game are not stored in Treatment object, they are stored in Match or Participant objects.
Treatment classes should inherit from ptree.models.participants.BaseTreatment, which gives you the following fields and methods.
The Match objects in this Treatment.
How much each Participant is getting paid to play the game. Needs to be set when you instantiate your Participant objects.
Very important. Returns a list of all the View classes that the participant gets routed through sequentially. (Not all pages have to be displayed for all participants; see the is_displayed() method). Must start with your app’s StartTreatment, and usually ends the Redemption Code view. The rest is up to you.
Inside the method, you should import the modules containing the views you want to use.
Example:
import donation.views as views
import ptree.views.concrete
return [views.StartTreatment,
ptree.views.concrete.AssignParticipantAndMatch,
views.IntroPage,
views.EnterOfferEncrypted,
views.ExplainRandomizationDetails,
views.EnterDecryptionKey,
views.NotifyOfInvalidEncryptedDonation,
views.EnterOfferUnencrypted,
views.NotifyOfShred,
views.Survey,
ptree.views.concrete.RedemptionCode]
Class attribute that specifies the number of participants in each match. For example, Prisoner’s Dilemma has 2 participants. a single-participant game would just have 1.
An experiment is generally a randomization between treatments, though it could just have one treatment.
Most experiments won’t need to access the experiment class, but info is provided here for the sake of completeness.
Returns the Treatment objects in this Experiment.
This method will get called when a participant arrives at your site, and needs to be randomized to a treatment. Unless you override it, this method returns a random choice between the treatments in the experiment, weighted by their randomization_weight:
def pick_treatment_for_incoming_participant(self):
choices = [(treatment, treatment.randomization_weight) for treatment in self.treatment_set.all()]
treatment = self.weighted_randomization_choice(choices)
return treatment