#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!
#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 :)