Skip to main content

Reaction time

Measure the delay between a stimulus onset event and the participant's next key press. The canonical psychophysics measurement in a LABO experience.

What this recipe does

For each stimulus-onset row in EventPoints.csv, find the first KeyDown in InputCapture.csv that occurred afterward and compute the time difference using MonotonicExecutionTime. Output is one reaction-time value per stimulus.

Files you need

  • EventPoints.csv — provides stimulus-onset timing.
  • InputCapture.csv — provides key-press timing.
  • ExperienceState.csv — optional, only if you need per-frame context (fps during the press, epoch name) attached to each reaction-time value.

Code

import pandas as pd

events = pd.read_csv("EventPoints.csv")
inputs = pd.read_csv("InputCapture.csv")

# 1. Filter to stimulus onsets.
# Adjust the filter to match how your experience tags stimulus events.
stim_onsets = events[
(events["Event_Type"] == "Response")
& (events["Event_Description"] == "Stimulus flash")
].sort_values("MonotonicExecutionTime").reset_index(drop=True)

# 2. Filter to key-down events for the response key(s).
response_keys = {"Space", "Return"}
key_downs = inputs[
(inputs["eventType"] == "KeyDown")
& (inputs["keyCode"].isin(response_keys))
].sort_values("MonotonicExecutionTime").reset_index(drop=True)

# 3. For each stimulus, find the first KeyDown strictly after it.
pairs = pd.merge_asof(
stim_onsets,
key_downs,
on="MonotonicExecutionTime",
direction="forward",
suffixes=("_stim", "_key"),
allow_exact_matches=False,
)

# 4. Reaction time in seconds.
pairs["reaction_time_s"] = pairs["MonotonicExecutionTime_key"] - pairs["MonotonicExecutionTime"]

# 5. Drop trials with no response (NaN in _key columns).
valid = pairs.dropna(subset=["MonotonicExecutionTime_key"])

print(valid[["EpochName_stim", "Event_Description", "keyCode", "reaction_time_s"]].describe())

Gotchas

  • Match your stimulus filter to your experience. The filter Event_Description == "Stimulus flash" is an example — use whatever description your EventPoint was configured with. If unsure, head(events) and read the Event_Description values.
  • direction="forward", not "nearest". You want the first response after the stimulus, not the closest in either direction. Using "nearest" can pair a stimulus with a key press that happened before it.
  • allow_exact_matches=False is deliberate — if the same MonotonicExecutionTime somehow lands on both a stimulus and a key press (vanishingly unlikely but possible), treat them as independent.
  • Trials with no response show as NaN in the merged row. Drop them explicitly rather than silently including them — a NaN reaction time is meaningful data (miss).
  • CPU time vs. display time. The stimulus MonotonicExecutionTime is when the CPU recorded the event. The participant actually saw the stimulus some milliseconds later — see Timing → Display-time correction. For high-precision RT, add the display offset to stimulus times before subtracting.
  • Response-key mismatch. Double-check response_keys against the keys configured on your KeyCodeDataCapture SO. If the participant's keys aren't in the capture config, no KeyDown rows exist for them.

Further reading