Chapter 07

jsPsychにおけるデータの取り扱い:オブジェクト

snishiyama
snishiyama
2022.07.31に更新

はじめに

実験を実施する上で,各試行の情報や遂行成績を保存することは非常に重要です。これまでの章ではデータの保存について触れてきませんでした。しかしながら実は,jsPsych は内部で自動的にある程度の情報を保存してくれています。その一方で,jsPsych が保存している情報は必ずしも十分ではない(あるいは後処理が大変な)ため,実験作成者が保存される情報をアレンジする必要があります。

本章ではまず,どのような情報がどのような形式で保存されているのかについて説明します。そしてアレンジするために必要な JavaScript の文法知識を紹介します。最後に,簡単な例で,保存データの編集に取り組みます。

本章の内容は,手を動かすというよりは覚えるという側面が強いので,これまでよりも大変かもしれませんが,本章の内容は jsPsych の動作自体の理解にも深く関わってきますので,頑張って取り組んでください。

jsPsych でのデータ扱われ方

jsPsych が内部でどのようにデータを保管しているのかを知るために,これまでのコードの最初に宣言していたinitJsPsych()を次のように変更してください。

const jsPsych = initJsPsych({
  on_finish: () => {
    jsPsych.data.displayData();
  },
}); // <-- 波括弧を忘れない! 括弧の始まりにも!

この変更の説明はさておき,変更の上,html を開いて実験を最後まで遂行してみてください。すると,一番最後に以下のようなものが表示されたと思います(以下の出力は注視点なしで 2 試行だけ実施した場合のものです)。

[
  {
    "rt": 2578,
    "stimulus": "<p style=\"font-size: 48px\">>>>>></p>",
    "response": "j",
    "trial_type": "html-keyboard-response",
    "trial_index": 0,
    "time_elapsed": 2580,
    "internal_node_id": "0.0-0.0"
  },
  {
    "rt": 669,
    "stimulus": "<p style=\"font-size: 48px\"><<><<</p>",
    "response": "j",
    "trial_type": "html-keyboard-response",
    "trial_index": 1,
    "time_elapsed": 3750,
    "internal_node_id": "0.0-1.0"
  }
]

表示されている情報を見てみると,rt, stimulus, responseなどとあり,その右側にそれらに対応してそうな値が表示されています。実際に,これらは実験のデータで,みなさんが(上の内容は筆者が)取り組んだデータになります。

先ほどのコードの追加は,実験の終了時に(on_finish)データを画面に表示するようにするものでした。関数名からもなんとなく察せられたのではないでしょうか(on_finishの指定の仕方など細かいことはとりあえず脇においてください)。

さて,波括弧{}のあとにname: value という構造はどうもこれまで散々書いてきた試行変数やブロック変数に似ています。それもそのはずで,試行変数も jsPsych の内部で取り扱われているデータも JavaScript のオブジェクトというデータ型を用いています[1]

jsPsych は反応時間rtや反応キーresponseなどある程度の情報を保存してくれていますが[2],実験によっては,jsPsych が自動で保存してくれるデータ以外にも,例えば正誤のようなデータを追加したいということがあると思います(今回の題材のフランカー課題もそうです)。少しコードを書けば,実験作成者が任意のデータを追加することができるようになるのですが,その書き方を理解するためには,オブジェクトについて知っておく必要があります。

オブジェクト

オブジェクトは,キー(key)と値(value)からなるデータ型です。連想配列と呼ばれることもあります。数値,文字列,真偽値以外のほとんどがオブジェクトです。そして,jsPsychで作っている変数のほとんどがオブジェクトです{キー: 値}というふうにしてオブジェクトを作成することができます。キーには文字列を使用します[3]が,値には様々なデータ型をとることができます。複数のキーおよび値を持つことができます。こういったオブジェクトのキー・値の組をプロパティ,特にキーのことをプロパティ名と呼ぶこともあります。

const obj1 = { a: 1 }; // キーの文字列にはクオーテーションはなくてもよい(あってもエラーにはならない)
// 値にはいろんなデータ型が使える
const obj2 = {
  key1: 1, // 数値
  key2: 'あ', // 文字列
  key3: true, // 真偽値
  key4: ['a', 'b', 'c'], // 配列
  key5: { hoge: 'geho' }, // オブジェクト
};

上述しましたが,obj2のような変数の宣言はjsPsychの試行を作成する際の宣言と同様です。

const trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<<<<<',
  choices: ['f', 'j'],
  post_trial_gap: 500,
};

const block = {
  timeline: [trial],
};

つまり,jsPsychの試行変数や,試行の系列・ブロックのための変数は,それだけではなんの動作もしないただのオブジェクトということです。これらのオブジェクトはjsPsych.run()という関数に入れることで心理学実験のように動作します。

ただし,試行変数などそれ自体はただのオブジェクトとはいえ,適当なプロパティ名を使用していいわけではありません。jsPsych.run()が適切に動作して意図した実験が実行されるためには,公式リファレンスで指定されているプロパティ名やデータ型を使用する必要があります。

オブジェクトのプロパティの値は,オブジェクト.プロパティ名 あるいは オブジェクト["プロパティ名"]で取得することができます。obj2key4key5のように値が配列やオブジェクトになっている場合は,それぞれに対応する値の取り出し方を利用すれば,続けて値を取り出すことができます。取り出した値を変数に格納することもできます。

obj2.key1; // 1
obj2['key4']; // ['a', 'b', 'c']
obj2.key5.hoge; // 'geho'
const hohoho = obj2['key5']['hoge'];

また,値の呼び出し方に少し変更を加えるだけで,プロパティの値を更新することができます。具体的にはオブジェクト.プロパティ名 = 値 あるいは オブジェクト["プロパティ名"] = 値とします。もし存在しないプロパティ名を指定した場合は,新しいプロパティを追加します。

obj1.b = 2; // 追加 obj1 は { a: 1, b: 2} になる
obj1.b = 100; // 更新 obj1 は { a: 1, b: 100} になる
obj1['b'] = 'あ'; // 更新 obj1 は { a: 1, b: 'あ'}

したがって,以下のような試行変数の宣言も可能です。可能というだけであって,する必要は全く無いです。

const trial = {};
trial.type = jsPsychHtmlKeyboardResponse;
trial.stimulus = 'hello world!';
trial.choices = ['f', 'j'];

試行のデータにアクセスする

冒頭でも説明したとおり,jsPsych では内部でデータをオブジェクト型で取り扱っています。そして,オブジェクトの扱い方は直前の節で紹介しました。最後に,内部で自動的に取り扱われているデータにアクセスしてみましょう。簡単な例として,この節では,jsPsych が自動で収集した反応時間にアクセス・編集した値を保存データに追加することに取り組みます。

以下のコードを例に話を進めます。よければ新しいファイルを作ってください。同じ刺激が 2 度繰り返し提示されるだけのコードです。

<!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({
      on_finish: () => {
        jsPsych.data.displayData();
      },
    }); // <-- 波括弧を忘れない! 括弧の始まりにも!

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

    jsPsych.run([trial, trial]);
  </script>
</html>

ここでは 2 試行実施されますが,試行のデータにアクセス・編集するには試行変数trialを以下のように変更します。

const trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '>>>>>',
  choices: ['f', 'j'],
  post_trial_gap: 500,
  on_finish: (data) => {
    data.newData = data.rt / 1000;
  },
};

変更の上,実行してみると,最後に以下のようなデータが表示されます。

[
  {
    "rt": 1175,
    "stimulus": ">>>>>",
    "response": "j",
    "trial_type": "html-keyboard-response",
    "trial_index": 0,
    "time_elapsed": 1176,
    "internal_node_id": "0.0-0.0",
    "newData": 1.175
  },
  {
    "rt": 561,
    "stimulus": ">>>>>",
    "response": "f",
    "trial_type": "html-keyboard-response",
    "trial_index": 1,
    "time_elapsed": 2239,
    "internal_node_id": "0.0-1.0",
    "newData": 0.561
  }
]

on_finish を設定しなかった場合の出力と比べてみると明らかですが,各試行のデータオブジェクトの最後にnewDataというプロパティが追加されていて,その値は,rtプロパティの値を 1000 で割った値になっています。

on_finishに設定した内容のおかげで新しい列が追加されています。具体的には,on_finishには引数を一つ受け取れる関数が指定されています。

(data) => {
  data.newData = data.rt / 1000;
};

on_finishは試行の終了時に動作します。引数を受け取れる関数を指定していた場合,その引数にその試行のデータが保存されたオブジェクトを渡します。今回の実装では与えられた引数をdataという変数名で{}内で利用できるようにしています。

そして,オブジェクトdatartプロパティにアクセスして,その値を 1000 で割った値をnewDataというプロパティ名をつけて data に新しく追加しています。

引数を受け取れる関数を指定していた場合,その引数にその試行のデータが保存されたオブジェクトを渡します。

このことがどういうことかよくわからないかもしれませんが,jsPsych 本体の動作に関わることなので,その説明には JavaScript の知識やそれなりの文量が必要になります。ひとまず,引数をひとつ受け取れる関数をon_finishに指定しておけば,その関数内で試行のデータを取り扱えるし,データを追加したりできるんだなと思っていてもらいたいです。もちろん,JavaScript が読める方は,jsPsych のソースコードを読めばより理解が深まるでしょう。

演習

  • これまで作成してきたメインのフランカー課題のコードに上記の変更を加えよう。
コード例
<!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({
      on_finish: () => {
        jsPsych.data.displayData();
      },
    }); // <-- 波括弧を忘れない! 括弧の始まりにも!

    const createTrial = (stim, fontSize) => {
      const trial = {
        type: jsPsychHtmlKeyboardResponse,
        stimulus: `<p style="font-size: ${fontSize}px">${stim}</p>`,
        choices: ['f', 'j'],
        post_trial_gap: 500,
        on_finish: (data) => {
          data.newData = data.rt / 1000; // <-- ココ!!!
        },
      };
      return trial;
    };

    const fixation = {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: '<p style="font-size: 48px">+</p>',
      choices: 'NO_KEYS',
      trial_duration: 500,
    };

    const createBlock = (stim, fontSize) => {
      const trial = createTrial(stim, fontSize);
      const block = {
        timeline: [fixation, trial],
      };
      return block;
    };

    // フォントサイズの指定は実用的ではないですが...
    const block1 = createBlock('<<<<<', 48);
    const block2 = createBlock('>>>>>', 48);
    const block3 = createBlock('<<><<', 32);
    const block4 = createBlock('>><>>', 30);

    const blocks = [block1, block2, block3, block4];
    const blocksRandom = jsPsych.randomization.repeat(blocks, 2);

    jsPsych.run(blocksRandom);
  </script>
</html>

おわりに

本章では,jsPsych のデータが内部では JavaScript のオブジェクト型で取り扱われていることを踏まえ,オブジェクト型の扱い方について紹介しました。また最後には試行のデータにアクセスして簡単な処理をすることにも取り組みました。

値を 1000 で割るというのは実用的ではありませんでした。次章では,より実用的な内容として,各試行の反応の正誤を保存データに追加する方法を紹介します。

脚注
  1. displayData()で表示されているのは,実際は json と呼ばれるデータ形式です。json は JavaScript Object Notation の略称ですが,JavaScript 内部のオブジェクトとは若干違います。ややこしいですが,jsPsych の内部ではデータはオブジェクト型ですが,画面に出力するときに,json 形式に変換しています。 ↩︎

  2. 試行変数のtype (=プラグイン)によって保存されるデータは異なります。適宜,データを出力してみたり,公式リファレンスを参照したりしてください。 ↩︎

  3. 数値もキーとして使用できますが,値を取り出すときに[]を使った方法しか使用できないので注意が必要です。シンボルというデータ型もキーに使用できますが,今回の記事ではそもそもシンボル型自体を紹介していないので,説明は割愛します。 ↩︎