Skip to content

Decoding

This demo demonstrates time-resolved multivariate pattern analysis (MVPA) for decoding experimental conditions from EEG data.

What is MVPA Decoding?

MVPA uses machine learning classifiers to decode experimental conditions from spatial patterns of brain activity:

  • Multivariate: Uses all channels simultaneously (not one channel at a time)

  • Pattern analysis: Finds distributed spatial patterns that discriminate conditions

  • Time-resolved: Decodes at each time point separately to track when information is available

This reveals when and how well neural patterns can distinguish between experimental conditions.

Why Use Decoding?

Information content:

Decoding tells you when information is present in neural patterns, even if traditional ERPs don't show clear differences.

Spatial patterns:

Uses distributed activity across channels, potentially more sensitive than univariate approaches.

Temporal dynamics:

Track when discriminative information emerges, peaks, and decays across the trial.

Workflow

1. Prepare data:

julia
participant_epochs = prepare_decoding(
    "epochs_good",
    condition_selection = conditions([1, 2]),
    sample_selection = samples((-0.2, 1.5))
)

2. Run decoding:

julia
all_decoded = decode_libsvm(
    participant_epochs,
    n_iterations = 5,
    n_folds = 3
)

Uses cross-validated support vector machine (SVM) classification.

3. Grand average:

julia
grand_avg = EegFun.grand_average(all_decoded)

Average decoding accuracy across participants.

4. Statistical testing:

julia
stats = test_against_chance(all_decoded, alpha = 0.05)
stats_cluster = test_against_chance_cluster(all_decoded, alpha = 0.05)

5. Visualization:

julia
plot_decoding(grand_avg, stats)

Cross-Validation

Decoding uses k-fold cross-validation:

  1. Split data into k folds

  2. Train on k-1 folds

  3. Test on held-out fold

  4. Repeat for all folds

  5. Average accuracy across folds

This prevents overfitting and gives unbiased accuracy estimates.

Statistical Testing

Multiple Comparison Correction:

MethodDescription
:noneNo correction (liberal)
:bonferroniDivide alpha by number of time points (conservative)
:clusterCluster-based permutation testing (recommended)

Cluster-based testing:

Identifies contiguous time intervals where decoding is above chance while controlling family-wise error rate.

Interpreting Results

Decoding accuracy:

  • 50% = Chance level (for 2-class problems)

  • 60-70% = Moderate decoding (information present)

  • >80% = Strong decoding (highly discriminative patterns)

Temporal profile:

  • Early peaks (< 200 ms): Sensory processing

  • Mid-latency (200-400 ms): Perceptual/cognitive processing

  • Late sustained (> 400 ms): Decision-making, motor preparation

Significance:

Only interpret time points that survive statistical testing with appropriate correction.

Demo Structure

Synthetic data:

Creates artificial data with controllable signal-to-noise ratio to validate the pipeline.

Real data:

Applies decoding to actual experimental data, comparing two conditions across participants.

Multiple corrections:

Demonstrates different statistical correction methods for comparison.

Best Practices

Data requirements:

  • Balanced classes: Equal number of trials per condition (use equalize_trials = true)

  • Sufficient trials: At least 30-50 trials per condition

  • Clean data: Artifact rejection before decoding

Cross-validation settings:

  • n_folds: 3-10 folds (fewer for small trial counts)

  • n_iterations: 5-20 iterations (more = more stable, but slower)

Statistical testing:

  • Use cluster-based correction as default

  • Bonferroni is very conservative for time-series data

  • Report corrected p-values and time intervals

Code Examples

Show Code
julia
# Demo: Multivariate Pattern Analysis (MVPA) / Decoding
# Shows time-resolved classification using LIBSVM, with synthetic data creation,
# grand averaging, and statistical testing (uncorrected, Bonferroni, cluster-based).

using EegFun
using DataFrames
using Random

@info EegFun.section("MVPA/DECODING MANUAL TEST")

# Create synthetic data for 10 participants
# Adjustable difficulty via signal_strength and noise_level
difficulty = "hard"

if difficulty == "easy" # maybe a bit extreme! :-)
    signal_strength, noise_level = 2.0, 0.1
elseif difficulty == "medium"
    signal_strength, noise_level = 1.0, 0.3
else  # hard
    signal_strength, noise_level = 0.25, 0.75
end

println("  Using difficulty: $difficulty (signal=$signal_strength, noise=$noise_level)")
all_synthetic = [
    [
        EegFun.create_synthetic_epochs(p, 1, "Cond1", 100; signal_strength = signal_strength, noise_level = noise_level),
        EegFun.create_synthetic_epochs(p, 2, "Cond2", 100; signal_strength = signal_strength, noise_level = noise_level),
    ] for p = 1:10
]
EegFun.plot_epochs(all_synthetic[1]) # VP1
EegFun.plot_epochs(all_synthetic[2]) # VP2
EegFun.plot_epochs(all_synthetic[3]) # VP3
# and so on

# Decode synthetic data (batch method)
decoded_synthetic = EegFun.decode_libsvm(all_synthetic; n_iterations = 20, n_folds = 3)
grand_avg_synthetic = EegFun.grand_average(decoded_synthetic)

EegFun.plot_decoding(decoded_synthetic)    # every "VP"
EegFun.plot_decoding(grand_avg_synthetic)  # grand average

# Test and plot with different methods
stats_none = EegFun.test_against_chance(decoded_synthetic, alpha = 0.05, correction_method = :none)
EegFun.plot_decoding(grand_avg_synthetic, stats_none, title = "Synthetic Data: No Correction")

stats_bonf = EegFun.test_against_chance(decoded_synthetic, alpha = 0.05, correction_method = :bonferroni)
EegFun.plot_decoding(grand_avg_synthetic, stats_bonf, title = "Synthetic Data: Bonferroni")

stats_cluster = EegFun.test_against_chance_cluster(decoded_synthetic, alpha = 0.05)
EegFun.plot_decoding(grand_avg_synthetic, stats_cluster, title = "Synthetic Data: Cluster-based")

See Also