oTree Forum >

Page randomization, control and treatment groups

#1 by pjp

Hi,

I have a problem with randomizing page sequence. Please see the full debugging MRE from the attached ZIP-folder. In short, I have a control group and 2 treatment groups. We want to obtain values pre-treatment and then values post-treatment conditional on the treatment group and its corresponding treatment channel {1, 2}.

The three possible scenarios for page sequences excluding randomization are:
Control: {A, B1, B2, C, D, E1, E2, F}
Treatment 1: {A, B1, B2, C, X1, E1, F}
Treatment 2: {A, B1, B2, C, X2, E2, F}

Page X# is the treatment. Questionnaire pages {B1, B2} (pre) map directly to {E1, E2} (post). For control, the order of {B1, B2} should map directly to {E1, E2}. So if control has order {B2, B1}, it should see post-treatment questionnaire in order {E2, E1}. The treatments should also see {B1, B2} or {B2, B1}, but then the {E1 or E2} is dictated by the treatment {X1, X2}. Pages A, C, D, and F are fixed per their position.

The page A should start the sequence always, then the pre-treatment questions B# shall be in a randomized order, and the post-treatment questions E# should match the randomized order of B#. Then in the middle, there are C, D for backgrounds of control, and treatment has only C in the middle of the sequence for background checks before continuing to treatment and its corresponding post-treatment question. Then F finishes off each and every page sequence.

One must note that all these actions must be done per the same round_number, so that the output file is as clean as possible. So the page display/randomization must be done via some other means than utilizing round numbers.

Use the following commands to initialize the virtual environment:
cd/path/buggy project
source ../new_otree_env/bin/activate
otree devserver

Thank you in advance!

#2 by pjp

The referred attachments. The previous commands are redundant.

#3 by DmitriiG

Hi 

Preface: I am not at all an expert, but I will try to explain the logic behind a potential solution.

If it is of paramount importance to keep the number of rounds to 1 (otherwise, you can use https://groups.google.com/g/otree/c/xp1K2DrvePk, but it seems from the code that you have already tried that) and your pages are simple surveys, you can try to "randomize" the pages via templates/getforms. 

The idea is to randomise control/treatment1/treatment2 and additionally define a random "order" variable for each participant, which you will then pass on to the template on each page. 

So, roughly, you could do the following (just a rough sketch, not tested): 

def creating_session(subsession: Subsession):
    import itertools
    if subsession.round_number == 1:
        treatment_cycle= itertools.cycle(['C','T1','T2'])
        for p in subsession.get_players():
            p.treatment = next(treatment_cycle)
            p.order=random.choice([True,False]
    ...

class A(Page):
    template_name = 'bug/A.html'
    ...
class BA(Page):
    template_name = 'bug/BA.html'
    @staticmethod
    def get_form_fields(player):
        if player.order:
            return [@FORMS FOR B1@]
        else:
            return [@FORMS FOR B2@]
    @staticmethod
    def vars_for_template(player: Player):
        return dict(
            random_Order = player.order
            )
############# Then, in your template for BA, you put both B1 and B2 pages, but using conditionals in the template, only show 1 or 2, something like this:
    {{ if random_Order }}
    @@@@template for B1@@@@
    {{ else }} 
    @@@@template for B2@@@@
{{ endif }}

############ Then, in your template for BB, you put both B1 and B2 pages again, but now do: 
    {{ if random_Order }}
    @@@@template for B2@@@@
    {{ else }} 
    @@@@template for B1@@@@
{{ endif }}


The above logic would be sufficient to randomise for b1/b2 for both control and treatments. The C page is for everyone. The next two pages you display for control only (as in your code), but add the same kind of logic - i.e., add both to a big template and change it on the page itself rather than through __init (but don't forget the code for the display and different forms), like this: 
Page EA template
{{ if random_Order }}
    @@@@template for E1@@@@
    {{ else }} 
    @@@@template for E2@@@@
{{ endif }}
Page EB template
{{ if random_Order }}
    @@@@template for E2@@@@
    {{ else }} 
    @@@@template for E1@@@@
{{ endif }}

Lastly, you have two pages that you show to treatment. 

Then your code for the page in init would  look something like this:

class X1(Page):
    template_name = 'bug/XA.html'
    @staticmethod
    def is_displayed(player: Player):
            return player.treatment != 'C'
            
    def get_form_fields(player):
        if player.treatment=='T1':
            return [@FORMS FOR X1@]
        else:
            return [@FORMS FOR X2@]
    @staticmethod
    def vars_for_template(player: Player):
        return dict(
            treatment = player.treatment
            )      
    
Page XA
{{ if treatment=='T1'}}
    @@@@template for X1@@@@
    {{ else }} 
    @@@@template for X2@@@@
{{ endif }}

Lastly, you add page XB, where the templates E1/E2 are shown depending on the treatment. 


I hope this works :)

#4 by pjp

Thank you for your response. 

I am quite confident that your suggestion (transferring the is_displayed method's functionality to the HTML and JS side) would work. However, there is a significant concern that prevented me from adopting this approach earlier, even though I briefly considered it a couple days ago. 

Moving essentially a backend functionality to the frontend poses a substantial risk. The software could become unstable with future updates to browsers (Chrome, Firefox, or Edge), I also have first-hand experience on such issues with oTree experiments. Moreover, developing on top of and maintaining such critical functions should ideally be done on the Python __init__.py side as much as possible.

If anyone else comes across this question, please take into account that such functionality is better handled on the Python side.

I hope I don't come across as too blunt. I just aim to be clear and concise.

#5 by DmitriiG (edited )

Well, you can achieve the same outcome without templates by simply having extra (duplicate) pages in your sequence: 
    A @displayed for everyone
    B1 @displayed for player.order==True
    B2 @displayed for everyone 
    BB1 @displayed for player.order==False 
    C @displayed for everyone 
    X1 @displayed for player.treatment=='T1'
    X2 @displayed for player.treatment=='T2'
    E1 @displayed for [(player.treatment=='C' AND player.order=True)  OR (player.treatment=='T1')]
    E2 @displayed for [(player.treatment=='C' OR player.treatment=='T2')]
    EE1 @displayed for [(player.treatment=='C' AND player.order=False)  OR (player.treatment=='T2')]
    f @for everyone
    
    
    page sequence=[a,b1,b2,bb1,c,x1,x2,e1,e2,ee1,f]
Where BB1 is simply the same page as B1 but with different display condition, etc 

If i am not mistaken, this achieves the same randomisation as my previous comment but doesn't shift things to frontend :)

Write a reply

Set forum username