はじめに
とある原稿を書いているうちに,昔作ったztreeトリートメントをアップデートしたい気持ちがふつふつと高まってきたので,otreeで新たに実験用コードを作成しました.以下忘れた時用のメモです.
otree準備
- アナコンダの最新版をインストール
pip install -U otree
でotreeをインストールする
こちらのサイトがたいへん参考になりました.
作業用ディレクトリの設定
異なるマシンで作業ができるようにdropbox上にフォルダを作ります.
たとえばC:\Users\Dropbox
にドロップボックスのローカルアドレスがあれば
otree startproject C:\Users\Dropbox\oTree
という感じでホームディレクトリをつくってあげるとよいでしょう.
はじめてディレクトリをつくるとサンプルコードもdlするかと聞かれるので,これはdlしておきましょう.
今回作ったようなトリートメントはcournot
ゲームをベースにして作ると簡単でした.
サンプルを探せば自分がやりたい実験に似たコードがあるはずなので,まずはそれをベースに少しずつ修正を加えると作業が楽でしょう (フルスクラッチでいきなり書くのは難しそう)
以降はマシンを変える度にdb.sqlite3
を消してから,テスト用のローカルサーバーを
otree devserver
でたてればOK.
すでに作成済みのトリートメントの実行
アナコンダのパワーシェルでトリートメントを含むディレクトリ移動する.
たとえばdropbox上に保存した場合,研究室等のマシンから
cd C:\Users\hogehoge\Dropbox\oTree
などと入力し,ディレクトリを移動する.移動したディレクトリにはsettings.py
があり,この中に目的のトリートメントを記入しておく.
(新しく作った場合はあとでここで記入する)
移動したらテスト用のローカルサーバーを
otree devserver
でたてる.
ブラウザでhttp://localhost:8000/
にアクセス
settingに登録したトリートメントが表示されるので実行(登録してないとメニューに出てこない) .各クライアントを別ウィンドウで開くためのリンクは,参加人数で設定した分だけ表示される.
アプリの準備
サンプルコードにcournot
ゲームのフォルダがあるのでコピーしてリネームしましょう.
相対剥奪なので,relative_deprivation
という名前にします.
このフォルダには主に
__init__.py
- Contribute.html
- Results.html
の三つのファイルがあります.
この中身を適当に修正して実験コードを作成します.一つ上の階層
C:\Users\Dropbox\oTree
には
- settings.py
というファイルがあります.この中の
SESSION_CONFIGS = [ dict( name='guess_two_thirds', display_name="Guess 2/3 of the Average", app_sequence=['guess_two_thirds', 'payment_info'], num_demo_participants=3, ), dict( name='survey', app_sequence=['survey', 'payment_info'], num_demo_participants=1 )]
に自分が作成したrelative_deprivation'
を登録します.guess_two_thirds
とsurvey
はデフォルトのdemoです.
結果は次のようなものになるでしょう.
SESSION_CONFIGS = [ dict( name='guess_two_thirds', display_name="Guess 2/3 of the Average", app_sequence=['guess_two_thirds', 'payment_info'], num_demo_participants=3, ), dict( name='survey', app_sequence=['survey', 'payment_info'], num_demo_participants=1 ), dict( name='relative_deprivation', app_sequence=['relative_deprivation', 'payment_info'], num_demo_participants=5 ) ]
settings.py
を保存したらotree devserver
でテスト用サーバを立ち上げます.demoの中に自分が登録した実験(relative_deprivation)があれば成功です.
コードの中身
アプリを構成するコードの中身はこんな感じです
__init__.py
クラスの定義や計算用関数を定義するpython
コードです.
python
になじみのある人は,なんとなく何をやっているか分かると思います.詳細は後ほど解説します.
from otree.api import * import random class C(BaseConstants): NAME_IN_URL = 'relative_deprivation' PLAYERS_PER_GROUP = 5 NUM_ROUNDS = 1 RETURN_RATE = random.randint(3,5) NUM_SUCCESS = random.randint(1,5) PROFIT = RETURN_RATE*100-100 #COST=100 class Subsession(BaseSubsession): pass class Group(BaseGroup): x = models.IntegerField() class Player(BasePlayer): action = models.IntegerField( choices=[[0, 'no'],[1,'yes (invest 100)']], label="Do you invest? ",initial=(0) ) outcome = models.IntegerField( choices=[[5, 'very satisfied'],[4, 'satisfied'],[3, 'neither'],[2, 'dissatisfied'], [1, 'very dissatisfied']], label='How do you feel about the result?', widget=widgets.RadioSelect, ) profit = models.IntegerField() # FUNCTIONS def set_profit(group: Group):#グループオブジェクトを引数にした関数 players = group.get_players()#playerを取得 group.x = sum([p.action for p in players])#投資者数の総数xを計算 xids=[]#投資者のplayeridを格納するリスト for p in players: if p.action==1: xids.append(p.id_in_group)#投資者のplayeridを全てリストに追加 #投資者数が多い場合の抽選 if C.NUM_SUCCESS < group.x: win=random.sample(xids,C.NUM_SUCCESS)#NUM_SUCCESSだけxidsから勝者を選択 for p in players: if p.action == 0: p.profit=0 # 非投資者の利得を計算 else: if C.NUM_SUCCESS >= group.x: p.profit= 100*(C.RETURN_RATE*p.action-p.action) else: if p.id_in_group in win: p.profit= 100*(C.RETURN_RATE*p.action-p.action) else: p.profit= -100 # PAGES class Contribute(Page): form_model = 'player' form_fields = ['action'] class ResultsWaitPage(WaitPage): after_all_players_arrive = set_profit class Results(Page): form_model = 'player' form_fields = ['outcome'] page_sequence = [Contribute, ResultsWaitPage, Results]
Contribute.html
つぎは入力画面のhtmlです.
htmlタグ直書き世代の自分にとっては懐かしいコードです(わしらの若い頃は,これを手打ちして「ホームページ」をつくっておったのじゃよ).
ちなみに{{C.PROFIT}}
のように変数名C.PROFIT
を囲むと,pythonで定義した変数をhtml上に表示させることができます.
{{ extends "global/Page.html" }} {{ block title }}Contribute{{ endblock }} {{ block content }} <p> You can choose "invest" or "not invest". </p> <p> {{C.NUM_SUCCESS}} players among those who invest will get a profit. </p> <p> You will get <ul> <strong>{{C.PROFIT}}, when you win.</strong> </ul> <ul> <strong>- 100 , when you lose.</strong> </ul> <ul> <strong>0 , when you do not invest.</strong> </ul> {{ formfields }} {{ next_button }} {{ endblock }}
Results.html
最後は結果表示と結果に対する満足度を聞くフォームを表示するhtmlです.
{{ block title }}Results{{ endblock }} {{ block content }} <p> Your profit is {{ player.profit }}. </p> <p> Please answer the following questions. </p> {{ formfields }} {{ next_button }} {{ endblock }}
実験の概要
相対的剥奪実験の具体例は以下の通りです.
- 被験者は100円投資or投資しないを選択できます
- 投資をえらんだ人は成功すれば300円もらえますが,失敗すると何ももらえません.
- 成功人数は2名です.投資した人の数が2名を超えると,成功者の2名以外は100円失います
セッションによって利益率R
と成功者数n
が変化します.一般的には
- 被験者は100円投資or投資しないを選択できます
- 投資をえらんだ人は成功すれば
100*R
円もらえますが,失敗すると何ももらえません. - 成功人数は
n
名です.投資した人の数がn
名を超えると,成功者のn
名以外は100円失います
というル-ルです
実験の流れ
直感的には実験は次のような順番で進行します.
- 実験用URLにアクセスしたプレイヤーに
Contribute.html
が表示され,プレイヤーは行動(投資するか否か)を選択します - 全プレイヤーが選択を終えると
__init__.py
で定義した関数(set_profit)がプレイヤーの利得を計算します Result.html
が表示され,各プレイヤーが受け取った利得がその中に表示されます.Result.html
の中に満足度を入力するフォームが表示されます.プレイヤーが満足度を選択すると実験は終了です- 結果が自動でデータベースに記録されます.
コードの概要
otreeでは基本変数と関数の定義を全て,__init__.py
内に記述します.htmlはプレイヤーからの入力の受け取りや,計算結果の表示を担当します.
otreeでは実験用に基本的なクラスである
class C(BaseConstants):
グローバル変数を定義するクラスclass Subsession(BaseSubsession):
セッションを定義するクラスclass Group(BaseGroup):
グループ属性を定義するクラスclass Player(BasePlayer):
プレイヤー属性を定義するクラス
などがあらかじめ定義されています.
それぞれ簡単に内容を確認します.
class C(BaseConstants): NAME_IN_URL = 'relative_deprivation' #実験の名称です PLAYERS_PER_GROUP = 5 #被験者数です NUM_ROUNDS = 1 #ラウンド数です RETURN_RATE = random.randint(3,5) #利益率です NUM_SUCCESS = random.randint(1,5) #成功者数です PROFIT = RETURN_RATE*100-100 #COST=100 #成功時の利得です
以上がグローバル変数です.ラウンドごとに水準を変化させる場合は, 利益率などをsessionクラスで定義します.
class Group(BaseGroup): x = models.IntegerField()
グループの属性です.被験者集団全体で決まる値をここで属性として定義します. 属性xは投資者数を記録します.この値は被験者の行動によって変化するので,グローバル変数としては定義できないことに注意します.
class Player(BasePlayer): action = models.IntegerField( choices=[[0, 'no'],[1,'yes (invest 100)']], label="Do you invest? ",initial=(0) ) outcome = models.IntegerField( choices=[[5, 'very satisfied'],[4, 'satisfied'],[3, 'neither'],[2, 'dissatisfied'], [1, 'very dissatisfied']], label='How do you feel about the result?', widget=widgets.RadioSelect, ) profit = models.IntegerField()
プレイヤーのクラスです.ここでは
- action #行動
- outcome #結果への評価(満足度)
- profit #受け取る利得
の3属性を定義します.idのような基本属性はもともと定義されているので,呼び出すだけで使えます.
def set_profit(group: Group):#グループオブジェクトを引数にした関数 players = group.get_players()#playerを取得 group.x = sum([p.action for p in players])#投資者数の総数xを計算 xids=[]#投資者のplayeridを格納するリスト for p in players: if p.action==1: xids.append(p.id_in_group)#投資者のplayeridを全てリストに追加 #投資者数が多い場合の抽選 if C.NUM_SUCCESS < group.x: win=random.sample(xids,C.NUM_SUCCESS)#NUM_SUCCESSだけxidsから勝者を選択 for p in players: if p.action == 0: p.profit=0 # 非投資者の利得を計算 else: if C.NUM_SUCCESS > group.x: p.profit= 100*(C.RETURN_RATE*p.action-p.action) else: if p.id_in_group in win: p.profit= 100*(C.RETURN_RATE*p.action-p.action) else: p.profit= -100
利得計算関数です.本実験の肝の部分です.投資者数x
がn
で設定した成功者数を下回る場合は,投資者全員が成功し,
n<x
の場合はランダムに成功者が決まるようにrandom.sample(xids,C.NUM_SUCCESS)
で計算しています.
そのため,投資選択者のidリストをxids.append(p.id_in_group)
で作ります.このときotreeのプレイヤーのデフォルト属性であるid_in_group
を利用します.
このようにotreeにはクラス毎によく利用する属性が準備されています.
# PAGES class Contribute(Page): form_model = 'player' form_fields = ['action'] class ResultsWaitPage(WaitPage): after_all_players_arrive = set_profit class Results(Page): form_model = 'player' form_fields = ['outcome'] page_sequence = [Contribute, ResultsWaitPage, Results]
htmlのクラスです.フォームからの入力などを定義します.
入力の受け取りにはプレイヤークラスで定義した属性をform_fields
で指定します.
回答選択肢などは,プレイヤークラスの方で定義しています.
まとめ
otreeで相対的剥奪オンライン実験用コードを作成しました.
ポイントは
- クールノーゲームのようにベースとなる実験を見つけ,それを上書き修正する
- クラス毎に定義すべき属性を理解する
です.ztreeに比べるとかなりコードが書きやすくなった印象です. pythonの基本とhtmlのタグを知っていれば,簡単な実験なら2~3時間くらいで書けると思います.