Repository: aeon-toolkit/aeon
Pull request #1814 (2024-07-16 21:35:08)

[MNT] Raise `numba` version bound
Allows for `numba` 0.60.0.

aeon-actions-bot[bot]: 
## Thank you for contributing to `aeon`

I have added the following labels to this PR based on the title: **[ $\\color{#EC843A}{\\textsf{maintenance}}$ ]**.


The [Checks](https://github.com/aeon-toolkit/aeon/pull/1814/checks) tab will show the status of our automated tests. You can click on individual test runs in the tab or "Details" in the panel below to see more information if there is a failure.

If our `pre-commit` code quality check fails, any trivial fixes will automatically be pushed to your PR unless it is a draft.

Don't hesitate to ask questions on the `aeon` [Slack](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA) channel if you have any.

### PR CI actions

These checkboxes will add labels to enable/disable CI functionality for this PR. This may not take effect immediately, and a new commit may be required to run the new configuration.

- [ ] Run `pre-commit` checks for all files
- [ ] Run all `pytest` tests and configurations
- [ ] Run all notebook example tests
- [ ] Run numba-disabled `codecov` tests
- [ ] Stop automatic `pre-commit` fixes (always disabled for drafts)
- [ ] Push an empty commit to re-run CI checks
    
MatthewMiddlehurst: 3.12 CI is running 0.60.0

Patch:
diff --git a/aeon/classification/base.py b/aeon/classification/base.py
index cf3316a2..ba3ac85c 100644
--- a/aeon/classification/base.py
+++ b/aeon/classification/base.py
@@ -21,7 +21,7 @@ State:
 __all__ = [
     "BaseClassifier",
 ]
-__maintainer__ = []
+__maintainer__ = ["TonyBagnall", "MatthewMiddlehurst"]
 
 import time
 from abc import ABC, abstractmethod
@@ -225,7 +225,7 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
         return self._predict_proba(X)
 
     @final
-    def fit_predict(self, X, y) -> np.ndarray:
+    def fit_predict(self, X, y, **kwargs) -> np.ndarray:
         """Fits the classifier and predicts class labels for X.
 
         fit_predict produces prediction estimates using just the train data.
@@ -262,6 +262,15 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
         y : np.ndarray
             1D np.array of float or str, of shape ``(n_cases)`` - class labels
             (ground truth) for fitting indices corresponding to instance indices in X.
+        kwargs : dict
+            key word arguments to configure the default cross validation if the base
+            class default fit_predict is used (i.e. if function ``_fit_predict`` is
+            not overridden. If ``_fit_predict`` is overridden, kwargs may not
+            function as expected. If ``_fit_predict`` is not overridden, valid input is
+            ``cv_size`` integer, which is the number of cross validation folds to use to
+            estimate train data. If ``cv_size`` is not passed, the default is 10.
+            If ``cv_size`` is greater than the minimum number of samples in any
+            class, it is set to this minimum.
 
         Returns
         -------
@@ -270,19 +279,18 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
             instance indices in
         """
         X, y, single_class = self._fit_setup(X, y)
-
         if single_class:
             n_cases = get_n_cases(X)
             y_pred = np.repeat(list(self._class_dictionary.keys()), n_cases)
         else:
-            y_pred = self._fit_predict(X, y)
+            y_pred = self._fit_predict(X, y, **kwargs)
 
         # this should happen last
         self._is_fitted = True
         return y_pred
 
     @final
-    def fit_predict_proba(self, X, y) -> np.ndarray:
+    def fit_predict_proba(self, X, y, **kwargs) -> np.ndarray:
         """Fits the classifier and predicts class label probabilities for X.
 
         fit_predict_proba produces probability estimates using just the train data.
@@ -320,6 +328,15 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
         y : np.ndarray
             1D np.array of float or str, of shape ``(n_cases)`` - class labels
             (ground truth) for fitting indices corresponding to instance indices in X.
+        kwargs : dict
+            key word arguments to configure the default cross validation if the base
+            class default fit_predict is used (i.e. if function ``_fit_predict`` is
+            not overridden. If ``_fit_predict`` is overridden, kwargs may not
+            function as expected. If ``_fit_predict`` is not overridden, valid input is
+            ``cv_size`` integer, which is the number of cross validation folds to use to
+            estimate train data. If ``cv_size`` is not passed, the default is 10.
+            If ``cv_size`` is greater than the minimum number of samples in any
+            class, it is set to this minimum.
 
         Returns
         -------
@@ -335,7 +352,7 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
             n_cases = get_n_cases(X)
             y_proba = np.repeat([[1]], n_cases, axis=0)
         else:
-            y_proba = self._fit_predict_proba(X, y)
+            y_proba = self._fit_predict_proba(X, y, **kwargs)
 
         # this should happen last
         self._is_fitted = True
@@ -495,7 +512,7 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
 
         return dists
 
-    def _fit_predict(self, X, y) -> np.ndarray:
+    def _fit_predict(self, X, y, **kwargs) -> np.ndarray:
         """Fits and predicts labels for sequences in X.
 
         Parameters
@@ -516,9 +533,10 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
             shape ``[n_cases]`` - predicted class labels indices correspond to
             instance indices in
         """
-        return self._fit_predict_default(X, y, "predict")
+        cv_size = BaseClassifier._get_folds(kwargs)
+        return self._fit_predict_default(X, y, "predict", cv_size)
 
-    def _fit_predict_proba(self, X, y) -> np.ndarray:
+    def _fit_predict_proba(self, X, y, **kwargs) -> np.ndarray:
         """Fits and predicts labels probabilities for sequences in X.
 
         Parameters
@@ -541,7 +559,8 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
             second dimension indices correspond to class labels, (i, j)-th entry is
             estimated probability that i-th instance is of class j
         """
-        return self._fit_predict_default(X, y, "predict_proba")
+        cv_size = BaseClassifier._get_folds(kwargs)
+        return self._fit_predict_default(X, y, "predict_proba", cv_size)
 
     def _fit_setup(self, X, y):
         # reset estimator at the start of fit
@@ -590,12 +609,11 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
 
         return y
 
-    def _fit_predict_default(self, X, y, method):
-        # fit the classifier
+    def _fit_predict_default(self, X, y, method, cv_size=10):
+        # fit the classifier to all the data
         self._fit(X, y)
 
-        # predict using cross-validation
-        cv_size = 10
+        # predict on training data using cross-validation
         _, counts = np.unique(y, return_counts=True)
         min_class = np.min(counts)
         if min_class < cv_size:
@@ -637,3 +655,16 @@ class BaseClassifier(BaseCollectionEstimator, ABC):
                     "but in predict it is ",
                     get_n_timepoints(X),
                 )
+
+    @staticmethod
+    def _get_folds(dict):
+        """Get the number of CV folds from kwargs dict."""
+        cv_size = 10
+        if "cv_size" in dict:
+            if not isinstance(dict["cv_size"], int) or dict["cv_size"] < 1:
+                raise ValueError(
+                    "cv_size must be an integer greater than 0, but found "
+                    f"{dict['cv_size']}"
+                )
+            cv_size = dict["cv_size"]
+        return cv_size
diff --git a/aeon/forecasting/compose/_pipeline.py b/aeon/forecasting/compose/_pipeline.py
index 021c5b61..5485f9c0 100644
--- a/aeon/forecasting/compose/_pipeline.py
+++ b/aeon/forecasting/compose/_pipeline.py
@@ -374,8 +374,8 @@ class ForecastingPipeline(_Pipeline):
             concatenation of `other` (first) with `self` (last).
             not nested, contains only non-TransformerPipeline `aeon` steps
         """
+        from aeon.transformations._legacy.compose import TransformerPipeline
         from aeon.transformations.base import BaseTransformer
-        from aeon.transformations.compose import TransformerPipeline
 
         _, ests = zip(*self.steps_)
         names = tuple(self._get_estimator_names(self.steps))
@@ -815,8 +815,8 @@ class TransformedTargetForecaster(_Pipeline):
             concatenation of `self` (first) with `other` (last).
             not nested, contains only non-TransformerPipeline `aeon` transformers
         """
+        from aeon.transformations._legacy.compose import TransformerPipeline
         from aeon.transformations.base import BaseTransformer
-        from aeon.transformations.compose import TransformerPipeline
 
         # we don't use names but _get_estimator_names to get the *original* names
         #   to avoid multiple "make unique" calls which may grow strings too much
@@ -860,8 +860,8 @@ class TransformedTargetForecaster(_Pipeline):
             concatenation of `other` (first) with `self` (last).
             not nested, contains only non-TransformerPipeline `aeon` steps
         """
+        from aeon.transformations._legacy.compose import TransformerPipeline
         from aeon.transformations.base import BaseTransformer
-        from aeon.transformations.compose import TransformerPipeline
 
         _, ests = zip(*self.steps_)
         names = tuple(self._get_estimator_names(self.steps))
diff --git a/aeon/forecasting/compose/_reduce.py b/aeon/forecasting/compose/_reduce.py
index 2644f021..851436ed 100644
--- a/aeon/forecasting/compose/_reduce.py
+++ b/aeon/forecasting/compose/_reduce.py
@@ -26,7 +26,7 @@ from aeon.forecasting.base._aeon import _BaseWindowForecaster
 from aeon.forecasting.base._base import DEFAULT_ALPHA
 from aeon.forecasting.base._fh import _index_range
 from aeon.regression.base import BaseRegressor
-from aeon.transformations.compose import FeatureUnion
+from aeon.transformations._legacy.compose import FeatureUnion
 from aeon.transformations.summarize import WindowSummarizer
 from aeon.utils.datetime import _shift
 from aeon.utils.index_functions import get_time_index
diff --git a/aeon/transformations/compose.py b/aeon/transformations/_legacy/compose.py
similarity index 100%
rename from aeon/transformations/compose.py
rename to aeon/transformations/_legacy/compose.py
diff --git a/aeon/transformations/base.py b/aeon/transformations/base.py
index 1fc51da7..68e76730 100644
--- a/aeon/transformations/base.py
+++ b/aeon/transformations/base.py
@@ -154,7 +154,7 @@ class BaseTransformer(BaseEstimator):
         TransformerPipeline object, concatenation of `self` (first) with `other` (last).
             not nested, contains only non-TransformerPipeline `aeon` transformers
         """
-        from aeon.transformations.compose import TransformerPipeline
+        from aeon.transformations._legacy.compose import TransformerPipeline
 
         # we wrap self in a pipeline, and concatenate with the other
         #   the TransformerPipeline does the rest, e.g., case distinctions on other
@@ -184,7 +184,7 @@ class BaseTransformer(BaseEstimator):
         TransformerPipeline object, concatenation of `other` (first) with `self` (last).
             not nested, contains only non-TransformerPipeline `aeon` transformers
         """
-        from aeon.transformations.compose import TransformerPipeline
+        from aeon.transformations._legacy.compose import TransformerPipeline
 
         # we wrap self in a pipeline, and concatenate with the other
         #   the TransformerPipeline does the rest, e.g., case distinctions on other
@@ -207,7 +207,7 @@ class BaseTransformer(BaseEstimator):
         -------
         MultiplexTransformer object
         """
-        from aeon.transformations.compose import MultiplexTransformer
+        from aeon.transformations._legacy.compose import MultiplexTransformer
 
         if isinstance(other, BaseTransformer):
             multiplex_self = MultiplexTransformer([self])
@@ -230,7 +230,7 @@ class BaseTransformer(BaseEstimator):
         FeatureUnion object, concatenation of `self` (first) with `other` (last).
             not nested, contains only non-TransformerPipeline `aeon` transformers
         """
-        from aeon.transformations.compose import FeatureUnion
+        from aeon.transformations._legacy.compose import FeatureUnion
 
         # we wrap self in a pipeline, and concatenate with the other
         #   the FeatureUnion does the rest, e.g., case distinctions on other
@@ -255,7 +255,7 @@ class BaseTransformer(BaseEstimator):
         FeatureUnion object, concatenation of `other` (first) with `self` (last).
             not nested, contains only non-FeatureUnion `aeon` transformers
         """
-        from aeon.transformations.compose import FeatureUnion
+        from aeon.transformations._legacy.compose import FeatureUnion
 
         # we wrap self in a pipeline, and concatenate with the other
         #   the TransformerPipeline does the rest, e.g., case distinctions on other
@@ -272,7 +272,7 @@ class BaseTransformer(BaseEstimator):
         -------
         `InvertTransform` object, containing `self`.
         """
-        from aeon.transformations.compose import InvertTransform
+        from aeon.transformations._legacy.compose import InvertTransform
 
         return InvertTransform(self)
 
@@ -286,7 +286,7 @@ class BaseTransformer(BaseEstimator):
         `OptionalPassthrough` object, containing `self`, with `passthrough=False`.
             The `passthrough` parameter can be set via `set_params`.
         """
-        from aeon.transformations.compose import OptionalPassthrough
+        from aeon.transformations._legacy.compose import OptionalPassthrough
 
         return OptionalPassthrough(self, passthrough=False)
 
diff --git a/aeon/transformations/lag.py b/aeon/transformations/lag.py
index ca50978c..a862fdab 100644
--- a/aeon/transformations/lag.py
+++ b/aeon/transformations/lag.py
@@ -458,7 +458,7 @@ class ReducerTransform(BaseTransformer):
         -------
         self: reference to self
         """
-        from aeon.transformations.compose import FeatureUnion, YtoX
+        from aeon.transformations._legacy.compose import FeatureUnion, YtoX
         from aeon.transformations.impute import Imputer
 
         impute_method = self.impute_method
diff --git a/docs/api_reference/transformations.rst b/docs/api_reference/transformations.rst
index a5967861..1a3b0772 100644
--- a/docs/api_reference/transformations.rst
+++ b/docs/api_reference/transformations.rst
@@ -313,18 +313,6 @@ These transformers create a series based on a sequence of sliding windows.
 
     HOG1DTransformer
 
-Multivariate-to-univariate
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-These transformers convert multivariate series to univariate.
-
-.. currentmodule:: aeon.transformations.compose
-
-.. autosummary::
-    :toctree: auto_generated/
-    :template: class.rst
-
-    ColumnConcatenator
 
 FeatureSelection
 ~~~~~~~~~~~~~~~~
diff --git a/examples/transformations/interpolation.ipynb b/examples/transformations/interpolation.ipynb
index 30fbb84f..02b1b368 100644
--- a/examples/transformations/interpolation.ipynb
+++ b/examples/transformations/interpolation.ipynb
@@ -12,7 +12,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 2,
    "metadata": {
     "execution": {
      "iopub.execute_input": "2020-12-19T14:31:58.456171Z",
@@ -27,8 +27,7 @@
     "from sklearn.pipeline import Pipeline\n",
     "\n",
     "from aeon.classification.convolution_based import RocketClassifier\n",
-    "from aeon.datasets import load_basic_motions\n",
-    "from aeon.transformations.compose import ColumnConcatenator"
+    "from aeon.datasets import load_basic_motions"
    ]
   },
   {
@@ -43,7 +42,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 3,
    "metadata": {
     "execution": {
      "iopub.execute_input": "2020-12-19T14:31:59.194445Z",
@@ -55,22 +54,17 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "0.25"
+      "text/plain": "1.0"
      },
-     "execution_count": 2,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "X, y = load_basic_motions(return_type=\"nested_univ\")\n",
+    "X, y = load_basic_motions()\n",
     "X_train, X_test, y_train, y_test = train_test_split(X, y)\n",
-    "\n",
-    "steps = [\n",
-    "    (\"concatenate\", ColumnConcatenator()),\n",
-    "    (\"classify\", RocketClassifier()),\n",
-    "]\n",
-    "clf = Pipeline(steps)\n",
+    "clf = RocketClassifier(num_kernels=200)\n",
     "clf.fit(X_train, y_train)\n",
     "clf.score(X_test, y_test)"
    ]
@@ -81,7 +75,8 @@
    "source": [
     "## If time series are unequal length, aeon's algorithm may raise an error\n",
     "\n",
-    "Now we are going to spoil the data set a little bit by randomly cutting the time series. This leads to unequal-length time series. Consequently, we have an error while attempt to train a classifier."
+    "Ifwe have unequal-length time series, classifiers without the capability to handle\n",
+    "this will raise an error."
    ]
   },
   {
@@ -100,19 +95,18 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "IndexError: Data seen by RocketClassifier instance has unequal length series, but this RocketClassifier instance cannot handle unequal length series. Calls with unequal length series may result in error or unreliable results.\n"
+      "ValueError: Data seen by instance of RocketClassifier has unequal length series, but RocketClassifier cannot handle unequal length series. \n"
      ]
     }
    ],
    "source": [
     "from aeon.datasets import load_plaid\n",
     "\n",
-    "X, y = load_plaid(return_type=\"nested_univ\")\n",
+    "X, y = load_plaid()\n",
     "X_train, X_test, y_train, y_test = train_test_split(X, y)\n",
     "\n",
     "try:\n",
-    "    clf = RocketClassifier()\n",
-    "    clf = Pipeline(steps)\n",
+    "    clf = RocketClassifier(num_kernels=200)\n",
     "    clf.fit(X_train, y_train)\n",
     "    clf.score(X_test, y_test)\n",
     "except ValueError as e:\n",
@@ -161,6 +155,15 @@
     "clf.fit(X_train, y_train)\n",
     "clf.score(X_test, y_test)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "outputs": [],
+   "source": [],
+   "metadata": {
+    "collapsed": false
+   }
   }
  ],
  "metadata": {
diff --git a/pyproject.toml b/pyproject.toml
index 7d9ea9db..768a2d17 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,7 +46,7 @@ classifiers = [
 requires-python = ">=3.9,<3.13"
 dependencies = [
     "deprecated>=1.2.13",
-    "numba>=0.55,<0.60.0",
+    "numba>=0.55,<0.61.0",
     "numpy>=1.21.0,<1.27.0",
     "packaging>=20.0",
     "pandas>=1.5.3,<2.1.0",


Test patch:
diff --git a/aeon/classification/tests/test_base.py b/aeon/classification/tests/test_base.py
index dcc8a171..200e19d4 100644
--- a/aeon/classification/tests/test_base.py
+++ b/aeon/classification/tests/test_base.py
@@ -262,6 +262,61 @@ def test_fit_predict():
     assert p.shape == (5,)
 
 
+def test_fit_predict_kwargs():
+    """Test fit_predict with cross validation kwargs."""
+    X = np.random.random(size=(5, 1, 10))
+    y = np.array([1, 0, 1, 0, 1])
+    cls = MockClassifier()
+    p = cls.fit_predict(X, y)
+    assert p.shape == (5,)
+
+    p = cls.fit_predict(X, y, cv_size=2)
+    assert p.shape == (5,)
+    with pytest.raises(ValueError, match="cv_size must be an integer greater than 0"):
+        cls.fit_predict(X, y, cv_size=0)
+    with pytest.raises(ValueError, match="cv_size must be an integer greater than 0"):
+        cls.fit_predict(X, y, cv_size="FOO")
+    y = np.array([0, 0, 0, 0, 1])
+    with pytest.raises(ValueError, match="All classes must have at least 2 values"):
+        cls.fit_predict(X, y, cv_size=20)
+    y = np.array([1, 0, 1, 0, 1])
+    p = cls.fit_predict_proba(X, y, cv_size=2)
+    assert p.shape == (5, 2)
+
+
+def test_score():
+    """Test base classifier scorer."""
+    X = np.random.random(size=(5, 1, 10))
+    y = np.array([1, 0, 1, 0, 1])
+    cls = MockClassifier()
+    cls.fit(X, y)
+    score = cls.score(X, y)
+    assert isinstance(score, float)
+    with pytest.raises(
+        ValueError,
+        match="can't handle a mix of binary and multilabel-indicator targets",
+    ):
+        score = cls.score(X, y, use_proba=True)
+    with pytest.raises(
+        ValueError,
+        match="can't handle a mix of binary and multilabel-indicator targets",
+    ):
+        score = cls.score(X, y, use_proba=True)
+    score = cls.score(X, y, metric="neg_log_loss")
+    assert isinstance(score, float)
+    score = cls.score(X, y, use_proba=True, metric="neg_log_loss")
+    with pytest.raises(
+        ValueError, match="The metric parameter should be either a string or a callable"
+    ):
+        score = cls.score(X, y, metric=42)
+
+    def dummy_metric(y_true, y_pred):
+        return 42.0
+
+    score = cls.score(X, y, metric=dummy_metric)
+    assert score == 42.0
+
+
 def test_fit_predict_single_class():
     """Test return of fit_predict/fit_predict_proba in case only single class."""
     X = np.ones(shape=(10, 20))
diff --git a/aeon/forecasting/compose/tests/test_pipeline.py b/aeon/forecasting/compose/tests/test_pipeline.py
index 56188eae..4b4f4324 100644
--- a/aeon/forecasting/compose/tests/test_pipeline.py
+++ b/aeon/forecasting/compose/tests/test_pipeline.py
@@ -95,7 +95,7 @@ def test_nesting_pipelines():
         ForecasterFitPredictUnivariateWithX,
     )
     from aeon.transformations._legacy._boxcox import _LogTransformer as LogTransformer
-    from aeon.transformations.compose import OptionalPassthrough
+    from aeon.transformations._legacy.compose import OptionalPassthrough
     from aeon.transformations.detrend import Detrender
 
     pipe = ForecastingPipeline(
diff --git a/aeon/transformations/tests/test_base.py b/aeon/transformations/tests/test_base.py
index de529712..06503085 100644
--- a/aeon/transformations/tests/test_base.py
+++ b/aeon/transformations/tests/test_base.py
@@ -26,8 +26,8 @@ from aeon.testing.utils.scenarios_transformers import (
     TransformerFitTransformSeriesMultivariate,
     TransformerFitTransformSeriesUnivariate,
 )
+from aeon.transformations._legacy.compose import FitInTransform, Id
 from aeon.transformations.base import BaseTransformer
-from aeon.transformations.compose import FitInTransform, Id
 from aeon.utils.validation import (
     abstract_types,
     is_collection,
diff --git a/aeon/transformations/tests/test_compose.py b/aeon/transformations/tests/test_compose.py
index 829ab604..af710b73 100644
--- a/aeon/transformations/tests/test_compose.py
+++ b/aeon/transformations/tests/test_compose.py
@@ -12,18 +12,18 @@ from aeon.testing.mock_estimators import MockTransformer
 from aeon.testing.utils.deep_equals import deep_equals
 from aeon.testing.utils.estimator_checks import _assert_array_almost_equal
 from aeon.transformations._legacy._boxcox import _LogTransformer
-from aeon.transformations._legacy.subset import _ColumnSelect
-from aeon.transformations._legacy.theta import (
-    _ThetaLinesTransformer as ThetaLinesTransformer,
-)
-from aeon.transformations.collection.pad import PaddingTransformer
-from aeon.transformations.compose import (
+from aeon.transformations._legacy.compose import (
     ColumnConcatenator,
     FeatureUnion,
     InvertTransform,
     OptionalPassthrough,
     TransformerPipeline,
 )
+from aeon.transformations._legacy.subset import _ColumnSelect
+from aeon.transformations._legacy.theta import (
+    _ThetaLinesTransformer as ThetaLinesTransformer,
+)
+from aeon.transformations.collection.pad import PaddingTransformer
 from aeon.transformations.impute import Imputer
 from aeon.transformations.summarize import SummaryTransformer
 
diff --git a/aeon/transformations/tests/test_featureizer.py b/aeon/transformations/tests/test_featureizer.py
index 685d7ab3..f6b4b3e0 100644
--- a/aeon/transformations/tests/test_featureizer.py
+++ b/aeon/transformations/tests/test_featureizer.py
@@ -7,7 +7,7 @@ from numpy.testing import assert_array_equal
 from aeon.datasets import load_longley
 from aeon.forecasting.model_selection import temporal_train_test_split
 from aeon.testing.mock_estimators import MockTransformer
-from aeon.transformations.compose import YtoX
+from aeon.transformations._legacy.compose import YtoX
 from aeon.transformations.lag import Lag
 
 y, X = load_longley()
diff --git a/aeon/transformations/tests/test_fitintransform.py b/aeon/transformations/tests/test_fitintransform.py
index c8c13510..19deb1ec 100644
--- a/aeon/transformations/tests/test_fitintransform.py
+++ b/aeon/transformations/tests/test_fitintransform.py
@@ -5,7 +5,7 @@ __all__ = []
 
 import numpy as np
 
-from aeon.transformations.compose import FitInTransform
+from aeon.transformations._legacy.compose import FitInTransform
 from aeon.transformations.series._boxcox import BoxCoxTransformer
 
 
diff --git a/aeon/transformations/tests/test_multiplexer.py b/aeon/transformations/tests/test_multiplexer.py
index f751b9b9..a2ecb26b 100644
--- a/aeon/transformations/tests/test_multiplexer.py
+++ b/aeon/transformations/tests/test_multiplexer.py
@@ -17,7 +17,7 @@ from aeon.forecasting.model_selection import (
 from aeon.forecasting.naive import NaiveForecaster
 from aeon.performance_metrics.forecasting import mean_absolute_percentage_error
 from aeon.testing.mock_estimators import MockTransformer
-from aeon.transformations.compose import MultiplexTransformer
+from aeon.transformations._legacy.compose import MultiplexTransformer
 
 
 def test_multiplex_transformer_alone():
diff --git a/aeon/transformations/tests/test_optionalpassthrough.py b/aeon/transformations/tests/test_optionalpassthrough.py
index e6792d27..e3b927c1 100644
--- a/aeon/transformations/tests/test_optionalpassthrough.py
+++ b/aeon/transformations/tests/test_optionalpassthrough.py
@@ -1,7 +1,19 @@
 """Tests for using OptionalPassthrough."""
 
 import pytest
+from sklearn.preprocessing import StandardScaler
 
+from aeon.datasets import load_airline, load_longley
+from aeon.forecasting.compose import TransformedTargetForecaster
+from aeon.forecasting.model_selection import (
+    ForecastingGridSearchCV,
+    SlidingWindowSplitter,
+)
+from aeon.forecasting.naive import NaiveForecaster
+from aeon.testing.data_generation import _make_hierarchical
+from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
+from aeon.transformations._legacy.compose import OptionalPassthrough
+from aeon.transformations.detrend import Deseasonalizer
 from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
@@ -14,19 +26,6 @@ def test_optionalpassthrough():
 
     Same as docstring example of OptionalPassthrough.
     """
-    from sklearn.preprocessing import StandardScaler
-
-    from aeon.datasets import load_airline
-    from aeon.forecasting.compose import TransformedTargetForecaster
-    from aeon.forecasting.model_selection import (
-        ForecastingGridSearchCV,
-        SlidingWindowSplitter,
-    )
-    from aeon.forecasting.naive import NaiveForecaster
-    from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
-    from aeon.transformations.compose import OptionalPassthrough
-    from aeon.transformations.detrend import Deseasonalizer
-
     # create pipeline
     pipe = TransformedTargetForecaster(
         steps=[
@@ -57,10 +56,6 @@ def test_optionalpassthrough():
 )
 def test_passthrough_does_not_broadcast_variables():
     """Test that OptionalPassthrough does not itself vectorize/broadcast columns."""
-    from aeon.datasets import load_longley
-    from aeon.transformations.compose import OptionalPassthrough
-    from aeon.transformations.detrend import Deseasonalizer
-
     _, X = load_longley()
 
     t = OptionalPassthrough(Deseasonalizer())
@@ -73,10 +68,6 @@ def test_passthrough_does_not_broadcast_variables():
 )
 def test_passthrough_does_not_broadcast_instances():
     """Test that OptionalPassthrough does not itself vectorize/broadcast rows."""
-    from aeon.testing.data_generation import _make_hierarchical
-    from aeon.transformations.compose import OptionalPassthrough
-    from aeon.transformations.detrend import Deseasonalizer
-
     X = _make_hierarchical()
 
     t = OptionalPassthrough(Deseasonalizer())