Sleep is a complex physiological state characterized by distinct stages, each with unique electroencephalographic (EEG) signatures. The American Academy of Sleep Medicine (AASM) defines five sleep stages: wakefulness (W), three non-REM stages (N1, N2, N3), and rapid eye movement sleep (REM). Accurate sleep staging is critical for diagnosing sleep disorders, assessing sleep quality, and understanding neurological health.
Traditional sleep scoring is a time-intensive manual process performed by trained polysomnographic technologists. This vignette demonstrates how PhysioEEG provides automated tools for:
These tools can accelerate research workflows, provide preliminary automated scoring for clinical review, and enable large-scale sleep studies.
First, load the PhysioEEG library and create simulated sleep EEG
data. The make_eeg_sleep() function generates synthetic
data with realistic spectral characteristics for each sleep stage.
library(PhysioEEG)
library(SummarizedExperiment)
library(ggplot2)
# Create a short excerpt of simulated sleep EEG for a fast, self-contained demo.
# Real polysomnography scores a full ~8-hour night; the API is identical at scale.
# Sampling rate: 256 Hz (common for clinical polysomnography)
# Channels: 6 standard EEG derivations
sr <- 256
n_minutes <- 20
n_time <- n_minutes * 60 * sr
eeg <- make_eeg_sleep(
n_time = n_time,
n_channels = 6,
sr = sr
)
# Inspect the data structure
print(eeg)
dim(SummarizedExperiment::assay(eeg, "raw"))
colData(eeg)$labelThe simulated dataset includes channels typically used in clinical sleep studies: F3, F4 (frontal), C3, C4 (central), O1, O2 (occipital). Real sleep EEG would also include EOG (eye movements) and EMG (muscle tone) for comprehensive staging.
Sleep EEG analysis requires appropriate filtering to preserve relevant frequency bands while removing artifacts and high-frequency noise.
The AASM recommends a bandpass filter of 0.3-35 Hz for sleep EEG:
# Apply AASM-recommended bandpass filter
eeg_filt <- eegFilter(
eeg,
lowcut = 0.3,
highcut = 35,
order = 4,
assay_name = "raw",
output_assay = "filtered"
)
# Re-referencing to linked mastoids (A1+A2)/2 is common for sleep
# This would require mastoid channels in real data
# For this example, we'll use the average reference
eeg_filt <- eegRereference(
eeg_filt,
ref_type = "average",
assay_name = "filtered",
output_assay = "reref"
)Before automated staging, it’s crucial to identify and handle artifacts that could confound results.
# Flag 30-second epochs whose peak amplitude exceeds +/-150 uV as artifacts
reref <- assay(eeg_filt, "reref")
epoch_len <- 30 * samplingRate(eeg_filt)
n_epochs <- nrow(reref) %/% epoch_len
artifact_epochs <- Filter(function(e) {
seg <- reref[((e - 1) * epoch_len + 1):(e * epoch_len), , drop = FALSE]
max(abs(seg)) > 150
}, seq_len(n_epochs))
artifact_epochs <- unlist(artifact_epochs)
# Mark artifact epochs for exclusion from analysis
metadata(eeg_filt)$artifact_epochs <- artifact_epochsThe eegSleepStage() function implements AASM-based
automated sleep staging using spectral power features across standard
frequency bands.
# Perform automatic sleep staging in 30-second epochs
stages <- eegSleepStage(
eeg_filt,
epoch_sec = 30, # AASM standard epoch length
assay_name = "reref"
)
# eegSleepStage() returns a data.frame with one row per epoch
head(stages)
# Sequence of stage labels and their distribution
head(stages$stage)
table(stages$stage)
# Spectral power features used for classification
grep("_power$", names(stages), value = TRUE)
# Store staging results in metadata (used by eegSleepMetrics)
metadata(eeg_filt)$sleep_stages <- stageseegSleepStage() returns a data.frame with one row per
30-second epoch and the following columns:
epoch: Epoch indexstage: Stage label (W, N1, N2, N3, REM) assigned to the
epochstart_sample, end_sample: Sample indices
spanning the epochdelta_power, theta_power,
alpha_power, sigma_power,
beta_power: Spectral power features used for
classificationLow-confidence epochs (no stage with >60% probability) may require manual review.
Sleep spindles are bursts of 11-16 Hz oscillations characteristic of N2 sleep, generated by thalamocortical networks. Spindle density is clinically relevant for memory consolidation and neurological assessment.
AASM criteria for spindles:
# Detect sleep spindles across all channels
spindles <- eegSpindleDetect(
eeg_filt,
freq_range = c(11, 16), # Sigma band
min_duration_ms = 500, # Minimum duration
max_duration_ms = 2000, # Maximum duration
threshold_sd = 2.0, # Amplitude threshold in SD of the background
assay_name = "reref"
)
# eegSpindleDetect() returns a data.frame of spindle events
# (channel, start/end sample, duration_ms, peak_sample, peak_amplitude,
# frequency_hz)
head(spindles)
# Spindle count per channel (channel index)
table(spindles$channel)
# Overall spindle density (spindles per minute of recording)
rec_minutes <- nrow(assay(eeg_filt, "reref")) / samplingRate(eeg_filt) / 60
nrow(spindles) / rec_minutes
# Visualize spindle onsets over time (hours)
if (nrow(spindles) > 0) {
onset_hours <- spindles$start_sample / samplingRate(eeg_filt) / 3600
hist(onset_hours,
xlab = "Time (hours)", ylab = "Spindle Count",
main = "Sleep Spindle Distribution")
}K-complexes are large amplitude negative-positive biphasic waveforms characteristic of N2 sleep, serving as markers of cortical downstates and sleep protection.
The eegKcomplexDetect() function identifies K-complexes
based on:
# Detect K-complexes (most prominent in central leads C3/C4)
kcomplexes <- eegKcomplexDetect(
eeg_filt,
min_neg_amplitude = 75, # μV negative deflection
min_duration_ms = 500,
max_duration_ms = 1500,
assay_name = "reref"
)
# eegKcomplexDetect() returns a data.frame of K-complex events
# (channel, negative/positive peak samples and amplitudes, duration_ms)
head(kcomplexes)
# Count per channel (channel index; C3/C4 are indices 1/2)
table(kcomplexes$channel)
# K-complex density (events per minute of recording)
nrow(kcomplexes) / rec_minutesK-complexes reflect:
Slow waves (0.5-2 Hz, >75 μV) are the hallmark of N3 sleep. Slow wave activity (SWA) is the gold standard measure of sleep depth and homeostatic sleep pressure.
# Detect slow waves (maximum amplitude is over frontal regions, F3/F4)
slow_waves <- eegSlowWaveDetect(
eeg_filt,
freq_range = c(0.5, 2.0), # Delta band slow waves
min_amplitude = 75, # μV (AASM criterion)
assay_name = "reref"
)
# eegSlowWaveDetect() returns a data.frame of slow-wave events
# (channel, start/end sample, negative_peak, positive_peak, duration_ms, slope)
head(slow_waves)
# Slow wave density (waves per minute of recording)
slow_waves_density <- nrow(slow_waves) / rec_minutes
slow_waves_density
# SWA: mean negative-peak amplitude
mean_amplitude <- mean(abs(slow_waves$negative_peak))
mean_amplitude
# Slow wave slope (marker of sleep depth)
mean(slow_waves$slope)Slow wave activity exhibits characteristic temporal dynamics:
# Full-night SWA dynamics (an exponential decline across sleep cycles) require
# a complete overnight recording. Here we summarise slow-wave amplitude over
# time by binning slow-wave events into successive segments of the recording.
if (nrow(slow_waves) > 0) {
sw_time_min <- slow_waves$start_sample / samplingRate(eeg_filt) / 60
n_bins <- 5
bin <- cut(sw_time_min, breaks = n_bins, labels = FALSE)
swa_by_bin <- tapply(abs(slow_waves$negative_peak), bin, mean)
plot(seq_along(swa_by_bin), swa_by_bin,
type = "b", pch = 19,
xlab = "Recording segment", ylab = "Mean slow-wave amplitude (uV)",
main = "Slow Wave Amplitude Across the Recording")
}The eegSleepMetrics() function computes standard
polysomnographic measures of sleep quality and continuity.
# Calculate comprehensive sleep metrics.
# eegSleepMetrics() reads the staging data.frame from metadata(x)$sleep_stages
# (set above), so it takes only the PhysioExperiment object.
metrics <- eegSleepMetrics(eeg_filt)
# Print standardized sleep report
print(metrics)
# Key metrics:
# - Total Sleep Time (TST): Total minutes of N1+N2+N3+REM
# - Sleep Efficiency (SE%): TST / Time in Bed × 100
# - Wake After Sleep Onset (WASO): Wake time after initial sleep
# - Sleep Latency (SL): Time from lights off to first sleep epoch
# - REM Latency (RL): Time from sleep onset to first REM
# - Stage percentages: %N1, %N2, %N3, %REM
# - Arousals per hour (if arousal detection performed)Healthy adult sleep typically shows:
Deviations suggest sleep disorders or other pathology.
The hypnogram is the standard visualization of sleep staging, showing sleep depth across the night.
# Create a hypnogram with custom colors
eegPlotHypnogram(
eeg_filt,
stages = metadata(eeg_filt)$sleep_stages,
epoch_sec = 30,
colors = c(
"W" = "#FF6B6B", # Red for wake
"N1" = "#4ECDC4", # Cyan for N1
"N2" = "#45B7D1", # Blue for N2
"N3" = "#1A535C", # Dark blue for N3
"REM" = "#FFE66D" # Yellow for REM
)
)
# The hypnogram accepts a stage vector directly; spindle- or SWA-density
# overlays can be layered on top with custom ggplot2 geoms if desired.
eegPlotHypnogram(
eeg_filt,
stages = metadata(eeg_filt)$sleep_stages,
epoch_sec = 30
)Here’s an integrated workflow for clinical sleep EEG analysis:
# 1. Load and preprocess data (short excerpt for a fast demo)
eeg <- make_eeg_sleep(n_time = 20 * 60 * 256, n_channels = 6, sr = 256)
eeg <- eegFilter(eeg, lowcut = 0.3, highcut = 35)
eeg <- eegRereference(eeg, ref_type = "average")
# 2. Automatic sleep staging
stages <- eegSleepStage(eeg, epoch_sec = 30)
metadata(eeg)$sleep_stages <- stages
# 3. Detect sleep-specific waveforms (each returns an event data.frame)
spindles <- eegSpindleDetect(eeg, freq_range = c(11, 16))
kcomplexes <- eegKcomplexDetect(eeg, min_neg_amplitude = 75)
slow_waves <- eegSlowWaveDetect(eeg, freq_range = c(0.5, 2.0), min_amplitude = 75)
# 4. Compute sleep architecture metrics (reads metadata$sleep_stages)
metrics <- eegSleepMetrics(eeg)
# 5. Generate visualization
eegPlotHypnogram(eeg, stages = stages)
# 6. Export comprehensive report
report <- list(
demographics = list(age = 35, sex = "M"),
sleep_metrics = metrics,
spindle_count = nrow(spindles),
kcomplex_count = nrow(kcomplexes),
swa_mean = mean(abs(slow_waves$negative_peak)),
n_epochs_scored = nrow(stages)
)
# Save report for clinical review
saveRDS(report, file.path(tempdir(), "sleep_study_report.rds"))While automated staging is powerful, clinicians should be aware of limitations:
PhysioEEG provides a comprehensive toolkit for automated sleep EEG analysis, from preprocessing through detection of sleep-specific oscillations to quantification of sleep architecture. These tools enable:
For further analysis, PhysioEEG integrates with time-frequency
analysis (eegTimeFreq()), connectivity analysis
(eegConnectivity()), and source localization
(eegSourceLoc()) to provide deeper insights into sleep
neurophysiology.