oTreeに関するメモ

__init__.pyの書き方

定数 C クラス

クラスとは?

Pythonクラスはデータ(属性)と処理(メソッド)をひとまとめにして、「同じ種類のオブジェクト」を作るための設計図です。詳細はこちらに参照してください。

oTreeでは、実験におけるの設定値・固定値(constants)をC クラスにまとめて置くことを推奨しています。

よく取り扱うの設定値としては、以下のようなものが挙げられます

  • NAME_IN_URL:アプリのURL識別子
  • NUM_ROUNDS:ラウンド数
  • PLAYERS_PER_GROUP:1グループあたりの人数
  • ENDOWMENT:初期保有額

実験のルールとして固定される値は class C(BaseConstants) にまとめておくと、コード全体で参照しやすくなります(例:C.NUM_ROUNDS)。

データモデル

oTree の基本モデルは Subsession / Group / Player の3つで、Player は Group に属し、Group は Subsession に属するという階層になっています。

Subsessionクラス

  • 1ラウンド = 1つの Subsession(同じラウンドにいる全参加者で共有されるデータ置き場)
  • round_number を持ち、そのラウンドの グループ編成・条件割当・共通の乱数 など「ラウンド単位の設定・状態」を扱います

組み込み関数 creating_session()

creating_session() は、セッション作成時に各 Subsession(=各ラウンド)について呼ばれる初期化処理で、ここでグルーピングや条件割当を行います。

  • oTreeでは、特定の名前の関数特定の名前を見つけると自動で呼び出してくれる決まった役割の関数があり、組み込み関数と呼ばれます。
  • creating_session()は重要な組み込み関数であり、管理画面で Create session を押した直後に実行され、「グルーピング」「条件割当」「初期値のセット」などをまとめて行います。
  • oTreeのno-self記法では、creating_sessionclass Subsession の中にメソッドとして書くのではなく、ファイル(init.py)直下に関数として定義する形がよく使われます
1
2
3
4
5
6
7
8
class Subsession(BaseSubsession):
    pass

def creating_session(subsession: Subsession):
    if subsession.round_number == 1:
        subsession.group_randomly()
    else:
        subsession.group_like_round(1)

Groupクラス

Groupクラスでは、同じグループに割り当てられた参加者たちに関するデータと関連するメソッドを格納します。

  • 例えば、group.get_players() メソッドで、そのグループに属する全プレイヤーのリストを取得できます

Playerクラス

Playerクラスは、個々の参加者に関するデータと関連するメソッドを格納します

  • たとえば、各参加者の回答(選択・入力値)、意思決定、得点・利得(payoff)など、参加者ごとに異なる値は基本的に Player に格納します。

ページ

参加者に表示される各画面は Page / WaitPage クラスとして定義し、最後に page_sequence で順番を指定します。

ここで、典型的な流れを例として紹介します。

  • Page1(Page):参加者の入力ページ
    • 当該ページで使う入力フォームのモデルを “player”, “group”, “subsession” から選んで文字列を渡します
    • どれか一つしか選べない.一つのページで player モデルのフィールドと group モデルのフィールドの両方の入力フォームを置くことはできない.たとえばとりあえず全部 player モデルでデータを記録しておき, oTree 内部で group モデルのフィールドに転記する,などで対処します.
1
2
3
class Page1(Page):
    form_model = 'player'
    form_fields = ['contribution']
  • Page2(WaitPage):全員揃うまで待つページ+同期処理

    • WaitPage は 同じグループの全員が到達するまで待機させます。

    • 全員が到達した瞬間に、after_all_players_arrive が 1回だけ呼ばれます。

    • 定義した compute 関数で集計を行います。

1
2
class Page2(WaitPage):
    after_all_players_arrive = compute
  • Page3(Page):結果表示ページ
1
2
class Page3(Page):
    pass
  • page_sequence
    • 参加者はこの順番でページを進みます
1
page_sequence = [Page1, Page2, Page3]

テンプレートファイルの書き方

基本

  • ファイル名はページクラスのクラス名と一致させます.たとえば
1
2
  class Page1(Page):
      pass

というクラスを定義していれば,テンプレートファイルの名前は Page1.html とします.

  • タイトルブロックblock titleendblock に挟まれた部分)
    • タイトルブロック内に,ページの冒頭で表示するタイトルを記述します.
    • 記述した文字列がブラウザウィンドウのタイトルにも表示されます
  • コンテンツブロックblock contentendblock に挟まれた部分)
    • コンテンツブロック内に,ページの本文を,HTMLタグも適宜使いながら記述します.
    • oTree サーバーに送信したいデータの入力フォームを作るためには,コンテンツブロック内に <input> タグなどを記述する.このとき name 属性に,記録するデータのフィールド名を設定します.

変数の展開

テンプレート内に 変数名 と記述すると, oTree サーバーはその箇所に変数を展開し,参加者には具体的な変数の中身が代入されて表示されます

たとえば,C.ENDOWMENT の値が 1000 であるとして,テンプレートに

1
あなたの初期保有はポイントです

と記述したとき,クライアントが oTree サーバーから受け取るHTMLデータは

1
あなたの初期保有は1000ポイントです

となリます。

settings.pyの書き方

SESSION_CONFIGS

  • セッションの構成を辞書型で定義する.
    • name: セッションの名前
    • display_name: 管理者画面で表示するセッション名(指定しなければ name が表示される)
    • app_sequence: アプリの順序をlistで設定
    • num_demo_participants: デモページでの人数
    • 独自のセッション変数(定数)
      • たとえば time_pressure=True と設定した場合,アプリのスクリプトの中では インスタンスオブジェクト.session.config["time_pressure"] で変数にアクセスできる(インスタンスオブジェクト ∈ { player, group, subsession } ).
      • https://otree.readthedocs.io/en/latest/treatments.html

oTreeのAPI

oTreeには、外部プログラム(他のWebサイトなど)がoTreeと通信できるようにする REST API があります。

そもそも「REST API」って何?

ざっくり言うと、ブラウザ上で操作して閲覧できるデータを、プログラムからも同様に取得するための「窓口」です。 REST API を利用することで、同じ操作を再現可能な自動処理として実行できるため、定期的なデータの取得や、複数セッションの一括取得など、手作業では煩雑になりがちな処理を効率化できます。

oTreeのAPIでデータを取得する

oTreeで実験を実施後、取得されたデータは、画面上部のメニュー内にあるDataから、CSVファイルなどを手動でダウンロードすることが可能です。

ただ、参加者が増えてセッション数が多くなったり、データ取得と分析を繰り返し行う場合には、REST APIを用いてデータを自動取得する方法が便利です。

Data画面の Show API URLs を開くと、データ取得用のエンドポイント(例:/api/export_wide)が表示されます。これらのURLに対して HTTPのGETリクエスト を送ることで、ブラウザでのダウンロード操作を行わなくても、プログラムからCSVを取得できます。たとえば、全アプリを1ファイルにまとめた wide 形式のデータは次のURLで取得できます。

  • 全データ(wide形式):/api/export_wide

  • Excel向け(BOM付き):/api/export_wide?format=csv_bom

  • セッション限定:/api/export_wide?session_code={SESSION_CODE}

実際の取得は、Python などのHTTPクライアントを使って行うことが多いです。

例えばPythonを使う場合は以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import pandas as pd
from io import BytesIO

url = "http://localhost:8000/api/export_app"  # APIのエンドポイントURL
params = {"app": "questionnaire"}  # 取得するアプリ名を指定
headers = {
    "Authorization": "Token YOUR_API_TOKEN"  # 認証用のAPIトークン
}

# GETリクエストを送信してデータを取得
r = requests.get(url, params=params, headers=headers)
print(f"URL requested: {r.url}")  # リクエストしたURLを表示
r.raise_for_status()  # エラーがあれば例外を発生させる

# レスポンスのバイナリデータをPandas DataFrameに変換
df = pd.read_csv(BytesIO(r.content))

# CSVファイルとして保存
with open("questionnaire.csv", "wb") as f:
    f.write(r.content)

このようにREST APIを用いることで、(1) セッションごとのデータ収集、(2) 定期的な自動ダウンロード、(3) 分析スクリプトとの連携による再現性の確保、が容易になります。特に、実験を複数回実施する場合や、データ取得から前処理・分析までを一連のパイプラインとして管理したい場合に有効です。