Repository: aeon-toolkit/aeon
Pull request #1704 (2024-06-19 09:27:48)

[MNT] Change `pre-commit` workflow checkout ref
#### Reference Issues/PRs

Reapplies #1662 since there are still failures on forks for this workflow.


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/1704/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)
    

Patch:
diff --git a/.github/workflows/pr_precommit.yml b/.github/workflows/pr_precommit.yml
index b7025945..76dde278 100644
--- a/.github/workflows/pr_precommit.yml
+++ b/.github/workflows/pr_precommit.yml
@@ -1,9 +1,6 @@
 name: PR pre-commit
 
 on:
-  push:
-    branches:
-      - main
   pull_request_target:
     branches:
       - main
@@ -27,7 +24,7 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
         with:
-          ref: ${{ github.head_ref }}
+          ref: ${{ github.event.pull_request.head.sha }}
           token: ${{ steps.app-token.outputs.token }}
 
       - name: Setup Python 3.10
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 624de6e9..7795f7eb 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -89,7 +89,7 @@ jobs:
           command: python -m pip install "${{ env.WHEELNAME }}[all_extras,dev]"
 
       - name: Tests
-        run: python -m pytest
+        run: python -m pytest -n logical
 
   upload-wheels:
     needs: test-wheels
diff --git a/MANIFEST.in b/MANIFEST.in
index d633d5b8..b502f37d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,6 @@
 recursive-include aeon *.py
 recursive-include aeon/benchmarking/example_results *.csv
 recursive-include aeon/datasets *.csv *.arff *.txt *.ts *.tsv *.tsf
-recursive-include docs *
-recursive-include examples *
-recursive-include extension_templates *.py
 include aeon/registry/README.md
 include .coveragerc
 include conftest.py
@@ -15,6 +12,8 @@ include README.md
 recursive-exclude .binder *
 recursive-exclude .github *
 recursive-exclude build_tools *
+recursive-exclude docs *
+recursive-exclude examples *
 exclude .all-contributorsrc
 exclude .codecov.yml
 exclude .gitattributes
diff --git a/aeon/forecasting/compose/_pipeline.py b/aeon/forecasting/compose/_pipeline.py
index 1e001dd9..825d8512 100644
--- a/aeon/forecasting/compose/_pipeline.py
+++ b/aeon/forecasting/compose/_pipeline.py
@@ -761,8 +761,7 @@ class TransformedTargetForecaster(_Pipeline):
     ...     ("detrender", Detrender()),
     ...     ("forecaster", NaiveForecaster(strategy="drift")),
     ... ])
-    >>> pipe.fit(y)
-    TransformedTargetForecaster(...)
+    >>> t = pipe.fit(y)
     >>> y_pred = pipe.predict(fh=[1,2,3])
 
         Example 2: without strings
@@ -1558,7 +1557,7 @@ class Permute(_DelegatedForecaster, BaseForecaster, _HeterogenousMetaEstimator):
     >>> from aeon.forecasting.base import ForecastingHorizon
     >>> from aeon.forecasting.compose import ForecastingPipeline, Permute
     >>> from aeon.forecasting.naive import NaiveForecaster
-    >>> from aeon.transformations.boxcox import BoxCoxTransformer
+    >>> from aeon.transformations.series._boxcox import BoxCoxTransformer
     >>> from aeon.transformations.exponent import ExponentTransformer
 
     Simple example: permute sequence of estimator in forecasting pipeline
diff --git a/aeon/transformations/_legacy/__init__.py b/aeon/transformations/_legacy/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/aeon/transformations/boxcox.py b/aeon/transformations/_legacy/_boxcox.py
similarity index 91%
rename from aeon/transformations/boxcox.py
rename to aeon/transformations/_legacy/_boxcox.py
index 5a898423..420d0b86 100644
--- a/aeon/transformations/boxcox.py
+++ b/aeon/transformations/_legacy/_boxcox.py
@@ -1,10 +1,9 @@
 """Implemenents Box-Cox and Log Transformations."""
 
 __maintainer__ = []
-__all__ = ["BoxCoxTransformer", "LogTransformer"]
+__all__ = ["_BoxCoxTransformer", "_LogTransformer"]
 
 import numpy as np
-from deprecated.sphinx import deprecated
 from scipy import optimize, special, stats
 from scipy.special import boxcox, inv_boxcox
 from scipy.stats import boxcox_llf, distributions, variation
@@ -41,21 +40,14 @@ def _calc_uniform_order_statistic_medians(n):
     return v
 
 
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="BoxCoxTransformer will be removed in version 0.10.0 and replaced with a "
-    "BaseSeriesTransformer version in the transformations.series module.",
-    category=FutureWarning,
-)
-class BoxCoxTransformer(BaseTransformer):
+class _BoxCoxTransformer(BaseTransformer):
     r"""Box-Cox power transform.
 
     Box-Cox transformation is a power transformation that is used to
     make data more normally distributed and stabilize its variance based
     on the hyperparameter lambda. [1]_
 
-    The BoxCoxTransformer solves for the lambda parameter used in the Box-Cox
+    The _BoxCoxTransformer solves for the lambda parameter used in the Box-Cox
     transformation given `method`, the optimization approach, and input
     data provided to `fit`. The use of Guerrero's method for solving for lambda
     requires the seasonal periodicity, `sp` be provided. [2]_
@@ -88,7 +80,7 @@ class BoxCoxTransformer(BaseTransformer):
 
     See Also
     --------
-    LogTransformer :
+    _LogTransformer :
         Transformer input data using natural log. Can help normalize data and
         compress variance of the series.
     aeon.transformations.exponent.ExponentTransformer :
@@ -113,14 +105,6 @@ class BoxCoxTransformer(BaseTransformer):
        Journal ofthe Royal Statistical Society, Series B, 26, 211-252.
     .. [2] V.M. Guerrero, "Time-series analysis supported by Power
        Transformations ", Journal of Forecasting, vol. 12, pp. 37-48, 1993.
-
-    Examples
-    --------
-    >>> from aeon.transformations.boxcox import BoxCoxTransformer
-    >>> from aeon.datasets import load_airline
-    >>> y = load_airline()
-    >>> transformer = BoxCoxTransformer()
-    >>> y_hat = transformer.fit_transform(y)
     """
 
     _tags = {
@@ -214,14 +198,7 @@ class BoxCoxTransformer(BaseTransformer):
         return Xt
 
 
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="LogTransformer will be removed in version 0.10.0 and replaced with a "
-    "BaseSeriesTransformer version in the transformations.series module.",
-    category=FutureWarning,
-)
-class LogTransformer(BaseTransformer):
+class _LogTransformer(BaseTransformer):
     """Natural logarithm transformation.
 
     The Natural logarithm transformation can be used to make the data more normally
@@ -238,7 +215,7 @@ class LogTransformer(BaseTransformer):
 
     See Also
     --------
-    BoxCoxTransformer :
+    _BoxCoxTransformer :
         Applies Box-Cox power transformation. Can help normalize data and
         compress variance of the series.
     aeon.transformations.exponent.ExponentTransformer :
@@ -251,14 +228,6 @@ class LogTransformer(BaseTransformer):
     Notes
     -----
     The log transformation is applied as :math:`ln(y)`.
-
-    Examples
-    --------
-    >>> from aeon.transformations.boxcox import LogTransformer
-    >>> from aeon.datasets import load_airline
-    >>> y = load_airline()
-    >>> transformer = LogTransformer()
-    >>> y_hat = transformer.fit_transform(y)
     """
 
     _tags = {
diff --git a/aeon/transformations/_legacy/tests/__init__.py b/aeon/transformations/_legacy/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/aeon/transformations/adapt.py b/aeon/transformations/adapt.py
index 7489a8be..33a93a39 100644
--- a/aeon/transformations/adapt.py
+++ b/aeon/transformations/adapt.py
@@ -223,7 +223,7 @@ class TabularToSeriesAdaptor(BaseTransformer):
 # TODO: remove in v0.11.0
 @deprecated(
     version="0.10.0",
-    reason="PandasTransformAdaptorwill be removed in version 0.11.0.",
+    reason="PandasTransformAdaptor will be removed in version 0.11.0.",
     category=FutureWarning,
 )
 class PandasTransformAdaptor(BaseTransformer):
diff --git a/aeon/transformations/augmenter.py b/aeon/transformations/augmenter.py
deleted file mode 100644
index fa98c5ed..00000000
--- a/aeon/transformations/augmenter.py
+++ /dev/null
@@ -1,222 +0,0 @@
-"""Series transformers for time series augmentation."""
-
-__maintainer__ = []
-__all__ = [
-    "WhiteNoiseAugmenter",
-    "ReverseAugmenter",
-    "InvertAugmenter",
-    "RandomSamplesAugmenter",
-]
-
-
-import numpy as np
-import pandas as pd
-from deprecated.sphinx import deprecated
-from scipy.stats import norm
-from sklearn.utils import check_random_state
-
-from aeon.transformations.base import BaseTransformer
-
-
-class _AugmenterTags:
-    _tags = {
-        "input_data_type": "Series",
-        "output_data_type": "Series",
-        "transform_labels": "None",
-        "instancewise": True,
-        "capability:missing_values": False,
-        "y_inner_type": "pd.Series",
-        "X_inner_type": "pd.DataFrame",
-        "X-y-must-have-same-index": False,
-        "fit_is_empty": True,
-        "transform-returns-same-time-index": False,
-        "capability:inverse_transform": False,
-    }
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="WhiteNoiseAugmenter will be removed in version 0.10.0.",
-    category=FutureWarning,
-)
-class WhiteNoiseAugmenter(_AugmenterTags, BaseTransformer):
-    r"""Augmenter adding Gaussian (i.e. white) noise to the time series.
-
-    If `transform` is given time series :math:`X={x_1, x_2, ... , x_n}`, then
-    returns :math:`X_t={x_1+e_1, x_2+e_2, ..., x_n+e_n}` where :math:`e_i` are
-    i.i.d. random draws from a normal distribution with mean :math:`\mu` = 0
-    and standard deviation :math:`\sigma` = ``scale``.
-    Time series augmentation by adding Gaussian Noise has been discussed among
-    others in [1] and [2].
-
-    Parameters
-    ----------
-    scale: float, default=1.0
-        Scale parameter specifies the standard deviation.
-    random_state: None or int or ``np.random.RandomState`` instance, default = None
-        If int or RandomState, use it for drawing the random variates.
-        If None, rely on ``self.random_state``.
-
-    References
-    ----------
-    ..[1]: WEN, Qingsong, et al. Time series data augmentation for deep
-      learning: A survey. arXiv preprint arXiv:2002.12478, 2020.
-    ..[2]: IWANA, Brian Kenji; UCHIDA, Seiichi. An empirical survey of data
-      augmentation for time series classification with neural networks. Plos
-      one, 2021, 16. Jg., Nr. 7, S. e0254841.
-    """
-
-    _allowed_statistics = [np.std]
-
-    def __init__(self, scale=1.0, random_state=42):
-        self.scale = scale
-        self.random_state = random_state
-        super().__init__()
-
-    def _transform(self, X, y=None):
-        if self.scale in self._allowed_statistics:
-            scale = self.scale(X)
-        elif isinstance(self.scale, (int, float)):
-            scale = self.scale
-        else:
-            raise TypeError(
-                "Type of parameter 'scale' must be a non-negative float value."
-            )
-        return X[0] + norm.rvs(0, scale, size=len(X), random_state=self.random_state)
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="ReverseAugmenter will be removed in version 0.10.0.",
-    category=FutureWarning,
-)
-class ReverseAugmenter(_AugmenterTags, BaseTransformer):
-    r"""Augmenter reversing the time series.
-
-    If `transform` is given a time series :math:`X={x_1, x_2, ... , x_n}`, then
-    returns :math:`X_t={x_n, x_{n-1}, ..., x_2, x_1}`.
-    Time series augmentation by reversing has been discussed e.g. in [1].
-
-    Examples
-    --------
-    >>> X = pd.Series([1,2,3,4,5])
-    >>> augmenter = ReverseAugmenter()
-    >>> Xt = augmenter.fit_transform(X)
-    >>> Xt
-    0    5
-    1    4
-    2    3
-    3    2
-    4    1
-    dtype: int64
-
-    References
-    ----------
-    ..[1]: IWANA, Brian Kenji; UCHIDA, Seiichi. An empirical survey of data
-      augmentation for time series classification with neural networks. Plos
-      one, 2021, 16. Jg., Nr. 7, S. e0254841.
-    """
-
-    def __init__(self):
-        super().__init__()
-
-    def _transform(self, X, y=None):
-        return X.loc[::-1].reset_index(drop=True, inplace=False)
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="InvertAugmenter will be removed in version 0.10.0.",
-    category=FutureWarning,
-)
-class InvertAugmenter(_AugmenterTags, BaseTransformer):
-    r"""Augmenter inverting the time series by multiplying it by -1.
-
-    If `transform` is given a time series :math:`X={x_1, x_2, ... , x_n}`, then
-    returns :math:`X_t={-x_1, -x_2, ... , -x_n}`.
-
-    Examples
-    --------
-    >>> X = pd.Series([1,2,3,4,5])
-    >>> augmenter = InvertAugmenter()
-    >>> Xt = augmenter.fit_transform(X)
-    >>> Xt
-    0   -1
-    1   -2
-    2   -3
-    3   -4
-    4   -5
-    dtype: int64
-    """
-
-    def __init__(self):
-        super().__init__()
-
-    def _transform(self, X, y=None):
-        return X.mul(-1)
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="RandomSamplesAugmenter will be removed in version 0.10.0.",
-    category=FutureWarning,
-)
-class RandomSamplesAugmenter(_AugmenterTags, BaseTransformer):
-    r"""Draw random samples from time series.
-
-    `transform` takes a time series :math:`X={x_1, x_2, ... , x_m}` with :math:`m`
-    elements and returns :math:`X_t={x_i, x_{i+1}, ... , x_n}`, where
-    :math:`{x_i, x_{i+1}, ... , x_n}` are :math:`n`=``n`` random samples drawn
-    from :math:`X` (with or `without_replacement`).
-
-    Parameters
-    ----------
-    n : int or float, default = 1.0
-        Specify an exact number of samples to draw, set `n` to an int value.
-        To specify the returned samples as a proportion of the given times series
-        set `n` to a float value :math:`n \in [0, 1]`. By default, the same number of
-        samples is returned as given by the input time series.
-    without_replacement : bool, default = True
-        Whether to draw without replacement. If True, every sample of the input times
-        series `X` will appear at most once in `Xt`.
-    random_state: None or int or ``np.random.RandomState`` instance, default = None
-        If int or RandomState, use it for drawing the random variates.
-        If None, rely on ``self.random_state``.
-    """
-
-    def __init__(
-        self,
-        n=1.0,
-        without_replacement=True,
-        random_state=42,
-    ):
-        if isinstance(n, float):
-            if n <= 0.0 or not np.isfinite(n):
-                raise ValueError("n must be a positive, finite number.")
-        elif isinstance(n, int):
-            if n < 1 or not np.isfinite(n):
-                raise ValueError("n must be a finite number >= 1.")
-        else:
-            raise ValueError("n must be int or float, not " + str(type(n))) + "."
-        self.n = n
-        self.without_replacement = without_replacement
-        self.random_state = random_state
-        super().__init__()
-
-    def _transform(self, X, y=None):
-        if isinstance(self.n, float):
-            n = int(np.ceil(self.n * len(X)))
-        else:
-            n = self.n
-        rng = check_random_state(self.random_state)
-        values = np.concatenate(X.values)
-        if self.without_replacement:
-            replace = False
-        else:
-            replace = True
-        Xt = rng.choice(values, n, replace)
-        return pd.DataFrame(Xt)
diff --git a/aeon/transformations/binning.py b/aeon/transformations/binning.py
deleted file mode 100644
index 056b5c85..00000000
--- a/aeon/transformations/binning.py
+++ /dev/null
@@ -1,157 +0,0 @@
-"""Time binning for turning series equally spaced."""
-
-__maintainer__ = []
-
-import warnings
-
-import numpy as np
-import pandas as pd
-from deprecated.sphinx import deprecated
-
-from aeon.transformations.base import BaseTransformer
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="TimeBinAggregate will be removed in version 0.10.0.",
-    category=FutureWarning,
-)
-class TimeBinAggregate(BaseTransformer):
-    r"""Bins time series and aggregates by bin.
-
-    In `transform`, applies `groupby` with `aggfunc` on the temporal coordinate.
-
-    More precisely:
-    `bins` encodes bins :math:`B_1, \dots, B_k` where :math:`B_i` are intervals,
-    in the reals or in a temporal (time stamp) range.
-
-    In `transform`, the estimator `TimeBinAggregate` collects values
-    at time stamps of `X` falling into :math:`B_i` as a sample :math:`S_i`,
-    and then applies `aggfunc` to :math:`S_i` to obtain an aggregate value :math:`v_i`.
-    The transformed series are values :math:`v_i` at time stamps :math:`t_i`,
-    determined from :math:`B_i` per the rule in `return_index`.
-
-    Parameters
-    ----------
-    bins : 1D array-like or pd.IntervalIndex
-        if 1D array-like, is interpreted as breaks of bins
-        breaks of bins defining intervals considered by aggfunc
-    aggfunc : callable *1D array-like -> float), optional, default=np.mean
-        Function used to aggregate the values in intervals.
-        Should have signature 1D -> float and defaults
-        to mean if None
-    return_index : str, one of the below; optional, default="range"
-        "range" = RangeIndex with bins indexed in same order as in `bins`
-        "bin_start" = transformed pd.DataFrame will be indexed by bin starts
-        "bin_end" = transformed pd.DataFrame will be indexed by bin starts
-        "bin_mid" = transformed pd.DataFrame will be indexed by bin midpoints
-        "bin" = transformed pd.DataFrame will have `bins` as `IntervalIndex`
-    """
-
-    _tags = {
-        "fit_is_empty": True,
-        "capability:multivariate": True,
-        "input_data_type": "Series",
-        # what is the abstract type of X: Series, or Panel
-        "output_data_type": "Series",
-        # what is the abstract type of y: None (not needed), Primitives, Series, Panel
-        "instancewise": True,
-        "X_inner_type": ["pd.DataFrame"],
-        "y_inner_type": "None",  # and for y?
-        "capability:missing_values": True,
-        "capability:unequal_length": True,
-        "capability:unequal_length:removes": True,
-        "transform-returns-same-time-index": False,
-        "capability:inverse_transform": False,
-    }
-
-    def __init__(self, bins, aggfunc=None, return_index="bin_start"):
-        self.bins = bins
-        self.aggfunc = aggfunc
-        self.return_index = return_index
-
-        if not isinstance(bins, pd.IntervalIndex):
-            self._bins = pd.IntervalIndex.from_breaks(bins)
-        else:
-            self._bins = bins
-
-        if self.aggfunc is None:
-            self._aggfunc = np.mean
-        else:
-            assert callable(aggfunc), (
-                "aggfunc should be callable with" "signature 1D -> float"
-            )
-            if aggfunc.__name__ == "<lambda>":
-                warnings.warn(
-                    "Save and load will not work with lambda functions", stacklevel=2
-                )
-            self._aggfunc = self.aggfunc
-
-        super().__init__()
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing core logic, called from transform
-
-        Parameters
-        ----------
-        X: data structure of type X_inner_type
-            if X_inner_type is list, _transform must support all types in it
-            Data to be transformed
-        y : data structure of type y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        transformed version of X
-        """
-        bins = self.bins
-        idx_cut = pd.cut(X.index, bins=self._bins, include_lowest=True)
-        Xt = X.groupby(idx_cut).apply(self._aggfunc)
-
-        if self.return_index == "range":
-            Xt = Xt.reset_index(drop=True)
-        elif self.return_index == "bin_start":
-            if bins is pd.IntervalIndex:
-                Xt.index = [x.left for x in Xt.index]
-            else:
-                Xt.index = bins[:-1]
-        elif self.return_index == "bin_end":
-            if bins is pd.IntervalIndex:
-                Xt.index = [x.right for x in Xt.index]
-            else:
-                Xt.index = bins[1:]
-        elif self.return_index == "bin_mid":
-            if bins is pd.IntervalIndex:
-                Xt.index = [(x.left + x.right) / 2 for x in Xt.index]
-            else:
-                Xt.index = [(bins[i] + bins[i + 1]) / 2 for i in range(len(bins))]
-        elif self.return_index == "bin":
-            Xt.index = self._bins
-        return Xt
-
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-        params1 = {"bins": [0, 1]}
-
-        params2 = {"bins": [0, 2, 4], "aggfunc": np.sum, "return_index": "bin_start"}
-        return [params1, params2]
diff --git a/aeon/transformations/bootstrap/_mbb.py b/aeon/transformations/bootstrap/_mbb.py
index 8bb2929c..f889f22b 100644
--- a/aeon/transformations/bootstrap/_mbb.py
+++ b/aeon/transformations/bootstrap/_mbb.py
@@ -7,12 +7,19 @@ from typing import Tuple, Union
 
 import numpy as np
 import pandas as pd
+from deprecated.sphinx import deprecated
 from sklearn.utils import check_random_state
 
+from aeon.transformations._legacy._boxcox import _BoxCoxTransformer
 from aeon.transformations.base import BaseTransformer
-from aeon.transformations.boxcox import BoxCoxTransformer
 
 
+# TODO: remove in v0.11.0
+@deprecated(
+    version="0.10.0",
+    reason="STLBootstrapTransformer will be removed in version 0.11.0.",
+    category=FutureWarning,
+)
 class STLBootstrapTransformer(BaseTransformer):
     """Creates a population of similar time series.
 
@@ -265,7 +272,7 @@ class STLBootstrapTransformer(BaseTransformer):
         )
 
         # fit boxcox to get lambda and transform X
-        self.box_cox_transformer_ = BoxCoxTransformer(
+        self.box_cox_transformer_ = _BoxCoxTransformer(
             sp=self.sp, bounds=self.lambda_bounds, method=self.lambda_method
         )
         self.box_cox_transformer_.fit(X)
@@ -399,6 +406,12 @@ class STLBootstrapTransformer(BaseTransformer):
         return params
 
 
+# TODO: remove in v0.11.0
+@deprecated(
+    version="0.10.0",
+    reason="MovingBlockBootstrapTransformer will be removed in version 0.11.0.",
+    category=FutureWarning,
+)
 class MovingBlockBootstrapTransformer(BaseTransformer):
     """Moving Block Bootstrapping method for synthetic time series generation.
 
diff --git a/aeon/transformations/clear_sky.py b/aeon/transformations/clear_sky.py
deleted file mode 100644
index 344fbb93..00000000
--- a/aeon/transformations/clear_sky.py
+++ /dev/null
@@ -1,370 +0,0 @@
-"""Clear sky transformer for solar time-series."""
-
-__maintainer__ = []
-
-import numpy as np
-import pandas as pd
-from deprecated.sphinx import deprecated
-from joblib import Parallel, delayed
-from scipy.stats import vonmises
-
-from aeon.transformations.base import BaseTransformer
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="ClearSky will be removed in version 0.10.0 and replaced with a "
-    "BaseSeriesTransformer version in the transformations.series module.",
-    category=FutureWarning,
-)
-class ClearSky(BaseTransformer):
-    """Clear sky transformer for solar data.
-
-    This is a transformation which converts a time series from it's original
-    domain into a percentage domain. The numerator at each time step in the
-    transformation is the input values, the denominator is a weighted
-    quantile of the time series for that particular time step. In the example
-    of solar power transformations, the denominator is an approximation of the
-    clear sky power, and the output of the transformation is the clearness index.
-
-    The clear sky power, i.e. the denominator, is calculated on a grid containing
-    each unique combination of time-of-day and day-of-year. The spacing of the
-    grid depends on the frequency of the input data.
-
-    The weights are defined using von-mises kernels with bandwidths chosen by the
-    user.
-
-    This transformation can be inacurate at low values, in the solar example during
-    early morning and late evening. Therefore, clear sky values below a threshold can
-    be fixed to zero in the transformed domain. Denominator values of zero are set
-    to zero in the transformed domain by default.
-
-    This transformer is based on the work detailed in [1]_.
-
-    Parameters
-    ----------
-    quantile_prob : float, default=0.95
-        The probability level used to calculate the weighted quantile
-    bw_diurnal : float, default=100
-        The bandwidth of the diurnal kernel. This is the kappa value of the
-        von mises kernel for time of day.
-    bw_annual : float, default=10
-        The bandwidth of the annual kernel. This is the kappa value of the
-        von mises kernel for day of year.
-    min_thresh : float, default=0
-        The threshold of the clear sky power below which values are
-        set to zero in the transformed domain.
-    n_jobs : int or None, default=None
-        Number of jobs to run in parallel.
-        None means 1 unless in a joblib.parallel_backend context.
-        -1 means using all processors.
-    backend : str, default="loky"
-        Specify the parallelisation backend implementation in joblib, where
-        "loky" is used by default.
-
-    References
-    ----------
-    .. [1] https://doi.org/10.1016/j.solener.2009.05.016
-
-    Examples
-    --------
-    >>> from aeon.transformations.clear_sky import ClearSky  # doctest: +SKIP
-    >>> from aeon.datasets import load_solar  # doctest: +SKIP
-    >>> y = load_solar()  # doctest: +SKIP
-    >>> transformer = ClearSky()  # doctest: +SKIP
-    >>> # takes ~1min
-    >>> y_trafo = transformer.fit_transform(y)  # doctest: +SKIP
-    """
-
-    _tags = {
-        "input_data_type": "Series",
-        "output_data_type": "Series",
-        "transform_labels": "None",
-        "instancewise": True,  # is this an instance-wise transform?
-        "capability:inverse_transform": True,  # can the transformer inverse transform?
-        "capability:multivariate": False,  # can the transformer handle multivariate X?
-        "X_inner_type": [
-            "pd.Series",
-        ],
-        "y_inner_type": "None",
-        "requires_y": False,  # does y need to be passed in fit?
-        "enforce_index_type": [
-            pd.DatetimeIndex,
-            pd.PeriodIndex,
-        ],  # index type that needs to be enforced in X/y
-        "fit_is_empty": False,  # is fit empty and can be skipped? Yes = True
-        "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
-        "transform-returns-same-time-index": True,
-        "skip-inverse-transform": False,  # is inverse-transform skipped when called?
-        "capability:unequal_length": False,
-        "capability:unequal_length:removes": True,  # ?
-        "capability:missing_values": False,
-        "capability:missing_values:removes": True,
-        "python_version": None,  # PEP 440 python version specifier to limit versions
-        "python_dependencies": "statsmodels",
-    }
-
-    def __init__(
-        self,
-        quantile_prob=0.95,
-        bw_diurnal=100,
-        bw_annual=10,
-        min_thresh=0,
-        n_jobs=None,
-        backend="loky",
-    ):
-        self.quantile_prob = quantile_prob
-        self.bw_diurnal = bw_diurnal
-        self.bw_annual = bw_annual
-        self.min_thresh = min_thresh
-        self.n_jobs = n_jobs
-        self.backend = backend
-
-        super().__init__()
-
-    def _fit(self, X, y=None):
-        """Fit transformer to X and y.
-
-        private _fit containing the core logic, called from fit
-
-        Parameters
-        ----------
-        X : Series of pd.DataFrame
-            Data used to estimate the clear sky power.
-        y : Ignored argument for interface compatibility.
-
-        Returns
-        -------
-        self: reference to self
-        """
-        # check that the data is formatted correctly etc
-        self.freq = _check_index(X)
-        # now get grid of model
-        df = pd.DataFrame(index=X.index)
-        df["yday"] = df.index.dayofyear
-        df["tod"] = df.index.hour + df.index.minute / 60 + df.index.second / 60
-
-        # set up smoothing grid
-        tod = pd.timedelta_range(start="0T", end="1D", freq=self.freq)[:-1]
-        tod = [(x.total_seconds() / (60 * 60)) for x in tod.to_pytimedelta()]
-        yday = pd.RangeIndex(start=1, stop=367)
-        indx = pd.MultiIndex.from_product([yday, tod], names=["yday", "tod"])
-
-        # set up parallel function and backend
-        parallel = Parallel(n_jobs=self.n_jobs, backend=self.backend)
-
-        def par_csp(x):
-            res = _clearskypower(
-                y=X,
-                q=self.quantile_prob,
-                tod_i=x[1],
-                doy_i=x[0],
-                tod_vec=df["tod"],
-                doy_vec=df["yday"],
-                bw_tod=self.bw_diurnal,
-                bw_doy=self.bw_annual,
-            )
-
-            return res
-
-        # calculate the csp
-        csp = parallel(delayed(par_csp)(name) for name in indx)
-        csp = pd.Series(csp, index=indx, dtype="float64")
-        self.clearskypower = csp.sort_index()
-
-        return self
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing core logic, called from transform
-
-        Parameters
-        ----------
-        X : Series of pd.DataFrame
-            Data used to be transformed.
-        y : Ignored argument for interface compatibility.
-
-        Returns
-        -------
-        X_trafo : transformed version of X
-        """
-        _freq_ind = _check_index(X)
-        if self.freq != _freq_ind:
-            raise ValueError(
-                """
-                Change in frequency detected from original input. Make sure
-                X is the same frequency as used in .fit().
-                """
-            )
-        # get required seasonal index
-        yday = X.index.dayofyear
-        tod = X.index.hour + X.index.minute / 60 + X.index.second / 60
-        indx_seasonal = pd.MultiIndex.from_arrays([yday, tod], names=["yday", "tod"])
-
-        # look up values and overwrite index
-        csp = self.clearskypower[indx_seasonal].copy()
-        csp.index = X.index
-        X_trafo = X / csp
-
-        # threshold for small morning/evening values
-        X_trafo[(csp <= self.min_thresh) & (X.notnull())] = 0
-
-        return X_trafo
-
-    def _inverse_transform(self, X, y=None):
-        """Inverse transform, inverse operation to transform.
-
-        private _inverse_transform containing core logic, called from
-        inverse_transform
-
-        Parameters
-        ----------
-        X : Series of pd.DataFrame
-            Data used to be inversed transformed.
-        y : Ignored argument for interface compatibility.
-
-        Returns
-        -------
-        X_trafo : inverse transformed version of X
-        """
-        _freq_ind = _check_index(X)
-        if self.freq != _freq_ind:
-            raise ValueError(
-                """
-                Change in frequency detected from original input. Make sure
-                X is the same frequency as used in .fit().
-                """
-            )
-        yday = X.index.dayofyear
-        tod = X.index.hour + X.index.minute / 60 + X.index.second / 60
-        indx_seasonal = pd.MultiIndex.from_arrays([yday, tod], names=["yday", "tod"])
-
-        # look up values and overwrite index
-        csp = self.clearskypower[indx_seasonal].copy()
-        csp.index = X.index
-        X_trafo = X * csp
-
-        return X_trafo
-
-    def _get_fitted_params(self):
-        """Get fitted parameters.
-
-        Returns
-        -------
-        fitted_params : dict
-        """
-        params = {"clearskypower": self.clearskypower}
-
-        return params
-
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-        params = {
-            "quantile_prob": 0.95,
-            "bw_diurnal": 100,
-            "bw_annual": 10,
-            "min_thresh": None,
-        }
-
-        return params
-
-
-def _clearskypower(y, q, tod_i, doy_i, tod_vec, doy_vec, bw_tod, bw_doy):
-    """Estimate the clear sky power for a given day-of-year and hour-of-day.
-
-    Parameters
-    ----------
-    y : Series of measurements
-    q : Probability level used for the quantile
-    tod_i : time-of-day of interest in hours
-    doy_i : day-of-year of interest in days
-    tod_vec : Series of time-of-day corresponding to the index of y
-    doy_vec: Series of day-of-year corresponding to the index of y
-    bw_tod : Kappa value used for defining weights for time-of-day
-    bw_doy : Kappa value used for defining weights for day-of-year
-
-    Returns
-    -------
-    csp : float
-        The clear sky power at tod_i and doy_i
-    """
-    from statsmodels.stats.weightstats import DescrStatsW
-
-    wts_tod = vonmises.pdf(
-        x=tod_i * 2 * np.pi / 24, kappa=bw_tod, loc=tod_vec * 2 * np.pi / 24
-    )
-    wts_doy = vonmises.pdf(
-        x=doy_i * 2 * np.pi / 365.25, kappa=bw_doy, loc=doy_vec * 2 * np.pi / 365.25
-    )
-
-    wts = wts_doy * wts_tod
-    wts = wts / wts.sum()
-
-    csp = DescrStatsW(y, weights=wts).quantile(probs=q).values[0]
-
-    return csp
-
-
-def _check_index(X):
-    """Check input value frequency is set and we have the correct index.
-
-    Parameters
-    ----------
-    X : Series or pd.DataFrame
-        Data used to be inversed transformed.
-
-    Raises
-    ------
-    ValueError : Input index must be class pd.DatetimeIndex or pd.PeriodIndex.
-    ValueError : Input index frequency cannot be inferred and is not set.
-    ValueError : Frequency of data not suitable for transformer as is.
-
-    Returns
-    -------
-    freq_ind : str or None
-        Frequency of data in string format
-
-    """
-    if not (isinstance(X.index, pd.DatetimeIndex)) | (
-        isinstance(X.index, pd.PeriodIndex)
-    ):
-        raise ValueError(
-            "Input index must be class pd.DatetimeIndex or pd.PeriodIndex."
-        )
-    # check that it has a frequency, if not infer
-    freq_ind = X.index.freq
-    if freq_ind is None:
-        freq_ind = pd.infer_freq(X.index)
-        if freq_ind is None:
-            raise ValueError("Input index frequency cannot be inferred and is not set.")
-
-    tod = pd.timedelta_range(start="0T", end="1D", freq=freq_ind)
-    # checck frequency of tod
-    if (tod.freq > pd.offsets.Day(1)) | (tod.freq < pd.offsets.Second(1)):
-        raise ValueError(
-            """
-            Transformer intended to be used with input frequency of greater than
-            or equal to one day and with a frequency of less or equal to than
-            1 second. Contributions welcome on adapting for these use cases.
-            """
-        )
-    return freq_ind
diff --git a/aeon/transformations/compose.py b/aeon/transformations/compose.py
index 444e3bc1..48db9d28 100644
--- a/aeon/transformations/compose.py
+++ b/aeon/transformations/compose.py
@@ -27,6 +27,7 @@ __all__ = [
     "TransformerPipeline",
     "YtoX",
 ]
+from aeon.transformations._legacy._boxcox import _BoxCoxTransformer
 from aeon.utils import ALL_TIME_SERIES_TYPES
 
 
@@ -746,11 +747,9 @@ class FitInTransform(BaseTransformer):
             `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
             `create_test_instance` uses the first (or only) dictionary in `params`
         """
-        from aeon.transformations.boxcox import BoxCoxTransformer
-
         params = [
-            {"transformer": BoxCoxTransformer()},
-            {"transformer": BoxCoxTransformer(), "skip_inverse_transform": False},
+            {"transformer": _BoxCoxTransformer()},
+            {"transformer": _BoxCoxTransformer(), "skip_inverse_transform": False},
         ]
         return params
 
@@ -1156,11 +1155,9 @@ class InvertTransform(_DelegatedTransformer):
             `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
             `create_test_instance` uses the first (or only) dictionary in `params`
         """
-        from aeon.transformations.boxcox import BoxCoxTransformer
-
         params1 = {"transformer": MockTransformer()}
-        # BoxCoxTransformer has fit
-        params2 = {"transformer": BoxCoxTransformer()}
+        # _BoxCoxTransformer has fit
+        params2 = {"transformer": _BoxCoxTransformer()}
 
         return [params1, params2]
 
@@ -1354,9 +1351,7 @@ class OptionalPassthrough(_DelegatedTransformer):
             `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
             `create_test_instance` uses the first (or only) dictionary in `params`
         """
-        from aeon.transformations.boxcox import BoxCoxTransformer
-
-        return {"transformer": BoxCoxTransformer(), "passthrough": False}
+        return {"transformer": _BoxCoxTransformer(), "passthrough": False}
 
 
 class ColumnwiseTransformer(BaseTransformer):
diff --git a/aeon/transformations/cos.py b/aeon/transformations/cos.py
deleted file mode 100644
index cb604024..00000000
--- a/aeon/transformations/cos.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""Implements cosine transformation."""
-
-import numpy as np
-from deprecated.sphinx import deprecated
-
-from aeon.transformations.base import BaseTransformer
-
-__maintainer__ = []
-__all__ = ["CosineTransformer"]
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="CosineTransformer will be removed in version 0.10.",
-    category=FutureWarning,
-)
-class CosineTransformer(BaseTransformer):
-    """Cosine transformation.
-
-    This is a wrapper around numpy's cosine function (see :func:`numpy.cos`).
-
-    See Also
-    --------
-    numpy.cos
-
-    Examples
-    --------
-    >>> from aeon.transformations.cos import CosineTransformer
-    >>> from aeon.datasets import load_airline
-    >>> y = load_airline()
-    >>> transformer = CosineTransformer()
-    >>> y_hat = transformer.fit_transform(y)
-    """
-
-    _tags = {
-        "input_data_type": "Series",
-        # what is the abstract type of X: Series, or Panel
-        "output_data_type": "Series",
-        # what abstract type is returned: Primitives, Series, Panel
-        "instancewise": True,
-        "X_inner_type": "np.ndarray",
-        "y_inner_type": "None",
-        "capability:multivariate": True,
-        "fit_is_empty": True,
-        "transform-returns-same-time-index": True,
-        "capability:inverse_transform": False,
-    }
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing the core logic, called from transform
-
-        Parameters
-        ----------
-        X : 2D np.ndarray
-            Data to be transformed
-        y : ignored argument for interface compatibility
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        Xt : 2D np.ndarray
-            transformed version of X
-        """
-        Xt = np.cos(X)
-        return Xt
-
-    def _inverse_transform(self, X, y=None):
-        """Inverse transform X and return an inverse transformed version.
-
-        core logic
-
-        Parameters
-        ----------
-        X : 2D np.ndarray
-            Data to be transformed
-        y : ignored argument for interface compatibility
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        Xt : 2D np.ndarray
-            inverse transformed version of X
-        """
-        Xt = np.arccos(X)
-        return Xt
diff --git a/aeon/transformations/exponent.py b/aeon/transformations/exponent.py
index edc018e2..d81f5f84 100644
--- a/aeon/transformations/exponent.py
+++ b/aeon/transformations/exponent.py
@@ -1,13 +1,12 @@
 """Implements transformers raise time series to user provided exponent."""
 
 __maintainer__ = []
-__all__ = ["ExponentTransformer", "SqrtTransformer"]
+__all__ = ["ExponentTransformer"]
 
 from warnings import warn
 
 import numpy as np
 import pandas as pd
-from deprecated.sphinx import deprecated
 
 from aeon.transformations.base import BaseTransformer
 
@@ -43,17 +42,6 @@ class ExponentTransformer(BaseTransformer):
         User supplied offset value.
         Scalar or 1D iterable with as many values as X columns in transform.
 
-    See Also
-    --------
-    BoxCoxTransformer :
-        Applies Box-Cox power transformation. Can help normalize data and
-        compress variance of the series.
-    LogTransformer :
-        Transformer input data using natural log. Can help normalize data and
-        compress variance of the series.
-    aeon.transformations.exponent.SqrtTransformer :
-        Transform input data by taking its square root. Can help compress
-        variance of input series.
 
     Notes
     -----
@@ -184,76 +172,3 @@ class ExponentTransformer(BaseTransformer):
             `create_test_instance` uses the first (or only) dictionary in `params`
         """
         return [{"power": 2.5, "offset": 1}]
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="SqrtTransformer will be removed in version 0.10.0.",
-    category=FutureWarning,
-)
-class SqrtTransformer(ExponentTransformer):
-    """Apply element-sise square root transformation to a time series.
-
-    Transformation performs the following operations element-wise:
-        * adds the constant `offset` (shift)
-        * applies the square root
-    Offset="auto" computes offset as the smallest offset that ensure all elements
-    are non-negative before taking the square root.
-
-    Parameters
-    ----------
-    offset : "auto", int or float, default="auto"
-        Offset to be added to the input timeseries prior to raising
-        the timeseries to the given `power`. If "auto" the series is checked to
-        determine if it contains negative values. If negative values are found
-        then the offset will be equal to the absolute value of the most negative
-        value. If not negative values are present the offset is set to zero.
-        If an integer or float value is supplied it will be used as the offset.
-
-    Attributes
-    ----------
-    offset : int or float
-        User supplied offset value.
-
-    See Also
-    --------
-    BoxCoxTransformer :
-        Applies Box-Cox power transformation. Can help normalize data and
-        compress variance of the series.
-    LogTransformer :
-        Transformer input data using natural log. Can help normalize data and
-        compress variance of the series.
-    aeon.transformations.exponent.ExponentTransformer :
-        Transform input data by raising it to an exponent. Can help compress
-        variance of series if a fractional exponent is supplied.
-
-    Notes
-    -----
-    For an input series `Z` the square root transformation is defined as
-    :math:`(Z + offset)^{0.5}`.
-    """
-
-    def __init__(self, offset="auto"):
-        super().__init__(power=0.5, offset=offset)
-
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-        return [{}, {"offset": 4.2}]
diff --git a/aeon/transformations/func_transform.py b/aeon/transformations/func_transform.py
index 75f0af86..8bf2e00d 100644
--- a/aeon/transformations/func_transform.py
+++ b/aeon/transformations/func_transform.py
@@ -50,17 +50,6 @@ class FunctionTransformer(BaseTransformer):
         if X passed to transform/inverse_transform is not on the list,
             it will be converted to the first list element before passed to funcs
 
-    See Also
-    --------
-    aeon.transformations.boxcox.LogTransformer :
-        Transformer input data using natural log. Can help normalize data and
-        compress variance of the series.
-    aeon.transformations.exponent.ExponentTransformer :
-        Transform input data by raising it to an exponent. Can help compress
-        variance of series if a fractional exponent is supplied.
-    aeon.transformations.exponent.SqrtTransformer :
-        Transform input data by taking its square root. Can help compress
-        variance of input series.
 
     Examples
     --------
diff --git a/aeon/transformations/kalman_filter.py b/aeon/transformations/kalman_filter.py
deleted file mode 100644
index c53a8b2f..00000000
--- a/aeon/transformations/kalman_filter.py
+++ /dev/null
@@ -1,745 +0,0 @@
-"""Kalman Filter Transformers.
-
-Series based transformers, based on Kalman Filter algorithm. Contains Base class
-and a transformer which is an adapter for the external package
-FilterPy.
-"""
-
-__maintainer__ = []
-__all__ = [
-    "BaseKalmanFilter",
-    "KalmanFilterTransformer",
-]
-
-from warnings import warn
-
-import numpy as np
-from deprecated.sphinx import deprecated
-
-from aeon.transformations.base import BaseTransformer
-
-
-def _get_t_matrix(time_t, matrices, shape, time_steps):
-    """Extract matrix to be used at iteration `time_t` of the Kalman filter iterations.
-
-    Parameters
-    ----------
-    time_t : int
-        The required time step.
-    matrices : np.ndarray
-    shape : tuple
-        The shape of a single matrix.
-    time_steps : int
-        Number of iterations.
-
-    Returns
-    -------
-        matrix : np.ndarray
-            matrix to be used at iteration `time_t`
-    """
-    matrices = np.asarray(matrices)
-    if matrices.shape == shape:
-        return matrices
-    if matrices.shape == (time_steps, *shape):
-        return matrices[time_t]
-    raise ValueError(
-        f"Shape of `matrices` {matrices.shape}, does not match single matrix"
-        f"`shape` {shape}, nor shape of list of matrices {(time_steps, *shape)}."
-    )
-
-
-def _validate_param_shape(param_name, matrix_shape, actual_shape, time_steps=None):
-    """Validate shape of matrix parameter.
-
-    Assert `actual_shape` equals to:
-        -   'shape' of a single matrix or
-        -   'shape' of time_steps matrices.
-    If neither, raise an informative `ValueError` that includes the parameter's name.
-
-    Parameters
-    ----------
-    param_name : str
-        The name of the matrix-parameter.
-    matrix_shape : tuple
-        The supposed shape of a single matrix.
-    actual_shape : tuple
-        The actual shape of matrix-parameter.
-    time_steps : int
-        actual_shape[0] if matrix-parameter is dynamic (matrix per time-step).
-
-    Raises
-    ------
-        ValueError
-            error with an informative message that includes parameter's name,
-            and the shape that parameter should have.
-    """
-    if time_steps is None:
-        if actual_shape != matrix_shape:
-            raise ValueError(
-                f"Shape of parameter `{param_name}` is: {actual_shape}, "
-                f"but should be: {matrix_shape}."
-            )
-    else:
-        matrices_shape = (time_steps, *matrix_shape)
-        if actual_shape not in [matrix_shape, matrices_shape]:
-            raise ValueError(
-                f"Shape of parameter `{param_name}` is: {actual_shape}, but should be: "
-                f"{matrix_shape} or {matrices_shape}."
-            )
-
-
-def _init_matrix(matrices, transform_func, default_val):
-    """Initialize default value if matrices.
-
-    If `matrices` is None, or transform input matrix to np.ndarray.
-
-    Parameters
-    ----------
-    matrices : np.ndarray
-    transform_func : transformation function from array-like to ndarray
-    default_val : np.ndarray
-
-    Returns
-    -------
-        transformed_matrices : np.ndarray
-            matrices as np.ndarray
-    """
-    return default_val if matrices is None else transform_func(matrices)
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="BaseKalmanFilter will be removed in version 0.10.",
-    category=FutureWarning,
-)
-class BaseKalmanFilter:
-    """Kalman Filter is used for denoising data, or inferring the hidden state of data.
-
-    Note - this class is a base class and should not be used directly.
-
-    The Kalman filter is an unsupervised algorithm consisting of
-    several mathematical equations which are used to create
-    an estimate of the state of a process.
-    The algorithm does this efficiently and recursively in a
-    way where the mean squared error is minimal.
-    The Kalman Filter has the ability to support estimations
-    of past, present and future states.
-    The strength of the Kalman Filter is in its ability to
-    infer the state of a system even when the exact nature of the
-    system is not known.
-    When given time series data, the Kalman filter creates a denoising effect
-    by removing noise from the data, and recovering the true
-    state of the underlying object we are tracking within the
-    data.
-    The Kalman Filter computations are based on five equations.
-
-    Two prediction equations:
-
-    -   State Extrapolation Equation - prediction or estimation of the future state,
-        based on the known present estimation.
-    -	Covariance Extrapolation Equation - the measure of uncertainty in our prediction.
-
-    Two update equations:
-
-    -   State Update Equation - estimation of the current state,
-        based on the known past estimation and present measurement.
-    -	Covariance Update Equation - the measure of uncertainty in our estimation.
-
-    Kalman Gain Equation – this is a required argument for the update equations.
-    It acts as a weighting parameter for the past estimations and the given measurement.
-    It defines the weight of the past estimation and
-    the weight of the measurement in estimating the current state.
-
-    Parameters
-    ----------
-    state_dim : int
-        System state feature dimension
-    state_transition : np.ndarray, default=None
-        of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
-        State transition matrix, also referred to as F, is a matrix
-        which describes the way the underlying series moves
-        through successive time periods.
-    process_noise : np.ndarray, default=None
-        of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
-        Process noise matrix, also referred to as Q,
-        the uncertainty of the dynamic model.
-    measurement_noise : np.ndarray, default=None
-        of shape (measurement_dim, measurement_dim) or
-        (time_steps, measurement_dim, measurement_dim).
-        Measurement noise matrix, also referred to as R,
-        represents the uncertainty of the measurements.
-    measurement_function : np.ndarray, default=None
-        of shape (measurement_dim, state_dim) or
-        (time_steps, measurement_dim, state_dim).
-        Measurement equation matrix, also referred to as H, adjusts
-        dimensions of measurements to match dimensions of state.
-    initial_state : np.ndarray, default=None
-        of shape (state_dim,).
-        Initial estimated system state, also referred to as x0.
-    initial_state_covariance : np.ndarray, default=None
-        of shape (state_dim, state_dim).
-        Initial estimated system state covariance, also referred to as P0.
-
-    References
-    ----------
-    .. [1] Greg Welch and Gary Bishop, "An Introduction to the Kalman Filter", 2006
-           https://www.cs.unc.edu/~welch/media/pdf/kalman_intro.pdf
-    .. [2] R.H.Shumway and D.S.Stoffer "An Approach to time
-           Series Smoothing and Forecasting Using the EM Algorithm", 1982
-           https://www.stat.pitt.edu/stoffer/dss_files/em.pdf
-    """
-
-    def __init__(
-        self,
-        state_dim,
-        state_transition=None,
-        process_noise=None,
-        measurement_noise=None,
-        measurement_function=None,
-        initial_state=None,
-        initial_state_covariance=None,
-    ):
-        self.state_dim = state_dim
-        # F/A
-        self.state_transition = state_transition
-        # Q
-        self.process_noise = process_noise
-        # R
-        self.measurement_noise = measurement_noise
-        # H/C
-        self.measurement_function = measurement_function
-        # X0
-        self.initial_state = initial_state
-        # P0
-        self.initial_state_covariance = initial_state_covariance
-
-        super().__init__()
-
-    def _get_shapes(self, state_dim, measurement_dim):
-        """Return dictionary with default shape of each matrix parameter.
-
-        Parameters
-        ----------
-        state_dim : int
-            Dimension of `state`.
-        measurement_dim : int
-            Dimension of measurements.
-
-        Returns
-        -------
-            shapes : dict
-                Dictionary with default shape of each matrix parameter
-        """
-        return {
-            "F": (state_dim, state_dim),
-            "Q": (state_dim, state_dim),
-            "R": (measurement_dim, measurement_dim),
-            "H": (measurement_dim, state_dim),
-            "X0": (state_dim,),
-            "P0": (state_dim, state_dim),
-        }
-
-    def _get_init_values(self, measurement_dim, state_dim):
-        """Initialize matrix parameters to default values and returns them.
-
-        Parameters
-        ----------
-        measurement_dim : int
-        state_dim : int
-
-        Returns
-        -------
-            Six matrix parameters F,Q,R,H,X0,P0 as np.ndarray
-        """
-        shapes = self._get_shapes(state_dim=state_dim, measurement_dim=measurement_dim)
-        F = _init_matrix(
-            matrices=self.state_transition,
-            transform_func=np.atleast_2d,
-            default_val=np.eye(*shapes["F"]),
-        )
-        Q = _init_matrix(
-            matrices=self.process_noise,
-            transform_func=np.atleast_2d,
-            default_val=np.eye(*shapes["Q"]),
-        )
-        R = _init_matrix(
-            matrices=self.measurement_noise,
-            transform_func=np.atleast_2d,
-            default_val=np.eye(*shapes["R"]),
-        )
-        H = _init_matrix(
-            matrices=self.measurement_function,
-            transform_func=np.atleast_2d,
-            default_val=np.eye(*shapes["H"]),
-        )
-        X0 = _init_matrix(
-            matrices=self.initial_state,
-            transform_func=np.atleast_1d,
-            default_val=np.zeros(*shapes["X0"]),
-        )
-        P0 = _init_matrix(
-            matrices=self.initial_state_covariance,
-            transform_func=np.atleast_2d,
-            default_val=np.eye(*shapes["P0"]),
-        )
-
-        return F, Q, R, H, X0, P0
-
-    def _set_attribute(
-        self, param_name, attr_name, value, matrix_shape, time_steps=None
-    ):
-        """Validate the shape of parameter and set as attribute if no error.
-
-        Parameters
-        ----------
-        param_name : str
-            The name of matrix-parameter.
-        attr_name : str
-            The name of corresponding attribute.
-        value : np.ndarray
-            The value of corresponding attribute.
-        matrix_shape : tuple
-            The supposed shape of a single matrix.
-        time_steps : int, default=None
-        """
-        _validate_param_shape(
-            param_name=param_name,
-            matrix_shape=matrix_shape,
-            actual_shape=value.shape,
-            time_steps=time_steps,
-        )
-        setattr(self, attr_name, value)
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="KalmanFilterTransformer will be removed in version 0.10.",
-    category=FutureWarning,
-)
-class KalmanFilterTransformer(BaseKalmanFilter, BaseTransformer):
-    """Kalman Filter Transformer.
-
-    Kalman Filter is used for denoising data or inferring the hidden
-    state of given data.
-
-    The Kalman Filter is an unsupervised algorithm, consisting of
-    several mathematical equations which are used to create
-    an estimate of the state of a process.
-
-    This class is the adapter for the `FilterPy` package into `aeon`.
-    `KalmanFilterTransformer` implements hidden inferred states and
-    denoising, depending on the boolean input parameter `denoising`.
-    In addition, `KalmanFilterTransformer` provides parameter
-    optimization via Expectation-Maximization (EM) algorithm.
-
-    Parameters
-    ----------
-    state_dim : int
-        System state feature dimension.
-    state_transition : np.ndarray, default=None
-        of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
-        State transition matrix, also referred to as `F`, is a matrix
-        which describes the way the underlying series moves
-        through successive time periods.
-    process_noise : np.ndarray, default=None
-        of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
-        Process noise matrix, also referred to as `Q`,
-        the uncertainty of the dynamic model.
-    measurement_noise : np.ndarray, default=None
-        of shape (measurement_dim, measurement_dim) or
-        (time_steps, measurement_dim, measurement_dim).
-        Measurement noise matrix, also referred to as `R`,
-        represents the uncertainty of the measurements.
-    measurement_function : np.ndarray, default=None
-        of shape (measurement_dim, state_dim) or
-        (time_steps, measurement_dim, state_dim).
-        Measurement equation matrix, also referred to as `H`, adjusts
-        dimensions of measurements to match dimensions of state.
-    initial_state : np.ndarray, default=None
-        of shape (state_dim,).
-        Initial estimated system state, also referred to as `X0`.
-    initial_state_covariance : np.ndarray, default=None
-        of shape (state_dim, state_dim).
-        Initial estimated system state covariance, also referred to as `P0`.
-    control_transition : np.ndarray, default=None
-        of shape (state_dim, control_variable_dim) or
-        (time_steps, state_dim, control_variable_dim).
-        Control transition matrix, also referred to as `G`.
-        `control_variable_dim` is the dimension of `control variable`,
-        also referred to as `u`.
-        `control variable` is an optional parameter for `fit` and `transform` functions.
-    denoising : bool, default=False
-        This parameter affects `transform`. If False, then `transform` will be inferring
-        hidden state. If True, uses `FilterPy` `rts_smoother` for denoising.
-    estimate_matrices : str or list of str, default=None
-        Subset of [`state_transition`, `measurement_function`,
-        `process_noise`, `measurement_noise`, `initial_state`,
-        `initial_state_covariance`]
-        or - `all`. If `estimate_matrices` is an iterable of strings,
-        only matrices in `estimate_matrices` will be estimated using EM algorithm.
-        If `estimate_matrices` is `all`,
-        then all matrices will be estimated using EM algorithm.
-
-        Note -
-            - parameters estimated by EM algorithm assumed to be constant.
-            - `control_transition` matrix cannot be estimated.
-
-    Notes
-    -----
-    `FilterPy` KalmanFilter documentation :
-        https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html
-
-    References
-    ----------
-    .. [1] Greg Welch and Gary Bishop, "An Introduction to the Kalman Filter", 2006
-           https://www.cs.unc.edu/~welch/media/pdf/kalman_intro.pdf
-    .. [2] R.H.Shumway and D.S.Stoffer "An Approach to time
-           Series Smoothing and Forecasting Using the EM Algorithm", 1982
-           https://www.stat.pitt.edu/stoffer/dss_files/em.pdf
-
-    Examples
-    --------
-        Basic example:
-    >>> import numpy as np  # doctest: +SKIP
-    >>> import aeon.transformations.kalman_filter as kf
-    >>> time_steps, state_dim, measurement_dim = 10, 2, 3
-    >>>
-    >>> X = np.random.rand(time_steps, measurement_dim) * 10
-    >>> transformer = kf.KalmanFilterTransformer(state_dim=state_dim) # doctest: +SKIP
-    >>> Xt = transformer.fit_transform(X=X)  # doctest: +SKIP
-
-        Example of - denoising, matrix estimation, missing values and transform with y:
-    >>> import numpy as np  # doctest: +SKIP
-    >>> import aeon.transformations.kalman_filter as kf
-    >>> time_steps, state_dim, measurement_dim = 10, 3, 3
-    >>> control_variable_dim = 2
-    >>>
-    >>> X = np.random.rand(time_steps, measurement_dim)
-    >>> # missing value
-    >>> X[0][0] = np.nan
-    >>>
-    >>> # y
-    >>> control_variable = np.random.rand(time_steps, control_variable_dim)
-    >>>
-    >>> # If matrices estimation is required, elements of `estimate_matrices`
-    >>> # are assumed to be constants.
-    >>> transformer = kf.KalmanFilterTransformer(  # doctest: +SKIP
-    ...     state_dim=state_dim,
-    ...     measurement_noise=np.eye(measurement_dim),
-    ...     denoising=True,
-    ...     )
-    >>> Xt = transformer.fit_transform(X=X, y=control_variable)  # doctest: +SKIP
-
-        Example of - dynamic inputs (matrix per time-step), missing values:
-    >>> import numpy as np  # doctest: +SKIP
-    >>> import aeon.transformations.kalman_filter as kf
-    >>> time_steps, state_dim, measurement_dim = 10, 4, 4
-    >>> control_variable_dim = 4
-    >>>
-    >>> X = np.random.rand(time_steps, measurement_dim)
-    >>> # missing values
-    >>> X[0] = [np.NaN for i in range(measurement_dim)]
-    >>>
-    >>> # y
-    >>> control_variable = np.random.rand(control_variable_dim)
-    >>>
-    >>> # Dynamic input -
-    >>> # `state_transition` provide different matrix for each time step.
-    >>> transformer = kf.KalmanFilterTransformer(  # doctest: +SKIP
-    ...     state_dim=state_dim,
-    ...     state_transition=np.random.rand(time_steps, state_dim, state_dim),
-    ...     )
-    >>> Xt = transformer.fit_transform(X=X, y=control_variable)  # doctest: +SKIP
-    """
-
-    _tags = {
-        "transform_labels": "Series",
-        "X_inner_type": "np.ndarray",
-        "y_inner_type": "np.ndarray",
-        "fit_is_empty": False,
-        "capability:unequal_length": False,
-        "capability:missing_values": True,  # can estimator handle missing data?
-        "capability:missing_values:removes": False,
-        "instancewise": True,  # is this an instance-wise transform?
-        "python_dependencies": ["filterpy", "numpy<1.24.0"],
-    }
-
-    def __init__(
-        self,
-        state_dim,
-        state_transition=None,
-        control_transition=None,
-        process_noise=None,
-        measurement_noise=None,
-        measurement_function=None,
-        initial_state=None,
-        initial_state_covariance=None,
-        estimate_matrices=None,
-        denoising=False,
-    ):
-        super().__init__(
-            state_dim=state_dim,
-            state_transition=state_transition,
-            process_noise=process_noise,
-            measurement_noise=measurement_noise,
-            measurement_function=measurement_function,
-            initial_state=initial_state,
-            initial_state_covariance=initial_state_covariance,
-        )
-        # G/B
-        self.control_transition = control_transition
-        self.estimate_matrices = estimate_matrices
-        self.denoising = denoising
-
-    def _fit(self, X, y=None):
-        """Fit transformer to X and y.
-
-        This method prepares the transformer.
-        The matrix initializations or estimations
-        (if requested by user) are calculated here.
-
-        Parameters
-        ----------
-        X : np.ndarray
-            of shape (time_steps, measurement_dim).
-            Data (measurements) to be transformed.
-            Missing values must be represented as np.NaN or np.nan.
-        y : ignored argument for interface compatibility
-
-        Returns
-        -------
-            self: reference to self
-        """
-        measurement_dim = X.shape[1]
-        time_steps = X.shape[0]
-        shapes = self._get_shapes(
-            state_dim=self.state_dim, measurement_dim=measurement_dim
-        )
-
-        if self.estimate_matrices is None:
-            (F_, Q_, R_, H_, X0_, P0_) = self._get_init_values(
-                measurement_dim, self.state_dim
-            )
-
-        self._set_attribute(
-            param_name="state_transition",
-            attr_name="F_",
-            value=F_,
-            matrix_shape=shapes["F"],
-            time_steps=time_steps,
-        )
-        self._set_attribute(
-            param_name="process_noise",
-            attr_name="Q_",
-            value=Q_,
-            matrix_shape=shapes["Q"],
-            time_steps=time_steps,
-        )
-        self._set_attribute(
-            param_name="measurement_noise",
-            attr_name="R_",
-            value=R_,
-            matrix_shape=shapes["R"],
-            time_steps=time_steps,
-        )
-        self._set_attribute(
-            param_name="measurement_function",
-            attr_name="H_",
-            value=H_,
-            matrix_shape=shapes["H"],
-            time_steps=time_steps,
-        )
-        self._set_attribute(
-            param_name="initial_state",
-            attr_name="X0_",
-            value=X0_,
-            matrix_shape=shapes["X0"],
-        )
-        self._set_attribute(
-            param_name="initial_state_covariance",
-            attr_name="P0_",
-            value=P0_,
-            matrix_shape=shapes["P0"],
-        )
-
-        return self
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        This method performs the transformation of the input data
-        according to the constructor input parameter `denoising`.
-        If `denoising` is True - then denoise data using
-        `FilterPy`'s `rts_smoother` function.
-        Else, infer hidden state using `FilterPy`'s `predict` and `update` functions.
-
-        Parameters
-        ----------
-        X : np.ndarray
-            of shape (time_steps, measurement_dim).
-            Data (measurements) to be transformed.
-            Missing values must be represented as np.NaN or np.nan.
-        y : np.ndarray, default=None
-            of shape (control_variable_dim,) or (time_steps, control_variable_dim).
-            `control variable`, also referred to as `u`.
-            if 2D, must be same length as X.
-
-        Returns
-        -------
-            X_transformed : np.ndarray
-                transformed version of X
-        """
-        import filterpy.kalman.kalman_filter as filterpy_kf
-
-        time_steps = X.shape[0]
-        measurement_dim = X.shape[1]
-
-        if y is None:
-            if self.control_transition is None:
-                y_dim = 1
-            else:
-                y_dim = np.atleast_2d(self.control_transition).shape[-1]
-                warn(
-                    "Class parameter `control_transition` was initiated with user data "
-                    "but received no data through `transform` argument, `y`. "
-                    "Therefore, omitting `control_transition` "
-                    "when calculating the result. ",
-                    stacklevel=2,
-                )
-            y = np.zeros(y_dim)
-
-        shapes = self._get_shapes(
-            state_dim=self.state_dim, measurement_dim=measurement_dim, u_dim=y.shape[-1]
-        )
-        G = _init_matrix(
-            matrices=self.control_transition,
-            transform_func=np.atleast_2d,
-            default_val=np.eye(*shapes["G"]),
-        )
-        _validate_param_shape(
-            param_name="control_transition",
-            matrix_shape=shapes["G"],
-            actual_shape=G.shape,
-            time_steps=time_steps,
-        )
-
-        x_priori = np.zeros((time_steps, *shapes["X0"]))
-        p_priori = np.zeros((time_steps, *shapes["P0"]))
-
-        x_posteriori = np.zeros((time_steps, *shapes["X0"]))
-        p_posteriori = np.zeros((time_steps, *shapes["P0"]))
-
-        x = self.X0_
-        p = self.P0_
-
-        # kalman filter iterations
-        for t in range(time_steps):
-            (zt, Ft, Gt, Qt, Rt, Ht, ut) = self._get_iter_t_matrices(
-                X=X, G=G, u=y, t=t, time_steps=time_steps, shapes=shapes
-            )
-
-            x, p = filterpy_kf.predict(x, p, u=ut, B=Gt, F=Ft, Q=Qt)
-            x_priori[t, :] = x
-            p_priori[t, :, :] = p
-
-            x, p = filterpy_kf.update(x, p, zt, R=Rt, H=Ht)
-            x_posteriori[t, :] = x
-            p_posteriori[t, :, :] = p
-
-        if self.denoising:
-            Fs = [self.F_] * time_steps if self.F_.ndim == 2 else self.F_
-            Qs = [self.Q_] * time_steps if self.Q_.ndim == 2 else self.Q_
-            return filterpy_kf.rts_smoother(
-                Xs=x_posteriori, Ps=p_posteriori, Fs=Fs, Qs=Qs
-            )[0]
-        return x_posteriori
-
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-        return {"state_dim": 2}
-
-    def _get_iter_t_matrices(self, X, G, u, t, time_steps, shapes):
-        """Extract data to be used at time step 't' of the Kalman filter iterations.
-
-        Parameters
-        ----------
-        X : np.ndarray
-            of shape (time_steps, measurement_dim).
-            Data (measurements).
-        G : np.ndarray
-            of shape (state_dim, control_variable_dim) or
-            (time_steps, state_dim, control_variable_dim).
-            control_transition matrix
-        u : np.ndarray
-            of shape (control_variable_dim,) or (time_steps, control_variable_dim).
-            u is the `control variable`. If `u` not given in _transform default
-            value is [0].
-        t : int
-            time step
-
-        Returns
-        -------
-            Seven matrices to be used at time step t
-        """
-        zt = None if any(np.isnan(X[t])) else X[t]
-        Ft = _get_t_matrix(
-            time_t=t, matrices=self.F_, shape=shapes["F"], time_steps=time_steps
-        )
-        Gt = _get_t_matrix(
-            time_t=t, matrices=G, shape=shapes["G"], time_steps=time_steps
-        )
-        Qt = _get_t_matrix(
-            time_t=t, matrices=self.Q_, shape=shapes["Q"], time_steps=time_steps
-        )
-        Rt = _get_t_matrix(
-            time_t=t, matrices=self.R_, shape=shapes["R"], time_steps=time_steps
-        )
-        Ht = _get_t_matrix(
-            time_t=t, matrices=self.H_, shape=shapes["H"], time_steps=time_steps
-        )
-        ut = _get_t_matrix(
-            time_t=t, matrices=u, shape=shapes["u"], time_steps=time_steps
-        )
-
-        return zt, Ft, Gt, Qt, Rt, Ht, ut
-
-    def _get_shapes(self, state_dim, measurement_dim, u_dim=1):
-        """Return dictionary with default shape of each matrix parameter.
-
-        Parameters
-        ----------
-        state_dim : int
-            Dimension of `state`.
-        measurement_dim : int
-            Dimension of measurements.
-        u_dim : int
-            Dimension of control variable u (y).
-
-        Returns
-        -------
-            shapes : dict
-                Dictionary with default shape of each matrix parameter
-        """
-        shapes = super()._get_shapes(state_dim, measurement_dim)
-        shapes["u"] = (u_dim,)
-        shapes["G"] = (state_dim, u_dim)
-        return shapes
diff --git a/aeon/transformations/pca.py b/aeon/transformations/pca.py
deleted file mode 100644
index ee9ad035..00000000
--- a/aeon/transformations/pca.py
+++ /dev/null
@@ -1,184 +0,0 @@
-"""sklearn PCA applied as transformation."""
-
-__maintainer__ = []
-__all__ = ["PCATransformer"]
-
-import pandas as pd
-from deprecated.sphinx import deprecated
-from sklearn.decomposition import PCA
-
-from aeon.transformations.base import BaseTransformer
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="PCATransformer will be removed in version 0.10 and replaced with a "
-    "BaseSeriesTransformer version in the transformations.series module.",
-    category=FutureWarning,
-)
-class PCATransformer(BaseTransformer):
-    """Principal Components Analysis applied as transformer.
-
-    Provides a simple wrapper around ``sklearn.decomposition.PCA``.
-
-    Parameters
-    ----------
-    n_components : int, float or 'mle', default=None
-        Number of components to keep.
-        if n_components is not set all components are kept::
-            n_components == min(n_samples, n_features)
-        If ``n_components == 'mle'`` and ``svd_solver == 'full'``, Minka's
-        MLE is used to guess the dimension. Use of ``n_components == 'mle'``
-        will interpret ``svd_solver == 'auto'`` as ``svd_solver == 'full'``.
-        If ``0 < n_components < 1`` and ``svd_solver == 'full'``, select the
-        number of components such that the amount of variance that needs to be
-        explained is greater than the percentage specified by n_components.
-        If ``svd_solver == 'arpack'``, the number of components must be
-        strictly less than the minimum of n_features and n_samples.
-        Hence, the None case results in::
-            n_components == min(n_samples, n_features) - 1
-    copy : bool, default=True
-        If False, data passed to fit are overwritten and running
-        fit(X).transform(X) will not yield the expected results,
-        use fit_transform(X) instead.
-    whiten : bool, default=False
-        When True (False by default) the `components_` vectors are multiplied
-        by the square root of n_samples and then divided by the singular values
-        to ensure uncorrelated outputs with unit component-wise variances.
-        Whitening will remove some information from the transformed signal
-        (the relative variance scales of the components) but can sometime
-        improve the predictive accuracy of the downstream estimators by
-        making their data respect some hard-wired assumptions.
-    svd_solver : {'auto', 'full', 'arpack', 'randomized'}, default='auto'
-        If auto :
-            The solver is selected by a default policy based on `X.shape` and
-            `n_components`: if the input data is larger than 500x500 and the
-            number of components to extract is lower than 80% of the smallest
-            dimension of the data, then the more efficient 'randomized'
-            method is enabled. Otherwise the exact full SVD is computed and
-            optionally truncated afterwards.
-        If full :
-            run exact full SVD calling the standard LAPACK solver via
-            `scipy.linalg.svd` and select the components by postprocessing
-        If arpack :
-            run SVD truncated to n_components calling ARPACK solver via
-            `scipy.sparse.linalg.svds`. It requires strictly
-            0 < n_components < min(X.shape)
-        If randomized :
-            run randomized SVD by the method of Halko et al.
-    tol : float, default=0.0
-        Tolerance for singular values computed by svd_solver == 'arpack'.
-        Must be of range [0.0, infinity).
-    iterated_power : int or 'auto', default='auto'
-        Number of iterations for the power method computed by
-        svd_solver == 'randomized'.
-        Must be of range [0, infinity).
-    n_oversamples : int, default=10
-        This parameter is only relevant when `svd_solver="randomized"`.
-        It corresponds to the additional number of random vectors to sample the
-        range of `X` so as to ensure proper conditioning. See
-        :func:`~sklearn.utils.extmath.randomized_svd` for more details.
-    power_iteration_normalizer : {'auto', 'QR', 'LU', 'none'}, default='auto'
-        Power iteration normalizer for randomized SVD solver.
-        Not used by ARPACK. See :func:`~sklearn.utils.extmath.randomized_svd`
-        for more details.
-    random_state : int, RandomState instance or None, default=None
-        Used when the 'arpack' or 'randomized' solvers are used. Pass an int
-        for reproducible results across multiple function calls.
-
-    Attributes
-    ----------
-    pca_ : sklearn.decomposition.PCA
-        The fitted PCA object
-    """
-
-    _tags = {
-        "input_data_type": "Series",
-        # what is the abstract type of X: Series, or Panel
-        "output_data_type": "Series",
-        # what abstract type is returned: Primitives, Series, Panel
-        "instancewise": False,  # is this an instance-wise transform?
-        "X_inner_type": "pd.DataFrame",
-        "y_inner_type": "None",
-        "capability:multivariate": True,
-        "fit_is_empty": False,
-        "capability:missing_values": False,
-        "python_version": ">=3.8",
-    }
-
-    def __init__(
-        self,
-        n_components=None,
-        copy=True,
-        whiten=False,
-        svd_solver="auto",
-        tol=0.0,
-        n_oversamples=10,
-        power_iteration_normalizer="auto",
-        iterated_power="auto",
-        random_state=None,
-    ):
-        self.n_components = n_components
-        self.copy = copy
-        self.whiten = whiten
-        self.svd_solver = svd_solver
-        self.tol = tol
-        self.n_oversamples = n_oversamples
-        self.power_iteration_normalizer = power_iteration_normalizer
-        self.iterated_power = iterated_power
-        self.random_state = random_state
-        super().__init__()
-
-    def _fit(self, X, y=None):
-        """Fit transformer to X and y.
-
-        private _fit containing the core logic, called from fit
-
-        Parameters
-        ----------
-        X: data structure of type X_inner_type
-            if X_inner_type is list, _fit must support all types in it
-            Data to fit transform to
-        y : Ignored
-
-        Returns
-        -------
-        self: reference to self
-        """
-        self.pca_ = PCA(
-            n_components=self.n_components,
-            copy=self.copy,
-            whiten=self.whiten,
-            svd_solver=self.svd_solver,
-            tol=self.tol,
-            n_oversamples=self.n_oversamples,
-            power_iteration_normalizer=self.power_iteration_normalizer,
-            iterated_power=self.iterated_power,
-            random_state=self.random_state,
-        )
-        self.pca_.fit(X=X)
-
-        return self
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing core logic, called from transform
-
-        Parameters
-        ----------
-        X: data structure of type X_inner_type
-            if X_inner_type is list, _transform must support all types in it
-            Data to be transformed
-        y : Ignored
-
-        Returns
-        -------
-        transformed version of X
-        """
-        Xt = self.pca_.transform(X=X)
-        columns = [f"PC_{i}" for i in range(Xt.shape[1])]
-        Xt = pd.DataFrame(Xt, index=X.index, columns=columns)
-
-        return Xt
diff --git a/aeon/transformations/scaledlogit.py b/aeon/transformations/scaledlogit.py
index 80b8ef76..4ea8af2b 100644
--- a/aeon/transformations/scaledlogit.py
+++ b/aeon/transformations/scaledlogit.py
@@ -36,10 +36,10 @@ class ScaledLogitTransformer(BaseTransformer):
 
     See Also
     --------
-    aeon.transformations.boxcox.LogTransformer :
+    aeon.transformations.boxcox._LogTransformer :
         Transformer input data using natural log. Can help normalize data and
         compress variance of the series.
-    aeon.transformations.boxcox.BoxCoxTransformer :
+    aeon.transformations.boxcox._BoxCoxTransformer :
         Applies Box-Cox power transformation. Can help normalize data and
         compress variance of the series.
     aeon.transformations.exponent.ExponentTransformer :
diff --git a/aeon/transformations/series/_boxcox.py b/aeon/transformations/series/_boxcox.py
index 71575dc2..41edd97a 100644
--- a/aeon/transformations/series/_boxcox.py
+++ b/aeon/transformations/series/_boxcox.py
@@ -46,7 +46,7 @@ class BoxCoxTransformer(BaseSeriesTransformer):
     make data more normally distributed and stabilize its variance based
     on the hyperparameter lambda. [1]_
 
-    The BoxCoxTransformer solves for the lambda parameter used in the Box-Cox
+    The _BoxCoxTransformer solves for the lambda parameter used in the Box-Cox
     transformation given `method`, the optimization approach, and input
     data provided to `fit`. The use of Guerrero's method for solving for lambda
     requires the seasonal periodicity, `sp` be provided. [2]_
@@ -79,7 +79,7 @@ class BoxCoxTransformer(BaseSeriesTransformer):
 
     See Also
     --------
-    LogTransformer :
+    _LogTransformer :
         Transformer input data using natural log. Can help normalize data and
         compress variance of the series.
     aeon.transformations.exponent.ExponentTransformer :
@@ -107,7 +107,7 @@ class BoxCoxTransformer(BaseSeriesTransformer):
 
     Examples
     --------
-    >>> from aeon.transformations.boxcox import BoxCoxTransformer
+    >>> from aeon.transformations.series._boxcox import BoxCoxTransformer
     >>> from aeon.datasets import load_airline
     >>> y = load_airline()
     >>> transformer = BoxCoxTransformer()
@@ -212,7 +212,7 @@ class LogTransformer(BaseSeriesTransformer):
 
     See Also
     --------
-    BoxCoxTransformer :
+    _BoxCoxTransformer :
         Applies Box-Cox power transformation. Can help normalize data and
         compress variance of the series.
     aeon.transformations.exponent.ExponentTransformer :
@@ -228,7 +228,7 @@ class LogTransformer(BaseSeriesTransformer):
 
     Examples
     --------
-    >>> from aeon.transformations.boxcox import LogTransformer
+    >>> from aeon.transformations.series._boxcox import LogTransformer
     >>> from aeon.datasets import load_airline
     >>> y = load_airline()
     >>> transformer = LogTransformer()
diff --git a/aeon/transformations/series/_scaled_logit.py b/aeon/transformations/series/_scaled_logit.py
index ecc93389..1cb97ac3 100644
--- a/aeon/transformations/series/_scaled_logit.py
+++ b/aeon/transformations/series/_scaled_logit.py
@@ -34,20 +34,6 @@ class ScaledLogitSeriesTransformer(BaseSeriesTransformer):
     upper_bound : float, optional, default=None
         upper bound of inverse transform function
 
-    See Also
-    --------
-    aeon.transformations.boxcox.LogTransformer :
-        Transformer input data using natural log. Can help normalize data and
-        compress variance of the series.
-    aeon.transformations.boxcox.BoxCoxTransformer :
-        Applies Box-Cox power transformation. Can help normalize data and
-        compress variance of the series.
-    aeon.transformations.exponent.ExponentTransformer :
-        Transform input data by raising it to an exponent. Can help compress
-        variance of series if a fractional exponent is supplied.
-    aeon.transformations.exponent.SqrtTransformer :
-        Transform input data by taking its square root. Can help compress
-        variance of input series.
 
     Notes
     -----
diff --git a/aeon/transformations/time_since.py b/aeon/transformations/time_since.py
deleted file mode 100644
index 1da801d5..00000000
--- a/aeon/transformations/time_since.py
+++ /dev/null
@@ -1,346 +0,0 @@
-"""A transformer to compute the time elapsed since a reference time."""
-
-from __future__ import annotations
-
-__maintainer__ = []
-
-import datetime
-import warnings
-from string import digits
-
-import numpy as np
-import pandas as pd
-from deprecated.sphinx import deprecated
-from pandas.api.types import is_any_real_numeric_dtype
-from pandas.tseries.frequencies import get_period_alias
-
-from aeon.transformations.base import BaseTransformer
-
-
-# TODO: remove in v0.10.0
-@deprecated(
-    version="0.9.0",
-    reason="TimeSince will be removed in version 0.10 and may be replaced with a "
-    "BaseSeriesTransformer version in the transformations.series module.",
-    category=FutureWarning,
-)
-class TimeSince(BaseTransformer):
-    """Get Element-wise time elapsed between the timeindex and a reference start time.
-
-    Creates a column(s) which represents: `t` - `start`, where `start` is
-    a reference time and `t` is the time index. The type of `start` must be
-    compatible with the index of `X` used in `.fit()` and `.transform()`.
-
-    The output can be converted to an integer representing the number of periods
-    elapsed since the start time by setting `to_numeric=True`. The period is
-    determined by the frequency of the index. For example, if the `freq` of
-    the index is "MS" or "M" then the output is the integer number of months
-    between `t` and `start`.
-
-    Parameters
-    ----------
-    start : a list of start times, optional, default=None (use earliest time in index)
-        a "start time" can be one of the following types:
-
-        * int: Start time to compute the time elapsed, use when index is integer.
-        * time-like: `Period` or `datetime`
-            Start time to compute the time elapsed.
-        * str: String is converted to datetime or period, depending on the index type, \
-            to give the start time.
-
-    to_numeric : string, default=True
-        Return the integer number of periods elapsed since `start`; the period
-        is defined by the frequency of the data. Converts datetime types to
-        pd.Period before calculating time differences.
-    freq : 'str', optional, default=None
-        Only used when X has a pd.DatetimeIndex without a specified frequency.
-        Specifies the frequency of the index of your data. The string should
-        match a pandas offset alias:
-        https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
-    keep_original_columns :  boolean, optional, default=False
-        Keep original columns in X passed to `.transform()`.
-    positive_only :  boolean, optional, default=False
-        Clips negative values to zero when `to_numeric` is True.
-
-    Examples
-    --------
-    >>> from aeon.datasets import load_airline
-    >>> from aeon.transformations.time_since import TimeSince
-    >>> X = load_airline()
-
-        Create a single column with time elapsed since start date of time series.
-        The output is in units of integer number of months, same as the index `freq`.
-    >>> transformer = TimeSince()
-    >>> Xt = transformer.fit_transform(X)
-
-        Create multiple columns with different start times. The output is in units
-        of integer number of months, same as the index `freq`.
-    >>> transformer = TimeSince(["2000-01", "2000-02"])
-    >>> Xt = transformer.fit_transform(X)
-    """
-
-    _tags = {
-        # what is the abstract type of X: Series, or Panel
-        "input_data_type": "Series",
-        # what abstract type is returned: Primitives, Series, Panel
-        "output_data_type": "Series",
-        "instancewise": True,  # is this an instance-wise transform?
-        "transform_labels": "None",
-        "X_inner_type": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
-        "y_inner_type": "None",
-        "univariate-only": False,
-        "requires_y": False,
-        "remember_data": False,
-        "fit_is_empty": False,  # is fit empty and can be skipped?
-        "X-y-must-have-same-index": False,
-        "enforce_index_type": [pd.PeriodIndex, pd.DatetimeIndex],
-        "transform-returns-same-time-index": True,
-        "capability:inverse_transform": False,  # can the transformer inverse transform?
-        "skip-inverse-transform": True,  # is inverse-transform skipped when called?
-        "capability:unequal_length": True,
-        "capability:unequal_length:removes": False,
-        "capability:missing_values": True,  # can estimator handle missing data?
-        "capability:missing_values:removes": False,
-    }
-
-    def __init__(
-        self,
-        start: list[str | datetime.datetime | pd.Period | None] | None = None,
-        *,
-        to_numeric: bool | None = True,
-        freq: str | None = None,
-        keep_original_columns: bool | None = False,
-        positive_only: bool | None = False,
-    ):
-        self.start = start
-        self.to_numeric = to_numeric
-        self.freq = freq
-        self.keep_original_columns = keep_original_columns
-        self.positive_only = positive_only
-        super().__init__()
-
-    def _fit(self, X, y=None):
-        """Fit transformer to X and y.
-
-        private _fit containing the core logic, called from fit
-
-        Parameters
-        ----------
-        X: data structure of type X_inner_type
-            if X_inner_type is list, _fit must support all types in it
-            Data to fit transform to
-        y : data structure of type y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        self: reference to self
-        """
-        time_index = _get_time_index(X)
-
-        if is_any_real_numeric_dtype(time_index):
-            if self.freq:
-                warnings.warn(
-                    "Index is integer type. `freq` will be ignored.", stacklevel=2
-                )
-            self.freq_ = None
-        elif isinstance(time_index, (pd.DatetimeIndex, pd.PeriodIndex)):
-            # Chooses first non None value
-            self.freq_ = time_index.freqstr or self.freq or pd.infer_freq(time_index)
-            if self.freq_ is None:
-                raise ValueError("X has no known frequency and none is supplied")
-            if (
-                (self.freq_ == time_index.freqstr)
-                and (self.freq_ != self.freq)
-                and (self.freq)
-            ):
-                warnings.warn(
-                    f"Using frequency from index: {time_index.freq}, which "
-                    f"does not match the frequency given: {self.freq}.",
-                    stacklevel=2,
-                )
-        else:
-            raise ValueError("Index must be of type int, datetime, or period.")
-
-        self.start_ = []
-        if self.start is None:
-            self.start_.append(time_index.min())
-        else:
-            for start in self.start:
-                if start is None:
-                    self.start_.append(time_index.min())
-                elif isinstance(start, str):
-                    if isinstance(time_index, pd.PeriodIndex):
-                        self.start_.append(pd.Period(start))
-                    else:
-                        self.start_.append(pd.to_datetime(start))
-                else:
-                    self.start_.append(start)
-
-        # Check `start_` is compatible with index
-        for start_ in self.start_:
-            if isinstance(time_index, pd.DatetimeIndex) and not isinstance(
-                start_, datetime.datetime
-            ):
-                raise ValueError(
-                    f"start_={start_} incompatible with a "
-                    f"datetime index. Check that `start` is of type "
-                    f"datetime or a pd.Datetime parsable string."
-                )
-            elif isinstance(time_index, pd.PeriodIndex) and not isinstance(
-                start_, pd.Period
-            ):
-                raise ValueError(
-                    f"start_={start_} incompatible with a "
-                    f"Period index. Check that `start` is of type "
-                    f"pd.Period or a pd.Period parsable string."
-                )
-            elif is_any_real_numeric_dtype(time_index) and not isinstance(
-                start_, (int, np.integer)
-            ):
-                raise ValueError(
-                    f"start_={start_} incompatible with a numeric index."
-                    f"Check that `start` is an integer."
-                )
-
-        return self
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing core logic, called from transform
-
-        Parameters
-        ----------
-        X: data structure of type X_inner_type
-            if X_inner_type is list, _transform must support all types in it
-            Data to be transformed
-        y : data structure of type y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        transformed version of X
-        """
-        time_index = _get_time_index(X)
-
-        Xt = pd.DataFrame(index=X.index)
-        for start_ in self.start_:
-            if self.to_numeric:
-                if isinstance(time_index, pd.DatetimeIndex):
-                    # To support calculating integer time differences when `freq`
-                    # is not compatible with timedelta type (e.g., "M", "MS", "Y")
-                    # X.index and `start` are first converted to pandas Period.
-
-                    # Infer the freq needed to convert to period. We use the
-                    # `get_period_alas` helper method from Pandas. This method
-                    # maps from a frequency that is compatible with a datetime
-                    # index to one that is compatible with a period index
-                    # (e.g., "MS" -> "M"). We must strip the freq str of any
-                    # integer multiplier (e.g., "15T" -> "T"). This is needed so that
-                    # `get_period_alias` returns the correct result.
-                    # If `get_period_alias` recieves a freq str with a multiplier
-                    # (e.g., "15T") it returns `None` which causes errors downstream.
-                    freq_ = _remove_digits_from_str(self.freq_)
-                    freq_period = get_period_alias(freq_)
-
-                    # Convert `start` and `time_index` to period.
-                    # Casting `start_` to PeriodIndex using pd.period_range
-                    # here so we can later cast it to an int using
-                    # `astype(int)` in _get_period_diff_as_int().
-                    start_period = pd.period_range(
-                        start=start_, periods=1, freq=freq_period
-                    )
-                    time_index_period = time_index.to_period(freq=freq_period)
-                    # Compute time differences.
-                    time_deltas = _get_period_diff_as_int(
-                        time_index_period, start_period
-                    )
-
-                elif isinstance(time_index, pd.PeriodIndex):
-                    if pd.__version__ < "1.5.0":
-                        # Earlier versions of pandas returned incorrect result
-                        # when taking a difference between Periods when the frequency
-                        # is a multiple of a unit (e.g. "15T"). Solution is to
-                        # cast to lowest frequency when taking difference (e.g., "T").
-                        freq_ = _remove_digits_from_str(self.freq_)
-
-                        # Change freq of `start` and `time_index`.
-                        # Casting `start_` to PeriodIndex using pd.period_range
-                        # here so we can later cast it to an int using
-                        # `astype(int)` in _get_period_diff_as_int().
-                        start_period = pd.period_range(
-                            start=start_.to_timestamp(), periods=1, freq=freq_
-                        )
-                        time_index = time_index.to_timestamp().to_period(freq_)
-                    else:
-                        # Casting `start_` to PeriodIndex using pd.period_range
-                        # here so we can later cast it to an int using
-                        # `astype(int)` in _get_period_diff_as_int().
-                        start_period = pd.period_range(
-                            start=start_, periods=1, freq=self.freq_
-                        )
-
-                    # Compute time differences.
-                    time_deltas = _get_period_diff_as_int(time_index, start_period)
-
-                elif is_any_real_numeric_dtype(time_index):
-                    time_deltas = time_index - start_
-            else:
-                time_deltas = time_index - start_
-
-            col_name = f"time_since_{start_}"
-            Xt[col_name] = time_deltas
-
-        if self.to_numeric and self.positive_only:
-            Xt = Xt.clip(lower=0)
-
-        if self.keep_original_columns:
-            Xt = pd.concat([X, Xt], axis=1, copy=True)
-
-        return Xt
-
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-        return [
-            {"start": None, "to_numeric": True},
-            {
-                "start": ["2000-01-01", "2000-01-02"],
-                "to_numeric": False,
-                "positive_only": True,
-            },
-        ]
-
-
-def _get_period_diff_as_int(x: pd.PeriodIndex, y: pd.PeriodIndex) -> pd.Index:
-    return x.astype("int64") - y.astype("int64")
-
-
-def _remove_digits_from_str(x: str) -> str:
-    return x.translate({ord(k): None for k in digits})
-
-
-def _get_time_index(X: pd.DataFrame) -> pd.PeriodIndex | pd.DatetimeIndex:
-    """Get time index from single and multi-index dataframes."""
-    if isinstance(X.index, pd.MultiIndex):
-        time_index = X.index.get_level_values(-1)
-    else:
-        time_index = X.index
-    return time_index
diff --git a/docs/api_reference/transformations.rst b/docs/api_reference/transformations.rst
index 7cad4480..37d5ab37 100644
--- a/docs/api_reference/transformations.rst
+++ b/docs/api_reference/transformations.rst
@@ -218,14 +218,14 @@ Lagging
 
     Lag
 
-Element-wise transforms
+Series transforms
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 These transformations apply a function element-wise.
 
 Depending on the transformer, the transformation parameters can be fitted.
 
-.. currentmodule:: aeon.transformations.boxcox
+.. currentmodule:: aeon.transformations.series._boxcox
 
 .. autosummary::
     :toctree: auto_generated/
@@ -242,14 +242,6 @@ Depending on the transformer, the transformation parameters can be fitted.
 
     ScaledLogitTransformer
 
-.. currentmodule:: aeon.transformations.cos
-
-.. autosummary::
-    :toctree: auto_generated/
-    :template: class.rst
-
-    CosineTransformer
-
 .. currentmodule:: aeon.transformations.exponent
 
 .. autosummary::
@@ -257,7 +249,6 @@ Depending on the transformer, the transformation parameters can be fitted.
     :template: class.rst
 
     ExponentTransformer
-    SqrtTransformer
 
 Detrending
 ~~~~~~~~~~
@@ -273,7 +264,7 @@ Detrending
     ConditionalDeseasonalizer
     STLTransformer
 
-.. currentmodule:: aeon.transformations.clear_sky
+.. currentmodule:: aeon.transformations.series._clear_sky
 
 .. autosummary::
     :toctree: auto_generated/
@@ -293,14 +284,6 @@ Filtering and denoising
 
     BKFilter
 
-.. currentmodule:: aeon.transformations.kalman_filter
-
-.. autosummary::
-    :toctree: auto_generated/
-    :template: class.rst
-
-    KalmanFilterTransformer
-
 .. currentmodule:: aeon.transformations.theta
 
 .. autosummary::
@@ -320,16 +303,8 @@ Slope
 
     SlopeTransformer
 
-Binning and segmentation
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. currentmodule:: aeon.transformations.binning
-
-.. autosummary::
-    :toctree: auto_generated/
-    :template: class.rst
-
-    TimeBinAggregate
+Segmentation
+~~~~~~~~~~~~
 
 .. currentmodule:: aeon.transformations.collection.interpolate
 
@@ -371,14 +346,6 @@ Seasonality and Date-Time Features
 
     DateTimeFeatures
 
-.. currentmodule:: aeon.transformations.time_since
-
-.. autosummary::
-    :toctree: auto_generated/
-    :template: class.rst
-
-    TimeSince
-
 .. currentmodule:: aeon.transformations.fourier
 
 .. autosummary::
@@ -425,20 +392,6 @@ These transformers convert multivariate series to univariate.
 
     ColumnConcatenator
 
-Augmentation
-~~~~~~~~~~~~
-
-.. currentmodule:: aeon.transformations.augmenter
-
-.. autosummary::
-    :toctree: auto_generated/
-    :template: class.rst
-
-    InvertAugmenter
-    RandomSamplesAugmenter
-    ReverseAugmenter
-    WhiteNoiseAugmenter
-
 FeatureSelection
 ~~~~~~~~~~~~~~~~
 
@@ -507,7 +460,7 @@ These transformations ensure all series in a panel have equal length
 Dimension reduction
 ~~~~~~~~~~~~~~~~~~~
 
-.. currentmodule:: aeon.transformations.pca
+.. currentmodule:: aeon.transformations.series._pca
 
 .. autosummary::
     :toctree: auto_generated/
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 167acfdf..630f9cc3 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -398,7 +398,7 @@ yourself, you will need to `pip install statsmodels` and `pip install pmdarima`)
 ```{code-block} python
 >>> import numpy as np
 >>> from aeon.datasets import load_airline
->>> from aeon.transformations.boxcox import BoxCoxTransformer
+>>> from aeon.transformations.series._boxcox import BoxCoxTransformer
 >>> from aeon.transformations.detrend import Deseasonalizer
 >>> from aeon.forecasting.arima import ARIMA
 >>> from aeon.forecasting.compose import TransformedTargetForecaster
@@ -494,7 +494,7 @@ to ARIMA order values for the forecasting pipeline we created in the previous ex
 ...     ForecastingGridSearchCV,
 ... )
 >>> from aeon.forecasting.arima import ARIMA
->>> from aeon.transformations.boxcox import BoxCoxTransformer
+>>> from aeon.transformations.series._boxcox import BoxCoxTransformer
 >>> from aeon.transformations.detrend import Deseasonalizer
 ...
 >>> y = load_airline()
diff --git a/examples/transformations/transformations.ipynb b/examples/transformations/transformations.ipynb
index 2e62aa3e..2aa195bc 100644
--- a/examples/transformations/transformations.ipynb
+++ b/examples/transformations/transformations.ipynb
@@ -90,7 +90,7 @@
    ],
    "source": [
     "from aeon.datasets import load_airline\n",
-    "from aeon.transformations.boxcox import BoxCoxTransformer\n",
+    "from aeon.transformations._legacy._boxcox import _BoxCoxTransformer as BoxCoxTransformer\n",
     "from aeon.transformations.summarize import SummaryTransformer\n",
     "from aeon.visualisation import plot_series\n",
     "\n",
diff --git a/extension_templates/classification.py b/extension_templates/classification.py
deleted file mode 100644
index e94a2a41..00000000
--- a/extension_templates/classification.py
+++ /dev/null
@@ -1,256 +0,0 @@
-"""
-Extension template for time series classifiers.
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template. This is NOT a
-    concrete class to import nor is it a base class to inherit from. This is to be used
-    as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory abstract methods, and optionally for optional methods
-- do not write to reserved variables: is_fitted, _is_fitted, classes_,
-    n_classes_, fit_time_, _class_dictionary, _n_jobs, _tags, _tags_dynamic
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- once complete: use as a local library, or contribute to aeon via PR
-
-Mandatory components:
-    fitting                 - _fit(self, X, y)
-    predicting classes      - _predict(self, X)
-
-Optional components:
-    data conversion and capabilities tags - _tags
-    fitted parameter inspection           - _get_fitted_params()
-    predicting class probabilities        - _predict_proba(self, X)
-
-Testing - implement if aeon classifier (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-
-"""
-
-import numpy as np
-
-from aeon.classification.base import BaseClassifier
-
-# todo: add any necessary imports here
-
-
-class MyTimeSeriesClassifier(BaseClassifier):
-    """Custom time series classifier. todo: write docstring.
-
-    todo: describe your custom time series classifier here
-
-    Parameters
-    ----------
-    param_a : int
-        descriptive explanation of param_a.
-    param_b : string, default='default'
-        descriptive explanation of param_b.
-    param_c : boolean, default= True
-        descriptive explanation of param_c.
-
-    Attributes
-    ----------
-    att_a_ : int
-        attributes are not passed to the constructor. If set in fit use following
-        underscore.
-
-    See Also
-    --------
-    Optional, related classes
-
-    Notes
-    -----
-    Optional, Anything you want
-
-    References
-    ----------
-    Optional, details of paper where algorithm described
-
-
-    Examples
-    --------
-    todo: Add a simple use case example here
-    """
-
-    # todo: if any imports are aeon soft dependencies:
-    #  * make sure to fill in the "python_dependencies" tag with the package import name
-
-    # optional todo: override base class estimator default tags here if necessary
-    # these are the default values, only add if different to these.
-    _tags = {
-        "X_inner_type": "numpy3D",  # which type do _fit/_predict accept, usually
-        # this is usually "numpy3D" for equal length time series, np-list for unequal
-        # length time series, see datatypes/panel/_registry.py for options.
-        "capability:multivariate": False,
-        "capability:unequal_length": False,
-        "capability:missing_values": False,
-        "capability:train_estimate": False,
-        "capability:contractable": False,
-        "capability:multithreading": False,
-        "python_version": None,  # PEP 440 python version specifier to limit versions
-    }
-
-    # todo: add any parameters to constructor
-    def __init__(self, param_a, param_b="default", param_c=None):
-        # Note that parameters passed and set in constructor should not be changed
-        # anywhere else. This is in order to comply with scikit learn structure.
-        # Instead, copy into local variables in fit and predict and use these.
-
-        # todo: copy parameters to self, use same names
-        self.param_a = param_a
-        self.param_b = param_b
-        self.param_c = param_c
-
-        # todo: change "MyTimeSeriesClassifier" to the name of the class
-        super().__init__()
-
-    # todo: implement this abstract function
-    def _fit(self, X, y):
-        """Fit time series classifier to training data.
-
-        private _fit containing the core logic, called from fit
-
-        Writes to self:
-            Sets fitted model attributes ending in "_".
-
-        Parameters
-        ----------
-        X : guaranteed to be of a type in self.get_tag("X_inner_type")
-            if self.get_tag("X_inner_type") = "numpy3D":
-                3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-        y : 1D np.array of int, of shape [n_cases] - class labels for fitting
-            indices correspond to instance indices in X
-
-        Returns
-        -------
-        self : Reference to self.
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X, y, if changing X and y make local copies.
-
-    # todo: implement this abstract function
-    def _predict(self, X) -> np.ndarray:
-        """Predict labels for sequences in X.
-
-        private _predict containing the core logic, called from predict
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-
-        Parameters
-        ----------
-        X : guaranteed to be of a type in self.get_tag("X_inner_type")
-            if self.get_tag("X_inner_type") = "numpy3D":
-                3D np.ndarray of shape = (n_cases, n_channels, n_timepoints)
-            if self.get_tag("X_inner_type") = "np-list":
-                list of 2D np.ndarray of shape = (n_cases,)
-
-        Returns
-        -------
-        y : 1D np.array of int, of shape [n_cases] - predicted class labels
-            indices correspond to instance indices in X
-        """
-
-    # todo: consider implementing this, optional
-    # if you do not implement it, then the default _predict_proba will be called.
-    # the default simply calls predict and sets probas to 0 or 1.
-
-    def _predict_proba(self, X) -> np.ndarray:
-        """Predicts labels probabilities for sequences in X.
-
-        private _predict_proba containing the core logic, called from predict_proba
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-
-        Parameters
-        ----------
-        X : guaranteed to be of a type in self.get_tag("X_inner_type")
-            if self.get_tag("X_inner_type") = "numpy3D":
-                3D np.ndarray of shape = (n_cases, n_channels, n_timepoints)
-            if self.get_tag("X_inner_type") = "np-list":
-                list of 2D np.ndarray of shape = (n_cases,)
-
-        Returns
-        -------
-        y : 2D array of shape [n_cases, n_classes] - predicted class probabilities
-            1st dimension indices correspond to instance indices in X
-            2nd dimension indices correspond to possible labels (integers)
-            (i, j)-th entry is predictive probability that i-th instance is of class j
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X
-
-    # todo: consider implementing this, optional
-    # implement only if different from default:
-    #   default retrieves all self attributes ending in "_"
-    #   and returns them with keys that have the "_" removed
-    # if not implementing, delete the method
-    #   avoid overriding get_fitted_params
-    def _get_fitted_params(self):
-        """Get fitted parameters.
-
-        private _get_fitted_params, called from get_fitted_params
-
-        State required:
-            Requires state to be "fitted".
-
-        Returns
-        -------
-        fitted_params : dict with str keys
-            fitted parameters, keyed by names of fitted parameter
-        """
-        # implement here
-        #
-        # when this function is reached, it is already guaranteed that self is fitted
-        #   this does not need to be checked separately
-        #
-        # parameters of components should follow the sklearn convention:
-        #   separate component name from parameter name by double-underscore
-        #   e.g., componentname__paramname
-
-    # todo: return default parameters, so that a test instance can be created
-    #   required for automated unit and integration testing of estimator
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            Reserved values for classifiers:
-                "results_comparison" - used in some classifiers to compare against
-                    previously generated results where the default set of parameters
-                    cannot produce suitable probability estimates
-                "contracting" - used in classifiers that set the
-                    "capability:contractable" tag to True to test contacting
-                    functionality
-                "train_estimate" - used in some classifiers that set the
-                    "capability:train_estimate" tag to True to allow for more efficient
-                    testing when relevant parameters are available
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries
-        return {"param_a": 42, "param_c": False}
diff --git a/extension_templates/clustering.py b/extension_templates/clustering.py
deleted file mode 100644
index d6197ef0..00000000
--- a/extension_templates/clustering.py
+++ /dev/null
@@ -1,229 +0,0 @@
-"""
-Extension template for clusterers.
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template
-    NOT a concrete class to import! This is NOT a base class or concrete class!
-    This is to be used as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory methods, and optionally for optional methods
-- do not write to reserved variables: is_fitted, _is_fitted, fit_time_,
-        _class_dictionary, _n_jobs, n_clusters, _tags, _tags_dynamic
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- ensure interface compatibility by testing clustering/tests
-- once complete: use as a local library, or contribute to aeon via PR
-- more details:
-    https://www.aeon-toolkit.org/en/stable/developer_guide/add_estimators.html
-
-Mandatory implements:
-    fitting            - _fit(self, X)
-
-Optional implements:
-    cluster assignment          -  _predict(self, X)
-    fitted parameter inspection -  _get_fitted_params()
-
-Testing - implement if aeon forecaster (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-"""
-
-import numpy as np
-
-from aeon.clustering import BaseClusterer
-
-# todo: add any necessary imports here
-
-
-class MyClusterer(BaseClusterer):
-    """Custom clusterer. todo: write docstring.
-
-    todo: describe your custom clusterer here
-
-    Hyper-parameters
-    ----------------
-    parama : int
-        descriptive explanation of parama
-    paramb : string, default='default'
-        descriptive explanation of paramb
-    paramc : boolean, default= whether paramb is not the default
-        descriptive explanation of paramc
-    and so on
-
-    Components
-    ----------
-    est : aeon.estimator, BaseEstimator descendant
-        descriptive explanation of est
-    est2: another estimator
-        descriptive explanation of est2
-    and so on
-    """
-
-    # optional todo: override base class estimator default tags here if necessary
-    # these are the default values, only add if different to these.
-    _tags = {
-        "X_inner_type": "numpy3D",  # which type do _fit/_predict accept, Other
-        # types are allowable, see datatypes/panel/_registry.py for options.
-        "capability:multivariate": False,
-        "capability:unequal_length": False,
-        "capability:missing_values": False,
-        "capability:multithreading": False,
-    }
-
-    # todo: add any hyper-parameters and components to constructor
-    def __init__(self, est, parama, est2=None, paramb="default", paramc=None):
-        # estimators should precede parameters
-        #  if estimators have default values, set None and initalize below
-
-        # todo: write any hyper-parameters and components to self
-        self.est = est
-        self.parama = parama
-        self.paramb = paramb
-        self.paramc = paramc
-
-        # todo: change "MyClusterer" to the name of the class
-        super().__init__()
-
-        # todo: optional, parameter checking logic (if applicable) should happen here
-        # if writes derived values to self, should *not* overwrite self.parama etc
-        # instead, write to self._parama, self._newparam (starting with _)
-
-        # todo: default estimators should have None arg defaults
-        #  and be initialized here
-        #  do this only with default estimators, not with parameters
-        # if est2 is None:
-        #     self.estimator = MyDefaultEstimator()
-
-    # todo: implement this abstract class, mandatory
-    def _fit(self, X):
-        """Fit time series clusterer to training data.
-
-        Parameters
-        ----------
-        X : Data to cluster, of type self.get_tag("X_inner_type")
-
-        Returns
-        -------
-        self:
-            Fitted estimator.
-        """
-        # implement here
-        # IMPORTANT: avoid side effects to X
-
-    # todo: implement this, mandatory
-    # at least one of _predict and _get_fitted_params should be implemented
-    def _predict(self, X) -> np.ndarray:
-        """Predict the closest cluster each sample in X belongs to.
-
-        Parameters
-        ----------
-        X : data to cluster based on model formed in _fit, of type self.get_tag(
-        "X_inner_type")
-        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.
-        """
-        # implement here
-        # IMPORTANT: avoid side effects to X
-
-    # todo: consider implementing this, optional
-    # implement only if different from default:
-    #   default retrieves all self attributes ending in "_"
-    #   and returns them with keys that have the "_" removed
-    # if not implementing, delete the method
-    #   avoid overriding get_fitted_params
-    # this is typically important for clustering
-    #   at least one of _predict and _get_fitted_params should be functional
-    def _get_fitted_params(self):
-        """Get fitted parameters.
-
-        private _get_fitted_params, called from get_fitted_params
-
-        State required:
-            Requires state to be "fitted".
-
-        Returns
-        -------
-        fitted_params : dict with str keys
-            fitted parameters, keyed by names of fitted parameter
-        """
-        # implement here
-        #
-        # when this function is reached, it is already guaranteed that self is fitted
-        #   this does not need to be checked separately
-        #
-        # parameters of components should follow the sklearn convention:
-        #   separate component name from parameter name by double-underscore
-        #   e.g., componentname__paramname
-
-    # todo: return default parameters, so that a test instance can be created
-    #   required for automated unit and integration testing of estimator
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for clusterers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries
-        # Testing parameter choice should cover internal cases well.
-        #
-        # this method can, if required, use:
-        #   class properties (e.g., inherited); parent class test case
-        #   imported objects such as estimators from aeon or sklearn
-        # important: all such imports should be *inside get_test_params*, not at the top
-        #            since imports are used only at testing time
-        #
-        # The parameter_set argument is not used for automated, module level tests.
-        #   It can be used in custom, estimator specific tests, for "special" settings.
-        # A parameter dictionary must be returned *for all values* of parameter_set,
-        #   i.e., "parameter_set not available" errors should never be raised.
-        #
-        # A good parameter set should primarily satisfy two criteria,
-        #   1. Chosen set of parameters should have a low testing time,
-        #      ideally in the magnitude of few seconds for the entire test suite.
-        #       This is vital for the cases where default values result in
-        #       "big" models which not only increases test time but also
-        #       run into the risk of test workers crashing.
-        #   2. There should be a minimum two such parameter sets with different
-        #      sets of values to ensure a wide range of code coverage is provided.
-        #
-        # example 1: specify params as dictionary
-        # any number of params can be specified
-        # params = {"est": value0, "parama": value1, "paramb": value2}
-        #
-        # example 2: specify params as list of dictionary
-        # note: Only first dictionary will be used by create_test_instance
-        # params = [{"est": value1, "parama": value2},
-        #           {"est": value3, "parama": value4}]
-        # return params
-        #
-        # example 3: parameter set depending on param_set value
-        #   note: only needed if a separate parameter set is needed in tests
-        # if parameter_set == "special_param_set":
-        #     params = {"est": value1, "parama": value2}
-        #     return params
-        #
-        # # "default" params - always returned except for "special_param_set" value
-        # params = {"est": value3, "parama": value4}
-        # return params
diff --git a/extension_templates/distance.py b/extension_templates/distance.py
deleted file mode 100644
index a5fe7be1..00000000
--- a/extension_templates/distance.py
+++ /dev/null
@@ -1,221 +0,0 @@
-"""Extension template for time series distance function.
-
-This is a quick guide on how to implement a new aeon distance function. A wish list
-for distance functions we would like to see is here
-https://github.com/aeon-toolkit/aeon/issues?q=is%3Aopen+is%3Aissue+label%3Adistances+
-
-You need to implement three public functions for a distance called "foo"
-
-TODO 1: foo_distance: distance between two series, returns a float
-
-Optional:
-TODO 2: foo_pairwise_distance: distance between a collection of series, returns
-a matrix
-TODO 3: foo_cost_matrix: cost matrix produced to compute distance between
-series.
-TODO 4: foo_alignment_path: path constructed to find distance, returns a list
-of (x, y) pairs
-
-Note many elastic distances calculate a cost
-matrix. Look at any of the distances to see how we structure this, but
-ultimately it is your choice how to internally design the calculations. Please
-use numba where ever possible.
-
-To contribute the distance to aeon, you need to adjust the file
-aeon/distance/_distance.py to make sure it is tested and available via the function
-distance. There are three things to do
-
-TODO 5: Add your distance to the DISTANCES list in aeon/distance/_distance.py
-At the bottom of the file there is a list containing all their distances and
-functions. The only required two keys are "name" and "distance". The others are optional
-DISTANCES = [
-    {
-        "name": "foo",
-        "distance": foo_distance,
-    },
-If you add the optional functions, you can add them too
-DISTANCES = [
-    {
-        "name": "foo",
-        "distance": foo_distance,
-        "pairwise_distance": foo_pairwise_distance,
-        "cost_matrix": foo_cost_matrix,
-        "alignment_path": foo_alignment_path,
-    },
-
-TODO 6: add your distance function to the utility functions in
-aeon/distances/_distance.py
-There are multiple utility functions in _distance.py that allow users to import a single
-function and call the distance via a string e.g. distance(x, y, metric="foo").
-For example to add your distance to the distance utility function:
-    def distance(
-        x: np.ndarray,
-        y: np.ndarray,
-        metric: Union[str, DistanceFunction],
-        **kwargs: Any,
-    ) -> float:
-add an if clause here returning a distance with relevant parameters (necessary because
-numba cant handle kwargs)
-    elif metric == "foo":
-        return foo_distance(
-            x,
-            y,
-            kwargs.get("para1"),
-            kwargs.get("para2",3),
-        )
-"""
-
-import numpy as np
-from numba import njit
-
-# Basic examples are given below but we highly recommend you look at the other
-# distances in aeon/distances/ to see how they are implemented. In particular, look at
-# aeon/distances/_euclidean.py for the basic structure and for more complex distances
-# that have a alignment path or a cost matrix. look at aeon/distances/_dtw.py
-
-
-# Simple example of implementing a distance function:
-
-
-# TODO 1: distance function
-# Give function a sensible name
-# The decorator means it will be JIT compiled and be much much faster (but it does not
-# have to be numba compiled)!
-# distance functions should accept both 1D and 2D arrrays (1D are univariate time
-# series shape (n_timepoints), 2D are multivariate (n_channels, n_timepoints). The
-# n_channels should match for both series, but distance functions should be able to
-# handle unequal length series. See aeon/distances/_euclidean.py for an example of how
-# to handle this.
-@njit(cache=True, fastmath=True)
-def foo_distance(
-    x: np.ndarray, y: np.ndarray, para1: float = None, para2: int = 3
-) -> float:
-    r"""Compute the my_distance between two time series.
-
-    # give docstring formal definition of foo distance. We use a meaningless
-    definition as an example
-
-    .. math::
-        my_distance(x, y) = para1*(x[0] - y[0])+para2
-
-    Parameters
-    ----------
-    x: np.ndarray, of shape (n_channels, n_timepoints) or (n_timepoints,)
-        First time series.
-    y: np.ndarray, of shape (m_channels, m_timepoints) or (m_timepoints,)
-        Second time series.
-    para1 : float, default = 0.1
-        Parameter for distance, usually None, a float or int.
-    para2 : int, default = 3
-        Parameter for distance, usually a float or int.
-
-    Returns
-    -------
-    float
-        My distance between x and y.
-
-    Raises
-    ------
-    ValueError
-        If x and y are not 1D or 2D arrays.
-
-    TODO give example output
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> x = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]])
-    >>> y = np.array([[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]])
-    >>> foo_distance(x, y, para1)
-    -11
-    """
-    # Note our distances are configured to work with either 1D or 2D
-    # TODO implement logic for 1D series
-    if x.ndim == 1 and y.ndim == 1:
-        return para1 * (x[0] - y[0]) + para2
-
-    # TODO implement logic for 2D series
-    if x.ndim == 2 and y.ndim == 2:
-        sum = 0
-        for i in range(x.shape[0]):
-            sum = sum + x[i][0] - y[i][0]
-        return para1 * sum + para2
-    raise ValueError("x and y must be 1D or 2D and of the same dinension")
-
-
-# TODO 2: Implement pairwise distances
-# If you dont implement this, then a general version will be used. If you could speed
-# the pairwise calculations up with numba, please do implement it.
-@njit(cache=True, fastmath=True)
-def foo_pairwise_distance(
-    X: np.ndarray, y: np.ndarray = None, para1: float = None, para2: int = None
-) -> np.ndarray:
-    """Compute the pairwise my distance between a collection of time series.
-
-    # Pairwise distance functions return a matrix of distances between a single
-    # collection of time series, two collections of time series or a collection of time
-    # series and a single series.
-
-    Parameters
-    ----------
-    X: np.ndarray, of shape (n_cases, n_channels, n_timepoints) or
-            (n_cases, n_timepoints)
-        A collection of time series instances.
-    # For X 3D np.ndarray as a collection of 2D series and 2D np.ndarray are treated
-    as a collection of 1D series.
-    y: np.ndarray, of shape (m_cases, m_channels, m_timepoints) or
-            (m_cases, m_timepoints) or (m_timepoints,), default=None
-        A collection of time series instances.
-    # If y is passed, its structure is inferred from X. If X is 3D, y must be either
-    3D (a collection of series) or 2D (a single series). If X is 2D, y must
-    be either 2d (a collection of univariate series) or 1D (a single univariate
-    series).
-    para1: float, default=None
-        The window to use for the bounding matrix. If None, no bounding matrix
-        is used.
-
-    Returns
-    -------
-    np.ndarray (n_cases, n_cases)
-        pairwise matrix of foo_distances between the instances of X.
-
-    Raises
-    ------
-    ValueError
-        If X is not 2D or 3D array when only passing X.
-        If X and y are not 1D, 2D or 3D arrays when passing both X and y.
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> # Distance between each time series in a collection of time series
-    >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]])
-    >>> my_pairwise_distance(X)
-    array([[  0.,  26., 108.],
-           [ 26.,   0.,  26.],
-           [108.,  26.,   0.]])
-
-    >>> # Distance between two collections of time series
-    >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]])
-    >>> y = np.array([[[11, 12, 13]],[[14, 15, 16]], [[17, 18, 19]]])
-    >>> dtw_pairwise_distance(X, y)
-    array([[300., 507., 768.],
-           [147., 300., 507.],
-           [ 48., 147., 300.]])
-
-    >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]])
-    >>> y_univariate = np.array([[11, 12, 13],[14, 15, 16], [17, 18, 19]])
-    """
-    from aeon.distances._utils import reshape_pairwise_to_multiple
-
-    if y is None:
-        # To self
-        if X.ndim == 2:
-            X = X.reshape((X.shape[0], 1, X.shape[1]))
-        if X.ndim != 3:
-            raise ValueError("x and y must be 1D, 2D, or 3D arrays")
-    else:
-        X, y = reshape_pairwise_to_multiple(X, y)
-    # NOW FIND AND RETURN PAIRWISE DISTANCE. If y is none, only calculate one
-    # diagonal and copy the other
-    return None
diff --git a/extension_templates/early_classification.py b/extension_templates/early_classification.py
deleted file mode 100644
index 107dec30..00000000
--- a/extension_templates/early_classification.py
+++ /dev/null
@@ -1,398 +0,0 @@
-"""
-Extension template for early time series classifiers.
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template
-    NOT a concrete class to import! This is NOT a base class or concrete class!
-    This is to be used as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory methods, and optionally for optional methods
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- ensure interface compatibility by aeon.testing.estimator_checks.check_estimator
-- once complete: use as a local library, or contribute to aeon via PR
-- more details:
-    https://www.aeon-toolkit.org/en/stable/developer_guide/add_estimators.html
-
-Mandatory implements:
-    fitting                 - _fit(self, X, y)
-    predicting classes      - _predict(self, X)
-    updating predictions    - _update_predict(self, X)
-    performance metrics     - _score(X, y)
-
-Optional implements:
-    data conversion and capabilities tags - _tags
-    fitted parameter inspection           - _get_fitted_params()
-    predicting class probabilities        - _predict_proba(self, X)
-    updating probability predictions      - _update_predict_proba(self, X)
-
-Testing - implement if aeon early classifier (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-"""
-
-from typing import Tuple
-
-import numpy as np
-
-from aeon.classification.early_classification import BaseEarlyClassifier
-
-# todo: add any necessary imports here
-
-
-class MyEarlyTimeSeriesClassifier(BaseEarlyClassifier):
-    """Custom early time series classifier. todo: write docstring.
-
-    todo: describe your custom early time series classifier here
-
-    Hyper-parameters
-    ----------------
-    parama : int
-        descriptive explanation of parama
-    paramb : string, default='default'
-        descriptive explanation of paramb
-    paramc : boolean, default= whether paramb is not the default
-        descriptive explanation of paramc
-    and so on
-
-    Components
-    ----------
-    est : aeon.estimator, BaseEstimator descendant
-        descriptive explanation of est
-    est2: another estimator
-        descriptive explanation of est2
-    and so on
-    """
-
-    # optional todo: override base class estimator default tags here if necessary
-    # these are the default values, only add if different to these.
-    _tags = {
-        "X_inner_type": "numpy3D",  # which type do _fit/_predict accept, usually
-        # this is either "numpy3D". Other
-        # types are allowable, see datatypes/panel/_registry.py for options.
-        "capability:multivariate": False,
-        "capability:unequal_length": False,
-        "capability:missing_values": False,
-        "capability:train_estimate": False,
-        "capability:contractable": False,
-        "capability:multithreading": False,
-    }
-
-    # todo: add any hyper-parameters and components to constructor
-    def __init__(self, est, parama, est2=None, paramb="default", paramc=None):
-        # estimators should precede parameters
-        #  if estimators have default values, set None and initalize below
-
-        # todo: write any hyper-parameters and components to self
-        self.est = est
-        self.parama = parama
-        self.paramb = paramb
-        self.paramc = paramc
-
-        # todo: change "MyEarlyTimeSeriesClassifier" to the name of the class
-        super().__init__()
-
-        # todo: optional, parameter checking logic (if applicable) should happen here
-        # if writes derived values to self, should *not* overwrite self.parama etc
-        # instead, write to self._parama, self._newparam (starting with _)
-
-        # todo: default estimators should have None arg defaults
-        #  and be initialized here
-        #  do this only with default estimators, not with parameters
-        # if est2 is None:
-        #     self.estimator = MyDefaultEstimator()
-
-        # todo: if tags of estimator depend on component tags, set these here
-        #  only needed if estimator is a composite
-        #  tags set in the constructor apply to the object and override the class
-        #
-        # example 1: conditional setting of a tag
-        # if est.foo == 42:
-        #   self.set_tags(capability:missing_values=True)
-        # example 2: cloning tags from component
-        #   self.clone_tags(est2, ["enforce_index_type", "capability:missing_values"])
-
-    # todo: implement this, mandatory
-    def _fit(self, X, y):
-        """Fit early time series classifier to training data.
-
-        core logic
-
-        Parameters
-        ----------
-        X : Training data of type self.get_tag("X_inner_type")
-        y : array-like, shape = [n_cases] - the class labels
-
-        Returns
-        -------
-        self : reference to self.
-
-        State change
-        ------------
-        creates fitted model (attributes ending in "_")
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # Note: when interfacing a model that has fit, with parameters
-        #   that are not data (X, y) or data-like,
-        #   but model parameters, *don't* add as arguments to fit, but treat as follows:
-        #   1. pass to constructor,  2. write to self in constructor,
-        #   3. read from self in _fit,  4. pass to interfaced_model.fit in _fit
-
-    # todo: implement this, mandatory
-    def _predict(self, X) -> Tuple[np.ndarray, np.ndarray]:
-        """Predict labels for sequences in X.
-
-        core logic
-
-        This method should update state_info with any values necessary to make future
-        decisions. It is recommended that the previous time stamp used for each case
-        should be stored in the state_info. The number of rows in state_info after the
-        method has been called should match the number of input rows.
-
-        Parameters
-        ----------
-        X : data not used in training, of type self.get_tag("X_inner_type")
-
-        Returns
-        -------
-        y : predictions of labels for X, np.ndarray
-        decisions : decisions on whether the prediction is sage, np.ndarray
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X
-
-        # At the end of the method, state_info should be updated to reflect the current
-        # state in the early classifiers decision-making process on the safety of
-        # predictions for cases in X.
-        # i.e. the number of consecutive 'safe' decisions required to return a final
-        # decision to use the returned predictions.
-
-    # todo: implement this, mandatory
-    def _update_predict(self, X) -> Tuple[np.ndarray, np.ndarray]:
-        """Update labels for sequences in X using a larger series length.
-
-        core logic
-
-        Uses information from previous decisions stored in state_info. This method
-        should update state_info with any values necessary to make future decisions.
-        It is recommended that the previous time stamp used for each case should be
-        stored in the state_info. The number of rows in state_info after the method has
-        been called should match the number of input rows.
-
-        Parameters
-        ----------
-        X : data not used in training, of type self.get_tag("X_inner_type")
-
-        Returns
-        -------
-        y : predictions of labels for X, np.ndarray
-        decisions : decisions on whether the prediction is sage, np.ndarray
-        """
-
-        # implement here
-        # IMPORTANT: update the number of rows in state_info to math the cases in
-        #            X at the beginning of the method.
-        # IMPORTANT: avoid side effects to X
-
-        # At the end of the method, state_info should be updated to reflect the current
-        # state in the early classifiers decision-making process on the safety of
-        # predictions for cases in X.
-        # i.e. the number of consecutive 'safe' decisions required to return a final
-        # decision to use the returned predictions.
-
-    # todo: consider implementing this, optional
-    # if you do not implement it, then the default _predict_proba will be  called.
-    # the default simply calls predict and sets probas to 0 or 1.
-    def _predict_proba(self, X) -> Tuple[np.ndarray, np.ndarray]:
-        """Predicts labels probabilities for sequences in X.
-
-        This method should update state_info with any values necessary to make future
-        decisions. It is recommended that the previous time stamp used for each case
-        should be stored in the state_info. The number of rows in state_info after the
-        method has been called should match the number of input rows.
-
-        Default behaviour is to call _predict and set the predicted class probability
-        to 1, other class probabilities to 0. Override if better estimates are
-        obtainable.
-
-        Parameters
-        ----------
-        X : data to predict y with, of type self.get_tag("X_inner_type")
-
-        Returns
-        -------
-        y : predictions of probabilities for class values of X, np.ndarray
-        decisions : decisions on whether the prediction is sage, np.ndarray
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X
-
-        # At the end of the method, state_info should be updated to reflect the current
-        # state in the early classifiers decision-making process on the safety of
-        # predictions for cases in X.
-        # i.e. the number of consecutive 'safe' decisions required to return a final
-        # decision to use the returned predictions.
-
-    # todo: consider implementing this, optional
-    # if you do not implement it, then the default _update_predict_proba will be called.
-    # the default simply calls predict and sets probas to 0 or 1.
-    def _update_predict_proba(self, X) -> Tuple[np.ndarray, np.ndarray]:
-        """Update labels probabilities for sequences in X using a larger series length.
-
-        Uses information from previous decisions stored in state_info. This method
-        should update state_info with any values necessary to make future decisions.
-        It is recommended that the previous time stamp used for each case should be
-        stored in the state_info. The number of rows in state_info after the method has
-        been called should match the number of input rows.
-
-        Default behaviour is to call _predict and set the predicted class probability
-        to 1, other class probabilities to 0. Override if better estimates are
-        obtainable.
-
-        Parameters
-        ----------
-        X : data to predict y with, of type self.get_tag("X_inner_type")
-
-        Returns
-        -------
-        y : predictions of probabilities for class values of X, np.ndarray
-        decisions : decisions on whether the prediction is sage, np.ndarray
-        """
-
-        # implement here
-        # IMPORTANT: update the number of rows in state_info to match the cases in
-        #            X at the beginning of the method.
-        # IMPORTANT: avoid side effects to X
-
-        # At the end of the method, state_info should be updated to reflect the current
-        # state in the early classifiers decision-making process on the safety of
-        # predictions for cases in X.
-        # i.e. the number of consecutive 'safe' decisions required to return a final
-        # decision to use the returned predictions.
-
-    # todo: implement this, mandatory
-    def _score(self, X, y) -> Tuple[float, float, float]:
-        """Scores predicted labels against ground truth labels on X.
-
-        Parameters
-        ----------
-        X : data not used in training, of type self.get_tag("X_inner_type")
-        y : array-like, shape = [n_cases] - the class labels
-
-        Returns
-        -------
-        accuracy: the accuracy of the predictions at the series length when a decision
-                  is made.
-        earliness: how much of the series length was required to make a prediction as a
-                   proportion of the full series length.
-        harmonic mean: score balancing accuracy and earliness.
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X, y
-
-        # HM: (2 * accuracy * (1 - earliness)) / (accuracy + (1 - earliness))
-
-    # todo: consider implementing this, optional
-    # implement only if different from default:
-    #   default retrieves all self attributes ending in "_"
-    #   and returns them with keys that have the "_" removed
-    # if not implementing, delete the method
-    #   avoid overriding get_fitted_params
-    def _get_fitted_params(self):
-        """Get fitted parameters.
-
-        private _get_fitted_params, called from get_fitted_params
-
-        State required:
-            Requires state to be "fitted".
-
-        Returns
-        -------
-        fitted_params : dict with str keys
-            fitted parameters, keyed by names of fitted parameter
-        """
-        # implement here
-        #
-        # when this function is reached, it is already guaranteed that self is fitted
-        #   this does not need to be checked separately
-        #
-        # parameters of components should follow the sklearn convention:
-        #   separate component name from parameter name by double-underscore
-        #   e.g., componentname__paramname
-
-    # todo: return default parameters, so that a test instance can be created
-    #   required for automated unit and integration testing of estimator
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            Reserved values for classifiers:
-                "results_comparison" - used for identity testing in some classifiers
-                    should contain parameter settings comparable to "TSC bakeoff"
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries
-        #
-        # this can, if required, use:
-        #   class properties (e.g., inherited); parent class test case
-        #   imported objects such as estimators from aeon or sklearn
-        # important: all such imports should be *inside get_test_params*, not at the top
-        #            since imports are used only at testing time
-        #
-        # The parameter_set argument is not used for most automated, module level tests.
-        #   It can be used in custom, estimator specific tests, for "special" settings.
-        #   For classification, this is also used in tests for reference settings,
-        #       such as published in benchmarking studies, or for identity testing.
-        # A parameter dictionary must be returned *for all values* of parameter_set,
-        #   i.e., "parameter_set not available" errors should never be raised.
-        #
-        # A good parameter set should primarily satisfy two criteria,
-        #   1. Chosen set of parameters should have a low testing time,
-        #      ideally in the magnitude of few seconds for the entire test suite.
-        #       This is vital for the cases where default values result in
-        #       "big" models which not only increases test time but also
-        #       run into the risk of test workers crashing.
-        #   2. There should be a minimum two such parameter sets with different
-        #      sets of values to ensure a wide range of code coverage is provided.
-        #
-        # example 1: specify params as dictionary
-        # any number of params can be specified
-        # params = {"est": value0, "parama": value1, "paramb": value2}
-        #
-        # example 2: specify params as list of dictionary
-        # note: Only first dictionary will be used by create_test_instance
-        # params = [{"est": value1, "parama": value2},
-        #           {"est": value3, "parama": value4}]
-        #
-        # example 3: parameter set depending on param_set value
-        #   note: only needed if a separate parameter set is needed in tests
-        # if parameter_set == "special_param_set":
-        #     params = {"est": value1, "parama": value2}
-        #     return params
-        #
-        # # "default" params
-        # params = {"est": value3, "parama": value4}
-        # return params
diff --git a/extension_templates/forecasting.py b/extension_templates/forecasting.py
deleted file mode 100644
index 72898e2b..00000000
--- a/extension_templates/forecasting.py
+++ /dev/null
@@ -1,624 +0,0 @@
-"""
-Extension template for forecasters.
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template
-    NOT a concrete class to import! This is NOT a base class or concrete class!
-    This is to be used as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory methods, and optionally for optional methods
-- do not write to reserved variables: is_fitted, _is_fitted, _X, _y, cutoff, _fh,
-    _cutoff, _converter_store_y, forecasters_, _tags, _tags_dynamic, _is_vectorized
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- ensure interface compatibility by aeon.testing.estimator_checks.check_estimator
-- once complete: use as a local library, or contribute to aeon via PR
-- more details:
-    https://www.aeon-toolkit.org/en/stable/developer_guide/add_estimators.html
-
-Mandatory implements:
-    fitting         - _fit(self, y, X=None, fh=None)
-    forecasting     - _predict(self, fh=None, X=None)
-
-Optional implements:
-    updating                    - _update(self, y, X=None, update_params=True):
-    predicting quantiles        - _predict_quantiles(self, fh, X=None, alpha=None)
-    OR predicting intervals     - _predict_interval(self, fh, X=None, coverage=None)
-    predicting variance         - _predict_var(self, fh, X=None, cov=False)
-    distribution forecast       - _predict_proba(self, fh, X=None)
-    fitted parameter inspection - _get_fitted_params()
-
-Testing - implement if aeon forecaster (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-"""
-
-# todo: write an informative docstring for the file or module, remove the above
-
-# todo: uncomment the following line, enter authors' GitHub IDs
-# __maintainer__ = []
-
-
-from aeon.forecasting.base import BaseForecaster
-
-# todo: add any necessary imports here
-
-
-class MyForecaster(BaseForecaster):
-    """Custom forecaster. todo: write docstring.
-
-    todo: describe your custom forecaster here
-
-    Parameters
-    ----------
-    parama : int
-        descriptive explanation of parama
-    paramb : string, default='default'
-        descriptive explanation of paramb
-    paramc : boolean, default= whether paramb is not the default
-        descriptive explanation of paramc
-    and so on
-    est : aeon.estimator, BaseEstimator descendant
-        descriptive explanation of est
-    est2: another estimator
-        descriptive explanation of est2
-    and so on
-    """
-
-    # todo: if any imports are aeon soft dependencies:
-    #  * make sure to fill in the "python_dependencies" tag with the package import name
-
-    # todo: fill out estimator tags here
-    #  tags are inherited from parent class if they are not set
-    # todo: define the forecaster scitype by setting the tags
-    #  the "forecaster scitype" is determined by the tags
-    #   y_input_type - the expected input type of y - univariate or multivariate or both
-    #  when changing y_input_type to multivariate or both:
-    #   y_inner_type should be changed to pd.DataFrame
-    # other tags are "safe defaults" which can usually be left as-is
-    _tags = {
-        # to list all valid tags with description, use aeon.registry.all_tags
-        #   all_tags(estimator_identifiers="forecaster", as_dataframe=True)
-        #
-        # behavioural tags: internal type
-        # -------------------------------
-        #
-        # y_inner_type, X_inner_type control which format X/y appears in
-        # in the inner functions _fit, _predict, etc
-        "y_inner_type": "pd.Series",
-        "X_inner_type": "pd.DataFrame",
-        # valid values: str and list of str
-        # if str, must be a valid mtype str, in aeon.datatypes.TYPE_REGISTER
-        #   of scitype Series, Panel (panel data) or Hierarchical (hierarchical series)
-        #   in that case, all inputs are converted to that one type
-        # if list of str, must be a list of valid str specifiers
-        #   in that case, X/y are passed through without conversion if on the list
-        #   if not on the list, converted to the first entry of the same scitype
-        #
-        # y_input_type controls whether internal y can be univariate/multivariate
-        # if multivariate is not valid, applies vectorization over variables
-        "y_input_type": "univariate",
-        # valid values: "univariate", "multivariate", "both"
-        #   "univariate": inner _fit, _predict, etc, receive only univariate series
-        #   "multivariate": inner methods receive only series with 2 or more variables
-        #   "both": inner methods can see series with any number of variables
-        #
-        # capability tags: properties of the estimator
-        # --------------------------------------------
-        #
-        # ignores-exogeneous-X = does estimator ignore the exogeneous X?
-        "ignores-exogeneous-X": False,
-        # valid values: boolean True (ignores X), False (uses X in non-trivial manner)
-        # CAVEAT: if tag is set to True, inner methods always see X=None
-        #
-        # requires-fh-in-fit = is forecasting horizon always required in fit?
-        "requires-fh-in-fit": True,
-        # valid values: boolean True (yes), False (no)
-        # if True, raises exception in fit if fh has not been passed
-        #
-        # X-y-must-have-same-index = can estimator handle different X/y index?
-        "X-y-must-have-same-index": True,
-        # valid values: boolean True (yes), False (no)
-        # if True, raises exception if X.index is not contained in y.index
-        #
-        # enforce_index_type = index type that needs to be enforced in X/y
-        "enforce_index_type": None,
-        # valid values: pd.Index subtype, or list of pd.Index subtype
-        # if not None, raises exception if X.index, y.index level -1 is not of that type
-        #
-        # capability:missing_values = can estimator handle missing data?
-        "capability:missing_values": False,
-        # valid values: boolean True (yes), False (no)
-        # if False, raises exception if y or X passed contain missing data (nans)
-        #
-        # capability:pred_int = does forecaster implement probabilistic forecasts?
-        "capability:pred_int": False,
-        # valid values: boolean True (yes), False (no)
-        # if False, exception raised if proba methods are called (predict_interval etc)
-        #
-        #
-        # dependency tags: python version and soft dependencies
-        # -----------------------------------------------------
-        #
-        # python version requirement
-        "python_version": None,
-        # valid values: str, PEP 440 valid python version specifiers
-        # raises exception at construction if local python veresion is incompatible
-        #
-        # soft dependency requirement
-        "python_dependencies": None,
-        # valid values: str or list of str
-        # raises exception at construction if modules at strings cannot be imported
-    }
-    #  in case of inheritance, concrete class should typically set tags
-    #  alternatively, descendants can set tags in __init__ (avoid this if possible)
-
-    # todo: add any hyper-parameters and components to constructor
-    def __init__(self, est, parama, est2=None, paramb="default", paramc=None):
-        # estimators should precede parameters
-        #  if estimators have default values, set None and initalize below
-
-        # todo: write any hyper-parameters and components to self
-        self.est = est
-        self.parama = parama
-        self.paramb = paramb
-        self.paramc = paramc
-
-        # todo: change "MyForecaster" to the name of the class
-        super().__init__()
-
-        # todo: optional, parameter checking logic (if applicable) should happen here
-        # if writes derived values to self, should *not* overwrite self.parama etc
-        # instead, write to self._parama, self._newparam (starting with _)
-
-        # todo: default estimators should have None arg defaults
-        #  and be initialized here
-        #  do this only with default estimators, not with parameters
-        # if est2 is None:
-        #     self.estimator = MyDefaultEstimator()
-
-        # todo: if tags of estimator depend on component tags, set these here
-        #  only needed if estimator is a composite
-        #  tags set in the constructor apply to the object and override the class
-        #
-        # example 1: conditional setting of a tag
-        # if est.foo == 42:
-        #   self.set_tags(capability:missing_values=True)
-        # example 2: cloning tags from component
-        #   self.clone_tags(est2, ["enforce_index_type", "capability:missing_values"])
-
-    # todo: implement this, mandatory
-    def _fit(self, y, X=None, fh=None):
-        """Fit forecaster to training data.
-
-        private _fit containing the core logic, called from fit
-
-        Writes to self:
-            Sets fitted model attributes ending in "_".
-
-        Parameters
-        ----------
-        y : aeon time series object
-            guaranteed to be of an mtype in self.get_tag("y_inner_type")
-            Time series to which to fit the forecaster.
-            if self.get_tag("y_input_type")=="univariate":
-                guaranteed to have a single column/variable
-            if self.get_tag("y_input_type")=="multivariate":
-                guaranteed to have 2 or more columns
-            if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, default=None
-            The forecasting horizon with the steps ahead to to predict.
-            Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
-            Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X :  aeon time series object, default=None
-            guaranteed to be of an mtype in self.get_tag("X_inner_type")
-            Exogeneous time series to fit to.
-
-        Returns
-        -------
-        self : reference to self
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to y, X, fh
-        #
-        # any model parameters should be written to attributes ending in "_"
-        #  attributes set by the constructor must not be overwritten
-        #  if used, estimators should be cloned to attributes ending in "_"
-        #  the clones, not the originals shoudld be used or fitted if needed
-        #
-        # Note: when interfacing a model that has fit, with parameters
-        #   that are not data (y, X) or forecasting-horizon-like,
-        #   but model parameters, *don't* add as arguments to fit, but treat as follows:
-        #   1. pass to constructor,  2. write to self in constructor,
-        #   3. read from self in _fit,  4. pass to interfaced_model.fit in _fit
-
-    # todo: implement this, mandatory
-    def _predict(self, fh, X=None):
-        """Forecast time series at future horizon.
-
-        private _predict containing the core logic, called from predict
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-            self.cutoff
-
-        Parameters
-        ----------
-        fh : guaranteed to be ForecastingHorizon or None, default=None
-            The forecasting horizon with the steps ahead to to predict.
-            If not passed in _fit, guaranteed to be passed here
-        X : aeon time series object, default=None
-            guaranteed to be of an mtype in self.get_tag("X_inner_type")
-            Exogeneous time series for the forecast
-
-        Returns
-        -------
-        y_pred : aeon time series object
-            should be of the same type as seen in _fit, as in "y_inner_type" tag
-            Point predictions
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X, fh
-
-    # todo: consider implementing this, optional
-    # if not implementing, delete the _update method
-    def _update(self, y, X=None, update_params=True):
-        """Update time series to incremental training data.
-
-        private _update containing the core logic, called from update
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-            self.cutoff
-
-        Writes to self:
-            Sets fitted model attributes ending in "_", if update_params=True.
-            Does not write to self if update_params=False.
-
-        Parameters
-        ----------
-        y : aeon time series object
-            guaranteed to be of an mtype in self.get_tag("y_inner_type")
-            Time series with which to update the forecaster.
-            if self.get_tag("y_input_type")=="univariate":
-                guaranteed to have a single column/variable
-            if self.get_tag("y_input_type")=="multivariate":
-                guaranteed to have 2 or more columns
-            if self.get_tag("y_input_type")=="both": no restrictions apply
-        X :  aeon time series object, default=None
-            guaranteed to be of an mtype in self.get_tag("X_inner_type")
-            Exogeneous time series for the forecast
-        update_params : bool, default=True
-            whether model parameters should be updated
-
-        Returns
-        -------
-        self : reference to self
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X, fh
-
-    # todo: consider implementing this, optional
-    # if not implementing, delete the _update_predict_single method
-    def _update_predict_single(self, y, fh, X=None, update_params=True):
-        """Update forecaster and then make forecasts.
-
-        Implements default behaviour of calling update and predict
-        sequentially, but can be overwritten by subclasses
-        to implement more efficient updating algorithms when available.
-        """
-        self.update(y, X, update_params=update_params)
-        return self.predict(fh, X)
-        # implement here
-        # IMPORTANT: avoid side effects to y, X, fh
-
-    # todo: consider implementing one of _predict_quantiles and _predict_interval
-    #   if one is implemented, the other one works automatically
-    #   when interfacing or implementing, consider which of the two is easier
-    #   both can be implemented if desired, but usually that is not necessary
-    #
-    # if _predict_var or _predict_proba is implemented, this will have a default
-    #   implementation which uses _predict_proba or _predict_var under normal assumption
-    #
-    # if implementing _predict_interval, delete _predict_quantiles
-    # if not implementing either, delete both methods
-    def _predict_quantiles(self, fh, X=None, alpha=None):
-        """Compute/return prediction quantiles for a forecast.
-
-        private _predict_quantiles containing the core logic,
-            called from predict_quantiles and possibly predict_interval
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-            self.cutoff
-
-        Parameters
-        ----------
-        fh : guaranteed to be ForecastingHorizon
-            The forecasting horizon with the steps ahead to to predict.
-        X :  aeon time series object, default=None
-            guaranteed to be of an mtype in self.get_tag("X_inner_type")
-            Exogeneous time series for the forecast
-        alpha : list of float (guaranteed not None and floats in [0,1] interval)
-            A list of probabilities at which quantile forecasts are computed.
-
-        Returns
-        -------
-        quantiles : pd.DataFrame
-            Column has multi-index: first level is variable name from y in fit,
-                second level being the values of alpha passed to the function.
-            Row index is fh, with additional (upper) levels equal to instance levels,
-                    from y seen in fit, if y_inner_type is Panel or Hierarchical.
-            Entries are quantile forecasts, for var in col index,
-                at quantile probability in second col index, for the row index.
-        """
-        # implement here
-        # IMPORTANT: avoid side effects to y, X, fh, alpha
-        #
-        # Note: unlike in predict_quantiles where alpha can be float or list of float
-        #   alpha in _predict_quantiles is guaranteed to be a list of float
-
-    # implement one of _predict_interval or _predict_quantiles (above), or delete both
-    #
-    # if implementing _predict_quantiles, delete _predict_interval
-    # if not implementing either, delete both methods
-    def _predict_interval(self, fh, X=None, coverage=None):
-        """Compute/return prediction quantiles for a forecast.
-
-        private _predict_interval containing the core logic,
-            called from predict_interval and possibly predict_quantiles
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-            self.cutoff
-
-        Parameters
-        ----------
-        fh : guaranteed to be ForecastingHorizon
-            The forecasting horizon with the steps ahead to to predict.
-        X :  aeon time series object, default=None
-            guaranteed to be of an mtype in self.get_tag("X_inner_type")
-            Exogeneous time series for the forecast
-        coverage : list of float (guaranteed not None and floats in [0,1] interval)
-           nominal coverage(s) of predictive interval(s)
-
-        Returns
-        -------
-        pred_int : pd.DataFrame
-            Column has multi-index: first level is variable name from y in fit,
-                second level coverage fractions for which intervals were computed.
-                    in the same order as in input `coverage`.
-                Third level is string "lower" or "upper", for lower/upper interval end.
-            Row index is fh, with additional (upper) levels equal to instance levels,
-                from y seen in fit, if y_inner_type is Panel or Hierarchical.
-            Entries are forecasts of lower/upper interval end,
-                for var in col index, at nominal coverage in second col index,
-                lower/upper depending on third col index, for the row index.
-                Upper/lower interval end forecasts are equivalent to
-                quantile forecasts at alpha = 0.5 - c/2, 0.5 + c/2 for c in coverage.
-        """
-        # implement here
-        # IMPORTANT: avoid side effects to y, X, fh, coverage
-        #
-        # Note: unlike in predict_interval where coverage can be float or list of float
-        #   coverage in _predict_interval is guaranteed to be a list of float
-
-    # todo: consider implementing _predict_var
-    #
-    # if _predict_proba or interval/quantiles are implemented, this will have a default
-    #   implementation which uses _predict_proba or quantiles under normal assumption
-    #
-    # if not implementing, delete _predict_var
-    def _predict_var(self, fh, X=None, cov=False):
-        """Forecast variance at future horizon.
-
-        private _predict_var containing the core logic, called from predict_var
-
-        Parameters
-        ----------
-        fh : guaranteed to be ForecastingHorizon or None, default=None
-            The forecasting horizon with the steps ahead to to predict.
-            If not passed in _fit, guaranteed to be passed here
-        X :  aeon time series object, default=None
-            guaranteed to be of an mtype in self.get_tag("X_inner_type")
-            Exogeneous time series for the forecast
-        cov : bool, default=False
-            if True, computes covariance matrix forecast.
-            if False, computes marginal variance forecasts.
-
-        Returns
-        -------
-        pred_var : pd.DataFrame, format dependent on `cov` variable
-            If cov=False:
-                Column names are exactly those of `y` passed in `fit`/`update`.
-                    For nameless formats, column index will be a RangeIndex.
-                Row index is fh, with additional levels equal to instance levels,
-                    from y seen in fit, if y_inner_type is Panel or Hierarchical.
-                Entries are variance forecasts, for var in col index.
-                A variance forecast for given variable and fh index is a predicted
-                    variance for that variable and index, given observed data.
-            If cov=True:
-                Column index is a multiindex: 1st level is variable names (as above)
-                    2nd level is fh.
-                Row index is fh, with additional levels equal to instance levels,
-                    from y seen in fit, if y_inner_type is Panel or Hierarchical.
-                Entries are (co-)variance forecasts, for var in col index, and
-                    covariance between time index in row and col.
-                Note: no covariance forecasts are returned between different variables.
-        """
-        # implement here
-        # implementing the cov=True case is optional and can be omitted
-
-    # todo: consider implementing _predict_proba
-    #
-    # if interval/quantiles or _predict_var are implemented, this will have a default
-    #   implementation which uses variance or quantiles under normal assumption
-    #
-    # if not implementing, delete _predict_proba
-    def _predict_proba(self, fh, X, marginal=True):
-        """Compute/return fully probabilistic forecasts.
-
-        private _predict_proba containing the core logic, called from predict_proba
-
-        Parameters
-        ----------
-        fh : int, list, np.array or ForecastingHorizon (not optional)
-            The forecasting horizon encoding the time stamps to forecast at.
-            if has not been passed in fit, must be passed, not optional
-        X : aeon time series object, default=None
-                Exogeneous time series for the forecast
-            Should be of same scitype (Series, Panel, or Hierarchical) as y in fit
-            if self.get_tag("X-y-must-have-same-index"),
-                X.index must contain fh.index and y.index both
-        marginal : bool, default=True
-            whether returned distribution is marginal by time index
-
-        Returns
-        -------
-        pred_dist : tfp Distribution object
-            if marginal=True:
-                batch shape is 1D and same length as fh
-                event shape is 1D, with length equal number of variables being forecast
-                i-th (batch) distribution is forecast for i-th entry of fh
-                j-th (event) index is j-th variable, order as y in `fit`/`update`
-            if marginal=False:
-                there is a single batch
-                event shape is 2D, of shape (len(fh), no. variables)
-                i-th (event dim 1) distribution is forecast for i-th entry of fh
-                j-th (event dim 1) index is j-th variable, order as y in `fit`/`update`
-        """
-        # implement here
-        # implementing the marginal=False case is optional and can be omitted
-
-    # todo: consider implementing this, optional
-    # if not implementing, delete the method
-    def _predict_moving_cutoff(self, y, cv, X=None, update_params=True):
-        """Make single-step or multi-step moving cutoff predictions.
-
-        Parameters
-        ----------
-        y : pd.Series
-        cv : temporal cross-validation generator
-        X : pd.DataFrame
-        update_params : bool
-
-        Returns
-        -------
-        y_pred = pd.Series
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to y, X, cv
-
-    # todo: consider implementing this, optional
-    # implement only if different from default:
-    #   default retrieves all self attributes ending in "_"
-    #   and returns them with keys that have the "_" removed
-    # if not implementing, delete the method
-    #   avoid overriding get_fitted_params
-    def _get_fitted_params(self):
-        """Get fitted parameters.
-
-        private _get_fitted_params, called from get_fitted_params
-
-        State required:
-            Requires state to be "fitted".
-
-        Returns
-        -------
-        fitted_params : dict with str keys
-            fitted parameters, keyed by names of fitted parameter
-        """
-        # implement here
-        #
-        # when this function is reached, it is already guaranteed that self is fitted
-        #   this does not need to be checked separately
-        #
-        # parameters of components should follow the sklearn convention:
-        #   separate component name from parameter name by double-underscore
-        #   e.g., componentname__paramname
-
-    # todo: implement this if this is an estimator contributed to aeon
-    #   or to run local automated unit and integration testing of estimator
-    #   method should return default parameters, so that a test instance can be created
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for forecasters.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries
-        # Testing parameter choice should cover internal cases well.
-        #
-        # this method can, if required, use:
-        #   class properties (e.g., inherited); parent class test case
-        #   imported objects such as estimators from aeon or sklearn
-        # important: all such imports should be *inside get_test_params*, not at the top
-        #            since imports are used only at testing time
-        #
-        # The parameter_set argument is not used for automated, module level tests.
-        #   It can be used in custom, estimator specific tests, for "special" settings.
-        # A parameter dictionary must be returned *for all values* of parameter_set,
-        #   i.e., "parameter_set not available" errors should never be raised.
-        #
-        # A good parameter set should primarily satisfy two criteria,
-        #   1. Chosen set of parameters should have a low testing time,
-        #      ideally in the magnitude of few seconds for the entire test suite.
-        #       This is vital for the cases where default values result in
-        #       "big" models which not only increases test time but also
-        #       run into the risk of test workers crashing.
-        #   2. There should be a minimum two such parameter sets with different
-        #      sets of values to ensure a wide range of code coverage is provided.
-        #
-        # example 1: specify params as dictionary
-        # any number of params can be specified
-        # params = {"est": value0, "parama": value1, "paramb": value2}
-        #
-        # example 2: specify params as list of dictionary
-        # note: Only first dictionary will be used by create_test_instance
-        # params = [{"est": value1, "parama": value2},
-        #           {"est": value3, "parama": value4}]
-        # return params
-        #
-        # example 3: parameter set depending on param_set value
-        #   note: only needed if a separate parameter set is needed in tests
-        # if parameter_set == "special_param_set":
-        #     params = {"est": value1, "parama": value2}
-        #     return params
-        #
-        # # "default" params - always returned except for "special_param_set" value
-        # params = {"est": value3, "parama": value4}
-        # return params
diff --git a/extension_templates/forecasting_simple.py b/extension_templates/forecasting_simple.py
deleted file mode 100644
index c4246163..00000000
--- a/extension_templates/forecasting_simple.py
+++ /dev/null
@@ -1,249 +0,0 @@
-"""
-Extension template for forecasters, SIMPLE version.
-
-Contains only bare minimum of implementation requirements for a functional forecaster.
-Also assumes *no composition*, i.e., no forecaster or other estimator components.
-For advanced cases (probabilistic, composition, etc),
-    see full extension template in forecasting.py
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template
-    NOT a concrete class to import! This is NOT a base class or concrete class!
-    This is to be used as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory methods, and optionally for optional methods
-- do not write to reserved variables: is_fitted, _is_fitted, _X, _y, cutoff, _fh,
-    _cutoff, _converter_store_y, forecasters_, _tags, _tags_dynamic, _is_vectorized
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- ensure interface compatibility by aeon.testing.estimator_checks.check_estimator
-- once complete: use as a local library, or contribute to aeon via PR
-- more details:
-    https://www.aeon-toolkit.org/en/stable/developer_guide/add_estimators.html
-
-Mandatory implements:
-    fitting         - _fit(self, y, X=None, fh=None)
-    forecasting     - _predict(self, fh=None, X=None)
-
-Testing - implement if aeon forecaster (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-"""
-
-# todo: write an informative docstring for the file or module, remove the above
-
-# todo: uncomment the following line, enter authors' GitHub IDs
-# __maintainer__ = []
-
-
-from aeon.forecasting.base import BaseForecaster
-
-# todo: add any necessary imports here
-
-
-class MyForecaster(BaseForecaster):
-    """Custom forecaster. todo: write docstring.
-
-    todo: describe your custom forecaster here
-
-    Parameters
-    ----------
-    parama : int
-        descriptive explanation of parama
-    paramb : string, default='default'
-        descriptive explanation of paramb
-    paramc : boolean, default= whether paramb is not the default
-        descriptive explanation of paramc
-    and so on
-    """
-
-    # todo: fill out estimator tags here
-    #  tags are inherited from parent class if they are not set
-    # todo: define the forecaster scitype by setting the tags
-    #  the "forecaster scitype" is determined by the tags
-    #   y_input_type - the expected input type of y - univariate or multivariate or both
-    # tag values are "safe defaults" which can usually be left as-is
-    _tags = {
-        # to list all valid tags with description, use aeon.registry.all_tags
-        #   all_tags(estimator_identifiers="forecaster", as_dataframe=True)
-        #
-        # behavioural tags: internal type
-        # -------------------------------
-        #
-        # y_inner_type, X_inner_type control which format X/y appears in
-        # in the inner functions _fit, _predict, etc
-        "y_inner_type": "pd.Series",
-        "X_inner_type": "pd.DataFrame",
-        # valid values: str and list of str
-        # if str, must be a valid mtype str, in aeon.datatypes.TYPE_REGISTER
-        #   of scitype Series, Panel (panel data) or Hierarchical (hierarchical series)
-        #   in that case, all inputs are converted to that one type
-        # if list of str, must be a list of valid str specifiers
-        #   in that case, X/y are passed through without conversion if on the list
-        #   if not on the list, converted to the first entry of the same scitype
-        #
-        # y_input_type controls whether internal y can be univariate/multivariate
-        # if multivariate is not valid, applies vectorization over variables
-        "y_input_type": "univariate",
-        # valid values: "univariate", "multivariate", "both"
-        #   "univariate": inner _fit, _predict, etc, receive only univariate series
-        #   "multivariate": inner methods receive only series with 2 or more variables
-        #   "both": inner methods can see series with any number of variables
-        #
-        # capability tags: properties of the estimator
-        # --------------------------------------------
-        #
-        # ignores-exogeneous-X = does estimator ignore the exogeneous X?
-        "ignores-exogeneous-X": False,
-        # valid values: boolean True (ignores X), False (uses X in non-trivial manner)
-        # CAVEAT: if tag is set to True, inner methods always see X=None
-        #
-        # requires-fh-in-fit = is forecasting horizon always required in fit?
-        "requires-fh-in-fit": True,
-        # valid values: boolean True (yes), False (no)
-        # if True, raises exception in fit if fh has not been passed
-    }
-
-    # todo: add any hyper-parameters and components to constructor
-    def __init__(self, parama, paramb="default", paramc=None):
-        # todo: write any hyper-parameters to self
-        self.parama = parama
-        self.paramb = paramb
-        self.paramc = paramc
-
-        # todo: change "MyForecaster" to the name of the class
-        super().__init__()
-
-        # todo: optional, parameter checking logic (if applicable) should happen here
-        # if writes derived values to self, should *not* overwrite self.parama etc
-        # instead, write to self._parama, self._newparam (starting with _)
-
-    # todo: implement this, mandatory
-    def _fit(self, y, X=None, fh=None):
-        """Fit forecaster to training data.
-
-        private _fit containing the core logic, called from fit
-
-        Writes to self:
-            Sets fitted model attributes ending in "_".
-
-        Parameters
-        ----------
-        y : guaranteed to be of a type in self.get_tag("y_inner_type")
-            Time series to which to fit the forecaster.
-            if self.get_tag("y_input_type")=="univariate":
-                guaranteed to have a single column/variable
-            if self.get_tag("y_input_type")=="multivariate":
-                guaranteed to have 2 or more columns
-            if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, default=None
-            The forecasting horizon with the steps ahead to to predict.
-            Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
-            Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : default=None
-            guaranteed to be of a type in self.get_tag("X_inner_type")
-            Exogeneous time series to fit to.
-
-        Returns
-        -------
-        self : reference to self
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to y, X, fh
-        #
-        # any model parameters should be written to attributes ending in "_"
-        #  attributes set by the constructor must not be overwritten
-        #
-        # Note: when interfacing a model that has fit, with parameters
-        #   that are not data (y, X) or forecasting-horizon-like,
-        #   but model parameters, *don't* add as arguments to fit, but treat as follows:
-        #   1. pass to constructor,  2. write to self in constructor,
-        #   3. read from self in _fit,  4. pass to interfaced_model.fit in _fit
-
-    # todo: implement this, mandatory
-    def _predict(self, fh, X=None):
-        """Forecast time series at future horizon.
-
-        private _predict containing the core logic, called from predict
-
-        State required:
-            Requires state to be "fitted".
-
-        Accesses in self:
-            Fitted model attributes ending in "_"
-            self.cutoff
-
-        Parameters
-        ----------
-        fh : guaranteed to be ForecastingHorizon or None, default=None
-            The forecasting horizon with the steps ahead to to predict.
-            If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, default=None
-            Exogenous time series
-
-        Returns
-        -------
-        y_pred : pd.Series
-            Point predictions
-        """
-
-        # implement here
-        # IMPORTANT: avoid side effects to X, fh
-
-    # todo: implement this if this is an estimator contributed to aeon
-    #   or to run local automated unit and integration testing of estimator
-    #   method should return default parameters, so that a test instance can be created
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for forecasters.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries.
-        # Testing parameter choice should cover internal cases well.
-        #   for "simple" extension, ignore the parameter_set argument.
-        #
-        # this method can, if required, use:
-        #   class properties (e.g., inherited); parent class test case
-        #   imported objects such as estimators from aeon or sklearn
-        # important: all such imports should be *inside get_test_params*, not at the top
-        #            since imports are used only at testing time
-        #
-        # A good parameter set should primarily satisfy two criteria,
-        #   1. Chosen set of parameters should have a low testing time,
-        #      ideally in the magnitude of few seconds for the entire test suite.
-        #       This is vital for the cases where default values result in
-        #       "big" models which not only increases test time but also
-        #       run into the risk of test workers crashing.
-        #   2. There should be a minimum two such parameter sets with different
-        #      sets of values to ensure a wide range of code coverage is provided.
-        #
-        # example 1: specify params as dictionary
-        # any number of params can be specified
-        # params = {"est": value0, "parama": value1, "paramb": value2}
-        #
-        # example 2: specify params as list of dictionary
-        # note: Only first dictionary will be used by create_test_instance
-        # params = [{"est": value1, "parama": value2},
-        #           {"est": value3, "parama": value4}]
-        #
-        # return params
diff --git a/extension_templates/transformer.py b/extension_templates/transformer.py
deleted file mode 100644
index a62d3428..00000000
--- a/extension_templates/transformer.py
+++ /dev/null
@@ -1,512 +0,0 @@
-"""
-Extension template for transformers.
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template
-    NOT a concrete class to import! This is NOT a base class or concrete class!
-    This is to be used as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory methods, and optionally for optional methods
-- do not write to reserved variables: is_fitted, _is_fitted, _X, _y,
-    _converter_store_X, transformers_, _tags, _tags_dynamic
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- ensure interface compatibility by aeon.testing.estimator_checks.check_estimator
-- once complete: use as a local library, or contribute to aeon via PR
-- more details:
-    https://www.aeon-toolkit.org/en/stable/developer_guide/add_estimators.html
-
-Mandatory implements:
-    fitting         - _fit(self, X, y=None)
-    transformation  - _transform(self, X, y=None)
-
-Optional implements:
-    inverse transformation      - _inverse_transform(self, X, y=None)
-    update                      - _update(self, X, y=None)
-    fitted parameter inspection - _get_fitted_params()
-
-Testing - implement if aeon transformer (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-"""
-
-# todo: write an informative docstring for the file or module, remove the above
-
-# todo: uncomment the following line, enter authors' GitHub IDs
-# __maintainer__ = []
-
-# todo: add any necessary aeon external imports here
-
-from aeon.transformations.base import BaseTransformer
-
-# todo: add any necessary aeon internal imports here
-
-
-class MyTransformer(BaseTransformer):
-    """Custom transformer. todo: write docstring.
-
-    todo: describe your custom transformer here
-        fill in sections appropriately
-        docstring must be numpydoc compliant
-
-    Parameters
-    ----------
-    parama : int
-        descriptive explanation of parama
-    paramb : string, default='default'
-        descriptive explanation of paramb
-    paramc : boolean, default= whether paramb is not the default
-        descriptive explanation of paramc
-    and so on
-    est : aeon.estimator, BaseEstimator descendant
-        descriptive explanation of est
-    est2: another estimator
-        descriptive explanation of est2
-    and so on
-    """
-
-    # todo: if any imports are aeon soft dependencies:
-    #  * make sure to fill in the "python_dependencies" tag with the package import name
-
-    # todo: fill out estimator tags here
-    #  tags are inherited from parent class if they are not set
-    #
-    # todo: define the transformer scitype by setting the tags
-    #   input_data_type: - the expected input type of X
-    #   output_data_type - the output type that transform produces
-    #   transform_labels - whether y is used and if yes which type
-    #   instancewise - whether transform uses all samples or acts by instance
-    #
-    # todo: define internal types for X, y in _fit/_transform by setting the tags
-    #   X_inner_type - the internal mtype used for X in _fit and _transform
-    #   y_inner_type - if y is used, the internal mtype used for y; usually "None"
-    #   setting this guarantees that X, y passed to _fit, _transform are of above types
-    #   for possible mtypes see datatypes.TYPE_REGISTER, or the datatypes tutorial
-    #
-    #  when input_data_type is set to Panel:
-    #   X_inner_type must be changed to one or a list of aeon Panel mtypes
-    #  when transform_labels is set to Series or Panel:
-    #   y_inner_type must be changed to one or a list of compatible aeon mtypes
-    #  the other tags are "safe defaults" which can usually be left as-is
-    _tags = {
-        # to list all valid tags with description, use aeon.registry.all_tags
-        #   all_tags(estimator_identifiers="transformer", as_dataframe=True)
-        #
-        #
-        # behavioural tags: transformer type
-        # ----------------------------------
-        #
-        # input_data_type, output_data_type, transform_labels
-        # control the input/output type of transform
-        #
-        # input_data_type, output_data_type, should be the
-        # simplest type that describes the mapping, taking into account vectorization
-        # a transform that produces Series when given Series, Panel when given Panel
-        #   should have both transform-input and transform-output as "Series"
-        # a transform that produces a tabular DataFrame (Table)
-        #   when given Series or Panel should have transform-input "Series"
-        #       and transform-output as "Primitives"
-        "input_data_type": "Series",
-        # valid values: "Series", "Panel"
-        "output_data_type": "Series",
-        # valid values: "Series", "Panel", "Primitives"
-        #
-        # instancewise = is fit_transform an instance-wise operation?
-        # instance-wise = only values of a given series instance are used to transform
-        #   that instance. Example: Fourier transform; non-example: series PCA
-        "instancewise": True,
-        #
-        # transform_labels types the y used in transform
-        #   if y is not used in transform, this should be "None"
-        "transform_labels": "None",
-        # valid values: "None" (not needed), "Primitives", "Series", "Panel"
-        #
-        #
-        # behavioural tags: internal type
-        # ----------------------------------
-        #
-        # X_inner_type, y_inner_type control which format X/y appears in
-        # in the inner functions _fit, _transform, etc
-        "X_inner_type": "pd.DataFrame",
-        "y_inner_type": "None",
-        # valid values: str and list of str
-        # if str, must be a valid mtype str, in aeon.datatypes.TYPE_REGISTER
-        #   of scitype Series, Panel (panel data) or Hierarchical (hierarchical series)
-        #   y_inner_type can also be of scitype Table (one row/instance per series)
-        #   in that case, all inputs are converted to that one type
-        # if list of str, must be a list of valid str specifiers
-        #   in that case, X/y are passed through without conversion if on the list
-        #   if not on the list, converted to the first entry of the same scitype
-        #
-        # univariate-only controls whether internal X can be univariate/multivariate
-        # if True (only univariate), always applies vectorization over variables
-        "univariate-only": False,
-        # valid values: True = inner _fit, _transform receive only univariate serie
-        #   False = uni- and multivariate series are passed to inner methods
-        #
-        # requires_y = does y need to be passed in fit?
-        "requires_y": False,
-        # valid values: False (no), True = exception is raised if no y is seen in _fit
-        #   y can be passed or not in _transform for either value of requires_y
-        #
-        # remember_data = whether all data seen is remembered as self._X
-        "remember_data": False,
-        # valid vales: False (no), True = self._X is created/update in fit/update
-        #   self._X is all X passed via fit or update, updated via update_data
-        #   self._X is of mtype seen in fit, update adds more data to the same container
-        #   self._X can be used (readonly) by the estimator in _fit, _transform, _update
-        #   if set to True, fit-is-empty must be set to False
-        #
-        # capability tags: properties of the estimator
-        # --------------------------------------------
-        #
-        # fit_is_empty = is fit empty and can be skipped?
-        "fit_is_empty": True,
-        # valid values: True = _fit is considered empty and skipped, False = No
-        # CAUTION: default is "True", i.e., _fit will be skipped even if implemented
-        #
-        # X-y-must-have-same-index = can estimator handle different X/y index?
-        "X-y-must-have-same-index": False,
-        # valid values: boolean True (yes), False (no)
-        # if True, raises exception if X.index is not contained in y.index
-        #
-        # enforce_index_type = index type that needs to be enforced in X/y
-        "enforce_index_type": None,
-        # valid values: pd.Index subtype, or list of pd.Index subtype
-        # if not None, raises exception if X.index, y.index level -1 is not of that type
-        #
-        # transform-returns-same-time-index = does transform return same index as input?
-        "transform-returns-same-time-index": False,
-        # valid values: boolean True (yes), False (no)
-        # if True, transform and inverse_transform returns should have
-        #   same length and same index (if pandas) as inputs
-        # no exception is raised if this tag is incorrectly set
-        #
-        # capability:inverse_transform = is inverse_transform implemented?
-        "capability:inverse_transform": False,
-        # valid values: boolean True (yes), False (no)
-        # if True, _inverse_transform must be implemented
-        # if False, exception is raised if inverse_transform is called,
-        #   unless the skip-inverse-transform tag is set to True
-        #
-        # skip-inverse-transform = is inverse-transform skipped when called?
-        "skip-inverse-transform": False,
-        # if False, capability:inverse_transform tag behaviour is as per devault
-        # if True, inverse_transform is the identity transform and raises no exception
-        #   this is useful for transformers where inverse_transform
-        #   may be called but should behave as the identity, e.g., imputers
-        #
-        # capability:unequal_length = can the transformer handle unequal length panels,
-        #   i.e., when passed unequal length instances in Panel or Hierarchical data
-        "capability:unequal_length": True,
-        # valid values: boolean True (yes), False (no)
-        # if False, may raise exception when passed unequal length Panel/Hierarchical
-        #
-        # capability:unequal_length:removes = if passed Panel/Hierarchical,
-        #   is transform result always guaranteed to be equal length (and series)?
-        "capability:unequal_length:removes": False,
-        # valid values: boolean True (yes), False (no)
-        # applicable only if output_data_type is not "Primitives"
-        # used for search index and validity checking, does not raise direct exception
-        #
-        # capability:missing_values = can the transformer handle missing data
-        # (np or pd.NA)?
-        "capability:missing_values": False,  # can estimator handle missing data?
-        # valid values: boolean True (yes), False (no)
-        # if False, may raise exception when passed time series with missing values
-        #
-        # capability:missing_values:removes = if passed time series
-        #   is transform result always guaranteed to contain no missing values?
-        "capability:missing_values:removes": False,
-        # valid values: boolean True (yes), False (no)
-        # used for search index and validity checking, does not raise direct exception
-        #
-        #
-        # dependency tags: python version and soft dependencies
-        # -----------------------------------------------------
-        #
-        # python version requirement
-        "python_version": None,
-        # valid values: str, PEP 440 valid python version specifiers
-        # raises exception at construction if local python veresion is incompatible
-        #
-        # soft dependency requirement
-        "python_dependencies": None,
-        # valid values: str or list of str
-        # raises exception at construction if modules at strings cannot be imported
-    }
-    # in case of inheritance, concrete class should typically set tags
-    #  alternatively, descendants can set tags in __init__
-    #  avoid if possible, but see __init__ for instructions when needed
-
-    # todo: add any hyper-parameters and components to constructor
-    def __init__(self, est, parama, est2=None, paramb="default", paramc=None):
-        # estimators should precede parameters
-        #  if estimators have default values, set None and initalize below
-
-        # todo: write any hyper-parameters and components to self
-        self.est = est
-        self.parama = parama
-        self.paramb = paramb
-        self.paramc = paramc
-
-        # todo: change "MyTransformer" to the name of the class
-        super().__init__()
-
-        # todo: optional, parameter checking logic (if applicable) should happen here
-        # if writes derived values to self, should *not* overwrite self.parama etc
-        # instead, write to self._parama, self._newparam (starting with _)
-
-        # todo: default estimators should have None arg defaults
-        #  and be initialized here
-        #  do this only with default estimators, not with parameters
-        # if est2 is None:
-        #     self.est2 = MyDefaultEstimator()
-
-        # todo: if tags of estimator depend on component tags, set these here
-        #  only needed if estimator is a composite
-        #  tags set in the constructor apply to the object and override the class
-        #
-        # example 1: conditional setting of a tag
-        # if est.foo == 42:
-        #   self.set_tags(capability:missing_values=True)
-        # example 2: cloning tags from component
-        #   self.clone_tags(est2, ["enforce_index_type", "capability:missing_values"])
-
-    # todo: implement this, mandatory (except in special case below)
-    def _fit(self, X, y=None):
-        """Fit transformer to X and y.
-
-        private _fit containing the core logic, called from fit
-
-        Parameters
-        ----------
-        X : Series or Panel of mtype X_inner_type
-            if X_inner_type is list, _fit must support all types in it
-            Data to fit transform to
-        y : Series or Panel of mtype y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        self: reference to self
-        """
-
-        # implement here
-        # X, y passed to this function are always of X_inner_type, y_inner_type
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # any model parameters should be written to attributes ending in "_"
-        #  attributes set by the constructor must not be overwritten
-        #  if used, estimators should be cloned to attributes ending in "_"
-        #  the clones, not the originals, should be used or fitted if needed
-        #
-        # special case: if no fitting happens before transformation
-        #  then: delete _fit (don't implement)
-        #   set "fit_is_empty" tag to True
-        #
-        # Note: when interfacing a model that has fit, with parameters
-        #   that are not data (X, y) or data-like,
-        #   but model parameters, *don't* add as arguments to fit, but treat as follows:
-        #   1. pass to constructor,  2. write to self in constructor,
-        #   3. read from self in _fit,  4. pass to interfaced_model.fit in _fit
-
-    # todo: implement this, mandatory
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing core logic, called from transform
-
-        Parameters
-        ----------
-        X : Series or Panel of mtype X_inner_type
-            if X_inner_type is list, _transform must support all types in it
-            Data to be transformed
-        y : Series or Panel of mtype y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        transformed version of X
-        """
-        # implement here
-        # X, y passed to this function are always of X_inner_type, y_inner_type
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # if transform-output is "Primitives":
-        #  return should be pd.DataFrame, with as many rows as instances in input
-        #  if input is a single series, return should be single-row pd.DataFrame
-        # if transform-output is "Series":
-        #  return should be of same mtype as input, X_inner_type
-        #  if multiple X_inner_type are supported, ensure same input/output
-        # if transform-output is "Panel":
-        #  return a multi-indexed pd.DataFrame of Panel mtype pd_multiindex
-        #
-        # todo: add the return mtype/scitype to the docstring, e.g.,
-        #  Returns
-        #  -------
-        #  X_transformed : Series of mtype pd.DataFrame
-        #       transformed version of X
-
-    # todo: consider implementing this, optional
-    # if not implementing, delete the _inverse_transform method
-    # inverse transform exists only if transform does not change scitype
-    #  i.e., Series transformed to Series
-    def _inverse_transform(self, X, y=None):
-        """Inverse transform, inverse operation to transform.
-
-        private _inverse_transform containing core logic, called from inverse_transform
-
-        Parameters
-        ----------
-        X : Series or Panel of mtype X_inner_type
-            if X_inner_type is list, _inverse_transform must support all types in it
-            Data to be inverse transformed
-        y : Series or Panel of mtype y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        inverse transformed version of X
-        """
-        # implement here
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # type conventions are exactly those in _transform, reversed
-        #
-        # for example: if transform-output is "Series":
-        #  return should be of same mtype as input, X_inner_type
-        #  if multiple X_inner_type are supported, ensure same input/output
-        #
-        # todo: add the return mtype/scitype to the docstring, e.g.,
-        #  Returns
-        #  -------
-        #  X_inv_transformed : Series of mtype pd.DataFrame
-        #       inverse transformed version of X
-
-    # todo: consider implementing this, optional
-    # if not implementing, delete the _update method
-    # standard behaviour is "no update"
-    # also delete in the case where there is no fitting
-    def _update(self, X, y=None):
-        """Update transformer with X and y.
-
-        private _update containing the core logic, called from update
-
-        Parameters
-        ----------
-        X : Series or Panel of mtype X_inner_type
-            if X_inner_type is list, _update must support all types in it
-            Data to update transformer with
-        y : Series or Panel of mtype y_inner_type, default=None
-            Additional data, e.g., labels for tarnsformation
-
-        Returns
-        -------
-        self: reference to self
-        """
-        # implement here
-        # X, y passed to this function are always of X_inner_type, y_inner_type
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # any model parameters should be written to attributes ending in "_"
-        #  attributes set by the constructor must not be overwritten
-        #  if used, estimators should be cloned to attributes ending in "_"
-        #  the clones, not the originals, should be used or fitted if needed
-
-    # todo: consider implementing this, optional
-    # implement only if different from default:
-    #   default retrieves all self attributes ending in "_"
-    #   and returns them with keys that have the "_" removed
-    # if not implementing, delete the method
-    #   avoid overriding get_fitted_params
-    def _get_fitted_params(self):
-        """Get fitted parameters.
-
-        private _get_fitted_params, called from get_fitted_params
-
-        State required:
-            Requires state to be "fitted".
-
-        Returns
-        -------
-        fitted_params : dict with str keys
-            fitted parameters, keyed by names of fitted parameter
-        """
-        # implement here
-        #
-        # when this function is reached, it is already guaranteed that self is fitted
-        #   this does not need to be checked separately
-        #
-        # parameters of components should follow the sklearn convention:
-        #   separate component name from parameter name by double-underscore
-        #   e.g., componentname__paramname
-
-    # todo: return default parameters, so that a test instance can be created
-    #   required for automated unit and integration testing of estimator
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries
-        # Testing parameter choice should cover internal cases well.
-        #
-        # this method can, if required, use:
-        #   class properties (e.g., inherited); parent class test case
-        #   imported objects such as estimators from aeon or sklearn
-        # important: all such imports should be *inside get_test_params*, not at the top
-        #            since imports are used only at testing time
-        #
-        # The parameter_set argument is not used for automated, module level tests.
-        #   It can be used in custom, estimator specific tests, for "special" settings.
-        # A parameter dictionary must be returned *for all values* of parameter_set,
-        #   i.e., "parameter_set not available" errors should never be raised.
-        #
-        # A good parameter set should primarily satisfy two criteria,
-        #   1. Chosen set of parameters should have a low testing time,
-        #      ideally in the magnitude of few seconds for the entire test suite.
-        #       This is vital for the cases where default values result in
-        #       "big" models which not only increases test time but also
-        #       run into the risk of test workers crashing.
-        #   2. There should be a minimum two such parameter sets with different
-        #      sets of values to ensure a wide range of code coverage is provided.
-        #
-        # example 1: specify params as dictionary
-        # any number of params can be specified
-        # params = {"est": value0, "parama": value1, "paramb": value2}
-        #
-        # example 2: specify params as list of dictionary
-        # note: Only first dictionary will be used by create_test_instance
-        # params = [{"est": value1, "parama": value2},
-        #           {"est": value3, "parama": value4}]
-        # return params
-        #
-        # example 3: parameter set depending on param_set value
-        #   note: only needed if a separate parameter set is needed in tests
-        # if parameter_set == "special_param_set":
-        #     params = {"est": value1, "parama": value2}
-        #     return params
-        #
-        # # "default" params - always returned except for "special_param_set" value
-        # params = {"est": value3, "parama": value4}
-        # return params
diff --git a/extension_templates/transformer_simple.py b/extension_templates/transformer_simple.py
deleted file mode 100644
index 493ad1d6..00000000
--- a/extension_templates/transformer_simple.py
+++ /dev/null
@@ -1,308 +0,0 @@
-"""
-Extension template for transformers, SIMPLE version.
-
-Contains only bare minimum of implementation requirements for a functional transformer.
-Also assumes *no composition*, i.e., no transformer or other estimator components.
-For advanced cases (inverse transform, composition, etc),
-    see full extension template in forecasting.py
-
-Purpose of this implementation template:
-    quick implementation of new estimators following the template
-    NOT a concrete class to import! This is NOT a base class or concrete class!
-    This is to be used as a "fill-in" coding template.
-
-How to use this implementation template to implement a new estimator:
-- make a copy of the template in a suitable location, give it a descriptive name.
-- work through all the "todo" comments below
-- fill in code for mandatory methods, and optionally for optional methods
-- do not write to reserved variables: is_fitted, _is_fitted, _X, _y,
-    _converter_store_X, transformers_, _tags, _tags_dynamic
-- you can add more private methods, but do not override BaseEstimator's private methods
-    an easy way to be safe is to prefix your methods with "_custom"
-- change docstrings for functions and the file
-- ensure interface compatibility by aeon.testing.estimator_checks.check_estimator
-- once complete: use as a local library, or contribute to aeon via PR
-- more details:
-    https://www.aeon-toolkit.org/en/stable/developer_guide/add_estimators.html
-
-Mandatory implements:
-    fitting         - _fit(self, X, y=None)
-    transformation  - _transform(self, X, y=None)
-
-Testing - implement if aeon transformer (not needed locally):
-    get default parameters for test instance(s) - get_test_params()
-"""
-
-# todo: write an informative docstring for the file or module, remove the above
-
-# todo: uncomment the following line, enter authors' GitHub IDs
-# __maintainer__ = []
-
-# todo: add any necessary aeon external imports here
-
-from aeon.transformations.base import BaseTransformer
-
-# todo: add any necessary aeon internal imports here
-
-
-class MyTransformer(BaseTransformer):
-    """Custom transformer. todo: write docstring.
-
-    todo: describe your custom transformer here
-        fill in sections appropriately
-        docstring must be numpydoc compliant
-
-    Parameters
-    ----------
-    parama : int
-        descriptive explanation of parama
-    paramb : string, default='default'
-        descriptive explanation of paramb
-    paramc : boolean, default= whether paramb is not the default
-        descriptive explanation of paramc
-    and so on
-    """
-
-    # todo: fill out estimator tags here
-    #  tags are inherited from parent class if they are not set
-    #
-    # todo: define the transformer type by setting the tags
-    #   input_data_type - the expected input type of X
-    #   output_data_type - the output type that transform produces
-    #   transform_labels - whether y is used and if yes which type
-    #   instancewise - whether transform uses all samples or acts by instance
-    #
-    # todo: define internal types for X, y in _fit/_transform by setting the tags
-    #   X_inner_type - the internal mtype used for X in _fit and _transform
-    #   y_inner_type - if y is used, the internal mtype used for y; usually "None"
-    #   setting this guarantees that X, y passed to _fit, _transform are of above types
-    #   for possible mtypes see datatypes.TYPE_REGISTER, or the datatypes tutorial
-    #
-    #  when input_data_type is set to Panel:
-    #   X_inner_type must be changed to one or a list of aeon Panel mtypes
-    #  when transform_labels is set to Series or Panel:
-    #   y_inner_type must be changed to one or a list of compatible aeon mtypes
-    #  the other tags are "safe defaults" which can usually be left as-is
-    _tags = {
-        # to list all valid tags with description, use aeon.registry.all_tags
-        #   all_tags(estimator_identifiers="transformer", as_dataframe=True)
-        #
-        #
-        # behavioural tags: transformer type
-        # ----------------------------------
-        #
-        # output_data_type, input_data_type, transform_labels
-        # control the input/output type of transform
-        #
-        # input_data_type, output_data_type should be the
-        # simplest type that describes the mapping, taking into account vectorization
-        # a transform that produces Series when given Series, Panel when given Panel
-        #   should have both transform-input and transform-output as "Series"
-        # a transform that produces a tabular DataFrame (Table)
-        #   when given Series or Panel should have transform-input "Series"
-        #       and transform-output as "Primitives"
-        "input_data_type": "Series",
-        # valid values: "Series", "Panel"
-        "output_data_type": "Series",
-        # valid values: "Series", "Panel", "Primitives"
-        #
-        # instancewise = is fit_transform an instance-wise operation?
-        # instance-wise = only values of a given series instance are used to transform
-        #   that instance. Example: Fourier transform; non-example: series PCA
-        "instancewise": True,
-        #
-        # transform_labels types the y used in transform
-        #   if y is not used in transform, this should be "None"
-        "transform_labels": "None",
-        # valid values: "None" (not needed), "Primitives", "Series", "Panel"
-        #
-        #
-        # behavioural tags: internal type
-        # ----------------------------------
-        #
-        # X_inner_type, y_inner_type control which format X/y appears in
-        # in the inner functions _fit, _transform, etc
-        "X_inner_type": "pd.DataFrame",
-        "y_inner_type": "None",
-        # valid values: str and list of str
-        # if str, must be a valid mtype str, in aeon.datatypes.TYPE_REGISTER
-        #   of type Series, Panel (panel data) or Hierarchical (hierarchical series)
-        #   y_inner_type can also be of type Table (one row/instance per series)
-        #   in that case, all inputs are converted to that one type
-        # if list of str, must be a list of valid str specifiers
-        #   in that case, X/y are passed through without conversion if on the list
-        #   if not on the list, converted to the first entry of the same type
-        #
-        # univariate-only controls whether internal X can be univariate/multivariate
-        # if True (only univariate), always applies vectorization over variables
-        "univariate-only": False,
-        # valid values: True = inner _fit, _transform receive only univariate serie
-        #   False = uni- and multivariate series are passed to inner methods
-        #
-        # requires_y = does y need to be passed in fit?
-        "requires_y": False,
-        # valid values: False (no), True = exception is raised if no y is seen in _fit
-        #   y can be passed or not in _transform for either value of requires_y
-        #
-        #
-        # capability tags: properties of the estimator
-        # --------------------------------------------
-        #
-        # fit_is_empty = is fit empty and can be skipped?
-        "fit_is_empty": True,
-        # valid values: True = _fit is considered empty and skipped, False = No
-        # CAUTION: default is "True", i.e., _fit will be skipped even if implemented
-        #
-        # capability:inverse_transform = is inverse_transform implemented?
-        "capability:inverse_transform": False,
-        # valid values: boolean True (yes), False (no)
-        # if True, _inverse_transform must be implemented
-        # if False, exception is raised if inverse_transform is called,
-        #   unless the skip-inverse-transform tag is set to True
-        #
-        # capability:unequal_length = can the transformer handle unequal length panels,
-        #   i.e., when passed unequal length instances in Panel or Hierarchical data
-        "capability:unequal_length": True,
-        # valid values: boolean True (yes), False (no)
-        # if False, may raise exception when passed unequal length Panel/Hierarchical
-        #
-        # capability:missing_values = can the transformer handle missing data
-        # (np or pd.NA)?
-        "capability:missing_values": False,  # can estimator handle missing data?
-        # valid values: boolean True (yes), False (no)
-        # if False, may raise exception when passed time series with missing values
-    }
-
-    # todo: add any hyper-parameters and components to constructor
-    def __init__(self, parama, paramb="default", paramc=None):
-        # todo: write any hyper-parameters to self
-        self.parama = parama
-        self.paramb = paramb
-        self.paramc = paramc
-
-        # todo: change "MyTransformer" to the name of the class
-        super().__init__()
-
-        # todo: optional, parameter checking logic (if applicable) should happen here
-        # if writes derived values to self, should *not* overwrite self.parama etc
-        # instead, write to self._parama, self._newparam (starting with _)
-
-    # todo: implement this, mandatory (except in special case below)
-    def _fit(self, X, y=None):
-        """Fit transformer to X and y.
-
-        private _fit containing the core logic, called from fit
-
-        Parameters
-        ----------
-        X : Series or Panel of mtype X_inner_type
-            if X_inner_type is list, _fit must support all types in it
-            Data to fit transform to
-        y : Series or Panel of mtype y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        self: reference to self
-        """
-
-        # implement here
-        # X, y passed to this function are always of X_inner_type, y_inner_type
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # any model parameters should be written to attributes ending in "_"
-        #  attributes set by the constructor must not be overwritten
-        #
-        # special case: if no fitting happens before transformation
-        #  then: delete _fit (don't implement)
-        #   set "fit_is_empty" tag to True
-        #
-        # Note: when interfacing a model that has fit, with parameters
-        #   that are not data (X, y) or data-like
-        #   but model parameters, *don't* add as arguments to fit, but treat as follows:
-        #   1. pass to constructor,  2. write to self in constructor,
-        #   3. read from self in _fit,  4. pass to interfaced_model.fit in _fit
-
-    # todo: implement this, mandatory
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing core logic, called from transform
-
-        Parameters
-        ----------
-        X : Series or Panel of mtype X_inner_type
-            if X_inner_type is list, _transform must support all types in it
-            Data to be transformed
-        y : Series or Panel of mtype y_inner_type, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        transformed version of X
-        """
-        # implement here
-        # X, y passed to this function are always of X_inner_type, y_inner_type
-        # IMPORTANT: avoid side effects to X, y
-        #
-        # if transform-output is "Primitives":
-        #  return should be pd.DataFrame, with as many rows as instances in input
-        #  if input is a single series, return should be single-row pd.DataFrame
-        # if transform-output is "Series":
-        #  return should be of same mtype as input, X_inner_type
-        #  if multiple X_inner_type are supported, ensure same input/output
-        # if transform-output is "Panel":
-        #  return a multi-indexed pd.DataFrame of Panel mtype pd_multiindex
-        #
-        # todo: add the return type to the docstring, e.g.,
-        #  Returns
-        #  -------
-        #  X_transformed : Series of mtype pd.DataFrame
-        #       transformed version of X
-
-    # todo: return default parameters, so that a test instance can be created
-    #   required for automated unit and integration testing of estimator
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-            Name of the set of test parameters to return, for use in tests. If no
-            special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for transformers.
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class
-            Each dict are parameters to construct an "interesting" test instance, i.e.,
-            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
-            `create_test_instance` uses the first (or only) dictionary in `params`
-        """
-
-        # todo: set the testing parameters for the estimators
-        # Testing parameters can be dictionary or list of dictionaries.
-        # Testing parameter choice should cover internal cases well.
-        #   for "simple" extension, ignore the parameter_set argument.
-        #
-        # A good parameter set should primarily satisfy two criteria,
-        #   1. Chosen set of parameters should have a low testing time,
-        #      ideally in the magnitude of few seconds for the entire test suite.
-        #       This is vital for the cases where default values result in
-        #       "big" models which not only increases test time but also
-        #       run into the risk of test workers crashing.
-        #   2. There should be a minimum two such parameter sets with different
-        #      sets of values to ensure a wide range of code coverage is provided.
-        #
-        # example 1: specify params as dictionary
-        # any number of params can be specified
-        # params = {"est": value0, "parama": value1, "paramb": value2}
-        #
-        # example 2: specify params as list of dictionary
-        # note: Only first dictionary will be used by create_test_instance
-        # params = [{"est": value1, "parama": value2},
-        #           {"est": value3, "parama": value4}]
-        #
-        # return params
diff --git a/pyproject.toml b/pyproject.toml
index d334fd51..20064796 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -60,7 +60,6 @@ dependencies = [
 all_extras = [
     "cloudpickle",
     "esig>=0.9.7,<0.9.8.3; python_version < '3.11'",
-    "filterpy>=1.4.5",
     "gluonts>=0.12.4",
     "h5py",
     "imbalanced-learn",
@@ -184,7 +183,6 @@ addopts = '''
     --durations 20
     --timeout 600
     --showlocals
-    --numprocesses logical
     --dist worksteal
     --reruns 2
     --only-rerun "crashed while running"


Test patch:
diff --git a/.github/workflows/periodic_tests.yml b/.github/workflows/periodic_tests.yml
index 9c8bdcd2..8c8e7700 100644
--- a/.github/workflows/periodic_tests.yml
+++ b/.github/workflows/periodic_tests.yml
@@ -114,7 +114,7 @@ jobs:
         run: python -m pip list
 
       - name: Run tests
-        run: python -m pytest
+        run: python -m pytest -n logical
 
       - name: Save new cache
         uses: actions/cache/save@v4
@@ -160,7 +160,7 @@ jobs:
         run: python -m pip list
 
       - name: Run tests
-        run: python -m pytest
+        run: python -m pytest -n logical
 
       - name: Save new cache
         uses: actions/cache/save@v4
@@ -195,7 +195,7 @@ jobs:
         run: python -m pip list
 
       - name: Run tests
-        run: python -m pytest --cov=aeon --cov-report=xml --timeout 1800
+        run: python -m pytest -n logical --cov=aeon --cov-report=xml --timeout 1800
 
       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v4
diff --git a/.github/workflows/pr_pytest.yml b/.github/workflows/pr_pytest.yml
index e989fcec..0edddd73 100644
--- a/.github/workflows/pr_pytest.yml
+++ b/.github/workflows/pr_pytest.yml
@@ -47,7 +47,7 @@ jobs:
         run: python -m pip list
 
       - name: Run tests
-        run: python -m pytest -k 'not TestAll'
+        run: python -m pytest -n logical -k 'not TestAll'
 
   pytest:
     runs-on: ${{ matrix.os }}
@@ -94,7 +94,7 @@ jobs:
 
       - name: Run tests
         # run the full test suit if a PR has the 'full pytest actions' label
-        run: python -m pytest --prtesting ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'full pytest actions') }}
+        run: python -m pytest -n logical --prtesting ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'full pytest actions') }}
 
   codecov:
     # run the code coverage job if a PR has the 'codecov actions' label
@@ -125,7 +125,7 @@ jobs:
         run: python -m pip list
 
       - name: Run tests
-        run: python -m pytest --cov=aeon --cov-report=xml --timeout 1800
+        run: python -m pytest -n logical --cov=aeon --cov-report=xml --timeout 1800
 
       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v4
diff --git a/aeon/forecasting/compose/tests/test_bagging.py b/aeon/forecasting/compose/tests/test_bagging.py
index 537b698f..1c6b6b17 100644
--- a/aeon/forecasting/compose/tests/test_bagging.py
+++ b/aeon/forecasting/compose/tests/test_bagging.py
@@ -7,8 +7,8 @@ from aeon.datasets import load_airline
 from aeon.forecasting.compose import BaggingForecaster
 from aeon.forecasting.compose._bagging import _calculate_data_quantiles
 from aeon.forecasting.naive import NaiveForecaster
+from aeon.transformations._legacy._boxcox import _LogTransformer
 from aeon.transformations.bootstrap import STLBootstrapTransformer
-from aeon.transformations.boxcox import LogTransformer
 from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 y = load_airline()
@@ -18,7 +18,7 @@ y = load_airline()
     not _check_soft_dependencies("statsmodels", severity="none"),
     reason="skip test if required soft dependency for BaggingForecaster not available",
 )
-@pytest.mark.parametrize("transformer", [LogTransformer, NaiveForecaster])
+@pytest.mark.parametrize("transformer", [_LogTransformer, NaiveForecaster])
 def test_bagging_forecaster_transformer_type_error(transformer):
     """Test that the right exception is raised for invalid transformer."""
     with pytest.raises(TypeError) as ex:
@@ -37,7 +37,7 @@ def test_bagging_forecaster_transformer_type_error(transformer):
     not _check_soft_dependencies("statsmodels", severity="none"),
     reason="skip test if required soft dependency not available",
 )
-@pytest.mark.parametrize("forecaster", [LogTransformer])
+@pytest.mark.parametrize("forecaster", [_LogTransformer])
 def test_bagging_forecaster_forecaster_type_error(forecaster):
     """Test that the right exception is raised for invalid forecaster."""
     with pytest.raises(TypeError) as ex:
diff --git a/aeon/forecasting/compose/tests/test_pipeline.py b/aeon/forecasting/compose/tests/test_pipeline.py
index 21f8f588..49a56f5b 100644
--- a/aeon/forecasting/compose/tests/test_pipeline.py
+++ b/aeon/forecasting/compose/tests/test_pipeline.py
@@ -94,7 +94,7 @@ def test_nesting_pipelines():
     from aeon.testing.utils.scenarios_forecasting import (
         ForecasterFitPredictUnivariateWithX,
     )
-    from aeon.transformations.boxcox import LogTransformer
+    from aeon.transformations._legacy._boxcox import _LogTransformer as LogTransformer
     from aeon.transformations.compose import OptionalPassthrough
     from aeon.transformations.detrend import Detrender
 
diff --git a/aeon/testing/mock_estimators/tests/test_mock_forecasters.py b/aeon/testing/mock_estimators/tests/test_mock_forecasters.py
index 1fb1863a..f9041d9a 100644
--- a/aeon/testing/mock_estimators/tests/test_mock_forecasters.py
+++ b/aeon/testing/mock_estimators/tests/test_mock_forecasters.py
@@ -22,7 +22,7 @@ from aeon.testing.mock_estimators._mock_forecasters import (
 )
 from aeon.testing.utils.deep_equals import deep_equals
 from aeon.transformations.base import BaseTransformer
-from aeon.transformations.boxcox import BoxCoxTransformer
+from aeon.transformations.series._boxcox import BoxCoxTransformer
 
 y_series = load_airline().iloc[:-5]
 y_frame = y_series.to_frame()
diff --git a/aeon/testing/test_config.py b/aeon/testing/test_config.py
index e85bc825..bf43eac7 100644
--- a/aeon/testing/test_config.py
+++ b/aeon/testing/test_config.py
@@ -21,10 +21,10 @@ if os.environ.get("CICD_RUNNING") == "1":
     import aeon.testing.utils._cicd_numba_caching  # noqa: F401
 
 EXCLUDE_ESTIMATORS = [
-    "BoxCoxTransformer",
+    "_BoxCoxTransformer",
     "TimeBinAggregate",
     "PartialAutoCorrelationTransformer",
-    "LogTransformer",
+    "_LogTransformer",
     "ReverseAugmenter",
     "RandomSamplesAugmenter",
     "SqrtTransformer",
diff --git a/aeon/transformations/series/tests/test_boxcox.py b/aeon/transformations/series/tests/test_boxcox.py
index 1c263225..fa98c9ab 100644
--- a/aeon/transformations/series/tests/test_boxcox.py
+++ b/aeon/transformations/series/tests/test_boxcox.py
@@ -47,7 +47,7 @@ def test_guerrero_against_r_implementation(bounds, r_lambda):
 
     Testing lambda values estimated by the R implementation of the Guerrero method
     https://github.com/robjhyndman/forecast/blob/master/R/guerrero.R
-    against the guerrero method in BoxCoxTransformer.
+    against the guerrero method in _BoxCoxTransformer.
     R code to generate the hardcoded value for bounds=(-1, 2) used in the test
     ('Airline.csv' contains the data from 'load_airline()'):
         airline_file <- read.csv(file = 'Airline.csv')[,c('Passengers')]
diff --git a/aeon/transformations/tests/test_compose.py b/aeon/transformations/tests/test_compose.py
index 2e8e3e7e..c813bf2b 100644
--- a/aeon/transformations/tests/test_compose.py
+++ b/aeon/transformations/tests/test_compose.py
@@ -11,7 +11,7 @@ from aeon.testing.data_generation import get_examples
 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.boxcox import LogTransformer
+from aeon.transformations._legacy._boxcox import _LogTransformer
 from aeon.transformations.collection.pad import PaddingTransformer
 from aeon.transformations.compose import (
     ColumnConcatenator,
@@ -185,9 +185,9 @@ def test_pipeline_column_vectorization():
 def test_pipeline_inverse():
     """Tests that inverse composition works, with inverse skips. Also see #3084."""
     X = load_airline()
-    t = LogTransformer() * Imputer()
+    t = _LogTransformer() * Imputer()
 
-    # LogTransformer has inverse_transform, and does not skip inverse transform
+    # _LogTransformer has inverse_transform, and does not skip inverse transform
     # therefore, pipeline should also not skip inverse transform, and have capability
     assert t.get_tag("capability:inverse_transform")
     assert not t.get_tag("skip-inverse-transform")
diff --git a/aeon/transformations/tests/test_fitintransform.py b/aeon/transformations/tests/test_fitintransform.py
index b7994e37..c8c13510 100644
--- a/aeon/transformations/tests/test_fitintransform.py
+++ b/aeon/transformations/tests/test_fitintransform.py
@@ -10,7 +10,7 @@ from aeon.transformations.series._boxcox import BoxCoxTransformer
 
 
 def test_transform_fitintransform():
-    """Test fit/transform against BoxCoxTransformer."""
+    """Test fit/transform against _BoxCoxTransformer."""
     X_train = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
     X_test = np.array([5, 4, 5, 4, 5, 4, 5, 4, 5, 4])
     fitintransform = FitInTransform(BoxCoxTransformer())