From f96bf81ec0ca6c1f8c3ac0fe4b5f5c359819be80 Mon Sep 17 00:00:00 2001 From: MatthewMiddlehurst Date: Thu, 9 Jan 2025 22:09:45 +0000 Subject: [PATCH 1/3] update ver and new tags --- aeon/base/_base.py | 5 +++++ aeon/classification/base.py | 4 ++-- aeon/clustering/base.py | 25 ++----------------------- aeon/regression/base.py | 6 ++---- pyproject.toml | 2 +- 5 files changed, 12 insertions(+), 30 deletions(-) diff --git a/aeon/base/_base.py b/aeon/base/_base.py index 41ac7010f3..1d363670fe 100644 --- a/aeon/base/_base.py +++ b/aeon/base/_base.py @@ -415,6 +415,11 @@ def __sklearn_is_fitted__(self): """Check fitted status and return a Boolean value.""" return self.is_fitted + def __sklearn_tags__(self): + """Return sklearn style tags for the estimator.""" + tags = super().__sklearn_tags__() + return tags + def _validate_data(self, **kwargs): """Sklearn data validation.""" raise NotImplementedError( diff --git a/aeon/classification/base.py b/aeon/classification/base.py index 03cbc356d6..92d3b304a8 100644 --- a/aeon/classification/base.py +++ b/aeon/classification/base.py @@ -26,6 +26,7 @@ class name: BaseClassifier import numpy as np import pandas as pd +from sklearn.base import ClassifierMixin from sklearn.metrics import get_scorer, get_scorer_names from sklearn.model_selection import cross_val_predict @@ -35,7 +36,7 @@ class name: BaseClassifier from aeon.utils.validation.labels import check_classification_y -class BaseClassifier(BaseCollectionEstimator): +class BaseClassifier(ClassifierMixin, BaseCollectionEstimator): """ Abstract base class for time series classifiers. @@ -66,7 +67,6 @@ def __init__(self): self.classes_ = [] # classes seen in y, unique labels self.n_classes_ = -1 # number of unique classes in y self._class_dictionary = {} - self._estimator_type = "classifier" super().__init__() diff --git a/aeon/clustering/base.py b/aeon/clustering/base.py index 6c8b4344ae..6502b4c331 100644 --- a/aeon/clustering/base.py +++ b/aeon/clustering/base.py @@ -7,11 +7,12 @@ from typing import final import numpy as np +from sklearn.base import ClusterMixin from aeon.base import BaseCollectionEstimator -class BaseClusterer(BaseCollectionEstimator): +class BaseClusterer(ClusterMixin, BaseCollectionEstimator): """Abstract base class for time series clusterers. Parameters @@ -26,10 +27,6 @@ class BaseClusterer(BaseCollectionEstimator): @abstractmethod def __init__(self): - # required for compatibility with some sklearn interfaces e.g. - # CalibratedClassifierCV - self._estimator_type = "clusterer" - super().__init__() @final @@ -132,24 +129,6 @@ def fit_predict(self, X, y=None) -> np.ndarray: to return. y: ignored, exists for API consistency reasons. - Returns - ------- - np.ndarray (1d array of shape (n_cases,)) - Index of the cluster each time series in X belongs to. - """ - return self._fit_predict(X, y) - - def _fit_predict(self, X, y=None) -> np.ndarray: - """Fit predict using base methods. - - Parameters - ---------- - X : np.ndarray (2d or 3d array of shape (n_cases, n_timepoints) or shape - (n_cases, n_channels, n_timepoints)). - Time series instances to train clusterer and then have indexes each belong - to return. - y: ignored, exists for API consistency reasons. - Returns ------- np.ndarray (1d array of shape (n_cases,)) diff --git a/aeon/regression/base.py b/aeon/regression/base.py index 5aed9c80b9..f1acab5a00 100644 --- a/aeon/regression/base.py +++ b/aeon/regression/base.py @@ -25,6 +25,7 @@ class name: BaseRegressor import numpy as np import pandas as pd +from sklearn.base import RegressorMixin from sklearn.metrics import get_scorer, get_scorer_names from sklearn.model_selection import cross_val_predict from sklearn.utils.multiclass import type_of_target @@ -33,7 +34,7 @@ class name: BaseRegressor from aeon.base._base import _clone_estimator -class BaseRegressor(BaseCollectionEstimator): +class BaseRegressor(RegressorMixin, BaseCollectionEstimator): """Abstract base class for time series regressors. The base regressor specifies the methods and method signatures that all @@ -54,9 +55,6 @@ class BaseRegressor(BaseCollectionEstimator): @abstractmethod def __init__(self): - # required for compatibility with some sklearn interfaces - self._estimator_type = "regressor" - super().__init__() @final diff --git a/pyproject.toml b/pyproject.toml index 0ccbd341d9..c6827a84f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dependencies = [ "numpy>=1.21.0,<2.1.0", "packaging>=20.0", "pandas>=2.0.0,<2.3.0", - "scikit-learn>=1.0.0,<1.6.0", + "scikit-learn>=1.0.0,<1.7.0", "scipy>=1.9.0,<1.15.0", "typing-extensions>=4.6.0", ] From 3c0d124a18f4c202c04759ecd5f27055c9882da7 Mon Sep 17 00:00:00 2001 From: MatthewMiddlehurst Date: Fri, 10 Jan 2025 11:59:00 +0000 Subject: [PATCH 2/3] default tags --- aeon/base/_base.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/aeon/base/_base.py b/aeon/base/_base.py index 1d363670fe..84ab552db0 100644 --- a/aeon/base/_base.py +++ b/aeon/base/_base.py @@ -417,8 +417,15 @@ def __sklearn_is_fitted__(self): def __sklearn_tags__(self): """Return sklearn style tags for the estimator.""" - tags = super().__sklearn_tags__() - return tags + aeon_tags = self.get_tags() + sklearn_tags = super().__sklearn_tags__() + sklearn_tags.non_deterministic = aeon_tags.get("non_deterministic", False) + sklearn_tags.target_tags.one_d_labels = True + sklearn_tags.input_tags.three_d_array = True + sklearn_tags.input_tags.allow_nan = aeon_tags.get( + "capability:missing_values", False + ) + return sklearn_tags def _validate_data(self, **kwargs): """Sklearn data validation.""" From f1e648515059c300fc225e690a59b8f3dc6158ab Mon Sep 17 00:00:00 2001 From: Antoine Guillaume Date: Mon, 3 Feb 2025 22:11:35 +0100 Subject: [PATCH 3/3] Update _shapelets.py Fix linear estimator coefs issue --- aeon/visualisation/estimator/_shapelets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aeon/visualisation/estimator/_shapelets.py b/aeon/visualisation/estimator/_shapelets.py index cadac4e036..8199895878 100644 --- a/aeon/visualisation/estimator/_shapelets.py +++ b/aeon/visualisation/estimator/_shapelets.py @@ -714,6 +714,8 @@ def _get_shp_importance(self, class_id): # classification for the given class_id if isinstance(classifier, LinearClassifierMixin): coefs = classifier.coef_ + if coefs.ndim == 1: + coefs = coefs[np.newaxis, :] n_classes = coefs.shape[0] if n_classes == 1: if isinstance(self.estimator, RDSTClassifier):