From bd57ae01b2b362cdc27c4cc60b7f9bdc4c7fc655 Mon Sep 17 00:00:00 2001 From: MatthewMiddlehurst Date: Tue, 13 Oct 2026 12:02:37 +0100 Subject: [PATCH 1/3] [BUG] Fix arguments dropped or misapplied in classifier and regressor getters Regressor: - 1nn-ed and 5nn-ed used the KNeighborsTimeSeriesRegressor default distance, which is dtw. Set euclidean. - 1nn-msm and 5nn-msm did not pass on kwargs. - Pass n_jobs to the kNN regressors. Classifier: - Pass n_jobs to rdst and to the 1nn-dtw-cv grid search. - Stop passing n_jobs to LogisticRegression, where it has no effect since scikit-learn 1.8 and is deprecated. - drcif-unequal passed n_estimators=500 as well as kwargs, raising TypeError when n_estimators was set. Keep 500 as the default and let kwargs change it. --- tsml_eval/experiments/_get_classifier.py | 10 +++++---- tsml_eval/experiments/_get_regressor.py | 20 +++++++++++++---- .../experiments/tests/test_classification.py | 22 +++++++++++++++++++ .../experiments/tests/test_regression.py | 21 ++++++++++++++++++ 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/tsml_eval/experiments/_get_classifier.py b/tsml_eval/experiments/_get_classifier.py index 4037728de..ccda73b0c 100644 --- a/tsml_eval/experiments/_get_classifier.py +++ b/tsml_eval/experiments/_get_classifier.py @@ -491,6 +491,7 @@ def _set_classifier_distance_based( estimator=KNeighborsTimeSeriesClassifier(), param_grid=param_grid, scoring="accuracy", + n_jobs=n_jobs, **kwargs, ) elif c == "grailclassifier" or c == "grail": @@ -724,12 +725,12 @@ def _set_classifier_interval_based( elif c == "drcif-unequal": from tsml_eval._wip.unequal_length._drcif6 import DrCIFClassifier + # n_estimators is not part of the name, so it can be changed using kwargs return DrCIFClassifier( - n_estimators=500, random_state=random_state, n_jobs=n_jobs, time_limit_in_minutes=fit_contract, - **kwargs, + **{"n_estimators": 500, **kwargs}, ) elif c == "summary-intervals": from aeon.classification.interval_based import RandomIntervalClassifier @@ -887,7 +888,7 @@ def _set_classifier_shapelet_based( elif c == "rdstclassifier" or c == "rdst": from aeon.classification.shapelet_based import RDSTClassifier - return RDSTClassifier(random_state=random_state, **kwargs) + return RDSTClassifier(random_state=random_state, n_jobs=n_jobs, **kwargs) elif ( c == "randomshapeletforestclassifier" or c == "randomshapeletforest" @@ -927,7 +928,8 @@ def _set_classifier_vector(c, random_state, n_jobs, fit_contract, checkpoint, kw elif c == "logisticregression" or c == "logistic": from sklearn.linear_model import LogisticRegression - return LogisticRegression(random_state=random_state, n_jobs=n_jobs, **kwargs) + # n_jobs has no effect since scikit-learn 1.8 and is deprecated + return LogisticRegression(random_state=random_state, **kwargs) elif c == "prevalidatedridgeclassifier" or c == "prevalidatedridge": from aeon.classification.sklearn import PrevalidatedRidgeClassifier diff --git a/tsml_eval/experiments/_get_regressor.py b/tsml_eval/experiments/_get_regressor.py index 9e6a777e7..514fdbf2d 100644 --- a/tsml_eval/experiments/_get_regressor.py +++ b/tsml_eval/experiments/_get_regressor.py @@ -269,22 +269,30 @@ def _set_regressor_distance_based( if r == "1nn-ed": from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor - return KNeighborsTimeSeriesRegressor(**kwargs) + return KNeighborsTimeSeriesRegressor( + distance="euclidean", n_jobs=n_jobs, **kwargs + ) elif r == "5nn-ed": from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor - return KNeighborsTimeSeriesRegressor(n_neighbors=5, **kwargs) + return KNeighborsTimeSeriesRegressor( + n_neighbors=5, distance="euclidean", n_jobs=n_jobs, **kwargs + ) elif r == "kneighborstimeseriesregressor" or r == "1nn-dtw": from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor return KNeighborsTimeSeriesRegressor( - distance="dtw", distance_params={"window": 0.1}, **kwargs + distance="dtw", distance_params={"window": 0.1}, n_jobs=n_jobs, **kwargs ) elif r == "5nn-dtw": from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor return KNeighborsTimeSeriesRegressor( - n_neighbors=5, distance="dtw", distance_params={"window": 0.1}, **kwargs + n_neighbors=5, + distance="dtw", + distance_params={"window": 0.1}, + n_jobs=n_jobs, + **kwargs, ) elif r == "1nn-msm": from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor @@ -293,6 +301,8 @@ def _set_regressor_distance_based( n_neighbors=1, distance="msm", distance_params={"window": None, "independent": True, "c": 1}, + n_jobs=n_jobs, + **kwargs, ) elif r == "5nn-msm": from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor @@ -301,6 +311,8 @@ def _set_regressor_distance_based( n_neighbors=5, distance="msm", distance_params={"window": None, "independent": True, "c": 1}, + n_jobs=n_jobs, + **kwargs, ) diff --git a/tsml_eval/experiments/tests/test_classification.py b/tsml_eval/experiments/tests/test_classification.py index 7a69451f1..97950b100 100644 --- a/tsml_eval/experiments/tests/test_classification.py +++ b/tsml_eval/experiments/tests/test_classification.py @@ -215,6 +215,28 @@ def test_get_classifier_by_name_invalid(): get_classifier_by_name("invalid") +@pytest.mark.parametrize("classifier", ["rdst", "1nn-dtw-cv"]) +def test_get_classifier_by_name_n_jobs(classifier): + """Test n_jobs is passed to classifiers which can use it.""" + c = get_classifier_by_name(classifier, n_jobs=2) + assert c.get_params()["n_jobs"] == 2 + + +def test_get_classifier_by_name_logistic_n_jobs(): + """Test n_jobs is not passed to LogisticRegression, where it is deprecated.""" + c = get_classifier_by_name("logistic", n_jobs=2) + assert c.get_params().get("n_jobs") is None + + +def test_get_classifier_by_name_drcif_unequal_n_estimators(): + """Test n_estimators can be set using kwargs for drcif-unequal.""" + c = get_classifier_by_name("drcif-unequal") + assert c.n_estimators == 500 + + c = get_classifier_by_name("drcif-unequal", n_estimators=2) + assert c.n_estimators == 2 + + def test_aeon_classifiers_available(): """Test all aeon classifiers are available.""" excluded = [ diff --git a/tsml_eval/experiments/tests/test_regression.py b/tsml_eval/experiments/tests/test_regression.py index c024ac12f..c29497a09 100644 --- a/tsml_eval/experiments/tests/test_regression.py +++ b/tsml_eval/experiments/tests/test_regression.py @@ -205,6 +205,27 @@ def test_get_regressor_by_name_invalid(): get_regressor_by_name("invalid") +@pytest.mark.parametrize( + "regressor, distance, n_neighbors", + [ + ("1nn-ed", "euclidean", 1), + ("5nn-ed", "euclidean", 5), + ("1nn-dtw", "dtw", 1), + ("5nn-dtw", "dtw", 5), + ("1nn-msm", "msm", 1), + ("5nn-msm", "msm", 5), + ], +) +def test_get_regressor_by_name_knn(regressor, distance, n_neighbors): + """Test kNN regressors use the settings in the name, n_jobs and kwargs.""" + r = get_regressor_by_name(regressor, n_jobs=2, weights="distance") + + assert r.distance == distance + assert r.n_neighbors == n_neighbors + assert r.n_jobs == 2 + assert r.weights == "distance" + + def test_aeon_regressors_available(): """Test all aeon regressors are available.""" excluded = [ From 396c253d72bd92bd7ffeefbac616e1bcf5309c12 Mon Sep 17 00:00:00 2001 From: MatthewMiddlehurst Date: Tue, 13 Oct 2026 12:25:16 +0100 Subject: [PATCH 2/3] Remove getter tests and keep drcif-unequal as it was Review feedback: the tests added little, n_estimators stays fixed for drcif-unequal and the LogisticRegression comment is not needed. --- tsml_eval/experiments/_get_classifier.py | 5 ++--- .../experiments/tests/test_classification.py | 22 ------------------- .../experiments/tests/test_regression.py | 21 ------------------ 3 files changed, 2 insertions(+), 46 deletions(-) diff --git a/tsml_eval/experiments/_get_classifier.py b/tsml_eval/experiments/_get_classifier.py index ccda73b0c..f78a18591 100644 --- a/tsml_eval/experiments/_get_classifier.py +++ b/tsml_eval/experiments/_get_classifier.py @@ -725,12 +725,12 @@ def _set_classifier_interval_based( elif c == "drcif-unequal": from tsml_eval._wip.unequal_length._drcif6 import DrCIFClassifier - # n_estimators is not part of the name, so it can be changed using kwargs return DrCIFClassifier( + n_estimators=500, random_state=random_state, n_jobs=n_jobs, time_limit_in_minutes=fit_contract, - **{"n_estimators": 500, **kwargs}, + **kwargs, ) elif c == "summary-intervals": from aeon.classification.interval_based import RandomIntervalClassifier @@ -928,7 +928,6 @@ def _set_classifier_vector(c, random_state, n_jobs, fit_contract, checkpoint, kw elif c == "logisticregression" or c == "logistic": from sklearn.linear_model import LogisticRegression - # n_jobs has no effect since scikit-learn 1.8 and is deprecated return LogisticRegression(random_state=random_state, **kwargs) elif c == "prevalidatedridgeclassifier" or c == "prevalidatedridge": from aeon.classification.sklearn import PrevalidatedRidgeClassifier diff --git a/tsml_eval/experiments/tests/test_classification.py b/tsml_eval/experiments/tests/test_classification.py index 97950b100..7a69451f1 100644 --- a/tsml_eval/experiments/tests/test_classification.py +++ b/tsml_eval/experiments/tests/test_classification.py @@ -215,28 +215,6 @@ def test_get_classifier_by_name_invalid(): get_classifier_by_name("invalid") -@pytest.mark.parametrize("classifier", ["rdst", "1nn-dtw-cv"]) -def test_get_classifier_by_name_n_jobs(classifier): - """Test n_jobs is passed to classifiers which can use it.""" - c = get_classifier_by_name(classifier, n_jobs=2) - assert c.get_params()["n_jobs"] == 2 - - -def test_get_classifier_by_name_logistic_n_jobs(): - """Test n_jobs is not passed to LogisticRegression, where it is deprecated.""" - c = get_classifier_by_name("logistic", n_jobs=2) - assert c.get_params().get("n_jobs") is None - - -def test_get_classifier_by_name_drcif_unequal_n_estimators(): - """Test n_estimators can be set using kwargs for drcif-unequal.""" - c = get_classifier_by_name("drcif-unequal") - assert c.n_estimators == 500 - - c = get_classifier_by_name("drcif-unequal", n_estimators=2) - assert c.n_estimators == 2 - - def test_aeon_classifiers_available(): """Test all aeon classifiers are available.""" excluded = [ diff --git a/tsml_eval/experiments/tests/test_regression.py b/tsml_eval/experiments/tests/test_regression.py index c29497a09..c024ac12f 100644 --- a/tsml_eval/experiments/tests/test_regression.py +++ b/tsml_eval/experiments/tests/test_regression.py @@ -205,27 +205,6 @@ def test_get_regressor_by_name_invalid(): get_regressor_by_name("invalid") -@pytest.mark.parametrize( - "regressor, distance, n_neighbors", - [ - ("1nn-ed", "euclidean", 1), - ("5nn-ed", "euclidean", 5), - ("1nn-dtw", "dtw", 1), - ("5nn-dtw", "dtw", 5), - ("1nn-msm", "msm", 1), - ("5nn-msm", "msm", 5), - ], -) -def test_get_regressor_by_name_knn(regressor, distance, n_neighbors): - """Test kNN regressors use the settings in the name, n_jobs and kwargs.""" - r = get_regressor_by_name(regressor, n_jobs=2, weights="distance") - - assert r.distance == distance - assert r.n_neighbors == n_neighbors - assert r.n_jobs == 2 - assert r.weights == "distance" - - def test_aeon_regressors_available(): """Test all aeon regressors are available.""" excluded = [ From a92ec06329ed14b7aed04a670c0af48df718865a Mon Sep 17 00:00:00 2001 From: MatthewMiddlehurst Date: Tue, 13 Oct 2026 12:29:02 +0100 Subject: [PATCH 3/3] Pass fit_contract to the interval forest classifiers and regressors The tsf, rise, cif and stsf names (and their -500 versions) did not pass fit_contract as time_limit_in_minutes, so a contract was ignored. drcif already passes it. --- tsml_eval/experiments/_get_classifier.py | 8 ++++++++ tsml_eval/experiments/_get_regressor.py | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/tsml_eval/experiments/_get_classifier.py b/tsml_eval/experiments/_get_classifier.py index f78a18591..9d1e00d4d 100644 --- a/tsml_eval/experiments/_get_classifier.py +++ b/tsml_eval/experiments/_get_classifier.py @@ -640,6 +640,7 @@ def _set_classifier_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "randomintervalspectralensembleclassifier" or c == "rise": @@ -650,6 +651,7 @@ def _set_classifier_interval_based( return RandomIntervalSpectralEnsembleClassifier( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "tsf-500": @@ -659,6 +661,7 @@ def _set_classifier_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "timeseriesforestclassifier" or c == "tsf": @@ -667,6 +670,7 @@ def _set_classifier_interval_based( return TimeSeriesForestClassifier( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "cif-500": @@ -676,6 +680,7 @@ def _set_classifier_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "canonicalintervalforestclassifier" or c == "cif": @@ -684,6 +689,7 @@ def _set_classifier_interval_based( return CanonicalIntervalForestClassifier( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "stsf-500": @@ -693,6 +699,7 @@ def _set_classifier_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "supervisedtimeseriesforest" or c == "stsf": @@ -701,6 +708,7 @@ def _set_classifier_interval_based( return SupervisedTimeSeriesForest( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif c == "drcif-500": diff --git a/tsml_eval/experiments/_get_regressor.py b/tsml_eval/experiments/_get_regressor.py index 514fdbf2d..f046f1c90 100644 --- a/tsml_eval/experiments/_get_regressor.py +++ b/tsml_eval/experiments/_get_regressor.py @@ -409,6 +409,7 @@ def _set_regressor_interval_based( return TimeSeriesForestRegressor( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif r == "tsf-i": @@ -434,6 +435,7 @@ def _set_regressor_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif r == "rise-500": @@ -445,6 +447,7 @@ def _set_regressor_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif r == "randomintervalspectralensembleregressor" or r == "rise": @@ -455,6 +458,7 @@ def _set_regressor_interval_based( return RandomIntervalSpectralEnsembleRegressor( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif r == "cif-500": @@ -464,6 +468,7 @@ def _set_regressor_interval_based( n_estimators=500, random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif r == "canonicalintervalforestregressor" or r == "cif": @@ -472,6 +477,7 @@ def _set_regressor_interval_based( return CanonicalIntervalForestRegressor( random_state=random_state, n_jobs=n_jobs, + time_limit_in_minutes=fit_contract, **kwargs, ) elif r == "drcif" or r == "drcifregressor":