2021/09/24
本チュートリアルの記事の内容を修正等したものを https://zenn.dev/snishiyama/books/jspsych-tutorial でオンラインブックとしてまとめ直しました。最新のjsPsychにも対応していますので,ぜひリンク先の書籍をご覧ください。なお,本記事はjsPsych v6.2.x
系まででの実験作成チュートリアルとしてそのまま残しておきます。
2021/03/15 補足
2021 年 2 月末くらいにリリースされたv6.3.0
になってsurvey-text
やsurvey-multi-choice
などsurvey-
系のプラグインで得られた反応を利用するための処理が少し変わりました。
-
v6.3.0
では,survey-text
などsurvey-
系のプラグインで取得したデータを取り出す際に,JSON.parse
を使う必要がなくなりました。 - また,
変数名.responses
ではなく,変数名.response
で得られたデータを参照するようになりました(複数形の s がいらない)。
まとめると,以下のサンプルコードでpar_info.id = JSON.parse(data.responses).participantID
となっていたものが,par_info.id = data.response.participantID
で良くなります(※未検証)。最新版をダウンロードしてこのチュートリアルに望んでいる場合は,その点に留意して以下の内容を読んでください。そのうちちゃんと修正しますが,ひとまずアナウンスにてご了承ください。その他重大な変更点については全体のまとめを参照してください。
はじめに
本記事は,「jsPsychによる心理学実験作成チュートリアル」の第6回の記事です。第5回では実験データの保存方法を紹介しました。今回は,参加者情報の取得について紹介します。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
参加者情報の取得
さっそく参加者情報を取得していきましょう。今回のチュートリアルで収集する情報は,参加者ID,年齢,性別の3つとします。参加者IDと年齢はjspsych-survey-text
,性別はjspsych-survey-multi-choice
というプラグインを使って収集していきます。まずは以下のコードを実行してみてください。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> <!-- これを足す -->
<script src="../jspsych.js"></script>
<script src="../plugins/jspsych-survey-text.js"></script>
<script src="../plugins/jspsych-survey-multi-choice.js"></script>
<link rel="stylesheet" href="../css/jspsych.css"></link>
</head>
<body></body>
<script>
var par_id = {
type: 'survey-text',
questions: [
{prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'},
],
button_label: '次へ',
};
var age = {
type: 'survey-text',
questions: [
{prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'},
],
button_label: '次へ',
};
var gender = {
type: 'survey-multi-choice',
questions: [
{prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'},
],
button_label: '次へ',
};
jsPsych.init({
timeline: [par_id, age, gender],
on_finish: function() {
jsPsych.data.get().localSave('csv', 'data.csv');
},
});
</script>
</html>
全部で3ページの実験(?)プログラムが実行されたはずです。まず,本題とは逸れますが,質問文に日本語を使っているので,<head>
の直後に<meta charset="utf-8">
が挿入されています(第1回参照)。
さて,本題です。survey-text
とsurvey-multi-choice
のどちらもquestions:
がメインの設定項目になります。どちらもquestions:[]
と[]
内に質問の内容や解答欄・選択肢を設定するための連想配列を持ってきますが,その連想配列内で設定できる内容がtext
とmulti-choice
で少し異なります。
survey-text
の場合,columns
という設定ができます。これは,回答を入力するためのテキストボックスの幅を決める項目で,数字が大きいほど幅が広くなります。適宜,想定される回答の長さに応じて変更してください。
survey-multi-choice
の場合,options
という設定ができます。これは,選択肢を指定する項目で,例のように,配列を使います。また,horizontal
という設定もあります。これは選択肢を横(horizontal
)に並べるか,縦に並べるかどうかを指定できます。horizontal: true
で横に並べることができます。また,この項目を設定しなければ,jsPsychは自動的に選択を縦に並べます。
残りは共通の設定で,prompt
には質問の内容,required
では回答を必須とするかどうか,name
には回答がデータに保存されるときの名前を指定しています。required
で回答を必須にしていないと無回答で次に進めてしまうので,参加者情報を集める場合に限っては常にtrue
でいいでしょう。
なお,questions
は複数形になっている通り,[]
内に質問用の連想配列を複数持ってくることができます。その場合,1つのページに2つ以上の質問を提示することができます。適宜利用してください。本チュートリアルでは参加者IDと年齢を尋ねる質問は別々のページで提示することにします。
var parID_age = {
type: 'survey-text',
questions: [
{prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'},
{prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'},
],
button_label: '次へ',
};
回答の保存方法をアレンジする
次に,回答が保存されたファイルを確認してみましょう。responses
という列に回答が保存されていますが,困ったことに{"participantID":"kla"}
のように連想配列の形式で保存されてしまっています。これでは分析しにくいので,回答の部分だけを取り出して別の列に保存する方法を考えていきましょう。
個人的に一番シンプルだと思う方法は,回答が終了したタイミングで反応を取り出しておくというものです。前回,on_finish
という項目を設けて,on_finish: function(data) {...}
とすることで,各試行の終了時にその試行で得られたデータにアクセスし,回答の正誤などを判定する方法を紹介しました。これと同様の方法を用います。具体的には,以下のようにします。
var par_info = {};
var par_id = {
type: 'survey-text',
questions: [
{prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'},
],
// ここから
on_finish: function(data) {
par_info.id = JSON.parse(data.responses).participantID
}
// ここまで
};
まず,JSON.parse(data.responses).participantID
について説明します。data.列名
で保存された特定のデータにアクセスできることは前回紹介したとおりです。先ほど保存されたファイルを確認した際に,回答はresponses
という名前の列に保存されていたので,data.responses
で回答を取り出すことができます。しかしながら,この時点ではdata.responses
に保存されている回答はjsonという形式になっています。これをjavascriptの連想配列に変換するために,JSON.parse()
を使います。ここでようやく{participantID:"kla"}
という連想配列が得られます。連想配列.キー名
でそのキーに対応する値を取り出すことができるので,.participantID
で"kla"
という文字列を得ることができます。このキー名は,質問項目の設定name:
で指定した文字列に一致します。
次に,par_info.id =
について説明します。ここでは,参加者IDを事前に作った連想配列var par_info = {}
に一時保存しています。前回の記事では,on_finish: function(data) {...}
の中でdata.新しい列名 = 値
とすることで,任意の値を新しい列に保存できることを紹介しました。同様に今回も,data.id = JSON.parse(...)
として保存しても構わないのですが,その場合参加者IDはたった1行にしか保存されません(試してみてください)。参加者ID,性別などの参加者情報はデータのすべての行にあるほうが分析がしやすい場合が多いので,実験の最後に「すべての行に参加者情報を保存する」という処理が実行できるように,ここでは別の連想配列に一時保存しています。
上記のコードを少しずつ変更して,他の参加者情報も一時保存するコードは以下のようになります。ここでは合わせて,最後に一時保存した参加者情報をすべての行に追加して保存しています。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> <!-- これを足す -->
<script src="../jspsych.js"></script>
<script src="../plugins/jspsych-survey-text.js"></script>
<script src="../plugins/jspsych-survey-multi-choice.js"></script>
<link rel="stylesheet" href="../css/jspsych.css"></link>
</head>
<body></body>
<script>
var par_info = {};
var par_id = {
type: 'survey-text',
questions: [
{prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'},
],
button_label: '次へ',
on_finish: function(data) {
par_info.id = JSON.parse(data.responses).participantID // 一時保存
}
};
var age = {
type: 'survey-text',
questions: [
{prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'},
],
button_label: '次へ',
on_finish: function(data) {
par_info.age = JSON.parse(data.responses).age // 一時保存
}
};
var gender = {
type: 'survey-multi-choice',
questions: [
{prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'},
],
button_label: '次へ',
on_finish: function(data) {
par_info.gender = JSON.parse(data.responses).gender // 一時保存
}
};
jsPsych.init({
timeline: [par_id, age, gender],
on_finish: function() {
jsPsych.data.addProperties(par_info); // すべての行に追加
jsPsych.data.get().localSave('csv', 'data.csv');
},
});
</script>
</html>
終盤にあるjsPsych.data.addProperties()
という関数が,すべての行に参加者情報を追加している部分になります。保存されたcsvファイルを確認すると,par_info.id
, .age
, .gender
のそれぞれid
, age
, gender
が列名になっていて,それらの列のすべての行に,回答が保存されていると思います。
timeline_variablesを使ってみる(余談)
先ほどのコードでは,参加者情報を集めるためにpar_id
,age
,gender
と3つの試行変数を作りました。しかし,それぞれの中身の共通部分は多いので,timeline_variables
を使ってまとめてしまいましょう。紹介はしますが,必須ではないです。
コード例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> <!-- これを足す -->
<script src="../jspsych.js"></script>
<script src="../plugins/jspsych-survey-text.js"></script>
<script src="../plugins/jspsych-survey-multi-choice.js"></script>
<link rel="stylesheet" href="../css/jspsych.css"></link>
</head>
<body></body>
<script>
var question_settings = [
{type: 'survey-text', colname: 'ID', question: {prompt: '参加者IDを入力してください', columns: 10, required: true}},
{type: 'survey-text', colname: 'age', question: {prompt: '年齢を入力してください', columns: 3, required: true}},
{type: 'survey-multi-choice', colname: 'gender', question: {prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true}},
];
var par_info = {};
var get_info = {
type: jsPsych.timelineVariable('type'),
questions: [jsPsych.timelineVariable('question')],
button_label: '次へ',
on_finish: function(data) {
var colname = jsPsych.timelineVariable('colname', true);
par_info[colname] = JSON.parse(data.responses).Q0
}
};
jsPsych.init({
timeline: [{
timeline_variables: question_settings,
timeline: [get_info],
}],
on_finish: function() {
jsPsych.data.addProperties(par_info); // すべての行に追加
jsPsych.data.get().localSave('csv', 'data.csv');
},
});
</script>
</html>
type
,questions
の中身,colname
(保存時の列名)が質問内容によって変わるので,各ページでそれらが変更されるように,question_settings
というtimeline_variables
を作っています。一つ前の例のコードと見比べてどのようにjsPsych.timelineVariable
を使っているかを確認してください。
2点,これまで説明していなかったものがあるので,それについて説明します。どちらもpar_info[colname] = JSON.parse(data.responses).Q0
に関わるものです
-
par_info[colname] =
とあるように,連想配列への要素の追加はpar_info.キー名
だけでなく,par_info[キー名]
でも行うことができます。 - 各質問内容の設定(
question_settigns
のquestion:
)でname:
を指定していません。指定しない場合,デフォルトの設定が反映され回答は{Q0: 回答}
と保存されます。そうすれば,どの質問でも,JSON.parse(data.responses).Q0
で回答を取り出せるようになります。name:
を指定してしまうと,この.Q0
の部分が指定された名前に変更されてしまうため,ここにもjsPsych.timelineVariable()
を使う必要がでてきます。
これまでのコードに取り入れる
最後に,今回の記事で紹介したコードをこれまでのコードと合体させます。基本的には付け足していけばいいですが,jsPsych.init({})
内にあるtimeline:[]
の部分には少し変更が必要です。前回までの記事では,以下のようにしていました。
jsPsych.init({
timeline: [{
timeline_variables: trial_types,
timeline: [trial],
}],
// 以下省略...
})
ここに,par_id
, age
, gender
を追加していく必要があります。以下のようにすればいいのですが,
jsPsych.init({
timeline: [par_id, age, gender, {
timeline_variables: trial_types,
timeline: [trial],
}],
// 以下省略...
})
このままだと外側のtimeline:[]
の中身が見にくくなってしまいます。実は,{timeline_variables: ..., timeline: ...}
の部分は事前に別のところで変数として作っておくことができます。この部分は,サイモン課題に対応しているので,下の例ではsimon
という名前をつけています。
var simon = {
timeline_variables: trial_types,
timeline: [trial],
}
jsPsych.init({
timeline: [par_id, age, gender, simon],
// 以下省略...
})
これで,実験の流れがかなり見やすくなったと思います。すべてまとめたコードは以下を参照してください。
コード例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../jspsych.js"></script>
<script src="../plugins/jspsych-html-keyboard-response.js"></script>
<script src="../plugins/jspsych-survey-text.js"></script>
<script src="../plugins/jspsych-survey-multi-choice.js"></script>
<link rel="stylesheet" href="../css/jspsych.css"></link>
<style>
.text_left {
position: absolute;
left: 40%;
top: 50%;
transform: translateY(-50%) translateX(-50%);
-webkit-transform: translateY(-50%) translateX(-50%);
}
.text_right {
position: absolute;
right: 40%;
top: 50%;
transform: translateY(-50%) translateX(50%);
-webkit-transform: translateY(-50%) translateX(50%);
}
</style>
</head>
<body></body>
<script>
// 参加者情報の取得
var par_info = {};
var par_id = {
type: 'survey-text',
questions: [
{prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'},
],
button_label: '次へ',
on_finish: function(data) {
par_info.id = JSON.parse(data.responses).participantID // 一時保存
}
};
var age = {
type: 'survey-text',
questions: [
{prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'},
],
button_label: '次へ',
on_finish: function(data) {
par_info.age = JSON.parse(data.responses).age // 一時保存
}
};
var gender = {
type: 'survey-multi-choice',
questions: [
{prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'},
],
button_label: '次へ',
on_finish: function(data) {
par_info.gender = JSON.parse(data.responses).gender // 一時保存
}
};
// サイモン課題
var trial_types = [
{letter: 'L', pos: 'left', cond: 'cong'},
{letter: 'R', pos: 'left', cond: 'incong'},
{letter: 'L', pos: 'right', cond: 'incong'},
{letter: 'R', pos: 'right', cond: 'cong'},
];
var trial = {
type: 'html-keyboard-response',
stimulus: function() {
return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>'
},
choices: ['f', 'j'], // 入力キーの指定
trial_duration: 1000, // 試行の持続時間
data: {
letter: jsPsych.timelineVariable('letter'),
pos: jsPsych.timelineVariable('pos'),
cond: jsPsych.timelineVariable('cond')
},
on_finish: function(data) {
data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press)
if (data.letter == 'L') {
data.correct = Number(data.key == 'f')
} else {
data.correct = Number(data.key == 'j')
}
}
}
var trial_types = jsPsych.randomization.repeat(trial_types, 2);
var simon = {
timeline_variables: trial_types,
timeline: [trial],
}
jsPsych.init({
timeline: [par_id, age, gender, simon],
default_iti: 250,
on_finish: function() {
jsPsych.data.addProperties(par_info);
jsPsych.data.get().localSave('csv', 'data.csv');
},
});
</script>
</html>
おわりに
今回は参加者情報の取得について紹介しました。次回は課題の教示(と注視点)です。