Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] Tests for feature based classifiers #2350

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
18 changes: 10 additions & 8 deletions aeon/classification/feature_based/_catch22.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class Catch22Classifier(BaseClassifier):
Number of classes. Extracted from the data.
classes_ : ndarray of shape (n_classes_)
Holds the label for each class.
estimator_ : sklearn classifier
The fitted estimator.

See Also
--------
Expand Down Expand Up @@ -171,7 +173,7 @@ def _fit(self, X, y):
parallel_backend=self.parallel_backend,
)

self._estimator = _clone_estimator(
self.estimator_ = _clone_estimator(
(
RandomForestClassifier(n_estimators=200)
if self.estimator is None
Expand All @@ -180,12 +182,12 @@ def _fit(self, X, y):
self.random_state,
)

m = getattr(self._estimator, "n_jobs", None)
m = getattr(self.estimator_, "n_jobs", None)
if m is not None:
self._estimator.n_jobs = self._n_jobs
self.estimator_.n_jobs = self._n_jobs

X_t = self._transformer.fit_transform(X, y)
self._estimator.fit(X_t, y)
self.estimator_.fit(X_t, y)

return self

Expand All @@ -205,7 +207,7 @@ def _predict(self, X) -> np.ndarray:
y : array-like, shape = [n_cases]
Predicted class labels.
"""
return self._estimator.predict(self._transformer.transform(X))
return self.estimator_.predict(self._transformer.transform(X))

def _predict_proba(self, X) -> np.ndarray:
"""Predicts labels probabilities for sequences in X.
Expand All @@ -223,12 +225,12 @@ def _predict_proba(self, X) -> np.ndarray:
y : array-like, shape = [n_cases, n_classes_]
Predicted probabilities using the ordering in classes_.
"""
m = getattr(self._estimator, "predict_proba", None)
m = getattr(self.estimator_, "predict_proba", None)
if callable(m):
return self._estimator.predict_proba(self._transformer.transform(X))
return self.estimator_.predict_proba(self._transformer.transform(X))
else:
dists = np.zeros((X.shape[0], self.n_classes_))
preds = self._estimator.predict(self._transformer.transform(X))
preds = self.estimator_.predict(self._transformer.transform(X))
for i in range(0, X.shape[0]):
dists[i, self._class_dictionary[preds[i]]] = 1
return dists
Expand Down
19 changes: 10 additions & 9 deletions aeon/classification/feature_based/_tsfresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class TSFreshClassifier(BaseClassifier):
Number of classes. Extracted from the data.
classes_ : ndarray of shape (n_classes_)
Holds the label for each class.
estimator_ : sklearn classifier
The fitted estimator.

See Also
--------
Expand Down Expand Up @@ -95,7 +97,6 @@ def __init__(
self.random_state = random_state

self._transformer = None
self._estimator = None
self._return_majority_class = False
self._majority_class = 0

Expand Down Expand Up @@ -134,7 +135,7 @@ def _fit(self, X, y):
chunksize=self.chunksize,
)
)
self._estimator = _clone_estimator(
self.estimator_ = _clone_estimator(
(
RandomForestClassifier(n_estimators=200)
if self.estimator is None
Expand All @@ -148,9 +149,9 @@ def _fit(self, X, y):
if self.verbose < 1:
self._transformer.disable_progressbar = True

m = getattr(self._estimator, "n_jobs", None)
m = getattr(self.estimator_, "n_jobs", None)
if m is not None:
self._estimator.n_jobs = self._n_jobs
self.estimator_.n_jobs = self._n_jobs

X_t = self._transformer.fit_transform(X, y)

Expand All @@ -166,7 +167,7 @@ def _fit(self, X, y):
self._return_majority_class = True
self._majority_class = np.argmax(np.unique(y, return_counts=True)[1])
else:
self._estimator.fit(X_t, y)
self.estimator_.fit(X_t, y)

return self

Expand All @@ -186,7 +187,7 @@ def _predict(self, X) -> np.ndarray:
if self._return_majority_class:
return np.full(X.shape[0], self.classes_[self._majority_class])

return self._estimator.predict(self._transformer.transform(X))
return self.estimator_.predict(self._transformer.transform(X))

def _predict_proba(self, X) -> np.ndarray:
"""Predict class probabilities for n instances in X.
Expand All @@ -206,12 +207,12 @@ def _predict_proba(self, X) -> np.ndarray:
dists[:, self._majority_class] = 1
return dists

m = getattr(self._estimator, "predict_proba", None)
m = getattr(self.estimator_, "predict_proba", None)
if callable(m):
return self._estimator.predict_proba(self._transformer.transform(X))
return self.estimator_.predict_proba(self._transformer.transform(X))
else:
dists = np.zeros((X.shape[0], self.n_classes_))
preds = self._estimator.predict(self._transformer.transform(X))
preds = self.estimator_.predict(self._transformer.transform(X))
for i in range(0, X.shape[0]):
dists[i, self._class_dictionary[preds[i]]] = 1
return dists
Expand Down
1 change: 1 addition & 0 deletions aeon/classification/feature_based/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for feature-based classification."""
21 changes: 21 additions & 0 deletions aeon/classification/feature_based/tests/test_catch22.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Test catch 22 classifier."""

import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import RidgeClassifier

from aeon.classification.feature_based import Catch22Classifier
from aeon.testing.data_generation import make_example_3d_numpy


def test_catch22():
"""Test catch22 with specific parameters."""
X, y = make_example_3d_numpy()
c22 = Catch22Classifier(n_jobs=1, estimator=RandomForestClassifier(n_jobs=1))
c22.fit(X, y)
p = c22.predict_proba(X)
assert c22.estimator_.n_jobs == 1 and c22.n_jobs == 1
c22 = Catch22Classifier(estimator=RidgeClassifier())
c22.fit(X, y)
p = c22.predict_proba(X)
assert np.all(np.isin(p, [0, 1]))
20 changes: 20 additions & 0 deletions aeon/classification/feature_based/tests/test_signature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Test summary classifier."""

import pytest
from sklearn.ensemble import RandomForestClassifier

from aeon.classification.feature_based import SignatureClassifier
from aeon.testing.data_generation import make_example_3d_numpy
from aeon.utils.validation._dependencies import _check_soft_dependencies


@pytest.mark.skipif(
not _check_soft_dependencies("esig", severity="none"),
reason="skip test if required soft dependency esig not available",
)
def test_signature_classifier():
"""Test the SignatureClassifier."""
X, y = make_example_3d_numpy()
cls = SignatureClassifier(estimator=None)
cls._fit(X, y)
assert isinstance(cls.pipeline.named_steps["classifier"], RandomForestClassifier)
21 changes: 21 additions & 0 deletions aeon/classification/feature_based/tests/test_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Test summary classifier."""

import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import RidgeClassifier

from aeon.classification.feature_based import SummaryClassifier
from aeon.testing.data_generation import make_example_3d_numpy


def test_summary_classifier():
"""Test the SummaryClassifier."""
X, y = make_example_3d_numpy()
cls = SummaryClassifier(estimator=RandomForestClassifier(n_jobs=1))
cls.fit(X, y)
p = cls.predict_proba(X)
assert cls.estimator_.n_jobs == 1 and cls.n_jobs == 1
cls = SummaryClassifier(estimator=RidgeClassifier())
cls.fit(X, y)
p = cls.predict_proba(X)
assert np.all(np.isin(p, [0, 1]))
39 changes: 39 additions & 0 deletions aeon/classification/feature_based/tests/test_tsfresh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Test summary classifier."""

import warnings

import numpy as np
import pytest
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import RidgeClassifier

from aeon.classification.feature_based import TSFreshClassifier
from aeon.testing.data_generation import make_example_3d_numpy
from aeon.utils.validation._dependencies import _check_soft_dependencies


@pytest.mark.skipif(
not _check_soft_dependencies("tsfresh", severity="none"),
reason="skip test if required soft dependency tsfresh not available",
)
def test_tsfresh_classifier():
"""Test the TSFreshClassifier."""
X, y = make_example_3d_numpy()
cls = TSFreshClassifier(estimator=RandomForestClassifier(n_jobs=1))
cls.fit(X, y)
p = cls.predict_proba(X)
assert cls.estimator_.n_jobs == 1 and cls.n_jobs == 1
cls = TSFreshClassifier(estimator=RidgeClassifier())
cls.fit(X, y)
p = cls.predict_proba(X)
assert np.all(np.isin(p, [0, 1]))
with warnings.catch_warnings():
X, y = make_example_3d_numpy(
n_cases=2, n_timepoints=3, random_state=0, n_labels=2
)
cls = TSFreshClassifier(relevant_feature_extractor=True)
cls.fit(X, y)
assert cls._return_majority_class is True
assert cls._majority_class in [0, 1]
cls.verbose = 1
cls.fit(X, y)