Repository: aeon-toolkit/aeon
Pull request #1788 (2024-07-13 13:09:41)

[DEP] Remove BKFilter BaseTransformer, redesign equivalent series transformer
removes deprecated BaseTransformer BKFilter and adapts the BaseSeriesTransformer so it does not require statsmodels to do pointless conversions to and from data frames

part of #1611 

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

I have added the following labels to this PR based on the title: **[ $\\color{#FEF1BE}{\\textsf{enhancement}}$ ]**.
I would have added the following labels to this PR based on the changes made: **[ $\\color{#41A8F6}{\\textsf{transformations}}$ ]**, however some package labels are already present.

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

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

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

### PR CI actions

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

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

Patch:
diff --git a/.github/workflows/periodic_github_maintenace.yml b/.github/workflows/periodic_github_maintenace.yml
index 35092472..e004d224 100644
--- a/.github/workflows/periodic_github_maintenace.yml
+++ b/.github/workflows/periodic_github_maintenace.yml
@@ -23,7 +23,7 @@ jobs:
           private-key: ${{ secrets.PR_APP_KEY }}
 
       - name: Stale Branches
-        uses: crs-k/stale-branches@v5.0.1
+        uses: crs-k/stale-branches@v5.0.2
         with:
           repo-token: ${{ steps.app-token.outputs.token }}
           days-before-stale: 140
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 7117eebf..e0deeb27 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -1,21 +1,19 @@
-# This workflow uses actions that are not certified by GitHub. They are provided
-# by a third-party and are governed by separate terms of service, privacy
-# policy, and support documentation.
-
 name: Scorecard supply-chain security
+
 on:
   branch_protection_rule:
   schedule:
-    - cron: '30 1 * * 7'
+    - cron: '30 1 * * 6'
   push:
-    branches: [ "main" ]
+    branches: 
+      - main
 
 permissions: read-all
 
 jobs:
   analysis:
     name: Scorecard analysis
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     permissions:
       # Needed to upload the results to code-scanning dashboard.
       security-events: write
@@ -23,28 +21,26 @@ jobs:
       id-token: write
 
     steps:
-      - name: "Checkout code"
-        uses: actions/checkout@v4 # v4.1.1
+      - name: Checkout code
+        uses: actions/checkout@v4
         with:
           persist-credentials: false
 
-      - name: "Run analysis"
-        uses: ossf/scorecard-action@v4.13.1
+      - name: Run analysis
+        uses: ossf/scorecard-action@v2.3.3
         with:
           results_file: results.sarif
           results_format: sarif
           publish_results: true
 
-      # Upload the results as artifacts (optional).
-      - name: "Upload artifact"
-        uses: actions/upload-artifact@v3
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
         with:
           name: SARIF file
           path: results.sarif
           retention-days: 5
 
-      # Upload the results to GitHub's code scanning dashboard (optional).
-      - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@v2
+      - name: Upload to code-scanning
+        uses: github/codeql-action/upload-sarif@v3
         with:
           sarif_file: results.sarif
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 80afe396..2dec9901 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -29,7 +29,7 @@ repos:
         args: [ "--create", "--python-folders", "aeon" ]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.5.1
+    rev: v0.5.2
     hooks:
       - id: ruff
         args: [ "--fix"]
diff --git a/aeon/classification/dictionary_based/_tde.py b/aeon/classification/dictionary_based/_tde.py
index 21a33bff..cdeffc42 100644
--- a/aeon/classification/dictionary_based/_tde.py
+++ b/aeon/classification/dictionary_based/_tde.py
@@ -257,7 +257,7 @@ class TemporalDictionaryEnsemble(BaseClassifier):
         max_window_searches = self.n_timepoints_ / 4
         max_window = int(self.n_timepoints_ * self.max_win_len_prop)
 
-        if self.min_window >= max_window:
+        if self.min_window > max_window:
             self._min_window = max_window
             warnings.warn(
                 f"TemporalDictionaryEnsemble warning: min_window = "
diff --git a/aeon/forecasting/base/_base.py b/aeon/forecasting/base/_base.py
index 3a829ee0..23b1091e 100644
--- a/aeon/forecasting/base/_base.py
+++ b/aeon/forecasting/base/_base.py
@@ -124,7 +124,7 @@ class BaseForecaster(BaseEstimator):
             not nested, contains only non-TransformerPipeline `aeon` transformers
         """
         from aeon.forecasting.compose import TransformedTargetForecaster
-        from aeon.transformations.adapt import TabularToSeriesAdaptor
+        from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
         from aeon.transformations.base import BaseTransformer
         from aeon.utils.sklearn import is_sklearn_transformer
 
@@ -155,7 +155,7 @@ class BaseForecaster(BaseEstimator):
             not nested, contains only non-TransformerPipeline `aeon` steps
         """
         from aeon.forecasting.compose import TransformedTargetForecaster
-        from aeon.transformations.adapt import TabularToSeriesAdaptor
+        from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
         from aeon.transformations.base import BaseTransformer
         from aeon.utils.sklearn import is_sklearn_transformer
 
@@ -186,7 +186,7 @@ class BaseForecaster(BaseEstimator):
             not nested, contains only non-TransformerPipeline `aeon` steps
         """
         from aeon.forecasting.compose import ForecastingPipeline
-        from aeon.transformations.adapt import TabularToSeriesAdaptor
+        from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
         from aeon.transformations.base import BaseTransformer
         from aeon.utils.sklearn import is_sklearn_transformer
 
diff --git a/aeon/forecasting/compose/_pipeline.py b/aeon/forecasting/compose/_pipeline.py
index 825d8512..021c5b61 100644
--- a/aeon/forecasting/compose/_pipeline.py
+++ b/aeon/forecasting/compose/_pipeline.py
@@ -240,7 +240,7 @@ class _Pipeline(_HeterogenousMetaEstimator, BaseForecaster):
         from aeon.forecasting.compose._reduce import DirectReductionForecaster
         from aeon.forecasting.naive import NaiveForecaster
         from aeon.testing.mock_estimators import MockTransformer
-        from aeon.transformations.adapt import TabularToSeriesAdaptor
+        from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
         from aeon.transformations.detrend import Detrender
 
         # StandardScaler does not skip fit, NaiveForecaster is not probabilistic
@@ -325,46 +325,6 @@ class ForecastingPipeline(_Pipeline):
         strings not passed in `steps` are replaced by unique generated strings
         i-th transformer in `steps_` is clone of i-th in `steps`
     forecaster_ : estimator, reference to the unique forecaster in steps_
-
-    Examples
-    --------
-    >>> from aeon.datasets import load_longley
-    >>> from aeon.forecasting.naive import NaiveForecaster
-    >>> from aeon.forecasting.compose import ForecastingPipeline
-    >>> from aeon.transformations.adapt import TabularToSeriesAdaptor
-    >>> from aeon.transformations.impute import Imputer
-    >>> from aeon.forecasting.base import ForecastingHorizon
-    >>> from aeon.forecasting.model_selection import temporal_train_test_split
-    >>> from sklearn.preprocessing import MinMaxScaler
-    >>> y, X = load_longley()
-    >>> y_train, _, X_train, X_test = temporal_train_test_split(y, X)
-    >>> fh = ForecastingHorizon(X_test.index, is_relative=False)
-
-        Example 1: string/estimator pairs
-    >>> pipe = ForecastingPipeline(steps=[
-    ...     ("imputer", Imputer(method="mean")),
-    ...     ("minmaxscaler", TabularToSeriesAdaptor(MinMaxScaler())),
-    ...     ("forecaster", NaiveForecaster(strategy="drift")),
-    ... ])
-    >>> pipe.fit(y_train, X_train)
-    ForecastingPipeline(...)
-    >>> y_pred = pipe.predict(fh=fh, X=X_test)
-
-        Example 2: without strings
-    >>> pipe = ForecastingPipeline([
-    ...     Imputer(method="mean"),
-    ...     TabularToSeriesAdaptor(MinMaxScaler()),
-    ...     NaiveForecaster(strategy="drift"),
-    ... ])
-
-        Example 3: using the dunder method
-        Note: * (= apply to `y`) has precedence over ** (= apply to `X`)
-    >>> forecaster = NaiveForecaster(strategy="drift")
-    >>> imputer = Imputer(method="mean")
-    >>> pipe = (imputer * MinMaxScaler()) ** forecaster
-
-        Example 3b: using the dunder method, alternative
-    >>> pipe = imputer ** MinMaxScaler() ** forecaster
     """
 
     _tags = {
diff --git a/aeon/transformations/adapt.py b/aeon/transformations/_legacy/adapt.py
similarity index 60%
rename from aeon/transformations/adapt.py
rename to aeon/transformations/_legacy/adapt.py
index 6445e262..0ada1eba 100644
--- a/aeon/transformations/adapt.py
+++ b/aeon/transformations/_legacy/adapt.py
@@ -4,18 +4,11 @@ __maintainer__ = []
 __all__ = ["TabularToSeriesAdaptor"]
 
 import numpy as np
-from deprecated.sphinx import deprecated
 from sklearn.base import clone
 
 from aeon.transformations.base import BaseTransformer
 
 
-# TODO: remove in v0.11.0
-@deprecated(
-    version="0.10.0",
-    reason="TabularToSeriesAdaptor will be removed in version 0.11.0.",
-    category=FutureWarning,
-)
 class TabularToSeriesAdaptor(BaseTransformer):
     """
     Adapt scikit-learn transformation interface to time series setting.
@@ -80,15 +73,6 @@ class TabularToSeriesAdaptor(BaseTransformer):
         Whether transformer_ should be fitted in transform (True), or in fit (False)
         recommended setting in forecasting (single series or hierarchical): False.
         recommended setting in classification, regression, clustering: True.
-
-    Examples
-    --------
-    >>> from aeon.transformations.adapt import TabularToSeriesAdaptor
-    >>> from sklearn.preprocessing import MinMaxScaler
-    >>> from aeon.datasets import load_airline
-    >>> y = load_airline()
-    >>> transformer = TabularToSeriesAdaptor(MinMaxScaler())
-    >>> y_hat = transformer.fit_transform(y)
     """
 
     _tags = {
@@ -224,142 +208,3 @@ class TabularToSeriesAdaptor(BaseTransformer):
         params2 = {"transformer": StandardScaler(), "fit_in_transform": True}
 
         return [params1, params2]
-
-
-# TODO: remove in v0.11.0
-@deprecated(
-    version="0.10.0",
-    reason="PandasTransformAdaptor will be removed in version 0.11.0.",
-    category=FutureWarning,
-)
-class PandasTransformAdaptor(BaseTransformer):
-    """
-    Adapt pandas transformations to aeon interface.
-
-    In `transform`, executes `pd.DataFrame` method of name `method` on data,
-    optionally with keywords arguments passed, via `kwargs` hyper-parameter.
-    The `apply_to` parameter controls what the data is upon which `method` is called:
-    "call" = for `X` seen in `transform`, "all"/"all_subset" = all data seen so far.
-    See below for details.
-
-    For hierarchical series, operation is applied by instance.
-
-    Parameters
-    ----------
-    method : str, optional, default = None = identity transform
-        Name of the method of DataFrame that is applied in transform.
-    kwargs : dict, optional, default = empty dict (no kwargs passed to method)
-        Arguments passed to DataFrame.method.
-    apply_to : str, one of "call", "all", "all_subset", optional, default = "call"
-        "call" = method is applied to `X` seen in transform only
-        "all" = method is applied to all `X` seen in `fit`, `update`, `transform`
-            more precisely, the application to `self._X` is returned
-        "all_subset" = method is applied to all `X` like for "all" value,
-            but before returning, result is sub-set to indices of `X` in `transform`
-        in "all", "all_subset", `X` seen in `transform` do not update `self._X`.
-    """
-
-    _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": "pd.DataFrame",
-        "y_inner_type": "None",
-        "capability:multivariate": True,
-        "transform-returns-same-time-index": False,
-        "fit_is_empty": False,
-        "capability:inverse_transform": False,
-        "remember_data": False,
-    }
-
-    def __init__(self, method, kwargs=None, apply_to="call"):
-        self.method = method
-        self.kwargs = kwargs
-        self.apply_to = apply_to
-
-        if not isinstance(apply_to, str):
-            raise TypeError(
-                f"apply_to parameter must be a str, but found {type(apply_to)}"
-            )
-        if apply_to not in ["call", "all", "all_subset"]:
-            raise ValueError(
-                'apply_to must be one of "call", "all", "all_subset", '
-                f'but found "{apply_to}"'
-            )
-
-        super().__init__()
-
-        if apply_to in ["all", "all_subset"]:
-            self.set_tags(**{"remember_data": True})
-
-        if apply_to == "all_subset":
-            self.set_tags(**{"transform-returns-same-time-index": True})
-
-        if apply_to == "call":
-            self.set_tags(**{"fit_is_empty": True})
-
-    def _transform(self, X, y=None):
-        """Transform X and return a transformed version.
-
-        private _transform containing the core logic, called from transform
-
-        Parameters
-        ----------
-        X : pd.DataFrame
-            Data to be transformed
-        y : ignored argument for interface compatibility
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        Xt : pd.DataFrame
-            transformed version of X
-        """
-        apply_to = self.apply_to
-        method = self.method
-        kwargs = self.kwargs
-
-        if kwargs is None:
-            kwargs = {}
-
-        if apply_to in ["all", "all_subset"]:
-            _X = X.combine_first(self._X)
-        else:
-            _X = X
-
-        Xt = getattr(_X, method)(**kwargs)
-
-        if apply_to in ["all_subset"]:
-            Xt = Xt.loc[X.index]
-
-        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.
-
-        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 = {"method": "diff"}
-        params2 = {"method": "diff", "kwargs": {"periods": 2}, "apply_to": "all_subset"}
-        params3 = {
-            "method": "shift",
-            "kwargs": {"periods": 12},
-            "apply_to": "all",
-        }
-
-        return [params1, params2, params3]
diff --git a/aeon/transformations/bkfilter.py b/aeon/transformations/bkfilter.py
deleted file mode 100644
index 0071de97..00000000
--- a/aeon/transformations/bkfilter.py
+++ /dev/null
@@ -1,147 +0,0 @@
-"""
-Implements Baxter-King bandpass filter transformation.
-
-Please see the original library
-(https://github.com/statsmodels/statsmodels/blob/main/statsmodels/tsa/filters/bk_filter.py)
-"""
-
-__maintainer__ = []
-__all__ = ["BKFilter"]
-
-
-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="BKFilter will be removed in version 0.10 and replaced with a "
-    "BaseSeriesTransformer version in the transformations.series module.",
-    category=FutureWarning,
-)
-class BKFilter(BaseTransformer):
-    """Filter a times series using the Baxter-King filter.
-
-    This is a wrapper around statsmodels' bkfilter function
-    (see 'sm.tsa.filters.bk_filter.bkfilter').
-
-    The Baxter-King filter is intended for economic and econometric time series
-    data and deals with the periodicity of the business cycle. Applying their
-    band-pass filter to a series will produce a new series that does not contain
-    fluctuations at a higher or lower frequency than those of the business cycle.
-    Baxter-King follow Burns and Mitchell's work on business cycles, which suggests
-    that U.S. business cycles typically last from 1.5 to 8 years.
-
-    Parameters
-    ----------
-    low : float
-        Minimum period for oscillations. Baxter and King recommend a value of 6
-        for quarterly data and 1.5 for annual data.
-
-    high : float
-        Maximum period for oscillations. BK recommend 32 for U.S. business cycle
-        quarterly data and 8 for annual data.
-
-    K : int
-        Lead-lag length of the filter. Baxter and King suggest a truncation
-        length of 12 for quarterly data and 3 for annual data.
-
-    Notes
-    -----
-    Returns a centered weighted moving average of the original series.
-
-    References
-    ----------
-    Baxter, M. and R. G. King. "Measuring Business Cycles: Approximate
-        Band-Pass Filters for Economic Time Series." *Review of Economics and
-        Statistics*, 1999, 81(4), 575-593.
-    """
-
-    _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,
-        "capability:multivariate": True,
-        "X_inner_type": "np.ndarray",
-        "y_inner_type": "None",
-        "enforce_index_type": [pd.RangeIndex],
-        "fit_is_empty": True,
-        "transform-returns-same-time-index": False,
-        "capability:unequal_length": True,
-        "capability:missing_values": False,
-        "remember_data": False,
-        "python_dependencies": "statsmodels",
-    }
-
-    def __init__(
-        self,
-        low=6,
-        high=32,
-        K=12,
-    ):
-        self.low = low
-        self.high = high
-        self.K = K
-        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 : array_like
-        A 1 or 2d ndarray. If 2d, variables are assumed to be in columns.
-
-        Returns
-        -------
-        transformed cyclical version of X
-        """
-        from scipy.signal import fftconvolve
-        from statsmodels.tools.validation import PandasWrapper, array_like
-
-        pw = PandasWrapper(X)
-        X = array_like(X, "X", maxdim=2)
-        omega_1 = 2.0 * np.pi / self.high
-        omega_2 = 2.0 * np.pi / self.low
-        bweights = np.zeros(2 * self.K + 1)
-        bweights[self.K] = (omega_2 - omega_1) / np.pi
-        j = np.arange(1, int(self.K) + 1)
-        weights = 1 / (np.pi * j) * (np.sin(omega_2 * j) - np.sin(omega_1 * j))
-        bweights[self.K + j] = weights
-        bweights[: self.K] = weights[::-1]
-        bweights -= bweights.mean()
-        if X.ndim == 2:
-            bweights = bweights[:, None]
-        X = fftconvolve(X, bweights, mode="valid")
-
-        return pw.wrap(X, append="cycle", trim_start=self.K, trim_end=self.K)
-
-    @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 = {"low": 6, "high": 24, "K": 12}
-        return params
diff --git a/aeon/transformations/compose.py b/aeon/transformations/compose.py
index 503025f1..500f764d 100644
--- a/aeon/transformations/compose.py
+++ b/aeon/transformations/compose.py
@@ -34,7 +34,7 @@ from aeon.utils import ALL_TIME_SERIES_TYPES
 
 def _coerce_to_aeon(other):
     """Check and format inputs to dunders for compose."""
-    from aeon.transformations.adapt import TabularToSeriesAdaptor
+    from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
 
     # if sklearn transformer, adapt to aeon transformer first
     if is_sklearn_transformer(other):
diff --git a/aeon/transformations/series/__init__.py b/aeon/transformations/series/__init__.py
index 34279718..1f71c6f5 100644
--- a/aeon/transformations/series/__init__.py
+++ b/aeon/transformations/series/__init__.py
@@ -10,6 +10,7 @@ __all__ = [
     "StatsModelsACF",
     "StatsModelsPACF",
     "ThetaTransformer",
+    "BKFilter",
 ]
 
 from aeon.transformations.series._acf import (
@@ -17,6 +18,7 @@ from aeon.transformations.series._acf import (
     StatsModelsACF,
     StatsModelsPACF,
 )
+from aeon.transformations.series._bkfilter import BKFilter
 from aeon.transformations.series._clasp import ClaSPTransformer
 from aeon.transformations.series._clear_sky import ClearSkyTransformer
 from aeon.transformations.series._dobin import Dobin
diff --git a/aeon/transformations/series/_bkfilter.py b/aeon/transformations/series/_bkfilter.py
index 430d4dd5..2b7c737a 100644
--- a/aeon/transformations/series/_bkfilter.py
+++ b/aeon/transformations/series/_bkfilter.py
@@ -1,32 +1,22 @@
-"""
-Implements Baxter-King bandpass filter transformation.
+"""Baxter-King bandpass filter transformation."""
 
-Please see the original library
-(https://github.com/statsmodels/statsmodels/blob/main/statsmodels/tsa/filters/bk_filter.py)
-"""
-
-__maintainer__ = []
+__maintainer__ = ["TonyBagnall"]
 __all__ = ["BKFilter"]
 
 
 import numpy as np
 
 from aeon.transformations.series.base import BaseSeriesTransformer
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class BKFilter(BaseSeriesTransformer):
     """Filter a times series using the Baxter-King filter.
 
-    This is a wrapper around statsmodels' bkfilter function
-    (see 'sm.tsa.filters.bk_filter.bkfilter').
+    The Baxter-King filter from econometrics  that uses a band pass filter to
+    removes the cycle component (seasonality) from the time series based on weighted
+    moving average with specified weights. It removes high or low frequency
+    patterns and returns a centred weighted moving average of the original series.
 
-    The Baxter-King filter is intended for economic and econometric time series
-    data and deals with the periodicity of the business cycle. Applying their
-    band-pass filter to a series will produce a new series that does not contain
-    fluctuations at a higher or lower frequency than those of the business cycle.
-    Baxter-King follow Burns and Mitchell's work on business cycles, which suggests
-    that U.S. business cycles typically last from 1.5 to 8 years.
 
     Parameters
     ----------
@@ -44,7 +34,8 @@ class BKFilter(BaseSeriesTransformer):
 
     Notes
     -----
-    Returns a centered weighted moving average of the original series.
+    Adapted from statsmodels implementation
+    https://github.com/statsmodels/statsmodels/blob/main/statsmodels/tsa/filters/bk_filter.py
 
     References
     ----------
@@ -54,22 +45,19 @@ class BKFilter(BaseSeriesTransformer):
 
     Examples
     --------
-    >>> from aeon.transformations.bkfilter import BKFilter # doctest: +SKIP
-    >>> import pandas as pd # doctest: +SKIP
-    >>> import statsmodels.api as sm # doctest: +SKIP
-    >>> dta = sm.datasets.macrodata.load_pandas().data # doctest: +SKIP
-    >>> index = pd.date_range(start='1959Q1', end='2009Q4', freq='Q') # doctest: +SKIP
-    >>> dta.set_index(index, inplace=True) # doctest: +SKIP
-    >>> bk = BKFilter(6, 24, 12) # doctest: +SKIP
-    >>> cycles = bk.fit_transform(X=dta[['realinv']]) # doctest: +SKIP
+    >>> import numpy as np
+    >>> from aeon.transformations.series._bkfilter import BKFilter
+    >>> X = np.random.random((1,100)) # Random series length 100
+    >>> bk = BKFilter()
+    >>> X2 = bk.fit_transform(X)
+    >>> X2.shape
+    (1, 76)
     """
 
     _tags = {
         "capability:multivariate": True,
         "X_inner_type": "np.ndarray",
         "fit_is_empty": True,
-        "capability:unequal_length": True,
-        "python_dependencies": "statsmodels",
     }
 
     def __init__(
@@ -90,19 +78,15 @@ class BKFilter(BaseSeriesTransformer):
 
         Parameters
         ----------
-        X : array_like
-        A 1 or 2d ndarray. If 2d, variables are assumed to be in columns.
+        X : np.ndarray
+            time series in shape (n_channels, n_timepoints)
 
         Returns
         -------
         transformed cyclical version of X
         """
-        _check_soft_dependencies("statsmodels", severity="error")
         from scipy.signal import fftconvolve
-        from statsmodels.tools.validation import PandasWrapper, array_like
 
-        pw = PandasWrapper(X)
-        X = array_like(X, "X", maxdim=2)
         omega_1 = 2.0 * np.pi / self.high
         omega_2 = 2.0 * np.pi / self.low
         bweights = np.zeros(2 * self.K + 1)
@@ -114,9 +98,9 @@ class BKFilter(BaseSeriesTransformer):
         bweights -= bweights.mean()
         if X.ndim == 2:
             bweights = bweights[:, None]
-        X = fftconvolve(X, bweights, mode="valid")
+        XTr = fftconvolve(X, bweights, mode="valid")
 
-        return pw.wrap(X, append="cycle", trim_start=self.K, trim_end=self.K)
+        return XTr
 
     @classmethod
     def get_test_params(cls, parameter_set="default"):
diff --git a/docs/api_reference/transformations.rst b/docs/api_reference/transformations.rst
index 24fd52cd..eae5c0f4 100644
--- a/docs/api_reference/transformations.rst
+++ b/docs/api_reference/transformations.rst
@@ -248,7 +248,7 @@ Detrending
 Filtering and denoising
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-.. currentmodule:: aeon.transformations.bkfilter
+.. currentmodule:: aeon.transformations.series._bkfilter
 
 .. autosummary::
     :toctree: auto_generated/
diff --git a/pyproject.toml b/pyproject.toml
index 67543712..1ebe7a58 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -83,7 +83,7 @@ all_extras = [
     "pyod>=1.1.3",
 
     # dependency of tensorflow, see issue #1724
-    "keras<3.4",
+    "keras<3.4.0",
     # for some reason, a heirarchical dask test fails on some Python versions with the
     # later versions of pyarrow and dask
     "dask[dataframe]<2024.3.0; python_version < '3.11'",
@@ -92,13 +92,13 @@ all_extras = [
 ]
 dl = [
     "keras-self-attention",
+    "tensorflow>=2.14; python_version < '3.12'",
     # dependency of tensorflow, see issue #1724
-    "keras<3.4",
-    "tensorflow>=2.12; python_version < '3.12'",
+    "keras<3.4.0",
 ]
 unstable_extras = [
-    "mrsqm>=0.0.1,<0.1.0; platform_system == 'Darwin' and python_version < '3.12'",  # requires gcc and fftw to be installed for Windows and some other OS (see http://www.fftw.org/index.html)
-    "pycatch22<=0.4.3",  # known to fail installation on some setups
+    "mrsqm>=0.0.7,<0.1.0; platform_system != 'Windows' and python_version < '3.12'",  # requires gcc and fftw to be installed for Windows and some other OS (see http://www.fftw.org/index.html)
+    "pycatch22",  # known to fail installation on some setups
     "pyfftw>=0.12.0; python_version < '3.12'",  # requires fftw to be installed for Windows and some other OS (see http://www.fftw.org/index.html)
 ]
 dev = [


Test patch:
diff --git a/aeon/classification/compose/tests/test_pipeline.py b/aeon/classification/compose/tests/test_pipeline.py
index 71db149c..b9b532cc 100644
--- a/aeon/classification/compose/tests/test_pipeline.py
+++ b/aeon/classification/compose/tests/test_pipeline.py
@@ -17,7 +17,6 @@ from aeon.testing.data_generation import (
 )
 from aeon.testing.mock_estimators import MockCollectionTransformer
 from aeon.testing.utils.estimator_checks import _assert_array_almost_equal
-from aeon.transformations.adapt import TabularToSeriesAdaptor
 from aeon.transformations.collection import (
     AutocorrelationFunctionTransformer,
     HOG1DTransformer,
@@ -33,12 +32,10 @@ from aeon.transformations.collection.feature_based import SevenNumberSummaryTran
     [
         PaddingTransformer(pad_length=15),
         SevenNumberSummaryTransformer(),
-        TabularToSeriesAdaptor(StandardScaler()),
         [PaddingTransformer(pad_length=15), Tabularizer(), StandardScaler()],
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
@@ -78,7 +75,6 @@ def test_classifier_pipeline(transformers):
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
diff --git a/aeon/classification/hybrid/tests/test_hc.py b/aeon/classification/hybrid/tests/test_hc.py
index 8feae930..5e50cb6b 100644
--- a/aeon/classification/hybrid/tests/test_hc.py
+++ b/aeon/classification/hybrid/tests/test_hc.py
@@ -1,5 +1,6 @@
 """Tests for HIVE-COTE."""
 
+import numpy as np
 import pytest
 
 from aeon.classification.hybrid import HIVECOTEV1, HIVECOTEV2
@@ -38,7 +39,8 @@ def test_hc2_defaults_and_verbosity():
     HIVECOTEV2._DEFAULT_N_PARA_SAMPLES = 10
     HIVECOTEV2._DEFAULT_MAX_ENSEMBLE_SIZE = 5
     HIVECOTEV2._DEFAULT_RAND_PARAMS = 5
-    X, y = make_example_3d_numpy(n_cases=20, n_timepoints=10, n_labels=2)
+    X, _ = make_example_3d_numpy(n_cases=20, n_timepoints=20, n_labels=2)
+    y = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
     hc2 = HIVECOTEV2(verbose=True)
     hc2.fit(X, y)
     assert hc2._stc_params == {"n_shapelet_samples": 10}
diff --git a/aeon/clustering/compose/tests/test_pipeline.py b/aeon/clustering/compose/tests/test_pipeline.py
index 935538f3..a0e0b0c5 100644
--- a/aeon/clustering/compose/tests/test_pipeline.py
+++ b/aeon/clustering/compose/tests/test_pipeline.py
@@ -15,7 +15,6 @@ from aeon.testing.data_generation import (
 )
 from aeon.testing.mock_estimators import MockCollectionTransformer
 from aeon.testing.utils.estimator_checks import _assert_array_almost_equal
-from aeon.transformations.adapt import TabularToSeriesAdaptor
 from aeon.transformations.collection import (
     AutocorrelationFunctionTransformer,
     HOG1DTransformer,
@@ -31,12 +30,10 @@ from aeon.transformations.collection.feature_based import SevenNumberSummaryTran
     [
         PaddingTransformer(pad_length=15),
         SevenNumberSummaryTransformer(),
-        TabularToSeriesAdaptor(StandardScaler()),
         [PaddingTransformer(pad_length=15), Tabularizer(), StandardScaler()],
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
@@ -76,7 +73,6 @@ def test_clusterer_pipeline(transformers):
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
diff --git a/aeon/forecasting/compose/tests/test_pipeline.py b/aeon/forecasting/compose/tests/test_pipeline.py
index baf8fec7..56188eae 100644
--- a/aeon/forecasting/compose/tests/test_pipeline.py
+++ b/aeon/forecasting/compose/tests/test_pipeline.py
@@ -18,7 +18,7 @@ from aeon.forecasting.trend import PolynomialTrendForecaster
 from aeon.testing.data_generation._legacy import get_examples, make_series
 from aeon.testing.mock_estimators import MockForecaster, MockTransformer
 from aeon.testing.utils.estimator_checks import _assert_array_almost_equal
-from aeon.transformations.adapt import TabularToSeriesAdaptor
+from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
 from aeon.transformations.detrend import Detrender
 from aeon.transformations.hierarchical.aggregate import Aggregator
 from aeon.transformations.impute import Imputer
diff --git a/aeon/regression/compose/tests/test_pipeline.py b/aeon/regression/compose/tests/test_pipeline.py
index e10905a4..7f91f921 100644
--- a/aeon/regression/compose/tests/test_pipeline.py
+++ b/aeon/regression/compose/tests/test_pipeline.py
@@ -17,7 +17,6 @@ from aeon.testing.data_generation import (
 )
 from aeon.testing.mock_estimators import MockCollectionTransformer
 from aeon.testing.utils.estimator_checks import _assert_array_almost_equal
-from aeon.transformations.adapt import TabularToSeriesAdaptor
 from aeon.transformations.collection import (
     AutocorrelationFunctionTransformer,
     HOG1DTransformer,
@@ -33,12 +32,10 @@ from aeon.transformations.collection.feature_based import SevenNumberSummaryTran
     [
         PaddingTransformer(pad_length=15),
         SevenNumberSummaryTransformer(),
-        TabularToSeriesAdaptor(StandardScaler()),
         [PaddingTransformer(pad_length=15), Tabularizer(), StandardScaler()],
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
@@ -78,7 +75,6 @@ def test_regressor_pipeline(transformers):
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
diff --git a/aeon/transformations/collection/compose/tests/test_pipeline.py b/aeon/transformations/collection/compose/tests/test_pipeline.py
index ca484362..9e1838ef 100644
--- a/aeon/transformations/collection/compose/tests/test_pipeline.py
+++ b/aeon/transformations/collection/compose/tests/test_pipeline.py
@@ -11,7 +11,6 @@ from aeon.testing.data_generation import (
 )
 from aeon.testing.mock_estimators import MockCollectionTransformer
 from aeon.testing.utils.estimator_checks import _assert_array_almost_equal
-from aeon.transformations.adapt import TabularToSeriesAdaptor
 from aeon.transformations.collection import (
     AutocorrelationFunctionTransformer,
     HOG1DTransformer,
@@ -28,12 +27,10 @@ from aeon.transformations.collection.feature_based import SevenNumberSummaryTran
     [
         PaddingTransformer(pad_length=15),
         SevenNumberSummaryTransformer(),
-        [TabularToSeriesAdaptor(StandardScaler())],
         [PaddingTransformer(pad_length=15), Tabularizer(), StandardScaler()],
         [PaddingTransformer(pad_length=15), SevenNumberSummaryTransformer()],
         [Tabularizer(), StandardScaler(), SevenNumberSummaryTransformer()],
         [
-            TabularToSeriesAdaptor(StandardScaler()),
             PaddingTransformer(pad_length=15),
             SevenNumberSummaryTransformer(),
         ],
diff --git a/aeon/transformations/series/tests/test_bkfilter.py b/aeon/transformations/series/tests/test_bkfilter.py
index 0a4f945a..31cf692a 100644
--- a/aeon/transformations/series/tests/test_bkfilter.py
+++ b/aeon/transformations/series/tests/test_bkfilter.py
@@ -2,28 +2,96 @@
 
 __maintainer__ = []
 
-import pandas as pd
-import pytest
-from numpy import array_equal
+import numpy as np
 
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
+def test_bkilter():
+    """Verify that the wrapped BKFilter estimator agrees with statsmodel.
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("statsmodels", severity="none"),
-    reason="skip test if required soft dependency for statsmodels.api not available",
-)
-def test_BKFilter_wrapper():
-    """Verify that the wrapped BKFilter estimator agrees with statsmodel."""
-    # moved all potential soft dependency import inside the test:
-    import statsmodels.api as sm
-
-    from aeon.transformations.series._bkfilter import BKFilter
+    Expected values generated with statsmodels with this code:
 
+    import statsmodels.api as sm
+    import pandas as pd
     dta = sm.datasets.macrodata.load_pandas().data
-    index = pd.date_range(start="1959Q1", end="2009Q4", freq="Q")
+    index = pd.Index(sm.tsa.datetools.dates_from_range("1959Q1", "2009Q3"))
     dta.set_index(index, inplace=True)
-    sm_cycles = sm.tsa.filters.bkfilter(dta[["realinv"]], 6, 24, 12)
-    bk = BKFilter(6, 24, 12)
-    sk_cycles = bk.fit_transform(X=dta[["realinv"]], axis=0)
-    assert array_equal(sm_cycles, sk_cycles)
+    dta.index = index
+    del dta["year"]
+    del dta["quarter"]
+    print(type(dta[['realinv']]))
+    print(dta[['realinv']].shape)
+    X = dta[['realinv']].head(40).to_numpy()
+    """
+    X = np.array(
+        [
+            [
+                286.89800,
+                310.85900,
+                289.22600,
+                299.35600,
+                331.72200,
+                298.15200,
+                296.37500,
+                259.76400,
+                266.40500,
+                286.24600,
+                310.22700,
+                315.46300,
+                334.27100,
+                331.03900,
+                336.96200,
+                325.65000,
+                343.72100,
+                348.73000,
+                360.10200,
+                364.53400,
+                379.52300,
+                377.77800,
+                386.75400,
+                389.91000,
+                429.14500,
+                429.11900,
+                444.44400,
+                446.49300,
+                484.24400,
+                475.40800,
+                470.69700,
+                472.95700,
+                460.00700,
+                440.39300,
+                453.03300,
+                462.83400,
+                472.90700,
+                492.02600,
+                476.05300,
+                480.99800,
+            ]
+        ]
+    )
+    expected = np.array(
+        [
+            [
+                7.32081,
+                2.88691,
+                -6.81898,
+                -13.49436,
+                -13.27936,
+                -9.40591,
+                -5.69109,
+                -5.13308,
+                -7.27347,
+                -9.24337,
+                -8.48292,
+                -4.44776,
+                2.40656,
+                10.68433,
+                19.46414,
+                28.09749,
+            ]
+        ]
+    )
+    from aeon.transformations.series._bkfilter import BKFilter
+
+    bk = BKFilter()
+    X2 = bk.fit_transform(X)
+    np.testing.assert_almost_equal(expected, X2, decimal=4)
diff --git a/aeon/transformations/tests/test_optionalpassthrough.py b/aeon/transformations/tests/test_optionalpassthrough.py
index 0395fbd8..e6792d27 100644
--- a/aeon/transformations/tests/test_optionalpassthrough.py
+++ b/aeon/transformations/tests/test_optionalpassthrough.py
@@ -23,7 +23,7 @@ def test_optionalpassthrough():
         SlidingWindowSplitter,
     )
     from aeon.forecasting.naive import NaiveForecaster
-    from aeon.transformations.adapt import TabularToSeriesAdaptor
+    from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
     from aeon.transformations.compose import OptionalPassthrough
     from aeon.transformations.detrend import Deseasonalizer
 
diff --git a/aeon/transformations/tests/test_pipeline.py b/aeon/transformations/tests/test_pipeline.py
index a7acf416..bc8dfb6f 100644
--- a/aeon/transformations/tests/test_pipeline.py
+++ b/aeon/transformations/tests/test_pipeline.py
@@ -7,7 +7,7 @@ from sklearn.preprocessing import FunctionTransformer
 from sklearn.tree import DecisionTreeClassifier
 
 from aeon.testing.data_generation import make_example_nested_dataframe
-from aeon.transformations.adapt import TabularToSeriesAdaptor
+from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
 from aeon.transformations.collection.segment import RandomIntervalSegmenter
 
 # load data
diff --git a/aeon/transformations/tests/test_sklearn_adaptor.py b/aeon/transformations/tests/test_sklearn_adaptor.py
index 09d56ce9..3ed06d25 100644
--- a/aeon/transformations/tests/test_sklearn_adaptor.py
+++ b/aeon/transformations/tests/test_sklearn_adaptor.py
@@ -7,7 +7,7 @@ from scipy.stats import boxcox
 from sklearn.preprocessing import PowerTransformer
 
 from aeon.datasets import load_airline
-from aeon.transformations.adapt import TabularToSeriesAdaptor
+from aeon.transformations._legacy.adapt import TabularToSeriesAdaptor
 
 
 def test_boxcox_transform():