Up

3件法による恒常法/上下法データの分析

岡本安晴

 

恒常法あるいは上下法(階段法)を3件法で行った場合のデータ分析を確率モデルにより行った。3件法は、「判らない/等しい」判断と閾値の区別ができないという批判があるが、これは3件法データを算術で分析する場合のことである。確率モデルを設定した場合は、「判らない/等しい」判断は、psychometric function (PF)のシフト量、閾値はPFの傾きとして区別される。むしろ、2件法を用いると、「判らない/等しい」と思われるときに、ランダムに「強い」あるいは「弱い」と判断することが強制されるが、ランダムに判断することは難しいという問題がある。広く採用されている2件法より、3件法の方が誤差が小さい(RMSEが小さい)という報告もある(岡本、2017,2019)。特に2件法でなければならないという理由がないならば、3件法(あるいは4件法)を用いるべきであると思われる。

3件法で上下法を行うことは煩雑であると思われるかもしれないが、提示刺激値をPCディスプレイ上に表示して、実験はPCのディスプレイに従って進めることができるプログラムも用意した。本ウェブサイト後半で説明する。

3件法による恒常法については岡本(2017、3件法による上下法については岡本(2019を参照されたい。恒常法の場合は、提示比較刺激値の組が予め決められているが、比較刺激値の間隔がJNDのほぼ2倍(弁別確率でほぼ0.91)であってもよいことが示唆されている(岡本、2017p. 90)。上下法の場合は、観察者の反応に応じて提示比較刺激値が変化するが、このときの変化量は弁別確率が約0.84が良いことが示唆されている(岡本、2019p99)。恒常法の場合は、提示比較刺激値はランダムに決められるが、上下法の場合は、次の方法で提示比較刺激値を選ぶことができる(岡本、2019)。

上下法では、提示刺激値が直前の試行における判断に依存して決まるため、被験者が提示刺激値を予測する可能性が高い。これを避けるため、2系列用意して2つの系列の試行をランダムに混ぜて実施する(二重上下法;大山、2005)。2つの系列を系列1と系列2として、系列でのものであることを右上に添え字として明記する。

3件法の場合、判断は「強い」、「同じ/わからない」、「弱い」の3通りである。これは、恒常法の場合も同じであるが、提示がランダムであるので、恒常法では2系列用意する必要はない。

上下法の場合は、系列の第試行における判断(反応)を以下のように表す。

系列の第試行における提示刺激値をで表すとき、次試行における提示刺激値を判断に応じて以下のように決める(岡本、2019)。

のとき,

,

のとき,

,

のとき,

判断がのときに前試行における刺激値との関係で次試行の刺激値が決められているのは、「強い」判断および「弱い」判断のデータが多い方が心理測定関数(psychometric function: PF)の傾きの推定が安定するからである。

なお、上記の上下法の手続きの実行は、実験者が観察者の反応に応じて2系列での提示刺激値を決めて行うのは煩雑であると思われる。プログラムによりPCのディスプレイ上に提示刺激値を表示して、それを見ながら実験できるようにプログラムを用意した。3件法による上下法実験補助プログラムとして、本サイトの後半に用意したので参照されたい。これらの実験補助プログラムは、圧縮ファイルupdown3catfiles.zipにまとめたので、ダウンロード展開すれば自由に利用できる。

データ分析においては、系列1のデータと系列2のデータをまとめてとおき、刺激に対する反応の確率を次式で与える(岡本、2019)。

ここで

であり、は累積標準正規分布関数である。これは、3件法恒常法の場合のモデル(岡本、2017、式(3))において、と置いたものである。

このとき、尤度関数を次式で与える。

である。2系列用いる上下法の場合は、総試行数は偶数であるが、恒常法の場合はデータ数に応じて奇数の場合もある。この尤度関数は、データが上下法以外の方法、例えば恒常法、で集められた場合にも適用できることに注意。データは、「強い」判断および「弱い」判断が確実なそれぞれ刺激値が十分に強いものと弱いものを含むと、の事後分布が拡散することを防ぐことができる。

この尤度関数に対して、事前分布として一様分布を設定したときの事後分布を求めるStanスクリプトをリスト1に示す。

 

リスト1 3件法データ確率モデルのStanスクリプト例(PM3Cat_EqC.stan

data {

    int N;              //      The number of trials

    int Res[N];         //      Responses: 

                        //           1 denotes -, 2 denotes ?, 3 denotes +

    real X[N];          //      Stimuli

    real mu_min;        //      Lower boundary of mu

    real mu_max;        //      Upper boundary of mu

}

 

parameters {

    real<lower = 0.0> sgm;

    real mu;

    real<lower = 0.0> C;

}

 

transformed parameters {

    vector[3] p[N];             //      Probabilities for X[N]

    for (i in 1:N){

        p[i][1] = 1 - Phi((X[i] - mu + C) / sgm);

        p[i][2] = Phi((X[i] - mu + C) / sgm) - Phi((X[i] - mu - C) / sgm);

        p[i][3] = Phi((X[i] - mu - C) / sgm);

    }

}

 

model {

    sgm ~ uniform(0, mu_max - mu_min); 

    mu ~ uniform(mu_min, mu_max);

    C ~ uniform(0, mu_max - mu_min);

    for (i in 1:N)

        Res[i] ~ categorical(p[i]);

}

 

の値は、−、?、+を1、2、3で表している。

リスト1のStanスクリプトを用いて、3件法デーを分析するスクリプト例MainPSEJND.pyを作成した。サンプルデータData3Cat.xlsxなどとともに圧縮ファイルpsejnd3cfiles.zipにまとめた。ダウンロード展開して、自由に利用されたい。

スクリプトMainPSEJND.pyを実行すると、以下のように入力データファイル名と出力データファイル名の入力が求められる。

 

(py37) PS D:\tips\PSE_JND_Bysn\psejnd3cfiles> Python MainPSEJND.py

Data file name (*.xlsx) = Data3Cat.xlsx

Output file name = Results.txt

 

上の例は、Windows 10においてAnacondaの仮想環境としてPython 3.7用のもの(py37と命名した)を用意して、pystan 2.17.1.0をインストールした仮想環境で実行したものである(2022.06.20)。既に他のpystanがインストールされている場合は、コマンド

conda install pystan==2.17.1.0

を実行して置き換えればよい。等号=2つである。

入力データファイルは、Excel形式のファイルとして用意する(図1)。

図1

 

第1行目に、変数名を適当に設定する。図1では、簡単にtsrが設定されている。2行目からデータを設定するが、1列目はデータの識別用である。図2では通し番号が設定されているが、任意の文字列でよい。2列目に提示刺激値を設定し、3列目に反応を設定する。反応は、「弱い」判断のときは1、「同じ/わからない」判断のときは2、「強い」判断のときは3を設定する。データの行の順序は任意である。これは、判断の確率が刺激値のみによって与えられるので、例えば、図2の2行目にある0番目のデータと101行目の99番目のデータを入れ替えても尤度関数値は変わらないからである。

入力データファイル名を設定すると、次に出力ファイル名の設定が求められる。適当なテキストファイル名を設定する。

入力ファイル名と出力ファイル名の設定が終わると、データが読み込まれ、Stanによる事後分布からのサンプリングが始まる。

サンプリングが終了すると、パラメータmu()の事後分布のグラフがMAP推定値、95 CIとともに表示される(図2)。グラフは、自動的にファイルとして保存されるので、プログラムの実行終了後も見ることができる。これは、図3以下のグラフも同様である。

図2

 

フォーム右上のX印をクリックしてフォームを閉じると、次に、sigma)の事後分布のグラフが、MAP推定値と95 CIとともに表示される(図3)。

図3

 

図3のフォームを閉じると、次にCの事後分布のグラフがMAP推定値と95 CIとともに表示される(図4)。

図4

 

図4のフォームを閉じると、次にPFとデータ値のグラフがMAP推定値に基づくPSEJNDの推定値とともに表示される(図5)。

図5

 

PFは、以下の式に基づくものである。

データ値は、1から3までの値であるが、これを0から1までの範囲に縮小して表示している。すなわち、

である。さらに、上下に少しランダムな変動を加えて、完全に重なって表示されることを防いでいる。

図5のフォームを閉じると図6のフォームが表示される。

図6

 

図5に表示されているPF(実線)と対応するデータ値(評定Rが2以上および3以上の比率)のKDEKernel Density Estimation)による平滑化されたグラフ(破線)が表示されている。図6のフォームを閉じると、以下のようにw_kの値の現在値が表示され、次の値の設定が求められる。

 

The current value of w_k = 4.528924941840808

The nexr value of w_k =

 

このw_kは平滑化におけるkernelの窓の幅で、この値が大きいと大まかな滑らかな曲線が得られ、小さいときは細かく変動した曲線、あるいは値が小さすぎると曲線が一部あるいは全部が消えてしまう。

w_kの値として0以下の値(図9の場合)を設定すると、プログラムは終了する。

 

The current value of w_k = 4.528924941840808

The nexr value of w_k = 0

 

Output file Results.txt was saved.

 

(py37) PS D:\tips\PSE_JND_Bysn\psejnd3cfiles>

 

表示されている出力ファイルResults.txtを開くと、以下のようになっている。

 

Data file...Data3Cat.xlsx

 

Number of data = 100

 

    0      257.50      3

    1      237.50      3

    2      222.50      3

    3      207.50      3

    4      192.50      1

    5      207.50      2

   96      207.50      3

   97      192.50      1

   98      207.50      1

   99      222.50      3

 

Summary...

Inference for Stan model: anon_model_06b3324a296857975de6b1d20d4bb0ed.

4 chains, each with iter=2000; warmup=1000; thin=1;

post-warmup draws per chain=1000, total post-warmup draws=4000.

 

       mean se_mean     sd   2.5%    25%    50%    75%  97.5%  n_eff   Rhat

C     10.15    0.03   1.72    7.2   8.94   10.0  11.19  13.87   2480    1.0

mu   203.81    0.04   1.89 200.06 202.55 203.86 205.05  207.5   2585    1.0

sgm   14.79    0.05   2.45   11.0  13.07  14.47  16.13  20.62   2214    1.0

lp__ -70.74    0.03   1.26 -73.91 -71.34 -70.41 -69.81 -69.31   1695    1.0

 

Samples were drawn using NUTS at Mon Jun 20 12:14:53 2022.

For each parameter, n_eff is a crude measure of effective sample size,

and Rhat is the potential scale reduction factor on split chains (at

convergence, Rhat=1).

 

MAP for mu = 203.94    95% CI for mu = [200.06, 207.49]

 

Map for sigma = 13.43    95% CI for sigma = [11.00, 20.62]

 

MAP for C = 9.46      95% CI for C = [7.20, 13.87]

 

MAP for PSE = 203.94,    95% CI for PSE = [200.06, 207.49]

MAP for JND = 9.06,    95% CI for JND= [7.42, 13.91]

 

読み込まれたデータの出力後、ベイズ分析の統計量が出力されている。データが正しく読み込まれたかどうかの確認と、ベイズ分析の統計量を出力ファイルでも読むことができる。

 

 

参考文献

岡本安晴(2017) 恒常法における反応カテゴリー数の影響.基礎心理学研究,3685-91https://doi.org/10.14947/psychono.36.11

岡本安晴(2019) 多件法による上下法.基礎心理学研究,3890-104https://doi.org/10.14947/psychono.38.2

大山正(2005).第7章 精神物理学的測定法. 大山正・岩脇三良・宮埜嘉夫(共著).心理学研究法――データ収集・分析から論文作成まで――.サイエンス社.

 

 

3件法による上下法実験補助プログラム

 

色や音などのように提示刺激がPCで制御できる場合は、上下法の手続きをプログラムで実装することにより実験の実行は自動的に行われ、実験実施時に実験者が観察者の判断に応じて系列内の次の刺激値を決める煩雑さを避けることができる。しかし、提示刺激がPCで制御されない場合も、提示刺激値をPCで自動的に示せると便利である。このためのプログラム例を2つ用意した。1つは、本サイトの前半で説明されている手続そのままプログラムしたものUpDownExp3CatCont.pyである。刺激値が上下法の手続きに従って算出されるので、提示刺激値の値が連続量であり任意の値が使用可能であることが前提とされている。しかし、錘や味覚などの場合は、用いる可能性のある物理刺激値が実験前に予め用意されていると思われる。予め用意されている提示刺激値から上下法の考え方で観察者の判断に応じて提示刺激を選ぶプログラムUpDownExp3CatFix.pyも用意した。2つのプログラムは、圧縮ファイルupdown3catfiles.zipにまとめたので、ダウンロード展開すれば、自由に利用することができる。まず、UpDownExp3CatCont.pyから説明を行い、その後、UpDownExp3CatFix.pyについて説明する。

 

提示刺激値が実験時に設定できる場合

プログラムUpDownExp3CatCont.py(本ウェブサイトの後ろ、リストA.1)を実行すると、上下法を行うために必要な設定が以下のように求められる。

 

(py37) PS D:\updown3catfiles> python .\UpDownExp3CatCont.py

出力ファイル名(.txt) = data.txt

初期値(大きい方)= 100

初期値(小さい方)= 10

Δ値(初期値)= 48

Δ値(下限値)= 5

ブロック数(1ブロック=4試行)= 5

初期値(大きい方)= 100.0

初期値(小さい方)= 10.0

Δ値(初期値)= 48.0

ブロック数 = 5

OK? (y/n) =

 

出力ファイル名で設定されたテキストファイルに上下法の実験結果が出力される。

上下法では2系列が用いられているが、大きい刺激値から始まる系列の最初の提示刺激値を「初期値(大きい方)」に、小さい刺激値から始まる系列の最初の提示刺激値を「初期値(小さい方)」に設定する。これらの値は十分に大きい、あるいは十分に小さい値として、「強い」あるいは「弱い」と確実に判断される値を設定すると安定した分析結果を得ることが期待される。Δ値(初期値)は、上下法における刺激値の変化量の初期値である。各系列の第1試行においてこの初期値だけ刺激値が変化するので、この初期値分の変化によって主観的等価点付近の値になるようにしておくとよいと思われる。Δ値は半減されていくので、Δ値が常に整数値であるようにするには、初期値をの形式の値を選ぶとよい。はΔ値の最小値以下の整数であり、は適当な正整数である。

Δ値(下限値)は、刺激値の変化量の下限値である。この下限値により上下法における刺激の変化が小さ過ぎないようにすることができる。

ブロック数は、本プログラムでは4試行を1ブロックとして扱っているので、4試行×ブロック数が総試行数になる。1ブロックは、系列1が2試行、系列2が2試行の構成となっており、ブロック内で試行をランダマイズして実験が行われる。

ブロック数を設定すると、以上の設定内容が表示され、これでよいかどうの確認が行われる。

文字「y」を入力すれば、設定完了として次に進むが、「n」を入力すれば再設定になる。

文字「y」を入力して、実験を始めると、まず、第1試行で提示する刺激値が表示される。以下の例では、刺激値10.0が示されている。

 

OK? (y/n) = y

 

試行:1/20

刺激値: 10.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)?

 

この刺激値に対する反応を入力する。次の例では、「小・弱」に相当する判断が得られたので、1と入力している。

 

試行:1/20

刺激値: 10.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)? 1

 

試行:2/20

刺激値: 100.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)?

 

第1試行における判断を入力すると、第2試行で提示する刺激値が表示される。上の例では刺激値100.0が示されている。

この刺激値に内する判断を、整数値1,2,あるいは3で入力すると、次に第3試行で提示する刺激値が表示される。

以下、同様に進める。

最後の試行が終了すると、図A.1が表示される。

A.1

 

各試行において提示された刺激値が系列別に色分けされてプロットされている。系列1がSer.A、系列2がSer.Bである。判断1が下向き三角形、判断2が小円、判断3が上向き三角形で表されている。

A.1のフォームを閉じるとプログラムの実行終了となる。

 

試行:20/20

刺激値: 47.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)? 2

          trial       stimulus       response

             B1           10.0              1

             A2          100.0              3

             B3           58.0              3

             A4           52.0              3

             A5           28.0              1

             A6           40.0              1

             B7           34.0              1

             B8           46.0              1

             B9           52.0              3

            A10           46.0              1

            A11           51.0              2

            B12           47.0              2

            A13           56.0              2

            A14           61.0              3

            B15           42.0              1

            B16           47.0              1

            B17           52.0              3

            A18           56.0              2

            A19           51.0              2

            B20           47.0              2

 

data.txt.was saved.

 

(py37) PS D:\updown3catfiles>

 

ディスプレイ画面には、実験結果と出力ファイルdata.txtが保存されたことが表示されている。

出力ファイルdata.txtExcelで開くことができる(図2)。

A.2

 

A.2の状態で、メニュ「ファイル|名前を付けて保存」を選び(図A.3)、Excelブックとして保存する(図A.4)。

A.3

 

A.4

 

このExcelファイルは、本ウェブサイトの前半で説明したプログラムの入力ファイルとして使用できる。

 

 

提示刺激値が予め用意されている場合

提示する刺激値を予め決めて用意しておく場合は、上下法において反応に応じて刺激値をΔだけ変えると用意されていない値になる可能性がある。この場合は、刺激値をΔだけ変えるのではなく、1つ隣の刺激値を採用することにすればよい。刺激値が

と用意されているとき、刺激値の値を増やすときはを、減らすときはを採用する。

プログラムUpDownExp3CatFix.py(本ウェブサイトの後ろ、リストA.2)を実行すると、以下のように、まず出力ファイル名の設定が求められる。

 

(py37) PS D:\updown3catfiles> python .\UpDownExp3CatFix.py

出力ファイル名(.txt) = data.txt

刺激系列(小ー>大)= 10 30 40 47 50 53 60 80 100

入力系列 =

['10', '30', '40', '47', '50', '53', '60', '80', '100']

1: 10

2: 30

3: 40

4: 47

5: 50

6: 53

7: 60

8: 80

9: 100

OK(y/n) =

 

出力ファイルには、実験の実行結果が設定した名前のテキストファイルに出力される。

出力ファイル名の設定の次に、使用する刺激値の設定が求められる。刺激値は、空白文字で区切って並べて入力する。並べた後、Enterキーを押すと、入力した刺激値が、横1行に並べたものと、縦に番号付きで並べたものが表示される。入力した値に問題がなければ、文字「y」を入力して、次に進む。文字「n」を入力すると、再設定できる。

文字「y」を入力すると、次にブロック数の入力が求められる。系列1と系列2の試行それぞれが2試行、計4試行で1ブロックである。ブロック内ごとに試行はランダマイズされている。以下の例ではブロック数は5が設定されているので、総試行数は4×5=20試行である。系列1は、用意された刺激値の最大値が最初の刺激値である。系列2は最小値が最初の刺激値である。

ブロック数を設定すると実験が始まり、以下の例では、試行1における刺激値10.0が示されている。

 

OK(y/n) = y

ブロック数(1ブロック=4試行)= 5

 

試行:1/20

刺激値: 10.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)?

 

刺激値の判断が「小・弱」に属するものであれば、反応として整数値1を入力する。以下の例では、1を入力して、次の試行2での刺激値が30.0であることが示されている。

 

試行:1/20

刺激値: 10.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)? 1

 

試行:2/20

刺激値: 30.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)?

 

試行2における刺激値30.0に対する反応を入力すると、次の試行3に続く。最後の試行、上の場合、20試行の反応を入力すると実験終了となり、図B.1のグラフが表示される。

B.1

 

各試行における提示刺激値がプロットされている。系列1が青色(Ser.A)、系列2が緑色(Ser.B)で表され、判断1が下向き三角形、判断2が小円、判断3が上向き三角形で表されている。

B.1のフォームを閉じると、プログラムの実行終了である。

 

試行:20/20

刺激値: 53.0

 

反応(1:小・弱, 2:不明・等しい, 3:大・強)? 2

          trial       stimulus       response

             B1           10.0              1

             B2           30.0              1

             A3          100.0              3

             A4           80.0              3

             A5           60.0              1

             A6           80.0              3

             B7           40.0              1

             B8           47.0              1

             A9           60.0              3

            B10           50.0              2

            B11           53.0              3

            A12           53.0              2

            B13           50.0              2

            A14           50.0              2

            B15           47.0              1

            A16           47.0              2

            A17           40.0              1

            B18           50.0              2

            A19           47.0              1

            B20           53.0              2

 

data.txt.was saved.

 

(py37) PS D:\updown3catfiles>

 

実験結果の表示の後、出力ファイルdata.txtが保存されたことが出力されている。

このファイルをExcelで開くと、図B.2のようになっている。

B.2

 

B.2の状態で、メニュ「ファイル|名前を付けて保存」でExcelファイルとして保存すると(図B.3、図B.4)、本サイトの前半で説明したプログラムで読み込める。

B.3

 

B.4

 

 

リストA.1 3件法提示刺激値表示プログラム(刺激値が連続量 UpDownExp3CatCont.py

import numpy as np

import matplotlib.pyplot as plt

 

class SubSer3Cat():

    def __init__(self, start_v=None, start_delta=None,

                 min_delta=None, cond=None, ser_id=None):

        self.v = start_v

        self.delta = start_delta

        self.min_delta = min_delta

        self.cond = cond

        self.ser_id = ser_id

 

    def next_state(self, response):

        if response <= 1:

            self.v += self.delta

            self.cond = 'up'

        elif response >= 3:

            self.v -= self.delta

            self.cond = 'down'

        else:

            if self.cond == 'up':

                self.v += self.delta

            else:

                self.v -= self.delta

        self.delta *= 0.5

        if self.delta < self.min_delta:

            self.delta = self.min_delta

 

ser = ['A']*2 + ['B']*2

 

fout_nm = input('出力ファイル名(.txt) = ')

fout = open(fout_nm, 'w')

 

while True:

    Start_A = float(input('初期値(大きい方)= '))

    Start_B = float(input('初期値(小さい方)= '))

    Start_delta = float(input('Δ値(初期値)= '))

    Min_delta = float(input('Δ値(下限値)= '))

    N_blocks = int(input('ブロック数(1ブロック=4試行)= '))

    print('初期値(大きい方)=', Start_A)

    print('初期値(小さい方)=', Start_B)

    print('Δ値(初期値)=', Start_delta)

    print('ブロック数 =', N_blocks)

    ck = input('OK? (y/n) = ')

    if ck in ['y', 'Y']:

        break

 

data = []

 

serA = SubSer3Cat(start_v = Start_A, start_delta = Start_delta, min_delta = Min_delta,

                  cond='down', ser_id='A')

 

serB = SubSer3Cat(start_v = Start_B, start_delta = Start_delta, min_delta = Min_delta,

                  cond='up', ser_id='B')

 

t = 0

for b in range(N_blocks):

    np.random.shuffle(ser)

    for i in range(4):

        t += 1

        print('\n試行:{0:}/{1:}'.format(t, N_blocks*4))

        if ser[i] == 'A':

            serX = serA

        else:

            serX = serB

           

        print('刺激値:', serX.v)

        res = int(input("\n反応(1:小・弱, 2:不明・等しい, 3:大・強)? "))

        data.append([f'{serX.ser_id}{t}', serX.v, res])

        serX.next_state(res)

           

print('{0:>15}{1:>15}{2:>15}'.format('trial', 'stimulus', 'response'))

fout.write('{0:>15}{1:>15}{2:>15}'.format('trial', 'stimulus', 'response'))

 

for i, v in enumerate(data):

    print('{0:>15}{1:>15}{2:>15}'.format(v[0], v[1], v[2]))

    fout.write('\n{0:>15}{1:>15}{2:>15}'.format(v[0], v[1], v[2]))

    plt.plot([i], [v[1]],'^' if v[2] == 3 else 'v' if v[2] == 1 else 'o',

             c = 'b' if v[0][0] == 'A' else 'g')

 

plt.plot([], 's', c = 'b', label = 'Ser.A')

plt.plot([], 's', c = 'g', label = 'Ser.B')

plt.plot([], '^', c = 'k', label = '3')

plt.plot([], 'o', c = 'k', label = '2')

plt.plot([], 'v', c = 'k', label = '1')

plt.title('Up-down Method')

plt.xlabel('Trial')

plt.ylabel('Comp. St.')

plt.legend()

plt.savefig('Fig' + fout_nm[:-4] + '.png')

plt.show()

 

print('\nFig' + fout_nm[:-4] + '.png was saved.')

fout.close()

print(fout_nm + '.was saved.\n')

 

 

リストA.2 3件法提示刺激値表示プログラム(刺激値が離散値UpDownExp3CatFix.py

import numpy as np

import matplotlib.pyplot as plt

 

class SubSer3CatFix():

    def __init__(self, series_v=None, start_s=None, cond=None, ser_id=None):

        self.series = series_v

        self.n = len(self.series)

        self.s = start_s

        self.cond = cond

        self.ser_id = ser_id

 

    def st_v(self):

        return self.series[self.s]

   

    def next_state(self, response):

        if response <= 1:

            self.s += 1

            if self.s >= self.n:

                self.s = self.n - 1

            self.cond = 'up'

        elif response >= 3:

            self.s -= 1

            if self.s < 0:

                self.s = 0

            self.cond = 'down'

        else:

            if self.cond == 'up':

                self.s += 1

                if self.s >= self.n:

                    self.s = self.n - 1

            else:

                self.s -= 1

                if self.s < 0:

                    self.s = 0

           

ser = ['A']*2 + ['B']*2

 

fout_nm = input('出力ファイル名(.txt) = ')

fout = open(fout_nm, 'w')

 

while True:

    series = input('刺激系列(小ー>大)= ').split()

    print('入力系列 =')

    print(series)

    for i, v in enumerate(series):

        print(f'{i+1}: {v}')

    #for v in series:

    #    print(v)

    ck = input('OK(y/n) = ')

    if ck in ['y', 'Y']:

        break

       

N_blocks = int(input('ブロック数(1ブロック=4試行)= '))

 

data = []

 

serA = SubSer3CatFix( series_v=[float(v) for v in series], start_s=len(series)-1, cond='down', ser_id='A')

serB = SubSer3CatFix( series_v=[float(v) for v in series], start_s=0, cond='up', ser_id='B')

 

t = 0

for b in range(N_blocks):

    np.random.shuffle(ser)

    for i in range(4):

        t += 1

        print('\n試行:{0:}/{1:}'.format(t, N_blocks*4))

        if ser[i] == 'A':

            serX = serA

        else:

            serX = serB

           

        print('刺激値:', serX.st_v())

        res = int(input("\n反応(1:小・弱, 2:不明・等しい, 3:大・強)? "))

        data.append([f'{serX.ser_id}{t}', serX.st_v(), res])

        serX.next_state(res)

           

print('{0:>15}{1:>15}{2:>15}'.format('trial', 'stimulus', 'response'))

fout.write('{0:>15}{1:>15}{2:>15}'.format('trial', 'stimulus', 'response'))

 

for i, v in enumerate(data):

    print('{0:>15}{1:>15}{2:>15}'.format(v[0], v[1], v[2]))

    fout.write('\n{0:>15}{1:>15}{2:>15}'.format(v[0], v[1], v[2]))

    plt.plot([i], [v[1]],'^' if v[2] == 3 else 'v' if v[2] == 1 else'o',

             c = 'b' if v[0][0] == 'A' else 'g')

 

plt.plot([], 's', c = 'b', label = 'Ser.A')

plt.plot([], 's', c = 'g', label = 'Ser.B')

plt.plot([], '^', c = 'k', label = '3')

plt.plot([], 'o', c = 'k', label = '2')

plt.plot([], 'v', c = 'k', label = '1')

plt.xlabel('Trial')

plt.ylabel('Comp. St.')

plt.title('Up-down Method')

plt.legend()

plt.savefig('Fig' + fout_nm[:-4] + '.png')

plt.show()

 

print('\nFig' + fout_nm[:-4] + '.png was saved.')

fout.close()

print(fout_nm + '.was saved.\n')

 

 

Up