Event Handling in PhysioExperiment

Introduction

Physiological experiments typically involve discrete events such as stimulus presentations, participant responses, and experimental condition markers. PhysioExperiment provides the PhysioEvents class and a set of functions for creating, querying, filtering, and modifying event information within PhysioExperiment objects.

This vignette covers the event system in detail: creating events, attaching them to data objects, querying and filtering, and converting between time and sample representations.

The PhysioEvents Class

PhysioEvents is a lightweight S4 class that stores event data as a DataFrame with four columns:

  • onset: event onset time in seconds
  • duration: event duration in seconds
  • type: event category (e.g., “stimulus”, “response”)
  • value: event label or value (e.g., “target”, “hit”)

Creating Events

library(PhysioExperiment)

# Create events with full specification
events <- PhysioEvents(
  onset = c(1.0, 2.5, 4.0, 5.5, 7.0),
  duration = c(0.5, 0.5, 0.5, 0.5, 0.5),
  type = c("stimulus", "response", "stimulus", "response", "stimulus"),
  value = c("target", "hit", "distractor", "false_alarm", "target")
)
events
#> PhysioEvents with 5 events
#> Event types: stimulus, response 
#>   onset duration     type       value
#> 1   1.0      0.5 stimulus      target
#> 2   2.5      0.5 response         hit
#> 3   4.0      0.5 stimulus  distractor
#> 4   5.5      0.5 response false_alarm
#> 5   7.0      0.5 stimulus      target

Default Values

When creating events, only onset is strictly required. Other fields receive sensible defaults:

# Duration defaults to 0, type to "event", value to ""
simple_events <- PhysioEvents(
  onset = c(1, 2, 3, 4, 5)
)
simple_events
#> PhysioEvents with 5 events
#> Event types: event 
#>   onset duration  type value
#> 1     1        0 event      
#> 2     2        0 event      
#> 3     3        0 event      
#> 4     4        0 event      
#> 5     5        0 event

# Single type is recycled across all events
stim_events <- PhysioEvents(
  onset = c(1, 2, 3, 4, 5),
  type = "stimulus"
)
stim_events
#> PhysioEvents with 5 events
#> Event types: stimulus 
#>   onset duration     type value
#> 1     1        0 stimulus      
#> 2     2        0 stimulus      
#> 3     3        0 stimulus      
#> 4     4        0 stimulus      
#> 5     5        0 stimulus

Inspecting Events

# Number of events
nEvents(events)
#> [1] 5

# The show method prints a summary
events
#> PhysioEvents with 5 events
#> Event types: stimulus, response 
#>   onset duration     type       value
#> 1   1.0      0.5 stimulus      target
#> 2   2.5      0.5 response         hit
#> 3   4.0      0.5 stimulus  distractor
#> 4   5.5      0.5 response false_alarm
#> 5   7.0      0.5 stimulus      target

Attaching Events to PhysioExperiment

Events are stored in the metadata slot of a PhysioExperiment object. Use setEvents() to attach them.

# Create a PhysioExperiment
pe <- PhysioExperiment(
  assays = list(raw = matrix(rnorm(2500), nrow = 2500, ncol = 4)),
  colData = S4Vectors::DataFrame(
    label = c("Fz", "Cz", "Pz", "Oz"),
    type = rep("EEG", 4)
  ),
  samplingRate = 250
)

# Attach events
pe <- setEvents(pe, events)

# Count events attached to the object
nEvents(pe)
#> [1] 5

Setting Events from a Data Frame

You can also pass a plain data.frame to setEvents(), which will be automatically converted to a PhysioEvents object:

event_df <- data.frame(
  onset = c(0.5, 1.5, 2.5),
  duration = c(0.2, 0.2, 0.2),
  type = c("stimulus", "stimulus", "stimulus"),
  value = c("A", "B", "A")
)

pe <- setEvents(pe, event_df)
getEvents(pe)
#> PhysioEvents with 3 events
#> Event types: stimulus 
#>   onset duration     type value
#> 1   0.5      0.2 stimulus     A
#> 2   1.5      0.2 stimulus     B
#> 3   2.5      0.2 stimulus     A

Retrieving Events

Use getEvents() to retrieve events from a PhysioExperiment. You can optionally filter by event type.

# First, set up events with mixed types
pe <- setEvents(pe, PhysioEvents(
  onset = c(1.0, 1.8, 2.5, 3.2, 4.0, 4.7),
  type = c("stimulus", "response", "stimulus", "response", "stimulus", "response"),
  value = c("target", "hit", "distractor", "correct_reject", "target", "hit")
))

# Get all events
all_events <- getEvents(pe)
all_events
#> PhysioEvents with 6 events
#> Event types: stimulus, response 
#>   onset duration     type          value
#> 1   1.0        0 stimulus         target
#> 2   1.8        0 response            hit
#> 3   2.5        0 stimulus     distractor
#> 4   3.2        0 response correct_reject
#> 5   4.0        0 stimulus         target
#> 6   4.7        0 response            hit

# Get only stimulus events
stim <- getEvents(pe, type = "stimulus")
stim
#> PhysioEvents with 3 events
#> Event types: stimulus 
#>   onset duration     type      value
#> 1   1.0        0 stimulus     target
#> 2   2.5        0 stimulus distractor
#> 3   4.0        0 stimulus     target

# Get only response events
resp <- getEvents(pe, type = "response")
nEvents(resp)
#> [1] 3

Adding Events

Use addEvents() to append new events to existing ones. Events are automatically sorted by onset time.

# Start with stimulus events
pe <- PhysioExperiment(
  assays = list(raw = matrix(rnorm(2500), nrow = 2500, ncol = 4)),
  samplingRate = 250
)

pe <- addEvents(pe, onset = c(1, 3, 5), type = "stimulus", value = "target")
nEvents(pe)  # 3
#> [1] 3

# Add response events
pe <- addEvents(pe,
  onset = c(1.5, 3.4),
  type = "response",
  value = c("hit", "hit")
)
nEvents(pe)  # 5
#> [1] 5

# Events are sorted by onset time
getEvents(pe)
#> PhysioEvents with 5 events
#> Event types: stimulus, response 
#>   onset duration     type  value
#> 1   1.0        0 stimulus target
#> 2   1.5        0 response    hit
#> 3   3.0        0 stimulus target
#> 4   3.4        0 response    hit
#> 5   5.0        0 stimulus target

Removing Events

Use removeEvents() to remove events by type, by index, or remove all events.

# Remove all response events
pe_stim_only <- removeEvents(pe, type = "response")
nEvents(pe_stim_only)
#> [1] 3

# Remove specific events by index
pe_fewer <- removeEvents(pe, indices = c(1, 2))
nEvents(pe_fewer)
#> [1] 3

# Remove all events
pe_no_events <- removeEvents(pe)
nEvents(pe_no_events)  # 0
#> [1] 0

Time and Sample Conversion

PhysioExperiment provides functions to convert between time in seconds and sample indices, which is useful for aligning events with signal data.

pe <- PhysioExperiment(
  assays = list(raw = matrix(rnorm(2500), nrow = 2500, ncol = 4)),
  samplingRate = 250
)

# Convert times to sample indices
sample_idx <- timeToSamples(pe, c(0.0, 1.0, 2.0))
sample_idx  # 1, 251, 501
#> [1]   1 251 501

# Convert sample indices back to times
times <- samplesToTime(pe, c(1, 251, 501))
times  # 0.0, 1.0, 2.0
#> [1] 0 1 2

Working with Event Windows

A common workflow is to extract data around events. You can combine event retrieval with time-based subsetting:

# Set up experiment with events
pe <- PhysioExperiment(
  assays = list(raw = matrix(rnorm(25000), nrow = 25000, ncol = 4)),
  colData = S4Vectors::DataFrame(label = c("Fz", "Cz", "Pz", "Oz")),
  samplingRate = 250
)

pe <- addEvents(pe,
  onset = c(10, 30, 50, 70),
  type = "stimulus",
  value = "target"
)

# Extract a window around the first event
events_df <- getEvents(pe)
first_onset <- events_df@events$onset[1]

# Extract 1 second before to 2 seconds after the event
pe_epoch <- extractWindow(pe, tmin = first_onset - 1, tmax = first_onset + 2)
duration(pe_epoch)  # approximately 3 seconds
#> [1] 3.004

Events and Time Concatenation

When combining PhysioExperiment objects along the time axis using rbindPhysio(), event onsets in the second object are automatically offset by the duration of the first:

pe1 <- PhysioExperiment(
  assays = list(raw = matrix(rnorm(1000), nrow = 1000, ncol = 4)),
  samplingRate = 250
)
pe1 <- addEvents(pe1, onset = c(1, 2), type = "stimulus")

pe2 <- PhysioExperiment(
  assays = list(raw = matrix(rnorm(1000), nrow = 1000, ncol = 4)),
  samplingRate = 250
)
pe2 <- addEvents(pe2, onset = c(1, 2), type = "stimulus")

pe_combined <- rbindPhysio(pe1, pe2)

# Events from pe2 are offset by the duration of pe1 (4 seconds)
getEvents(pe_combined)
#> PhysioEvents with 4 events
#> Event types: stimulus 
#>   onset duration     type value
#> 1     1        0 stimulus      
#> 2     2        0 stimulus      
#> 3     5        0 stimulus      
#> 4     6        0 stimulus

Summary

The PhysioExperiment event system provides:

  • PhysioEvents(): constructor for event objects with onset, duration, type, and value fields
  • setEvents() / getEvents(): attach and retrieve events from PhysioExperiment objects
  • addEvents() / removeEvents(): incrementally modify the event set
  • nEvents(): count events
  • timeToSamples() / samplesToTime(): convert between time and sample representations

These tools form the foundation for event-related analyses such as epoching and ERP computation in downstream packages.

Session Info

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] PhysioExperiment_2.3.1 rmarkdown_2.32        
#> 
#> loaded via a namespace (and not attached):
#>  [1] cli_3.6.6                   knitr_1.52                 
#>  [3] rlang_1.3.0                 xfun_0.61                  
#>  [5] otel_0.2.0                  generics_0.1.4             
#>  [7] DelayedArray_0.39.8         jsonlite_2.0.0             
#>  [9] S4Vectors_0.51.10           SummarizedExperiment_1.43.0
#> [11] buildtools_1.0.0            htmltools_0.5.9            
#> [13] maketools_1.3.2             sys_3.4.3                  
#> [15] sass_0.4.10                 stats4_4.6.1               
#> [17] MatrixGenerics_1.25.0       Biobase_2.73.2             
#> [19] grid_4.6.1                  Seqinfo_1.3.2              
#> [21] abind_1.4-8                 evaluate_1.0.5             
#> [23] jquerylib_0.1.4             fastmap_1.2.0              
#> [25] IRanges_2.47.5              yaml_2.3.12                
#> [27] lifecycle_1.0.5             compiler_4.6.1             
#> [29] MultiAssayExperiment_1.39.1 XVector_0.53.0             
#> [31] lattice_0.23-1              digest_0.6.39              
#> [33] R6_2.6.1                    SparseArray_1.13.4         
#> [35] GenomicRanges_1.65.4        Matrix_1.7-6               
#> [37] bslib_0.12.0                tools_4.6.1                
#> [39] matrixStats_1.5.0           S4Arrays_1.13.2            
#> [41] BiocGenerics_0.59.12        cachem_1.1.0