Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-24.04
tools:
python: "3.9"
python: "3.10"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
Expand All @@ -18,5 +18,9 @@ sphinx:
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: requirements-dev.txt
install:
- method: pip
path: .
extra_requirements:
- docs
- full
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ Contributions are welcome! If you have suggestions or find any bugs, please open
- Plan and implement enhancements for visualization features.
- Integrate new visual components and testing.

#### Descriptors Module
- Add a future connectivity descriptor family built on `mne-connectivity`.
- Start that connectivity family with phase-based measures such as `PLV`, with room for later extensions like `ciPLV`, `PLI`, and `wPLI`.
- Add a future wavelet-based descriptor batch built on `PyWavelets`.
- Start that wavelet batch with `sure_entropy`.
- Keep `log_energy_entropy` on the roadmap, but finalize its scientific definition before implementation.

#### Dim reduction:
- Add parallelism

Expand Down
6 changes: 6 additions & 0 deletions coco_pipe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Package initializer for the coco_pipe package.
"""

from .descriptors import (
DescriptorConfig,
DescriptorPipeline,
)
from .dim_reduction import (
METHODS,
BaseReducer,
Expand All @@ -22,6 +26,8 @@

# Core exports
__all__ = [
"DescriptorConfig",
"DescriptorPipeline",
"DimReduction",
"METHODS",
"interpret_features",
Expand Down
2 changes: 2 additions & 0 deletions coco_pipe/decoding/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from .configs import ExperimentConfig
from .core import Experiment
from .registry import get_estimator_cls, register_estimator
from .utils import cross_validate_score

__all__ = [
"ExperimentConfig",
"register_estimator",
"get_estimator_cls",
"Experiment",
"cross_validate_score",
]
79 changes: 79 additions & 0 deletions coco_pipe/decoding/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, clone
from sklearn.metrics import (
accuracy_score,
balanced_accuracy_score,
Expand All @@ -39,6 +40,8 @@
StratifiedKFold,
train_test_split,
)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

from .configs import CVConfig

Expand Down Expand Up @@ -262,3 +265,79 @@ def get_scorer(name: str) -> Callable:
f"Unknown metric '{name}'. Available: {sorted(list(metrics.keys()))}"
)
return metrics[name]


def cross_validate_score(
estimator: BaseEstimator,
X: np.ndarray,
y: Sequence,
*,
groups: Optional[Sequence] = None,
cv_config: Optional[CVConfig] = None,
metric: str = "balanced_accuracy",
use_scaler: bool = False,
) -> float:
"""
Compute one mean cross-validated score for an estimator.

Parameters
----------
estimator : BaseEstimator
Estimator to fit inside each fold.
X : np.ndarray
Input features with shape ``(n_samples, n_features)``.
y : sequence
Target labels aligned with ``X``.
groups : sequence, optional
Group labels aligned with ``X``.
cv_config : CVConfig, optional
Cross-validation configuration. Defaults to a 5-fold stratified
strategy, or 5-fold stratified-group strategy when groups are
provided.
metric : str, default="balanced_accuracy"
Metric name resolved through :func:`get_scorer`.
use_scaler : bool, default=False
When ``True``, wraps the estimator in a ``StandardScaler`` pipeline.

Returns
-------
float
Mean cross-validated score.
"""
X = np.asarray(X)
y = np.asarray(y).reshape(-1)
if len(X) != len(y):
raise ValueError("X and y must have matching sample counts.")

group_values = None
if groups is not None:
group_values = np.asarray(groups).reshape(-1)
if len(group_values) != len(y):
raise ValueError("groups must align with X and y.")

if cv_config is None:
cv_config = CVConfig(
strategy="stratified_group_kfold"
if group_values is not None
else "stratified",
n_splits=5,
shuffle=True,
random_state=42,
)

scorer = get_scorer(metric)
cv = get_cv_splitter(cv_config, groups=group_values)
base_estimator = estimator
if use_scaler:
base_estimator = Pipeline(
[("scaler", StandardScaler()), ("clf", clone(estimator))]
)

scores = []
for train_idx, test_idx in cv.split(X, y, group_values):
model = clone(base_estimator)
model.fit(X[train_idx], y[train_idx])
y_pred = model.predict(X[test_idx])
scores.append(float(scorer(y[test_idx], y_pred)))

return float(np.nanmean(scores)) if scores else float("nan")
7 changes: 7 additions & 0 deletions coco_pipe/descriptors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .configs import DescriptorConfig
from .core import DescriptorPipeline

__all__ = [
"DescriptorConfig",
"DescriptorPipeline",
]
Loading
Loading