Skip to contents

Introduction

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:

  • Automatic sleep staging based on AASM criteria using spectral features
  • Detection of sleep-specific waveforms: spindles, K-complexes, and slow waves
  • Quantification of sleep architecture: total sleep time, efficiency, stage distribution
  • Visualization: hypnograms for clinical interpretation

These tools can accelerate research workflows, provide preliminary automated scoring for clinical review, and enable large-scale sleep studies.

Setup and Data Preparation

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)$label

The 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.

Preprocessing for Sleep EEG

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:

  • High-pass (0.3 Hz): Removes slow DC drift and movement artifacts
  • Low-pass (35 Hz): Removes muscle artifact and electrical noise while preserving beta activity
# 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"
)

Artifact Detection and Removal

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_epochs

Automatic Sleep Staging

The eegSleepStage() function implements AASM-based automated sleep staging using spectral power features across standard frequency bands.

Sleep Stage Characteristics

  • Wake (W): Low delta, high alpha (8-12 Hz) and beta (>13 Hz) when eyes open
  • N1: Theta activity (4-7 Hz) dominates, alpha dropout
  • N2: Sleep spindles (11-16 Hz bursts) and K-complexes
  • N3: High delta power (0.5-2 Hz), slow wave sleep (SWS)
  • REM: Mixed frequency, low amplitude, theta in sawtooth waves
# 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 <- stages

Interpreting Staging Output

eegSleepStage() returns a data.frame with one row per 30-second epoch and the following columns:

  • epoch: Epoch index
  • stage: Stage label (W, N1, N2, N3, REM) assigned to the epoch
  • start_sample, end_sample: Sample indices spanning the epoch
  • delta_power, theta_power, alpha_power, sigma_power, beta_power: Spectral power features used for classification

Low-confidence epochs (no stage with >60% probability) may require manual review.

Sleep Spindle Detection

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.

Detection Parameters

AASM criteria for spindles:

  • Frequency: 11-16 Hz (sigma band)
  • Duration: 0.5-2.0 seconds
  • Amplitude: At least 2x background activity
# 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")
}

Clinical Significance

  • Normal adults: 2-5 spindles per minute during N2/N3
  • Reduced density: Schizophrenia, autism, aging
  • Increased density: Early Alzheimer’s (compensatory), some epilepsies
  • Asymmetry: May indicate focal pathology

K-Complex Detection

K-complexes are large amplitude negative-positive biphasic waveforms characteristic of N2 sleep, serving as markers of cortical downstates and sleep protection.

Detection Methodology

The eegKcomplexDetect() function identifies K-complexes based on:

  • Morphology: Negative deflection followed by positive component
  • Amplitude: >75 μV peak-to-peak
  • Duration: 0.5-1.5 seconds total
# 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_minutes

Clinical Applications

K-complexes reflect:

  • Cortical integrity: Reduced in dementia, encephalopathy
  • Arousal threshold: More K-complexes = better sleep protection
  • Age effects: Decrease in amplitude and frequency with aging
  • Evoked K-complexes: Can be elicited by auditory stimuli

Slow Wave Detection and Analysis

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.

Slow Wave Detection

# 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)

SWA Dynamics Across the Night

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")
}

Clinical Interpretation

  • Elevated SWA: Sleep deprivation, recovery sleep
  • Reduced SWA: Aging, insomnia, depression, neurodegenerative disease
  • Frontal SWA deficit: ADHD, schizophrenia
  • SWA slope: Steeper slopes correlate with better cognition

Sleep Architecture Metrics

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)

Normal Adult Sleep Architecture

Healthy adult sleep typically shows:

  • Sleep Efficiency: >85%
  • Sleep Latency: <30 minutes
  • REM Latency: 60-120 minutes
  • N1: 2-5% of TST
  • N2: 45-55% of TST
  • N3: 15-25% of TST (decreases with age)
  • REM: 20-25% of TST
  • WASO: <30 minutes

Deviations suggest sleep disorders or other pathology.

Hypnogram Visualization

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
)

Clinical Interpretation and Reporting

Complete Analysis Workflow

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"))

Abnormalities to Look For

Insomnia Pattern

  • Low sleep efficiency (<80%)
  • Prolonged sleep latency (>30 min)
  • Excessive WASO (>60 min)
  • Reduced N3 percentage

Sleep Apnea

  • Fragmented sleep with frequent arousals
  • Stage shifts following apneic events
  • Would require integration with respiratory signals

Depression

  • Shortened REM latency (<60 min)
  • Increased REM percentage
  • Reduced SWA in first sleep cycle

Neurodegenerative Disease

  • Reduced spindle density
  • Lower SWA amplitude
  • REM sleep behavior disorder (requires EMG)

Limitations of Automated Staging

While automated staging is powerful, clinicians should be aware of limitations:

  1. No EMG/EOG: This implementation uses EEG only; true polysomnography includes eye movements and muscle tone
  2. Artifact sensitivity: Heavy artifacts can cause misclassification
  3. Individual variation: Algorithm trained on population norms may not capture individual sleep patterns
  4. Ambiguous epochs: Transitional sleep may have low classification confidence
  5. Requires validation: Automated staging should be reviewed by trained personnel for clinical decisions

Best Practices

  • Manual review: Check low-confidence epochs (probability <60%)
  • Compare channels: Spindles may be focal; check multiple derivations
  • Context matters: Integrate with clinical history and other polysomnographic signals
  • Normative data: Compare metrics to age-matched norms
  • Longitudinal tracking: Sleep architecture changes across nights; single studies may not represent typical sleep

Conclusion

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:

  • Rapid screening of large sleep study datasets
  • Objective quantification of sleep quality metrics
  • Research applications investigating sleep’s role in cognition and health
  • Clinical decision support when combined with expert review

For further analysis, PhysioEEG integrates with time-frequency analysis (eegTimeFreq()), connectivity analysis (eegConnectivity()), and source localization (eegSourceLoc()) to provide deeper insights into sleep neurophysiology.

References

  • Iber C, et al. (2007). The AASM Manual for the Scoring of Sleep and Associated Events. American Academy of Sleep Medicine.
  • Rechtschaffen A, Kales A (1968). A Manual of Standardized Terminology, Techniques and Scoring System for Sleep Stages of Human Subjects.
  • Berry RB, et al. (2017). The AASM Manual for the Scoring of Sleep and Associated Events: Rules, Terminology and Technical Specifications, Version 2.4.

Session Information