Getting data from your experiment¶
Of course, for your experiment to be useful you’ll need to record the results! Here we’ll take a look at different ways of getting results out of a jsPsych experiment.
Testing¶
As we’ve seen before, if you’re just testing your experiment you can show the results after the experiment by changing your initJsPsych:
var jsPsych = initJsPsych({
on_finish: function() {
jsPsych.data.displayData();
}
});
The default is to display the data in JSON format, which is not very readable.
You can easily display it in CSV format, though:
var jsPsych = initJsPsych({
on_finish: function() {
jsPsych.data.displayData('csv');
}
});
This produces something which should be easier to read, like this:
"rt","stimulus","key_press","trial_type","trial_index","time_elapsed","internal_node_id"
"9373","Dog2.jpg","32","image-keyboard-response","0","9377","0.0-0.0-0.0"
"555","Dog3.jpg","32","image-keyboard-response","1","9940","0.0-0.0-0.1"
"355","Dog1.jpg","32","image-keyboard-response","2","10305","0.0-0.0-0.2"
"381","Dog3.jpg","32","image-keyboard-response","3","10690","0.0-0.0-0.3"
"378","Dog1.jpg","32","image-keyboard-response","4","11071","0.0-0.0-0.4"
"333","Dog2.jpg","32","image-keyboard-response","5","11407","0.0-0.0-0.5"
"329","Dog1.jpg","32","image-keyboard-response","6","11741","0.0-0.0-0.6"
"370","Dog2.jpg","32","image-keyboard-response","7","12113","0.0-0.0-0.7"
"424","Dog3.jpg","32","image-keyboard-response","8","12539","0.0-0.0-0.8"
Exercise¶
Go back and change the factorial
example to show a CSV table.
(Here’s how, in case you get stuck).
What’s missing from the table? 2 (see footnote for the answer!)
Sending the data to the server¶
First, we’ll write a small program that runs on the server when a particular page is accessed. This receives the data and saves it to disk.
server_data
and security
You’ll have noticed that on the server there’s a folder called server_data
. This is where
the results will be stored.
For security reasons, it would be bad for your experiment to be
able to add files to the public_html
area. Anything there could then be seen by the outside
world, and could then be used to spread viruses or for other malicious purposes. 1
It may seem far-fetched, but institutions like universities are often targeted for this purpose as people are more likely to trust a link on a university website.
This small program is in a different programming language called PHP. I won’t go into the details of this – if you’re interested, PHP has a lot in common with Javascript.
Create a file called save_data.php
, and add the code found on this page.
Now upload the file to the same place as the factorial
example.
As I said before, this code will receive the data. Now we need to write some code to send the data. Remember that the experiment is running entirely in the participant’s web browser. We’ll write some code that sends all of the data from the experiment to the server. This will happen right at the end of the experiment.
In the experiment.js
file, add two new functions:
// helps save_data to try again if data saving fails
async function fetch_with_retry(...args) {
let count = 0;
while(count < 3) {
try {
let response = await fetch(...args);
if (response.status !== 200) {
throw new Error("Didn't get 200 Success");
}
return response;
} catch(error) {
console.log(error);
}
count++;
await new Promise(x => setTimeout(x, 250));
}
throw new Error("Too many retries");
}
// save some data (as text) to the filename given
async function save_data(name, data_in){
var url = 'save_data.php';
var data_to_send = {filename: name, filedata: data_in};
await fetch_with_retry(url, {
method: 'POST',
body: JSON.stringify(data_to_send),
headers: new Headers({
'Content-Type': 'application/json'
})
});
}
Data loss (and what’s going on here?)
Sometimes in online experiments, data are lost due to bad internet connections. The version of save_data
above, and its helper function, try to prevent this. If sending data fails, it will try again (up to a total of three times, 250ms apart).
How it does this is beyond the scope of this course, in particular I’m not going to explain await
and async
.
Now finally, we need to change the experiment to send the data. Change your call to initJsPsych
to contain:
var jsPsych = initJsPsych({
on_finish: async function() {
var experiment_data = jsPsych.data.get();
return save_data("test.csv", experiment_data.csv());
}
});
This calls our new save_data
function with a filename (test.csv
) and a CSV copy of the data.
It replaces the previous code which displayed the data. (If you want to display the data as well, you
can add the line jsPsych.data.displayData('csv');
back in).
Note that the previous code called the displayData()
function, which just shows the data on the screen.
This new code calls jsPsych.data.get()
to get a DataCollection
object. Then we call the DataCollection
’s
csv()
method, to get that data as CSV. DataCollection
objects are a new feature of jsPsych, which
give you lots of control over your data. We’ll take a look at some specific things later – for now,
here’s a link to the documentation
How it works¶
The protocol used for the web, HTTP, has two different ways of getting web pages. 3 These are called GET and POST.
To use GET, a web browser sends a request with a URL 4 and gets back a page. Any extra information in a GET must be included in the URL. It will look something like this:
http://example.com/page?colour=red&size=3
Here after the location of the page, there are two values – “colour” is “red” and “size” is “3”.
Instead of sending data this way, for larger amounts of data, a POST is used. This might be used to send data from a web form, for example. The data are not sent in the URL – instead, the browser sends them attached to the request, in a way that isn’t visible to the user.
The data that we send is:
{ filename: "test.csv", filedata: "\"rt\",\"stimulus\",\"key_press\",\"trial_type\",\"trial_index\", .... " }
where filedata
contains the whole contents of the CSV file. The PHP program at the other end receives this data
in the POST.
It opens a file corresponding to the given filename, and saves the data in it.
Solution¶
Here’s an example experiment which demonstrates saving data at the end of the experiment.
Adding new data fields¶
You will almost certainly want to store more data than jsPsych gives you by default.
Some data will remain the same for the whole test for each participant, such as participant number or demographics. Other data will change for each trial. Let’s look at both of these.
Data that doesn’t change¶
You can add this using jspsych.data.addProperties()
. For example, let’s add the date and time of the start of
the experiment.
At the top of your code, after var jsPsych = initJsPsych(....);
, add the code:
jsPsych.data.addProperties({ start_time: (new Date()).toISOString() });
This adds a new column with the time at the start of the experiment. (Of course, you have to be cautious with this information, as it will give the time on the participant’s computer!)
Data that does change¶
You can add extra information that varies for each trial. If you haven’t already, add a fixation node to your
current copy of the factorial
experiment. (You can see how this is done
here ). The fixation uses the html-keyboard-response
plugin so
remember to add this to your experiment.html
file.
Now run the experiment again. You’ll see that the fixation node also generates a line in the output.
"rt","stimulus","key_press","trial_type","trial_index","time_elapsed","internal_node_id"
"null","+","null","html-keyboard-response","0","753","0.0-0.0-0.0"
"1010","Dog1.jpg","32","image-keyboard-response","1","1777","0.0-0.0-1.0"
"null","+","null","html-keyboard-response","2","2283","0.0-0.0-0.1"
We might want to log the trial type so we can filter these (or other nodes) out of data saved at the end of the experiment. To add this to the nodes, we use the data
field.
In the fixation
node, add:
data: { type: 'fixation' }
Remember that you’ll need to add a comma to the line before, so something like:
var fixation = {
....
response_ends_trial: false,
data: { type: 'fixation' }
};
Now do the same for the trial
node, but add:
data: { type: 'trial' }
Run your experiment again. There should be a new column, with “trial” or “fixation”. This will make it easier to filter out fixations in code, or when you do your analysis.
Filters
If you want to try this out using jsPsych’s built in filters, make sure you have data saving implemented as in the previous section.
Replace jsPsych.data.get()
with
jsPsych.data.get().filter({ type: 'trial' });
That should return just the data from the nodes with “type” equal to “trial”.
While this is good for testing, it’s always safer to save all the raw data, and filter it in analysis.
If you get filtering wrong in analysis, you can run it again. If you get filtering wrong when saving the data, anything which was filtered out is gone forever!
We can also add new fields which change every time. In the trial
node, change data
part to read:
data: {
type: "trial",
trial_duration: jsPsych.timelineVariable('duration'),
fixation_duration: jsPsych.timelineVariable('fixation_duration')
}
This will tell jsPsych to copy these values into the data. Reload the experiment and you should see two new columns for these values.
Sending the data line by line¶
For some experiments you may want to send each line individually. This requires a little more effort!
Make a copy of your experiment – we’ll adapt this one to send the data for each trial as it’s completed.
Delete on_finish
and the associated code from initJsPsych
.
Instead, we’ll add on_finish
to the trials you want to record.
on_finish
in initJsPsych specifies a function to run when the whole experiment finishes.
on_finish
in a trial specifies a function to run when that trial finishes. The function is passed the data from the trial, which makes it easy to save.
In the trial
node, add:
on_finish: save_data_line
This specifies a new function to be called every time this trial finishes.
At the top of your code, just after var jsPsych = initJsPsych(....);
, add this new function:
function save_data_line(data) {
// choose the data we want to save
var data_to_save = [
data.type, data.stimulus, data.trial_duration, data.fixation_duration, data.rt
];
// join these with commas and add a newline
var line = data_to_save.join(',')+"\n";
save_data("test.csv", line);
}
This will work with the save_data.php
code, because it will append new data sent to an existing file.
Here’s an example experiment which demonstrates saving data line-by-line.
Exercise¶
In online experiments it’s quite common to have the participant type in an ID number, for example their Crowdflower ID or Amazon MTurk number, that will allow you to verify their participation and pay them.
Add a node at the beginning of your code which allows the user to input an ID, using
the survey-text plugin . (Remember you’ll also have to add a <script>
tag
to your experiment.html
file to load the plugin). Add this node to your experiment
at the beginning. This works a little differently to the plugins we’ve seen before,
so be sure to read the documentation before you start.
Before you go any further, run the experiment and check that this new node only appears once at the beginning of the experiment. Check the console to make sure there are no errors.
In your new node, add a new item on_finish
. This specifies a function to call
when the trial is finished. Create an anonymous function (this is a function
without a name – see this section), and inside it use
jsPsych.data.addProperties
(see this section) to add
a new column to the data which includes the ID. The function you pass to
on_finish
receives the data from the trial as an
argument – take a look at the documentation
here .
Hint: to get the response out of the survey-text
trial, use
data.response.Q0
(If you gave your question a name, you’ll need to use this instead of “Q0”).
This is quite involved so don’t be too worried if you don’t get it straight away – take some time to look in the documentation, use the Developer Tools, and feel free to ask questions!
Here’s an example solution to this exercise which takes the
result of a survey-text
node and adds it as a new column.
Footnotes
- 1
This doesn’t apply to our server,
jspsychlearning.ppls.ed.ac.uk
, which is behind the University firewall – but most real online experiments will be made accessible to the world.- 2
The
trial_duration
field is missing – we’ll see how to add this to the output later on.- 3
…. and a few other methods for things like changing and deleting pages, but these are seldom used.
- 4
In case you’ve ever wondered, U niform R esource L ocator.