Note
Go to the end to download the full example code.
Cross-Session Motor Imagery#
This example show how to perform a cross session motor imagery analysis on the very popular dataset 2a from the BCI competition IV.
We will compare two pipelines :
CSP+LDA
Riemannian Geometry+Logistic Regression
We will use the LeftRightImagery paradigm. This will restrict the analysis to two classes (left hand versus right hand) and use AUC as metric.
The cross session evaluation context will evaluate performance using a leave one session out cross-validation. For each session in the dataset, a model is trained on every other session and performance are evaluated on the current session.
# Authors: Alexandre Barachant <alexandre.barachant@gmail.com>
# Sylvain Chevallier <sylvain.chevallier@uvsq.fr>
#
# License: BSD (3-clause)
import matplotlib.pyplot as plt
from mne.decoding import CSP
from pyriemann.estimation import Covariances
from pyriemann.tangentspace import TangentSpace
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
import moabb
import moabb.analysis.plotting as moabb_plt
from moabb.analysis.chance_level import chance_by_chance
from moabb.datasets import BNCI2014_001
from moabb.evaluations import CrossSessionEvaluation
from moabb.paradigms import LeftRightImagery
moabb.set_log_level("info")
/home/runner/work/moabb/moabb/.venv/lib/python3.11/site-packages/optuna/integration/sklearn.py:14: FutureWarning: `optuna.integration.sklearn` has been deprecated in v4.9.0. This feature will be removed in v6.0.0. See https://github.com/optuna/optuna/releases/tag/v4.9.0. Use `optuna_integration.sklearn` instead.
optuna_warn(f"{msg} Use `optuna_integration.sklearn` instead.", FutureWarning)
Create Pipelines#
Pipelines must be a dict of sklearn pipeline transformer.
The CSP implementation is based on the MNE implementation. We selected 8 CSP components, as usually done in the literature.
The Riemannian geometry pipeline consists in covariance estimation, tangent space mapping and finally a logistic regression for the classification.
pipelines = {}
pipelines["CSP+LDA"] = make_pipeline(CSP(n_components=8), LDA())
pipelines["RG+LR"] = make_pipeline(
Covariances(), TangentSpace(), LogisticRegression(solver="lbfgs")
)
Evaluation#
We define the paradigm (LeftRightImagery) and the dataset (BNCI2014_001). The evaluation will return a DataFrame containing a single AUC score for each subject / session of the dataset, and for each pipeline.
Results are saved into the database, so that if you add a new pipeline, it will not run again the evaluation unless a parameter has changed. Results can be overwritten if necessary.
paradigm = LeftRightImagery()
# Because this is being auto-generated we only use 2 subjects
dataset = BNCI2014_001()
dataset.subject_list = dataset.subject_list[:2]
datasets = [dataset]
overwrite = False # set to True if we want to overwrite cached results
evaluation = CrossSessionEvaluation(
paradigm=paradigm, datasets=datasets, suffix="examples", overwrite=overwrite
)
results = evaluation.process(pipelines)
print(results.head())
[codecarbon WARNING @ 23:45:29] Multiple instances of codecarbon are allowed to run at the same time.
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base CSP+LDA | BNCI2014-001 | 1 | 0train: Score 0.932
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base RG+LR | BNCI2014-001 | 1 | 0train: Score 0.950
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base CSP+LDA | BNCI2014-001 | 1 | 1test: Score 0.955
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base RG+LR | BNCI2014-001 | 1 | 1test: Score 0.963
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base CSP+LDA | BNCI2014-001 | 2 | 0train: Score 0.527
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base RG+LR | BNCI2014-001 | 2 | 0train: Score 0.574
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base CSP+LDA | BNCI2014-001 | 2 | 1test: Score 0.646
2026-07-14 23:45:56,521 INFO MainThread moabb.evaluations.base RG+LR | BNCI2014-001 | 2 | 1test: Score 0.586
/home/runner/work/moabb/moabb/moabb/analysis/results.py:189: H5pyDeprecationWarning: Creating a dataset without passing data or dtype is deprecated. Pass an explicit dtype. Using dtype='f4' will keep the current default behaviour.
dset.create_dataset(
score time ... pipeline codecarbon_task_name
0 0.931713 0.183806 ... CSP+LDA 2bbba6a2-fe81-4ef5-99af-1f29382a9435
1 0.955440 0.173149 ... CSP+LDA 3786616b-7ce4-416b-ae03-401f25d672d1
2 0.527199 0.179962 ... CSP+LDA 87b8fc43-ca61-40a7-b30b-8c67345dcb28
3 0.646219 0.188580 ... CSP+LDA ad7809a4-fb93-49e9-8ae8-f6876dacc29a
4 0.950424 0.106839 ... RG+LR 74db793d-4903-46da-86f9-624e9765c4c0
[5 rows x 13 columns]
Plot Results#
Here we plot the results using the MOABB plotting utilities with chance
level annotations. The score_plot visualizes all the data with one
score per subject for every dataset and pipeline. The paired_plot
compares two algorithms head-to-head.
chance_levels = chance_by_chance(results, alpha=[0.05, 0.01])
fig, _ = moabb_plt.score_plot(results, chance_level=chance_levels)
plt.show()

2026-07-14 23:45:56,567 WARNING MainThread moabb.analysis.plotting Dataset names are too similar, turning off name shortening
/home/runner/work/moabb/moabb/moabb/analysis/plotting.py:434: UserWarning: The palette list has more values (6) than needed (2), which may not be intended.
sea.stripplot(
The paired plot compares CSP+LDA versus RG+LR. Each point represents the score of a single session. An algorithm outperforms the other when most points fall in its quadrant.
fig = moabb_plt.paired_plot(results, "CSP+LDA", "RG+LR", chance_level=chance_levels)
plt.show()

Total running time of the script: (0 minutes 37.292 seconds)