15 Accessing and Summarizing Data
- Access and filter data using jsPsych.data.get(), .filter(), and .select() methods
- Calculate statistics and extract data subsets using built-in DataCollection methods
- Use data analysis inside on_finish functions to access previous trial information
Now that you understand how to work with individual trial data in the on_finish function, let’s explore how to access and analyze all the data collected throughout your experiment. The jsPsych.data.get() function provides live access to your data, as it’s being collected. In fact, at any moment during your experiment, you could open up the JavaScript console, type jsPsych.data.get() and press Enter, and it would print out the data that had been collected up until that moment. If you complete another trial, and run that function again, you’d get the updated data with everything, including the newest trial. If you’re curious, go to one of the previous lab assignments and try it out!
15.1 What is jsPsych.data.get()?
The jsPsych.data.get() function returns a DataCollection object containing all the data collected so far in your experiment. Think of it as taking a snapshot of everything that has happened up to that point.
When you call jsPsych.data.get(), you get back a DataCollection that contains an array of trial objects. Each trial is a JavaScript object with properties (key-value pairs). Here’s what a single, typical trial might look like:
{
trial_type: "html-keyboard-response",
trial_index: 5,
time_elapsed: 12847,
rt: 1205,
stimulus: "DOCTOR",
response: "f",
task: "recognition_test",
word_type: "old",
correct_response: "f",
subject_id: "q4jr9zbl",
correct: true
}Each row in your data represents one trial, and each column represents a property that was recorded. Some properties are automatically added by jsPsych (like trial_type, rt, and time_elapsed), while others come from your trial definitions, addProperties(), or on_finish functions.
However, experiments contain many trials, and so, all the trial objects are stored in an array [ ]:
[
{
trial_type: "html-keyboard-response",
trial_index: 0,
time_elapsed: 2121,
rt: 2121,
stimulus: "<div style='text-align: center;'><h2>Word Recognition Task</h2><p>In this task, you will see words one at a time.</p><p>Press <strong>F</strong> if you think the word is OLD (you've seen it before)</p><p>Press <strong>J</strong> if you think the word is NEW (you haven't seen it before)</p><p>Press any key to begin.</p></div>",
response: "f",
subject_id: "q4jr9zbl",
experiment_version: "1.0",
task: null,
word: null,
word_type: null,
correct_response: null,
correct: null
},
{
trial_type: "html-keyboard-response",
trial_index: 1,
time_elapsed: 3002,
rt: 879,
stimulus: "DOCTOR",
response: "f",
subject_id: "q4jr9zbl",
experiment_version: "1.0",
task: "recognition_test",
word: "DOCTOR",
word_type: "old",
correct_response: "f",
correct: true
},
{
trial_type: "html-keyboard-response",
trial_index: 2,
time_elapsed: 6266,
rt: 3264,
stimulus: "PLANET",
response: "j",
subject_id: "q4jr9zbl",
experiment_version: "1.0",
task: "recognition_test",
word: "PLANET",
word_type: "new",
correct_response: "j",
correct: true
},
{
trial_type: "html-keyboard-response",
trial_index: 3,
time_elapsed: 7033,
rt: 767,
stimulus: "GARDEN",
response: "f",
subject_id: "q4jr9zbl",
experiment_version: "1.0",
task: "recognition_test",
word: "GARDEN",
word_type: "old",
correct_response: "f",
correct: true
},
{
trial_type: "html-keyboard-response",
trial_index: 4,
time_elapsed: 7842,
rt: 808,
stimulus: "WINDOW",
response: "j",
subject_id: "q4jr9zbl",
experiment_version: "1.0",
task: "recognition_test",
word: "WINDOW",
word_type: "new",
correct_response: "j",
correct: true
}
]This array shows how jsPsych stores data from multiple trials. Notice that:
- Each element in the array is a complete trial object with all its properties
- Properties are consistent across trials (same keys), but values differ based on what happened in each trial
- The first trial (index 0) is the welcome screen, so task-specific properties like word and correct are null
- Subsequent trials (indices 1-4) are the actual recognition test trials with all properties filled in
- Global properties like subject_id and experiment_version appear in every trial because they were added with addProperties()
- Trial-specific properties like rt, response, and correct vary based on participant behavior and trial content
15.2 Viewing Your Data During Development
When you’re building and testing your experiment, you’ll want to examine the data being collected. The best way to do this is through your browser’s JavaScript console.
To open the JavaScript console:
- Chrome/Edge: Press F12 or Ctrl+Shift+I (Cmd+Option+I on Mac), then click the “Console” tab
- Firefox: Press F12 or Ctrl+Shift+K (Cmd+Option+K on Mac)
- Safari: Press Cmd+Option+C (you may need to enable the Developer menu first)
Once you have the console open, you can run these commands to examine your data. The second command print a prettier version of the data than is typically displayed if you just run jsPsych.data.get() by converting it to a table of the values.
// Print all the data in its original format
jsPsych.data.get();
// Display all data values in the console as a table (very useful!)
console.table(jsPsych.data.get().values());The console.table() command is particularly helpful because it displays your data in a neat table format in the console, making it easy to see what’s being collected.
15.3 Filtering Your Data
jsPsych provides a rich set of built-in functions for working with your data. These functions allow you to filter, aggregate, and analyze your data without needing external tools. Understanding these functions can be helpful for calculating performance statistics for feedback or updating the experiment based on performance. You can read more about these tools in the documentation, but I’ll review some of them in brief
15.3.1 Basic Filtering with .filter()
https://www.jspsych.org/v8/reference/jspsych-data/#filter
The .filter() method lets you select trials that match specific criteria:
const all_data = jsPsych.data.get();
// Get only trials from the test phase
const test_trials = all_data.filter({task: 'recognition_test'});
// Get only correct responses
const correct_trials = all_data.filter({correct: true});
// Get trials that match multiple criteria (AND logic)
// Return any rows that have task = 'recognition_test' AND correct = true
const correct_test_trials = all_data.filter({task: 'recognition_test', correct: true});
// Use an array for OR logic - trials from either word type
// Return any rows that have word_type = 'old' OR word_type = 'new'
const old_or_new = all_data.filter([{word_type: 'old'}, {word_type: 'new'}]);15.3.2 Advanced Filtering with .filterCustom()
https://www.jspsych.org/v8/reference/jspsych-data/#filtercustom
For more complex filtering, use .filterCustom() with a function. This allows us to write a custom function to filter in any way we want. It takes trial as the input, and we can filter based on any of the trial parameters that we have stored in the data. Though, we have to manually set what is returned from the function.
const all_data = jsPsych.data.get();
// Get trials with response times between 200-2000ms
const valid_rt_trials = all_data.filterCustom(
function(trial) {
return trial.rt >= 200 && trial.rt <= 2000;
}
);
// Get trials where participants pressed specific keys
const f_responses = all_data.filterCustom(
function(trial) {
return trial.response === 'f';
}
);
// Get trials from the second half of the experiment
// First count how many total trials there are
const total_trials = all_data.count();
// Then filter based on trial count
const second_half = all_data.filterCustom(
function(trial) {
return trial.trial_index >= total_trials / 2;
}
);15.3.3 Selecting Specific Data Columns using .select()
https://www.jspsych.org/v8/reference/jspsych-data/#select
Use .select() to extract specific properties from your trials. Remember that when we convert our data to a table, every row is a trial and every column is a property. So, this is like selecting columns from our data set.
15.3.4 Getting Subsets of Trials
https://www.jspsych.org/v8/reference/jspsych-data/#first-last
jsPsych provides convenient methods for getting specific portions of your data:
const all_data = jsPsych.data.get();
// Get the first 5 trials
const first_trials = all_data.first(5).trials;
// Get the last 10 trials
const recent_trials = all_data.last(10).trials;
// Get just the very last trial
const last_trial = all_data.last(1).trials;
// Count total number of trials
const total_count = all_data.count();15.4 Calculating Statistics
https://www.jspsych.org/v8/reference/jspsych-data/#datacolumn
Once you have selected data, you can calculate various statistics. For instance, if I select the column that contains the response times, I can then compute the mean, median, etc.
const all_data = jsPsych.data.get();
const response_times = all_data.select('rt');
// Basic statistics
const mean_rt = response_times.mean();
const median_rt = response_times.median();
const min_rt = response_times.min();
const max_rt = response_times.max();
const total_rt = response_times.sum();
const rt_count = response_times.count();
// Variability measures
const rt_variance = response_times.variance();
const rt_std_dev = response_times.sd();
console.log("RT Statistics:");
console.log("Mean: " + mean_rt + "ms");
console.log("Median: " + median_rt + "ms");
console.log("Range: " + min_rt + "-" + max_rt + "ms");
console.log("Standard Deviation: " + rt_std_dev + "ms");15.5 Chaining Methods for Complex Analysis
Often, we have a mix of different trials mixed together. This requires us to both filter and select to be able to calculate statistics. We can do that by ‘chaining’ together methods. This works by putting each function after the other. These methods will occur sequentially. You can think of it as asking it to ‘first filter by X’, then ‘select Y’, then ‘calculate the mean’.
const all_data = jsPsych.data.get();
// Calculate accuracy rate for recognition test trials only
const test_accuracy = all_data
.filter({task: 'recognition_test'})
.filter({correct: true})
.count() / all_data.filter({task: 'recognition_test'}).count();
// Get mean RT for correct responses to old words
const old_word_correct_rt = all_data
.filter({word_type: 'old', correct: true})
.select('rt')
.mean();
// Count fast responses (under 500ms) in recognition trials
const fast_recognition_count = all_data
.filter({task: 'recognition_test'})
.filterCustom(function(trial) { return trial.rt < 500; })
.count();
// Get the response times from the last 20 correct trials
const recent_correct_rts = all_data
.filter({correct: true})
.last(20)
.select('rt');
console.log("Recent correct RTs:", recent_correct_rts.values);
console.log("Mean of recent correct RTs:", recent_correct_rts.mean());16 Using data.get() inside on_finish
Now that you understand how to access and analyze all your data, you can use these techniques within on_finish functions to make decisions based on overall performance. When we get to the later chapters on dynamic flow, we’ll add logic that will change what participants see based on these calculations. For now, we’ll just look at how we would implement them, storing their results in the data.
const recognition_trial = {
timeline: [
{
type: jsPsychHtmlKeyboardResponse,
stimulus: jsPsych.timelineVariable("word"),
choices: ['f', 'j'],
data: {
task: "recognition_test",
word: jsPsych.timelineVariable("word"),
word_type: jsPsych.timelineVariable("word_type"),
correct_response: jsPsych.timelineVariable("correct_response")
},
on_finish: function(data) {
// First, determine accuracy for this trial
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response);
// Now get all previous data and count how many trials we've completed
const all_data = jsPsych.data.get();
const test_trials = all_data.filter({task: 'recognition_test'});
// Add the trial number (how many recognition trials we've done so far)
data.trial_number = test_trials.count() + 1; // +1 because current trial isn't included yet
// Calculate running accuracy (only if we have previous trials)
if (test_trials.count() > 0) {
const correct_trials = test_trials.filter({correct: true});
data.running_accuracy = correct_trials.count() / test_trials.count();
} else {
data.running_accuracy = null; // First trial, no previous data
}
}
}
],
timeline_variables: [
{ word: "DOCTOR", word_type: "old", correct_response: "f" },
{ word: "PLANET", word_type: "new", correct_response: "j" },
{ word: "GARDEN", word_type: "old", correct_response: "f" },
{ word: "WINDOW", word_type: "new", correct_response: "j" },
{ word: "BRIDGE", word_type: "old", correct_response: "f" },
{ word: "FOREST", word_type: "new", correct_response: "j" }
]
};This approach allows you to track performance trends throughout the experiment and store that information with each trial. In later chapters, you’ll learn how to use this information to provide feedback to participants or adjust the difficulty of your experiment dynamically.
The combination of on_finish for individual trial processing and jsPsych.data.get() for data analysis gives you tools for creating intelligent, adaptive experiments that can respond to participant performance in real-time.