Chapter 04

【フランカー課題】刺激提示のランダマイズ・自作関数

snishiyama
snishiyama
2022.07.30に更新

本章の目的

前章では 4 種類の文字刺激を素朴に系列提示するコードを書きました。前回の内容では以下の問題が残っていました。

  • コードの冗長性
  • ランダム化や複数回提示

本章ではこれらの問題に対処します。このために,試行変数を生成する関数(function)を自作します。うまく活用すれば簡潔に実験を作成できるようになるので,ぜひこの章で習得しましょう。

関数

関数は JavaScript 自体に用意されている要素で,一連の処理をまとめたものです。関数はconst 関数名 = (引数) => {まとめたい処理}で作成することができます[1]。引数とは{}内の処理で使用したい外部の値のことです。

例えば,引数に与えられた数値を 2 乗する関数 square は以下のように作成できます [2]

const square = (num) => {
  return num * num; // 引数に与えられた数値を2乗する
};

作成後,呼び出すことで{}内の処理が実行されます。計算の結果を変数に格納することもできます。

square(3); // 9
let kekka = square(5); // 計算結果 25 を kekka という変数に格納する

関数定義時に書いてあったreturnは処理の結果を関数の外に吐き出しています。これがないと,処理の結果を得ることができません。

const square = (num) => {
  num * num; // return がない
};
square(3); // undefined になる

定義する関数の動作によりますが,基本的にはreturnが必要だということを覚えておいてください。

試行変数を返す関数を定義する

ここからが本題です。前章で書いたコードでは,新しい試行変数を作成する際に,ほとんど同じ内容にもかかわらず一部が異なるためにすべての設定を書いていました。

const trial1 = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<<<<<', // <-- ここだけ違う
  choices: ['f', 'j'],
  post_trial_gap: 500,
};

const trial2 = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '>>>>>', // <-- ここだけ違う
  choices: ['f', 'j'],
  post_trial_gap: 500,
};

冗長でも動けばいいのだからこれでいいと思う方もいるかもしれません。確かにコードの質はともかくとしてエラーなく動くことは重要です。しかしながら,編集・管理のコストについても考慮するほうがいいでしょう。共通の設定部分を編集する必要が生じた場合を想像してみれば,上記のようなコードの管理・編集コストが高いことがわかると思います。例えば許容するキー入力 choices を別のものに差し替えたいという場合,該当するすべての行を編集する必要があります。ここでは 2 つ(前回の例では 4 つ)なので,それほど大変ではないかもしれませんが,実際の実験でもっと多様な刺激を使用する場合,その数だけの試行変数を作成していたら,それに比例して編集の手間が増加します。

そこで関数の出番です。関数では一連の処理をまとめており,宣言した際の名前を呼び出すだけで,その処理を実行することができます。引数を利用することでその引数ごとの結果を得ることができます。

つまり,この節のタイトルのとおり,stimulusが異なる試行変数を作成する関数を作成・利用すれば,共通の設定を何度も書く必要がなくなります。具体的には以下のような関数を作成します。

const createTrial = (stim) => {
  const trial = {
    type: jsPsychHtmlKeyboardResponse,
    stimulus: stim, // 適宜変更したい箇所
    choices: ['f', 'j'],
    post_trial_gap: 500,
  };
  return trial;
};

この関数 createTrial では試行変数を作成して,それを返しています。関数は引数を一つ受け取り,それを関数内でstimという名前で利用できるようになっています。そして与えられた引数stimは試行変数の設定項目stimulusに指定されています。以下のように,提示したい刺激を引数として与えることで,これまで作成していたような試行変数を作成することができます。

createTrial('<<<<<');
// {
//   type: jsPsychHtmlKeyboardResponse,
//   stimulus: '<<<<<',
//   choices: ['f', 'j'],
//   post_trial_gap: 500,
// }

関数が返す結果を変数に入れれば,たった 1 行で試行変数を作成できます。

const trial1 = createTrial('<<<<<');

試行変数をたった一つ作成するだけであれば恩恵はほとんどありませんが,2 つ以上作成するとなればかなり手間が減るはずです。前回の最後の演習をもう一度取り上げます。関数によって書くコード量がかなり減ることを実感してください。

演習 1 (前回の演習 2 + α)

  • 4 つの刺激 <<<<<, >>>>>, <<><<, >><>> がそれぞれ 1 回ずつ提示されるようにしてみよう
  • さらに, --<--, -->-- も提示されるようにしてみよう
演習 1 のコード例
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <script src="../jspsych/dist/jspsych.js"></script>
    <script src="../jspsych/dist/plugin-html-keyboard-response.js"></script>
    <link rel="stylesheet" href="../jspsych/dist/jspsych.css" />
  </head>
  <body></body>
  <script>
    const jsPsych = initJsPsych();

    const createTrial = (stim) => {
      const trial = {
        type: jsPsychHtmlKeyboardResponse, // 試行のタイプ指定
        stimulus: stim, // 提示する刺激
        choices: ['f', 'j'],
        post_trial_gap: 500,
      };
      return trial;
    };

    const trial1 = createTrial('<<<<<');
    const trial2 = createTrial('>>>>>');
    const trial3 = createTrial('<<><<');
    const trial4 = createTrial('>><>>');
    const trial5 = createTrial('-->--');
    const trial6 = createTrial('--<--');

    jsPsych.run([trial1, trial2, trial3, trial4]);
    // jsPsych.run([trial1, trial2, trial3, trial4, trial5, trial6]);
  </script>
</html>

前回の演習のコード例だと,<script></script> の間の行数が 30 行ほどあったのですが,このコード例は 15 行ほどになりました。--<--, -->-- の追加も考慮すると自作関数はかなり利点があるのが実感できたのではないでしょうか。

刺激提示順のランダマイズ

刺激の提示順をランダムにすることは心理学実験でよくある操作です。今回の実験作成方法であれば,刺激は試行変数に組み込まれていますので,試行の実施順をランダムにすれば,刺激の提示順をランダムにすることができます。

試行の実施順はjsPsych.run()の引数に与えられた配列によって決まります。具体的には,左から順に実施されます。したがって,配列内の要素の並び順をランダムにすることができれば実施順もランダムになります。

jsPsych.run([trial1, trial2, trial3, trial4]);

本題に入る前に一つ補足です。jsPsych.run()の引数に指定する配列は予め変数に格納しておくことができます。

const trials = [trial1, trial2, trial3, trial4];
jsPsych.run(trials);

変数の宣言の仕方は試行変数の場合と同じです。= の右辺にjsPsych.run()の引数にしていた配列が指定されています。その代わりに,jsPsych.run()の引数には宣言しておいた変数trialsを入れています。

それでは本題です。配列の要素の並び順をランダマイズするには,jsPsych.randomization.repeat() という関数を使うのが楽です。以下のように使います。

const trials = [trial1, trial2, trial3, trial4];
const trialsRandom = jsPsych.randomization.repeat(trials, 2);
jsPsych.run(trialsRandom);

jsPsych.randomization.repeat() には 2 つの引数を指定します[3]。一つ目の引数に,ランダマイズしたい配列を指定します。2 つ目の引数には要素を繰り返す回数を指定します。ここでは 2 つ目の引数に2を指定しているので,それぞれの要素を 2 つにした上で,全体をシャッフルしています。つまり,4 種類の試行が2回ずつ計 8 試行がランダムな順で実施されます。第2引数によって好きに試行数を変更することができます。

この関数は新たな配列を返すので, trialsRandomという変数に格納しています。jsPsych.run()の引数に指定する変数名も変え忘れないように注意してください。

関数呼び出しを1回で済ませる(発展)

本章では,刺激それぞれをcreateTrialの引数に入れて試行変数を作成し,試行変数の配列を作成して,それをランダマイズしました。とはいえ,この方法も刺激の数だけ関数呼び出しをコピペして,結果の変数を配列に加えるという作業をしている(演習 1 を参照)ので,刺激の数が増えてくると手段として苦しくなってきます。

配列のmap()メソッドを利用すると,手間(とプログラムの行数)が少し減ります。 map()メソッドは,引数に関数を取り,配列の各要素に対して順番にその関数を適用し,各結果の値を配列で返します。

例えば,以下のように使います。

const numbers = [1, 2, 3];
const squares = numbers.map((x) => square(x)); // square関数は本章の最初で定義した関数

squaresには配列[1, 4, 9]が格納されています。map()の引数がわかりにくいですが,よく見ると() =>というように,今回紹介した関数定義の文法が登場しています。ここでは,配列numbersの各要素を引数xとして受け取る関数がワンポイントで定義されています(即時関数といいます)。そして,その関数の処理内容(=>の先)が,square(x)になっています。このようにして,map()の引数として指定された即時関数によって,配列の各要素が 2 乗されます。

それでは,本章のコードをmap()メソッドを用いたものに変更すると,以下のようになります。変更点(&全体像)がわかりやすいように,1 行目initJsPsych()から記載しています。

const jsPsych = initJsPsych();

const createTrial = (stim) => {
  const trial = {
    type: jsPsychHtmlKeyboardResponse,
    stimulus: stim,
    choices: ['f', 'j'],
    post_trial_gap: 500,
  };
  return trial;
};

const stimuli = ['<<<<<', '>>>>>', '<<<><<', '>><>>']; // <-- ココ!!
const trials = stimuli.map((stim) => createTrial(stim)); // <-- ココ!!
const trialsRandom = jsPsych.randomization.repeat(trials, 2);
jsPsych.run(trialsRandom);

なんと全体を 15 行ほどで書けてしまいました。ポイントはconst stimuli = ...(下から 4 行目)とconst trials = ... (下から 3 行目)の 2 行です(これら以外の行はこれまで例示してきたとおりです)。

まず,下から 4 行目で刺激の配列stimuliを作成しています。続けてmap()メソッドを用いて刺激配列の各要素に対してcreateTrial()を適用し,試行変数の配列を作成しています。

このようにしておくと,刺激が増減したときに変更する部分が,刺激配列の宣言部分(=下から 4 行目)のみになります。刺激の増減があると,コードを修正する必要があるのは仕方がないことです。修正する箇所が減るということは,手間が減るだけでなく,ミスする可能性も小さくすることができます。それは今回のテーマである自作関数の作成によってある程度達成できましたが,map()のようなものを使うなどして,コピペの部分を減らしていくことを徹底してみてもいいかもしれません。

おわりに

本章では自作関数を使って刺激系列を提示する方法を解説しました。系列のランダム化なども考慮すると,前回のコードと比べてかなり見やすく編集の容易なコードになったと思います。ご自身の実験コードを作成する際にもぜひ活用してみてください。

今回の内容でフランカー課題はだいたい完成しました。次章以降では,ここまでの作成した課題を実験として利用できるようさらにブラッシュアップさせていきます。

脚注
  1. JavaScript では関数を作成する方法がいくつかあります。厄介なことに,その作成方法によって定義された関数の利用方法も変わってきます。この記事で紹介しているのは,アロー関数というものです。詳しくは このサイト などを参考にしてください。 ↩︎

  2. 引数に数値以外が与えられたときのエラー処理がないなどありますが,簡単のため省略します。 ↩︎

  3. 実際は引数を 3 つ受け取ることができますが,3 つ目の引数は今回の目的には関係ないので省略しています。 ↩︎