Skip to content

Commit 9bbe177

Browse files
1 parent b83e582 commit 9bbe177

407 files changed

Lines changed: 3865 additions & 3003 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Binary file not shown.
Binary file not shown.
Binary file not shown.

docs/_downloads/18de027e6e95284d05272fbe0013972a/plot_benchmark_grid_search.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import matplotlib.pyplot as plt
1818

1919
from moabb import benchmark, set_log_level
20+
from moabb.analysis.chance_level import chance_by_chance
2021
from moabb.analysis.plotting import score_plot
22+
from moabb.paradigms import LeftRightImagery
2123

2224

2325
set_log_level("info")
@@ -82,5 +84,11 @@
8284
# pandas dataframe, and can be used to generate figures. The analysis & figures
8385
# are saved in the ``benchmark`` folder.
8486

85-
score_plot(results)
87+
###############################################################################
88+
# Compute chance levels for the dataset used in the benchmark.
89+
90+
paradigm = LeftRightImagery()
91+
chance_levels = chance_by_chance(results, alpha=[0.05, 0.01])
92+
93+
score_plot(results, chance_level=chance_levels)
8694
plt.show()

docs/_downloads/1cbb6292421895e4fed9e809f647c388/plot_benchmark.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import matplotlib.pyplot as plt
1717

1818
from moabb import benchmark, set_log_level
19+
from moabb.analysis.chance_level import chance_by_chance
1920
from moabb.analysis.plotting import score_plot
2021
from moabb.paradigms import LeftRightImagery
2122

@@ -120,5 +121,10 @@
120121
# pandas dataframe, and can be used to generate figures. The analysis & figures
121122
# are saved in the ``benchmark`` folder.
122123

123-
score_plot(results)
124+
###############################################################################
125+
# Compute chance levels for the dataset used in the benchmark.
126+
127+
chance_levels = chance_by_chance(results, alpha=[0.05, 0.01])
128+
129+
score_plot(results, chance_level=chance_levels)
124130
plt.show()
Binary file not shown.

docs/_downloads/2c7daa9cad24568f4eaa6c6fac36dd5c/plot_cross_session_motor_imagery.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
# License: BSD (3-clause)
2727

2828
import matplotlib.pyplot as plt
29-
import seaborn as sns
3029
from mne.decoding import CSP
3130
from pyriemann.estimation import Covariances
3231
from pyriemann.tangentspace import TangentSpace
@@ -35,6 +34,8 @@
3534
from sklearn.pipeline import make_pipeline
3635

3736
import moabb
37+
import moabb.analysis.plotting as moabb_plt
38+
from moabb.analysis.chance_level import chance_by_chance
3839
from moabb.datasets import BNCI2014_001
3940
from moabb.evaluations import CrossSessionEvaluation
4041
from moabb.paradigms import LeftRightImagery
@@ -92,36 +93,20 @@
9293
# Plot Results
9394
# ----------------
9495
#
95-
# Here we plot the results. We first make a pointplot with the average
96-
# performance of each pipeline across session and subjects.
97-
# The second plot is a paired scatter plot. Each point representing the score
98-
# of a single session. An algorithm will outperform another is most of the
99-
# points are in its quadrant.
100-
101-
fig, axes = plt.subplots(1, 2, figsize=[8, 4], sharey=True)
102-
103-
sns.stripplot(
104-
data=results,
105-
y="score",
106-
x="pipeline",
107-
ax=axes[0],
108-
jitter=True,
109-
alpha=0.5,
110-
zorder=1,
111-
palette="Set1",
112-
)
113-
sns.pointplot(data=results, y="score", x="pipeline", ax=axes[0], palette="Set1")
96+
# Here we plot the results using the MOABB plotting utilities with chance
97+
# level annotations. The ``score_plot`` visualizes all the data with one
98+
# score per subject for every dataset and pipeline. The ``paired_plot``
99+
# compares two algorithms head-to-head.
114100

115-
axes[0].set_ylabel("ROC AUC")
116-
axes[0].set_ylim(0.5, 1)
101+
chance_levels = chance_by_chance(results, alpha=[0.05, 0.01])
117102

118-
paired = results.pivot_table(
119-
values="score", columns="pipeline", index=["subject", "session"]
120-
)
121-
paired = paired.reset_index()
103+
fig, _ = moabb_plt.score_plot(results, chance_level=chance_levels)
104+
plt.show()
122105

123-
sns.regplot(data=paired, y="RG+LR", x="CSP+LDA", ax=axes[1], fit_reg=False)
124-
axes[1].plot([0, 1], [0, 1], ls="--", c="k")
125-
axes[1].set_xlim(0.5, 1)
106+
###############################################################################
107+
# The paired plot compares CSP+LDA versus RG+LR. Each point represents the
108+
# score of a single session. An algorithm outperforms the other when most
109+
# points fall in its quadrant.
126110

111+
fig = moabb_plt.paired_plot(results, "CSP+LDA", "RG+LR", chance_level=chance_levels)
127112
plt.show()

docs/_downloads/33bee7d7204a7cb56293bd1d838b63f8/plot_statistical_analysis.ipynb

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"\n# Statistical Analysis\n\nThe MOABB codebase comes with convenience plotting utilities and some\nstatistical testing. This tutorial focuses on what those exactly are and how\nthey can be used.\n"
7+
"\n# Statistical Analysis and Chance Level Assessment\n\nThe MOABB codebase comes with convenience plotting utilities and some\nstatistical testing. This tutorial focuses on what those exactly are and how\nthey can be used.\n\nIn addition, we demonstrate how to compute and visualize statistically\nadjusted chance levels following Combrisson & Jerbi (2015). The theoretical\nchance level (100/c %) only holds for infinite sample sizes. With finite\ntest samples, classifiers can exceed this threshold purely by chance \u2014 an\neffect that grows stronger as the sample size decreases. The adjusted chance\nlevel, derived from the inverse survival function of the binomial\ndistribution, gives the minimum accuracy needed to claim statistically\nsignificant decoding at a given alpha level.\n"
88
]
99
},
1010
{
@@ -15,7 +15,7 @@
1515
},
1616
"outputs": [],
1717
"source": [
18-
"# Authors: Vinay Jayaram <vinayjayaram13@gmail.com>\n#\n# License: BSD (3-clause)\n# sphinx_gallery_thumbnail_number = -2\n\nimport matplotlib.pyplot as plt\nfrom mne.decoding import CSP\nfrom pyriemann.estimation import Covariances\nfrom pyriemann.tangentspace import TangentSpace\nfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.pipeline import make_pipeline\n\nimport moabb\nimport moabb.analysis.plotting as moabb_plt\nfrom moabb.analysis.meta_analysis import ( # noqa: E501\n compute_dataset_statistics,\n find_significant_differences,\n)\nfrom moabb.datasets import BNCI2014_001\nfrom moabb.evaluations import CrossSessionEvaluation\nfrom moabb.paradigms import LeftRightImagery\n\n\nmoabb.set_log_level(\"info\")\n\nprint(__doc__)"
18+
"# Authors: Vinay Jayaram <vinayjayaram13@gmail.com>\n#\n# License: BSD (3-clause)\n# sphinx_gallery_thumbnail_number = -2\n\nimport matplotlib.pyplot as plt\nfrom mne.decoding import CSP\nfrom pyriemann.estimation import Covariances\nfrom pyriemann.tangentspace import TangentSpace\nfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.pipeline import make_pipeline\n\nimport moabb\nimport moabb.analysis.plotting as moabb_plt\nfrom moabb.analysis.chance_level import (\n adjusted_chance_level,\n chance_by_chance,\n)\nfrom moabb.analysis.meta_analysis import ( # noqa: E501\n compute_dataset_statistics,\n find_significant_differences,\n)\nfrom moabb.datasets import BNCI2014_001\nfrom moabb.evaluations import CrossSessionEvaluation\nfrom moabb.paradigms import LeftRightImagery\n\n\nmoabb.set_log_level(\"info\")\n\nprint(__doc__)"
1919
]
2020
},
2121
{
@@ -58,7 +58,7 @@
5858
"cell_type": "markdown",
5959
"metadata": {},
6060
"source": [
61-
"## MOABB Plotting\n\nHere we plot the results using some of the convenience methods within the\ntoolkit. The score_plot visualizes all the data with one score per subject\nfor every dataset and pipeline.\n\n"
61+
"## Chance Level Computation\n\nThe theoretical chance level for a *c*-class problem is 100/*c* (e.g. 50%\nfor 2 classes). However, this threshold assumes an **infinite** number of\ntest samples. In practice, with a finite number of trials, a classifier\ncan exceed the theoretical chance level purely by chance \u2014 especially when\nthe sample size is small.\n\nCombrisson & Jerbi (2015) demonstrated that classifiers applied to pure\nGaussian noise can yield accuracies well above the theoretical chance\nlevel when the number of test samples is limited. For example, with only\n40 observations in a 2-class problem, a decoding accuracy of 70% can\noccur by chance alone, far above the 50% theoretical threshold.\n\nTo address this, they proposed computing a **statistically adjusted\nchance level** using the inverse survival function of the binomial\ncumulative distribution. This gives the minimum accuracy required to\nclaim that decoding significantly exceeds chance at a given significance\nlevel *alpha*. Stricter alpha values (e.g. 0.001 vs 0.05) require higher\naccuracy to assert significance.\n\nNote that the number of classes depends on the **paradigm**, not the raw\ndataset. BNCI2014_001 has 4 motor imagery classes, but LeftRightImagery\nselects only left_hand and right_hand (2 classes).\n\n"
6262
]
6363
},
6464
{
@@ -69,14 +69,14 @@
6969
},
7070
"outputs": [],
7171
"source": [
72-
"fig = moabb_plt.score_plot(results)\nplt.show()"
72+
"n_classes = len(paradigm.used_events(dataset))\nprint(f\"Number of classes (from paradigm): {n_classes}\")\n# theoretical chance level: 1 / n_classes\nprint(f\"Theoretical chance level: {1.0 / n_classes:.2f}\")\n\n# Adjusted chance level for 144 test trials at alpha=0.05\n# (BNCI2014_001 has 144 trials per class per session)\nn_test_trials = 144 * n_classes\nprint(\n f\"Adjusted chance level (n={n_test_trials}, alpha=0.05): \"\n f\"{adjusted_chance_level(n_classes, n_test_trials, 0.05):.4f}\"\n)"
7373
]
7474
},
7575
{
7676
"cell_type": "markdown",
7777
"metadata": {},
7878
"source": [
79-
"For a comparison of two algorithms, there is the paired_plot, which plots\nperformance in one versus the performance in the other over all chosen\ndatasets. Note that there is only one score per subject, regardless of the\nnumber of sessions.\n\n"
79+
"The convenience function :func:`chance_by_chance` reads\n``n_samples_test`` and ``n_classes`` directly from the results DataFrame,\nso no dataset objects are needed.\n\n"
8080
]
8181
},
8282
{
@@ -87,14 +87,68 @@
8787
},
8888
"outputs": [],
8989
"source": [
90-
"fig = moabb_plt.paired_plot(results, \"CSP+LDA\", \"RG+LDA\")\nplt.show()"
90+
"chance_levels = chance_by_chance(results, alpha=[0.05, 0.01, 0.001])\n\nprint(\"\\nChance levels:\")\nfor name, levels in chance_levels.items():\n print(f\" {name}:\")\n print(f\" Theoretical: {levels['theoretical']:.2f}\")\n for alpha, threshold in sorted(levels[\"adjusted\"].items()):\n print(f\" Adjusted (alpha={alpha}): {threshold:.4f}\")"
9191
]
9292
},
9393
{
9494
"cell_type": "markdown",
9595
"metadata": {},
9696
"source": [
97-
"## Statistical Testing and Further Plots\n\nIf the statistical significance of results is of interest, the method\ncompute_dataset_statistics allows one to show a meta-analysis style plot as\nwell. For an overview of how all algorithms perform in comparison with each\nother, the method find_significant_differences and the summary_plot are\npossible.\n\n"
97+
"## MOABB Plotting with Chance Levels\n\nHere we plot the results using the convenience methods within the toolkit.\nThe ``score_plot`` visualizes all the data with one score per subject for\nevery dataset and pipeline.\n\nBy passing the ``chance_level`` parameter, the plot draws the correct\ntheoretical chance level line and, when adjusted levels are available, also\ndraws significance threshold lines at each alpha level.\n\n"
98+
]
99+
},
100+
{
101+
"cell_type": "code",
102+
"execution_count": null,
103+
"metadata": {
104+
"collapsed": false
105+
},
106+
"outputs": [],
107+
"source": [
108+
"fig, _ = moabb_plt.score_plot(results, chance_level=chance_levels)\nplt.show()"
109+
]
110+
},
111+
{
112+
"cell_type": "markdown",
113+
"metadata": {},
114+
"source": [
115+
"## Distribution Plot with KDE\n\nThe ``distribution_plot`` combines a violin plot (showing the KDE density\nof scores) with a strip plot (showing individual data points). This gives\na richer view of score distributions compared to the strip plot alone.\n\n"
116+
]
117+
},
118+
{
119+
"cell_type": "code",
120+
"execution_count": null,
121+
"metadata": {
122+
"collapsed": false
123+
},
124+
"outputs": [],
125+
"source": [
126+
"fig, _ = moabb_plt.distribution_plot(results, chance_level=chance_levels)\nplt.show()"
127+
]
128+
},
129+
{
130+
"cell_type": "markdown",
131+
"metadata": {},
132+
"source": [
133+
"## Paired Plot with Chance Level\n\nFor a comparison of two algorithms, the ``paired_plot`` shows performance\nof one versus the other. When ``chance_level`` is provided, the axis limits\nare adjusted accordingly and dashed crosshair lines mark the theoretical\nchance level. When adjusted significance thresholds are included, a shaded\nband highlights the region that is not significantly above chance.\n\n"
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"execution_count": null,
139+
"metadata": {
140+
"collapsed": false
141+
},
142+
"outputs": [],
143+
"source": [
144+
"fig = moabb_plt.paired_plot(results, \"CSP+LDA\", \"RG+LDA\", chance_level=chance_levels)\nplt.show()"
145+
]
146+
},
147+
{
148+
"cell_type": "markdown",
149+
"metadata": {},
150+
"source": [
151+
"## Statistical Testing and Further Plots\n\nIf the statistical significance of results is of interest, the method\n``compute_dataset_statistics`` allows one to show a meta-analysis style plot\nas well. For an overview of how all algorithms perform in comparison with\neach other, the method ``find_significant_differences`` and the\n``summary_plot`` are possible.\n\n"
98152
]
99153
},
100154
{
@@ -130,7 +184,7 @@
130184
"cell_type": "markdown",
131185
"metadata": {},
132186
"source": [
133-
"The summary plot shows the effect and significance related to the hypothesis\nthat the algorithm on the y-axis significantly outperformed the algorithm on\nthe x-axis over all datasets\n\n"
187+
"The summary plot shows the effect and significance related to the hypothesis\nthat the algorithm on the y-axis significantly outperformed the algorithm on\nthe x-axis over all datasets.\n\n"
134188
]
135189
},
136190
{

docs/_downloads/3bb6d35e266c94de44c44ec75aa9d550/plot_mne_and_scikit_estimators.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from sklearn.preprocessing import StandardScaler
3434

3535
import moabb
36+
from moabb.analysis.chance_level import chance_by_chance
3637
from moabb.analysis.meta_analysis import ( # noqa: E501
3738
compute_dataset_statistics,
3839
find_significant_differences,
@@ -187,7 +188,9 @@ def transform(self, X, y=None):
187188
##############################################################################
188189
# We could compare the Euclidean and Riemannian performance using a `paired_plot`
189190

190-
paired_plot(all_res, "XDAWN LR", "RG LR")
191+
chance_levels = chance_by_chance(all_res, alpha=[0.05, 0.01])
192+
193+
paired_plot(all_res, "XDAWN LR", "RG LR", chance_level=chance_levels)
191194

192195
##############################################################################
193196
# All the results could be compared and statistical analysis could highlight the
Binary file not shown.

0 commit comments

Comments
 (0)