Skip to content

Commit 2898da6

Browse files
authored
Merge pull request #3167 from jspsych/skip-data-logging
Allow skipping data logging for a trial with `record_data: false`
2 parents 72ddfa0 + 007d862 commit 2898da6

File tree

9 files changed

+151
-6
lines changed

9 files changed

+151
-6
lines changed

.changeset/lucky-glasses-crash.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"jspsych": minor
3+
---
4+
5+
Added `record_data` as a parameter available for any trial. Setting `record_data: false` will prevent data from being stored in the jsPsych data object for that trial.
6+

docs/overview/data.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ var trial = {
7070
}
7171
```
7272

73+
### Skipping data collection for a particular trial
74+
75+
Sometimes you may want to skip data collection for a particular trial. This can be done by setting the `record_data` parameter to `false`. This is useful if you want your data output to only contain the trials that collect relevant responses from the participant. For example, you might want to skip data collection for trials that just present a fixation cross for a fixed period of time.
76+
77+
```js
78+
var trial = {
79+
type: jsPsychImageKeyboardResponse,
80+
stimulus: 'imgA.jpg',
81+
record_data: false
82+
}
83+
```
84+
7385
## Aggregating and manipulating jsPsych data
7486

7587
When accessing the data with `jsPsych.data.get()` the returned object is a special data collection object that exposes a number of methods for aggregating and manipulating the data. The full list of methods is detailed in the [data module documentation](../reference/jspsych-data.md).

docs/overview/plugins.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ There is also a set of parameters that can be specified for any plugin:
6262
| css_classes | string | null | A list of CSS classes to add to the jsPsych display element for the duration of this trial. This allows you to create custom formatting rules (CSS classes) that are only applied to specific trials. For more information and examples, see the [Controlling Visual Appearance page](../overview/style.md) and the "css-classes-parameter.html" file in the jsPsych examples folder. |
6363
| save_trial_parameters | object | `{}` | An object containing any trial parameters that should or should not be saved to the trial data. Each key is the name of a trial parameter, and its value should be `true` or `false`, depending on whether or not its value should be saved to the data. If the parameter is a function that returns the parameter value, then the value that is returned will be saved to the data. If the parameter is always expected to be a function (e.g., an event-related callback function), then the function itself will be saved as a string. For more examples, see the "save-trial-parameters.html" file in the jsPsych examples folder. |
6464
| save_timeline_variables | boolean or array | `false` | If set to `true`, then all timeline variables will have their current value recorded to the data for this trial. If set to an array, then any variables listed in the array will be saved.
65+
| record_data | boolean | `true` | If set to `false`, then the data for this trial will not be recorded. |
6566

6667
### The data parameter
6768

packages/jspsych/src/JsPsych.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,14 +357,20 @@ export class JsPsych {
357357
},
358358

359359
onTrialResultAvailable: (trial: Trial) => {
360-
trial.getResult().time_elapsed = this.getTotalTime();
361-
this.data.write(trial);
360+
const result = trial.getResult();
361+
if (result) {
362+
result.time_elapsed = this.getTotalTime();
363+
this.data.write(trial);
364+
}
362365
},
363366

364367
onTrialFinished: (trial: Trial) => {
365368
const result = trial.getResult();
366369
this.options.on_trial_finish(result);
367-
this.options.on_data_update(result);
370+
371+
if (result) {
372+
this.options.on_data_update(result);
373+
}
368374

369375
if (this.progressBar && this.options.auto_update_progress_bar) {
370376
this.progressBar.progress = this.timeline.getNaiveProgress();

packages/jspsych/src/timeline/Timeline.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,19 @@ describe("Timeline", () => {
292292
expect(onTimelineFinish).toHaveBeenCalledTimes(1);
293293
});
294294

295+
it("loop function ignores data from trials where `record_data` is false", async () => {
296+
const loopFunction = jest.fn();
297+
loopFunction.mockReturnValue(false);
298+
299+
const timeline = createTimeline({
300+
timeline: [{ type: TestPlugin, record_data: false }, { type: TestPlugin }],
301+
loop_function: loopFunction,
302+
});
303+
304+
await timeline.run();
305+
expect((loopFunction.mock.calls[0][0] as DataCollection).count()).toBe(1);
306+
});
307+
295308
describe("with timeline variables", () => {
296309
it("repeats all trials for each set of variables", async () => {
297310
const xValues = [];

packages/jspsych/src/timeline/Trial.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,20 @@ describe("Trial", () => {
725725
expect(trial.getResult()).toEqual(expect.objectContaining({ my: "result" }));
726726
expect(trial.getResults()).toEqual([expect.objectContaining({ my: "result" })]);
727727
});
728+
729+
it("does not return the result when the `record_data` trial parameter is `false`", async () => {
730+
TestPlugin.setManualFinishTrialMode();
731+
const trial = createTrial({ type: TestPlugin, record_data: false });
732+
trial.run();
733+
734+
expect(trial.getResult()).toBeUndefined();
735+
expect(trial.getResults()).toEqual([]);
736+
737+
await TestPlugin.finishTrial();
738+
739+
expect(trial.getResult()).toBeUndefined();
740+
expect(trial.getResults()).toEqual([]);
741+
});
728742
});
729743

730744
describe("evaluateTimelineVariable()", () => {

packages/jspsych/src/timeline/Trial.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,16 @@ export class Trial extends TimelineNode {
299299
}
300300

301301
/**
302-
* Returns the result object of this trial or `undefined` if the result is not yet known.
302+
* Returns the result object of this trial or `undefined` if the result is not yet known or the
303+
* `record_data` trial parameter is `false`.
303304
*/
304305
public getResult() {
305-
return this.result;
306+
return this.getParameterValue("record_data") === false ? undefined : this.result;
306307
}
307308

308309
public getResults() {
309-
return this.result ? [this.result] : [];
310+
const result = this.getResult();
311+
return result ? [result] : [];
310312
}
311313

312314
/**

packages/jspsych/src/timeline/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ export interface TrialDescription extends Record<string, any> {
5555
/** https://www.jspsych.org/latest/overview/extensions/ */
5656
extensions?: Parameter<TrialExtensionsConfiguration>;
5757

58+
/**
59+
* Whether to record the data of this trial. Defaults to `true`.
60+
*/
61+
record_data?: Parameter<boolean>;
62+
5863
// Events
5964

6065
/** https://www.jspsych.org/latest/overview/events/#on_start-trial */
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
2+
import { pressKey, startTimeline } from "@jspsych/test-utils";
3+
4+
import { initJsPsych } from "../../src";
5+
6+
describe("The record_data parameter", () => {
7+
it("Defaults to true", async () => {
8+
const { getData } = await startTimeline([
9+
{
10+
type: htmlKeyboardResponse,
11+
stimulus: "<p>foo</p>",
12+
},
13+
]);
14+
15+
await pressKey(" ");
16+
17+
expect(getData().count()).toBe(1);
18+
});
19+
20+
it("Can be set to false to prevent the data from being recorded", async () => {
21+
const onFinish = jest.fn();
22+
const onTrialFinish = jest.fn();
23+
const onDataUpdate = jest.fn();
24+
25+
const { getData } = await startTimeline(
26+
[
27+
{
28+
type: htmlKeyboardResponse,
29+
stimulus: "<p>foo</p>",
30+
record_data: false,
31+
on_finish: onFinish,
32+
},
33+
],
34+
{ on_trial_finish: onTrialFinish, on_data_update: onDataUpdate }
35+
);
36+
37+
await pressKey(" ");
38+
39+
expect(getData().count()).toBe(0);
40+
expect(onFinish).toHaveBeenCalledWith(undefined);
41+
expect(onTrialFinish).toHaveBeenCalledWith(undefined);
42+
expect(onDataUpdate).not.toHaveBeenCalled();
43+
});
44+
45+
it("Can be set as a timeline variable", async () => {
46+
const jsPsych = initJsPsych();
47+
const { getData } = await startTimeline(
48+
[
49+
{
50+
timeline: [
51+
{
52+
type: htmlKeyboardResponse,
53+
stimulus: "<p>foo</p>",
54+
record_data: jsPsych.timelineVariable("record_data"),
55+
},
56+
],
57+
timeline_variables: [{ record_data: true }, { record_data: false }],
58+
},
59+
],
60+
jsPsych
61+
);
62+
63+
await pressKey(" ");
64+
await pressKey(" ");
65+
66+
expect(getData().count()).toBe(1);
67+
});
68+
69+
it("Can be set as a function", async () => {
70+
const jsPsych = initJsPsych();
71+
const { getData } = await startTimeline(
72+
[
73+
{
74+
type: htmlKeyboardResponse,
75+
stimulus: "<p>foo</p>",
76+
record_data: () => false,
77+
},
78+
],
79+
jsPsych
80+
);
81+
82+
await pressKey(" ");
83+
84+
expect(getData().count()).toBe(0);
85+
});
86+
});

0 commit comments

Comments
 (0)