跳转至

数据存储、汇总和操作

jsPsych中的数据:永久和暂时的数据

有两种数据存储方式:将数据存储在内存中和将数据永久保存。永久保存的数据在浏览器关闭后仍然存在,一般是保存在数据库或者是文件中。保存在内存中的数据只在运行jsPsych的浏览器窗口没有关闭时存在。

jsPsych有很多和内存中数据交互的功能,但和永久存储的数据的交互功能很少。事实上,我们就是这样设计的,因为永久存储数据的方式多种多样,我们不能限制你使用其中某一种。不过,永久存储数据显然是实验很重要的一部分,因此本页的第二部分会就永久存储数据给出一些建议。

在jsPsych的数据结构中存储数据

jsPsych内置了一个数据集,随着实验运行不断变化。每一个试次都会向该数据集添加数据,我们可以通过很多函数,如jsPsych.data.get()(会返回全部数据),访问数据。

在大多数情况下,收集数据的过程是自动且不可见的。插件自身会对数据进行存储,所以一般情况下和数据唯一的交互是在实验结束的时候,这时候可以将数据永久保存下来(详见后文)。但是,有些时候我们也需要一些和数据额外的交互,尤其是我们想要记录插件默认情况下不记录的数据,如被试编号或分组。此时,我们可能需要对每个试次添加数据。例如,在使用Stroop范式时,我们可能需要对试次的一致与不一致进行标记。这些情况会在后文进行说明。

给所有试次添加数据

很多时候,我们需要给实验的所有试次添加数据,如,给每个试次添加被试的ID。我们可以通过jsPsych.data.addProperties()函数实现这一功能。示例如下:

// generate a random subject ID with 15 characters
var subject_id = jsPsych.randomization.randomID(15);

// pick a random condition for the subject at the start of the experiment
var condition_assignment = jsPsych.randomization.sampleWithoutReplacement(['conditionA', 'conditionB', 'conditionC'], 1)[0];

// record the condition assignment in the jsPsych data
// this adds a property called 'subject' and a property called 'condition' to every trial
jsPsych.data.addProperties({
  subject: subject_id,
  condition: condition_assignment
});

给部分试次添加数据

我们可以通过试次的data参数向某个试次中添加数据。data参数是一个对象,对象中的每个键-值对都会添加到该试次存储的数据中。

var trial = {
  type: 'image-keyboard-response',
  stimulus: 'imgA.jpg',
  data: { image_type: 'A' }
}

如果使用了嵌套的时间线,这样声明的数据也会被保存。

var block = {
  type: 'image-keyboard-response',
  data: { image_type: 'A' },
  timeline: [
    {stimulus: 'imgA1.jpg'},
    {stimulus: 'imgA2.jpg'}
  ]
}

试次的数据对象也可以在on_finish中进行更新。我们可以覆盖数据中的属性值。如果需要记录的数据与试次的进行情况有关,可以使用这种方式。

var trial = {
  type: 'image-keyboard-response',
  stimulus: 'imgA.jpg',
  on_finish: function(data){
    if(jsPsych.pluginAPI.compareKeys(data.response, 'j')){
      data.correct = true;
    } else {
      data.correct = false;
    }
  }
}

汇总、操作jsPsych记录的数据

我们使用jsPsych.data.get()访问数据时,会返回一个数据集对象,包含了一系列汇总、操作数据的方法。完整的方法参见 数据模块的文档

下面是对数据集对象进行操作的一些示例。

所有image-keyboard-response插件产生的数据:

var data = jsPsych.data.get().filter({trial_type: 'image-keyboard-response'});

所有categorize-image插件产生的数据中被试反应正确的那部分:

var data = jsPsych.data.get().filter({trial_type: 'categorize-image', correct: true});

所有反应时范围在100 - 500ms之间的数据:

var data = jsPsych.data.get().filterCustom(function(x){ return x.rt >= 100 && x.rt <=500 });

连续进行筛选,选取特定插件产生的反应时高于100ms的数据:

var data = jsPsych.data.get().filter({trial_type: 'image-keyboard-response'}).filterCustom(function(x){ return x.rt > 100; });

获取最后n个试次产生的数据:

var n = 3;
var data = jsPsych.data.get().last(n);

获取最后n个被试反应正确的试次的数据:

var n = 3;
var data = jsPsych.data.get().filter({correct: true}).last(n);

获取前n个试次的数据:

var n = 3;
var data = jsPsych.data.get().first(n);

对数据集中的试次进行计数:

var count = jsPsych.data.get().filter({correct: true}).count();

从数据集中选取出反应时数据:

var response_times = jsPsych.data.get().select('rt');

计算数据集中反应时数据的描述统计值:

jsPsych.data.get().select('rt').mean();
jsPsych.data.get().select('rt').sum();
jsPsych.data.get().select('rt').min();
jsPsych.data.get().select('rt').max();
jsPsych.data.get().select('rt').variance();
jsPsych.data.get().select('rt').sd();
jsPsych.data.get().select('rt').median();
jsPsych.data.get().select('rt').count();

将数据保存为文件

这是把数据保存在运行实验的服务器上最简单的方式之一,我们可以通过PHP和JavaScript代码实现这一功能。这个方法可以将被试的数据保存为CSV文件并存储在服务器上。只有把试验运行在一个装有PHP环境的服务器或本地服务器(如,XAMPP)时这个方法才会生效

我们用PHP将文件写入服务器:

<?php
$post_data = json_decode(file_get_contents('php://input'), true); 
// the directory "data" must be writable by the server
$name = "data/".$post_data['filename'].".csv"; 
$data = $post_data['filedata'];
// write the file to disk
file_put_contents($name, $data);
?>

file_put_contents($filename, $data)方法需要写入新文件的权限。解决这个的简单办法是在服务器上创建一个用于存放数据的文件夹,然后用chmod命令给所有用户往该路径写入的权限。在上面的示例中,使用了data/文件夹存储文件。

使用上面的PHP代码时,我们还需用在jsPsych的代码中向服务器发送filenamefiledata信息。我们通过AJAX实现这一功能。

function saveData(name, data){
  var xhr = new XMLHttpRequest();
  xhr.open('POST', 'write_data.php'); // 'write_data.php' is the path to the php file described above.
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.send(JSON.stringify({filename: name, filedata: data}));
}

// call the saveData function after the experiment is over
jsPsych.init({
   // code to define the experiment structure would go here...
   on_finish: function(){ saveData("experiment_data", jsPsych.data.get().csv()); }
});

在真正做实验时,我们需要把文件名和被试编号这种独特的标识符联系起来,否则新的数据可能会覆盖之前存储的文件。

注意,根据文件的权限,这样保存CSV文件可能所有人都能访问。一种解决方案是把CSV文件存储在web路径以外的路径下。此时,我们需要把上面的PHP代码中的路径从/data改为无法通过网络直接访问的文件夹。只有在我们有web路径以外路径的访问权限时才可以采用这种方式。

将数据存储在MySQL数据库中

一种存储jsPsych产生的数据的较为理想的办法是写入数据库。

数据库的选择多种多样,而MySQL是使用较多的一种关系数据库,它可以免费使用,且安装十分简单。下面的代码假定你的服务器上已经安装了MySQL且可以运行PHP代码。如果你是在本地机器上运行实验,则需要安装本地服务器环境,如XAMPP

我们需要两个PHP脚本。第一个是数据库的配置文件,我们将其命名为database_config.php并保存在服务器上。在这个文件里,我们需要写入数据库的一些配置项。我们可能需要根据安装MySQL时的配置方式对下面代码中的一些内容进行修改。

<?php
  $servername = "localhost";
  $port = 3306;
  $username = "username";
  $password = "password";
  $dbname = "database";
  $table = "tablename";
?>

第二个PHP脚本负责将数据写入数据库。该脚本会读取数据库,以确定数据表中有哪些列,然后将匹配这些列的数据写入数据库。这个特性是出于安全性的考虑。我们将这个文件命名为write_data.php并保存在服务器上。

<?php

// this path should point to your configuration file.
include('database_config.php');

$data_array = json_decode(file_get_contents('php://input'), true);

try {
  $conn = new PDO("mysql:host=$servername;port=$port;dbname=$dbname", $username, $password);
  $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  // First stage is to get all column names from the table and store
  // them in $col_names array.
  $stmt = $conn->prepare("SHOW COLUMNS FROM `$table`");
  $stmt->execute();
  $col_names = array();
  while($row = $stmt->fetchColumn()) {
    $col_names[] = $row;
  }
  // Second stage is to create prepared SQL statement using the column
  // names as a guide to what values might be in the JSON.
  // If a value is missing from a particular trial, then NULL is inserted
  $sql = "INSERT INTO $table VALUES(";
  for($i = 0; $i < count($col_names); $i++){
    $name = $col_names[$i];
    $sql .= ":$name";
    if($i != count($col_names)-1){
      $sql .= ", ";
    }
  }
  $sql .= ");";
  $insertstmt = $conn->prepare($sql);
  for($i=0; $i < count($data_array); $i++){
    for($j = 0; $j < count($col_names); $j++){
      $colname = $col_names[$j];
      if(!isset($data_array[$i][$colname])){
        $insertstmt->bindValue(":$colname", null, PDO::PARAM_NULL);
      } else {
        $insertstmt->bindValue(":$colname", $data_array[$i][$colname]);
      }
    }
    $insertstmt->execute();
  }
  echo '{"success": true}';
} catch(PDOException $e) {
  echo '{"success": false, "message": ' . $e->getMessage();
}
$conn = null;
?>

我们在JavaScript中使用AJAX发送数据。

function saveData() {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', 'write_data.php'); // change 'write_data.php' to point to php script.
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.onload = function() {
    if(xhr.status == 200){
      var response = JSON.parse(xhr.responseText);
      console.log(response.success);
    }
  };
  xhr.send(jsPsych.data.get().json());
}

我们可以在on_finish中或call-function插件中调用上面定义的saveData()函数。

// with on_finish handler
jsPsych.init({
  on_finish: saveData
});

// with call-function plugin
timeline.push({
  type: 'call-function',
  func: saveData
});