Repository: aeon-toolkit/aeon
Pull request #913 (2023-11-20 15:46:17)

[ENH] Update `pandas` version
Updates the version of the `pandas` dependency. Part of https://github.com/aeon-toolkit/aeon/pull/909, cut down to make it a bit smaller.

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}}$ ]**.


The [Checks](https://github.com/aeon-toolkit/aeon/pull/913/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!
    
MatthewMiddlehurst: benching this for now. would rather start the module from scratch than delve into it to perform random repairs.

Patch:
diff --git a/aeon/transformations/base.py b/aeon/transformations/base.py
index cdc3f55a..a4e8d693 100644
--- a/aeon/transformations/base.py
+++ b/aeon/transformations/base.py
@@ -1,71 +1,11 @@
-"""
-Base class template for transformers.
-
-    class name: BaseTransformer
-
-Covers all types of transformers.
-Type and behaviour of transformer is determined by the following tags:
-    "input_data_type" tag with values "Primitives" or "Series"
-        this determines expected type of input of transform
-        if "Primitives", expected inputs X are pd.DataFrame
-        if "Series", expected inputs X are Series or Panel
-        Note: placeholder tag for upwards compatibility currently only "Series" is
-        supported
-    "output_data_type" tag with values "Primitives", or "Series"
-        this determines type of output of transform
-        if "Primitives", output is pd.DataFrame with as many rows as X has instances
-        i-th instance of X is transformed into i-th row of output
-        if "Series", output is a Series or Panel, with as many instances as X i-th
-        instance of X is transformed into i-th instance of output
-        Series are treated as one-instance-Panels
-        if Series is input, output is a 1-row pd.DataFrame or a Series
-    "instancewise" tag which is boolean
-        if True, fit/transform is statistically independent by instance
-
-Class defining methods:
-    fitting         - fit(self, X, y=None)
-    transform       - transform(self, X, y=None)
-    fit&transform   - fit_transform(self, X, y=None)
-    updating        - update(self, X, y=None)
-
-Inspection methods:
-    hyper-parameter inspection  - get_params()
-    fitted parameter inspection - get_fitted_params()
-
-State:
-    fitted model/strategy   - by convention, any attributes ending in "_"
-    fitted state flag       - is_fitted (property)
-    fitted state inspection - check_is_fitted()
-"""
-
-__maintainer__ = []
-__all__ = [
-    "BaseTransformer",
-]
-
-from itertools import product
-from typing import Union
-
-import numpy as np
-import pandas as pd
+"""Base class for transformers."""
 
-from aeon.base import BaseEstimator
-from aeon.utils.validation._dependencies import _check_estimator_deps
-
-# single/multiple primitives
-Primitive = Union[np.integer, int, float, str]
-Primitives = np.ndarray
-
-# tabular/cross-sectional data
-Tabular = Union[pd.DataFrame, np.ndarray]  # 2d arrays
+__maintainer__ = ["TonyBagnall"]
+__all__ = ["BaseTransformer"]
 
-# univariate/multivariate series
-UnivariateSeries = Union[pd.Series, np.ndarray]
-MultivariateSeries = Union[pd.DataFrame, np.ndarray]
-Series = Union[UnivariateSeries, MultivariateSeries]
 
-# panel/longitudinal/series-as-features data
-Panel = Union[pd.DataFrame, np.ndarray]  # 3d or nested array
+from aeon.base import BaseEstimator
+from aeon.utils.validation._dependencies import _check_estimator_deps
 
 
 def _coerce_to_list(obj):
@@ -85,10 +25,6 @@ class BaseTransformer(BaseEstimator):
         "output_data_type": "Series",
         "transform_labels": "None",
         "instancewise": True,
-        "capability:multivariate": True,  # can the transformer handle multivariate X?
-        "X_inner_type": "pd.DataFrame",
-        # this can be a Panel mtype even if transform-input is Series, vectorized
-        "y_inner_type": "None",
         "requires_y": False,  # does y need to be passed in fit?
         "enforce_index_type": None,  # index type that needs to be enforced in X/y
         "fit_is_empty": True,  # is fit empty and can be skipped? Yes = True
@@ -106,7 +42,7 @@ class BaseTransformer(BaseEstimator):
         "remember_data": False,  # whether all data seen is remembered as self._X
     }
 
-    # allowed types for transformers - Series and Panel
+    # allowed types for transformers - Series and Collections
     ALLOWED_INPUT_TYPES = [
         "pd.Series",
         "pd.DataFrame",
@@ -137,26 +73,21 @@ class BaseTransformer(BaseEstimator):
 
         Writes to self:
         _is_fitted : flag is set to True.
-        _X : X, coerced copy of X, if remember_data tag is True
-        possibly coerced to inner type or update_data compatible type
-        by reference, when possible
-        model attributes (ending in "_") : dependent on estimator
 
         Parameters
         ----------
-        X : Series or Panel, any supported type
+        X : Series or Collection, any supported type
             Data to fit transform to, of python type as follows:
-                Series: pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
-                Panel: pd.DataFrame with 2-level MultiIndex, list of pd.DataFrame,
-                nested pd.DataFrame, or pd.DataFrame in long/wide format.
-        y : Series or Panel, default=None
+                Series: 2D np.ndarray shape (n_channels, n_timepoints)
+                Collection: 3D np.ndarray shape (n_cases, n_channels, n_timepoints)
+                or list of 2D np.ndarray, case i has shape (n_channels, n_timepoints_i)
+        y : Series, default=None
             Additional data, e.g., labels for transformation.
 
         Returns
         -------
         self : a fitted instance of the estimator
         """
-        # input checks and datatype conversion
         self._fit(X=X, y=y)
         # this should happen last: fitted state is set to True
         self._is_fitted = True
@@ -171,48 +102,16 @@ class BaseTransformer(BaseEstimator):
 
         Accesses in self:
         _is_fitted : must be True
-        _X : optionally accessed, only available if remember_data tag is True
-        fitted model attributes (ending in "_") : must be set, accessed by _transform
 
         Parameters
         ----------
-        X : Series or Panel, any supported type
-            Data to be transformed, of python type as follows:
-                Series: pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
-                Panel: pd.DataFrame with 2-level MultiIndex, list of pd.DataFrame,
-                    nested pd.DataFrame, or pd.DataFrame in long/wide format
-        y : Series or Panel, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        transformed version of X
-        type depends on type of X and output_data_type tag:
-            |          | `transform`  |                        |
-            |   `X`    |  `-output`   |     type of return     |
-            |__________|______________|________________________|
-            | `Series` | `Primitives` | `pd.DataFrame` (1-row) |
-            | `Panel`  | `Primitives` | `pd.DataFrame`         |
-            | `Series` | `Series`     | `Series`               |
-            | `Panel`  | `Series`     | `Panel`                |
-            | `Series` | `Panel`      | `Panel`                |
-        instances in return correspond to instances in `X`
-        combinations not in the table are currently not supported
-
-        Explicitly, with examples:
-            if `X` is `Series` (e.g., `pd.DataFrame`) and `transform-output` is `Series`
-                then the return is a single `Series` of the same type
-                Example: detrending a single series
-            if `X` is `Panel` (e.g., `pd-multiindex`) and `transform-output` is `Series`
-                then the return is `Panel` with same number of instances as `X`
-                    (the transformer is applied to each input Series instance)
-                Example: all series in the panel are detrended individually
-            if `X` is `Series` or `Panel` and `transform-output` is `Primitives`
-                then the return is `pd.DataFrame` with as many rows as instances in `X`
-                Example: i-th row of the return has mean and variance of the i-th series
-            if `X` is `Series` and `transform-output` is `Panel`
-                then the return is a `Panel` object of type `pd-multiindex`
-                Example: i-th instance of the output is the i-th window running over `X`
+        X : Series or Collection, any supported type
+            Data to fit transform to, of python type as follows:
+                Series: 2D np.ndarray shape (n_channels, n_timepoints)
+                Collection: 3D np.ndarray shape (n_cases, n_channels, n_timepoints)
+                or list of 2D np.ndarray, case i has shape (n_channels, n_timepoints_i)
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
         """
         # check whether is fitted
         self.check_is_fitted()
@@ -225,53 +124,15 @@ class BaseTransformer(BaseEstimator):
 
         Fits the transformer to X and y and returns a transformed version of X.
 
-        State change: changes state to "fitted".
-
-        Writes to self:
-        _is_fitted : flag is set to True.
-        _X : X, coerced copy of X, if remember_data tag is True
-            possibly coerced to inner type or update_data compatible type
-            by reference, when possible
-        model attributes (ending in "_") : dependent on estimator
-
         Parameters
         ----------
-        X : Series or Panel, any supported type
-            Data to be transformed, of python type as follows:
-                Series: pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
-                Panel: pd.DataFrame with 2-level MultiIndex, list of pd.DataFrame,
-                    nested pd.DataFrame, or pd.DataFrame in long/wide format
-        y : Series or Panel, default=None
-            Additional data, e.g., labels for transformation
-
-        Returns
-        -------
-        transformed version of X
-        type depends on type of X and output_data_type tag:
-            |   `X`    | `tf-output`  |     type of return     |
-            |__________|______________|________________________|
-            | `Series` | `Primitives` | `pd.DataFrame` (1-row) |
-            | `Panel`  | `Primitives` | `pd.DataFrame`         |
-            | `Series` | `Series`     | `Series`               |
-            | `Panel`  | `Series`     | `Panel`                |
-            | `Series` | `Panel`      | `Panel`                |
-        instances in return correspond to instances in `X`
-        combinations not in the table are currently not supported
-
-        Explicitly, with examples:
-            if `X` is `Series` (e.g., `pd.DataFrame`) and `transform-output` is `Series`
-                then the return is a single `Series` of the same mtype
-                Example: detrending a single series
-            if `X` is `Panel` (e.g., `pd-multiindex`) and `transform-output` is `Series`
-                then the return is `Panel` with same number of instances as `X`
-                    (the transformer is applied to each input Series instance)
-                Example: all series in the panel are detrended individually
-            if `X` is `Series` or `Panel` and `transform-output` is `Primitives`
-                then the return is `pd.DataFrame` with as many rows as instances in `X`
-                Example: i-th row of the return has mean and variance of the i-th series
-            if `X` is `Series` and `transform-output` is `Panel`
-                then the return is a `Panel` object of type `pd-multiindex`
-                Example: i-th instance of the output is the i-th window running over `X`
+        X : Series or Collection, any supported type
+            Data to fit transform to, of python type as follows:
+                Series: 2D np.ndarray shape (n_channels, n_timepoints)
+                Collection: 3D np.ndarray shape (n_cases, n_channels, n_timepoints)
+                or list of 2D np.ndarray, case i has shape (n_channels, n_timepoints_i)
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
         """
         Xt = self._fit_transform(X=X, y=y)
         return Xt
@@ -279,32 +140,26 @@ class BaseTransformer(BaseEstimator):
     def inverse_transform(self, X, y=None):
         """Inverse transform X and return an inverse transformed version.
 
-        Currently it is assumed that only transformers with tags
-            "input_data_type"="Series", "output_data_type"="Series",
-        have an inverse_transform.
-
         State required:
             Requires state to be "fitted".
 
         Accesses in self:
         _is_fitted : must be True
-        _X : optionally accessed, only available if remember_data tag is True
-        fitted model attributes (ending in "_") : accessed by _inverse_transform
 
         Parameters
         ----------
-        X : Series or Panel, any supported type
-            Data to be inverse transformed, of python type as follows:
-                Series: pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
-                Panel: pd.DataFrame with 2-level MultiIndex, list of pd.DataFrame,
-                    nested pd.DataFrame, or pd.DataFrame in long/wide format
-        y : Series or Panel, default=None
-            Additional data, e.g., labels for transformation
+        X : Series or Collection, any supported type
+            Data to fit transform to, of python type as follows:
+                Series: 2D np.ndarray shape (n_channels, n_timepoints)
+                Collection: 3D np.ndarray shape (n_cases, n_channels, n_timepoints)
+                or list of 2D np.ndarray, case i has shape (n_channels, n_timepoints_i)
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
 
         Returns
         -------
         inverse transformed version of X
-            of the same type as X, and conforming to mtype format specifications
+            of the same type as X,
         """
         if self.get_tag("skip-inverse-transform"):
             return X
@@ -327,26 +182,16 @@ class BaseTransformer(BaseEstimator):
 
         Accesses in self:
         _is_fitted : must be True
-        _X : accessed by _update and by update_data, if remember_data tag is True
-        fitted model attributes (ending in "_") : must be set, accessed by _update
-
-        Writes to self:
-        _X : updated by values in X, via update_data, if remember_data tag is True
-        fitted model attributes (ending in "_") : only if update_params=True
-            type and nature of update are dependent on estimator
 
         Parameters
         ----------
-        X : Series or Panel, any supported type
+        X : Series or Collection, any supported type
             Data to fit transform to, of python type as follows:
-                Series: pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
-                Panel: pd.DataFrame with 2-level MultiIndex, list of pd.DataFrame,
-                    nested pd.DataFrame, or pd.DataFrame in long/wide format
-        y : Series or Panel, default=None
-            Additional data, e.g., labels for transformation
-        update_params : bool, default=True
-            whether the model is updated. Yes if true, if false, simply skips call.
-            argument exists for compatibility with forecasting module.
+                Series: 2D np.ndarray shape (n_channels, n_timepoints)
+                Collection: 3D np.ndarray shape (n_cases, n_channels, n_timepoints)
+                or list of 2D np.ndarray, case i has shape (n_channels, n_timepoints_i)
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
 
         Returns
         -------
@@ -385,44 +230,8 @@ class BaseTransformer(BaseEstimator):
         fitted_params : dict with str-valued keys
             Dictionary of fitted parameters, paramname : paramvalue
             keys-value pairs include:
-
-            * always: all fitted parameters of this object, as via `get_param_names`
-              values are fitted parameter value for that key, of this object
-            * if `deep=True`, also contains keys/value pairs of component parameters
-              parameters of components are indexed as `[componentname]__[paramname]`
-              all parameters of `componentname` appear as `paramname` with its value
-            * if `deep=True`, also contains arbitrary levels of component recursion,
-              e.g., `[componentname]__[componentcomponentname]__[paramname]`, etc
         """
-        # if self is not vectorized, run the default get_fitted_params
-        if not getattr(self, "_is_vectorized", False):
-            return super().get_fitted_params(deep=deep)
-
-        # otherwise, we delegate to the instances' get_fitted_params
-        # instances' parameters are returned at dataframe-slice-like keys
-        fitted_params = {}
-
-        # transformers contains a pd.DataFrame with the individual transformers
-        transformers = self.transformers_
-
-        # return transformers in the "transformers" param
-        fitted_params["transformers"] = transformers
-
-        def _to_str(x):
-            if isinstance(x, str):
-                x = f"'{x}'"
-            return str(x)
-
-        # populate fitted_params with transformers and their parameters
-        for ix, col in product(transformers.index, transformers.columns):
-            trafo = transformers.loc[ix, col]
-            trafo_key = f"transformers.loc[{_to_str(ix)},{_to_str(col)}]"
-            fitted_params[trafo_key] = trafo
-            trafo_params = trafo.get_fitted_params(deep=deep)
-            for key, val in trafo_params.items():
-                fitted_params[f"{trafo_key}__{key}"] = val
-
-        return fitted_params
+        return super().get_fitted_params(deep=deep)
 
     def _fit(self, X, y=None):
         """Fit transformer to X and y.
@@ -434,14 +243,12 @@ class BaseTransformer(BaseEstimator):
         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 : Series or Panel of type y_inner_type, default=None
-            Additional data, e.g., labels for tarnsformation
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
 
         Returns
         -------
         self: a fitted instance of the estimator
-
-        See extension_templates/transformer.py for implementation details.
         """
         # default fit is "no fitting happens"
         return self
@@ -454,27 +261,14 @@ class BaseTransformer(BaseEstimator):
         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 : Series or Panel, default=None
-            Additional data, e.g., labels for transformation
+            if X_inner_type is list, _fit must support all types in it
+            Data to fit transform to
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
 
         Returns
         -------
         transformed version of X
-        type depends on type of X and output_data_type tag:
-            |          | `transform`  |                        |
-            |   `X`    |  `-output`   |     type of return     |
-            |__________|______________|________________________|
-            | `Series` | `Primitives` | `pd.DataFrame` (1-row) |
-            | `Panel`  | `Primitives` | `pd.DataFrame`         |
-            | `Series` | `Series`     | `Series`               |
-            | `Panel`  | `Series`     | `Panel`                |
-            | `Series` | `Panel`      | `Panel`                |
-        instances in return correspond to instances in `X`
-        combinations not in the table are currently not supported
-
-        See extension_templates/transformer.py for implementation details.
         """
         raise NotImplementedError("abstract method")
 
@@ -490,14 +284,12 @@ class BaseTransformer(BaseEstimator):
         X: data structure of type X_inner_type
             if X_inner_type is list, _fit_transform must support all types in it
             Data to fit transform to
-        y : Series or Panel of type y_inner_type, default=None
-            Additional data, e.g., labels for tarnsformation
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
 
         Returns
         -------
         self: a fitted instance of the estimator
-
-        See extension_templates/transformer.py for implementation details.
         """
         # Non-optimized default implementation; override when a better
         # method is possible for a given algorithm.
@@ -514,8 +306,9 @@ class BaseTransformer(BaseEstimator):
         X: data structure of type X_inner_type
             if X_inner_type is list, _inverse_transform must support all types in it
             Data to be transformed
-        y : Series or Panel, default=None
-            Additional data, e.g., labels for transformation
+        y : Series, default=None
+            Additional data, e.g., labels for transformation.
+
 
         Returns
         -------
@@ -536,7 +329,7 @@ class BaseTransformer(BaseEstimator):
         X: data structure of type 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 type y_inner_type, default=None
+        y : Series, default=None
             Additional data, e.g., labels for tarnsformation
 
         Returns
diff --git a/aeon/transformations/collection/__init__.py b/aeon/transformations/collection/__init__.py
index 8b121ea2..6d3331f5 100644
--- a/aeon/transformations/collection/__init__.py
+++ b/aeon/transformations/collection/__init__.py
@@ -26,27 +26,27 @@ __all__ = [
     "Normalise",
 ]
 
+from aeon.transformations.collection._acf import AutocorrelationFunctionTransformer
+from aeon.transformations.collection._ar_coefficient import ARCoefficientTransformer
 from aeon.transformations.collection._downsample import DownsampleTransformer
+from aeon.transformations.collection._dwt import DWTTransformer
+from aeon.transformations.collection._hog1d import HOG1DTransformer
+from aeon.transformations.collection._matrix_profile import MatrixProfile
 from aeon.transformations.collection._normalise import Normalise
 from aeon.transformations.collection._pad import Padder
+from aeon.transformations.collection._periodogram import PeriodogramTransformer
+from aeon.transformations.collection._reduce import Tabularizer
 from aeon.transformations.collection._resize import Resizer
+from aeon.transformations.collection._scaler import TimeSeriesScaler
+from aeon.transformations.collection._segment import (
+    IntervalSegmenter,
+    RandomIntervalSegmenter,
+    SlidingWindowSegmenter,
+)
+from aeon.transformations.collection._slope import SlopeTransformer
 from aeon.transformations.collection._truncate import Truncator
-from aeon.transformations.collection.acf import AutocorrelationFunctionTransformer
-from aeon.transformations.collection.ar_coefficient import ARCoefficientTransformer
 from aeon.transformations.collection.base import BaseCollectionTransformer
 from aeon.transformations.collection.channel_selection import (
     ElbowClassPairwise,
     ElbowClassSum,
 )
-from aeon.transformations.collection.dwt import DWTTransformer
-from aeon.transformations.collection.hog1d import HOG1DTransformer
-from aeon.transformations.collection.matrix_profile import MatrixProfile
-from aeon.transformations.collection.periodogram import PeriodogramTransformer
-from aeon.transformations.collection.reduce import Tabularizer
-from aeon.transformations.collection.scaler import TimeSeriesScaler
-from aeon.transformations.collection.segment import (
-    IntervalSegmenter,
-    RandomIntervalSegmenter,
-    SlidingWindowSegmenter,
-)
-from aeon.transformations.collection.slope import SlopeTransformer
diff --git a/aeon/transformations/collection/acf.py b/aeon/transformations/collection/_acf.py
similarity index 100%
rename from aeon/transformations/collection/acf.py
rename to aeon/transformations/collection/_acf.py
diff --git a/aeon/transformations/collection/ar_coefficient.py b/aeon/transformations/collection/_ar_coefficient.py
similarity index 99%
rename from aeon/transformations/collection/ar_coefficient.py
rename to aeon/transformations/collection/_ar_coefficient.py
index 492ec58a..45b96257 100644
--- a/aeon/transformations/collection/ar_coefficient.py
+++ b/aeon/transformations/collection/_ar_coefficient.py
@@ -68,7 +68,7 @@ class ARCoefficientTransformer(BaseCollectionTransformer):
         order = self.order(X) if callable(self.order) else self.order
         if order > n_timepoints - self.min_values:
             order = n_timepoints - self.min_values
-        if order < 0:
+        if order <= 0:
             order = 1
 
         if order > n_timepoints - 1:
diff --git a/aeon/transformations/collection/dwt.py b/aeon/transformations/collection/_dwt.py
similarity index 97%
rename from aeon/transformations/collection/dwt.py
rename to aeon/transformations/collection/_dwt.py
index a9f79876..7a913498 100644
--- a/aeon/transformations/collection/dwt.py
+++ b/aeon/transformations/collection/_dwt.py
@@ -4,7 +4,7 @@ __maintainer__ = []
 
 import numpy as np
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 from aeon.utils.numba.wavelets import haar_transform
 
 
diff --git a/aeon/transformations/collection/hog1d.py b/aeon/transformations/collection/_hog1d.py
similarity index 95%
rename from aeon/transformations/collection/hog1d.py
rename to aeon/transformations/collection/_hog1d.py
index 1dcc84c6..347d9cab 100644
--- a/aeon/transformations/collection/hog1d.py
+++ b/aeon/transformations/collection/_hog1d.py
@@ -5,7 +5,7 @@ import numbers
 
 import numpy as np
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 from aeon.utils import split_series
 
 
@@ -48,7 +48,7 @@ class HOG1DTransformer(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_cases, 1, n_timepoints]
+        X : 3D np.ndarray of shape (n_cases, 1, n_timepoints)
             collection of time series to transform
         y : ignored argument for interface compatibility
 
@@ -60,8 +60,6 @@ class HOG1DTransformer(BaseCollectionTransformer):
         """
         # Get information about the dataframe
         n_cases, n_channels, n_timepoints = X.shape
-        if n_channels > 1:
-            raise ValueError("HOG1D does not support multivariate time series.")
         # Check the parameters are appropriate
         self._check_parameters(n_timepoints)
         transX = []
diff --git a/aeon/transformations/collection/matrix_profile.py b/aeon/transformations/collection/_matrix_profile.py
similarity index 97%
rename from aeon/transformations/collection/matrix_profile.py
rename to aeon/transformations/collection/_matrix_profile.py
index c0ddf4c2..f367e9c9 100644
--- a/aeon/transformations/collection/matrix_profile.py
+++ b/aeon/transformations/collection/_matrix_profile.py
@@ -4,7 +4,7 @@ __maintainer__ = []
 
 import numpy as np
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 
 
 def _sliding_dot_products(q, t, q_len, t_len):
@@ -217,8 +217,7 @@ class MatrixProfile(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-            panel of time series to transform
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
         y : ignored argument for interface compatibility
 
         Returns
diff --git a/aeon/transformations/collection/periodogram.py b/aeon/transformations/collection/_periodogram.py
similarity index 91%
rename from aeon/transformations/collection/periodogram.py
rename to aeon/transformations/collection/_periodogram.py
index 4e386aad..62585940 100644
--- a/aeon/transformations/collection/periodogram.py
+++ b/aeon/transformations/collection/_periodogram.py
@@ -7,14 +7,15 @@ import math
 
 import numpy as np
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 from aeon.utils.validation import check_n_jobs
 
 
 class PeriodogramTransformer(BaseCollectionTransformer):
     """Periodogram transformer.
 
-    This transformer converts a time series into its periodogram representation.
+    This transformer converts a collection of time series into its periodogram
+    representation.
 
     Parameters
     ----------
@@ -82,7 +83,7 @@ class PeriodogramTransformer(BaseCollectionTransformer):
             kwargs = {"mode": self.pad_with}
             if self.pad_with == "constant":
                 kwargs["constant_values"] = self.constant_value
-
+            len = int(math.pow(2, math.ceil(math.log(X.shape[2], 2))) - X.shape[2])
             X = np.pad(
                 X,
                 (
@@ -90,9 +91,7 @@ class PeriodogramTransformer(BaseCollectionTransformer):
                     (0, 0),
                     (
                         0,
-                        int(
-                            math.pow(2, math.ceil(math.log(X.shape[2], 2))) - X.shape[2]
-                        ),
+                        len,
                     ),
                 ),
                 **kwargs,
diff --git a/aeon/transformations/collection/reduce.py b/aeon/transformations/collection/_reduce.py
similarity index 79%
rename from aeon/transformations/collection/reduce.py
rename to aeon/transformations/collection/_reduce.py
index 59d7bc6f..ce8bf75d 100644
--- a/aeon/transformations/collection/reduce.py
+++ b/aeon/transformations/collection/_reduce.py
@@ -3,7 +3,7 @@
 __maintainer__ = []
 __all__ = ["Tabularizer"]
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 
 
 class Tabularizer(BaseCollectionTransformer):
@@ -23,12 +23,12 @@ class Tabularizer(BaseCollectionTransformer):
     }
 
     def _transform(self, X, y=None):
-        """Transform nested pandas dataframe into tabular dataframe.
+        """Transform collection into tabular dataframe.
 
         Parameters
         ----------
-        X : pandas DataFrame or 3D np.ndarray
-            panel of time series to transform
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
+            Collection of time series to transform.
         y : ignored argument for interface compatibility
 
         Returns
diff --git a/aeon/transformations/collection/scaler.py b/aeon/transformations/collection/_scaler.py
similarity index 100%
rename from aeon/transformations/collection/scaler.py
rename to aeon/transformations/collection/_scaler.py
diff --git a/aeon/transformations/collection/segment.py b/aeon/transformations/collection/_segment.py
similarity index 99%
rename from aeon/transformations/collection/segment.py
rename to aeon/transformations/collection/_segment.py
index 0d1906cd..afb28d91 100644
--- a/aeon/transformations/collection/segment.py
+++ b/aeon/transformations/collection/_segment.py
@@ -6,7 +6,7 @@ import numpy as np
 import pandas as pd
 from sklearn.utils import check_random_state
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 from aeon.utils.datetime import get_time_index
 from aeon.utils.validation import check_window_length
 
@@ -407,7 +407,7 @@ class SlidingWindowSegmenter(BaseCollectionTransformer):
     Examples
     --------
     >>> from aeon.datasets import load_unit_test
-    >>> from aeon.transformations.collection.segment import SlidingWindowSegmenter
+    >>> from aeon.transformations.collection import SlidingWindowSegmenter
     >>> data = np.array([[[1, 2, 3, 4, 5, 6, 7, 8]], [[5, 5, 5, 5, 5, 5, 5, 5]]])
     >>> seggy = SlidingWindowSegmenter(window_length=4)
     >>> data2 = seggy.fit_transform(data)
diff --git a/aeon/transformations/collection/slope.py b/aeon/transformations/collection/_slope.py
similarity index 94%
rename from aeon/transformations/collection/slope.py
rename to aeon/transformations/collection/_slope.py
index 390f049a..a7786262 100644
--- a/aeon/transformations/collection/slope.py
+++ b/aeon/transformations/collection/_slope.py
@@ -7,7 +7,7 @@ import math
 
 import numpy as np
 
-from aeon.transformations.collection import BaseCollectionTransformer
+from aeon.transformations.collection.base import BaseCollectionTransformer
 from aeon.utils import split_series
 
 
@@ -29,7 +29,7 @@ class SlopeTransformer(BaseCollectionTransformer):
     Examples
     --------
     >>> import numpy as np
-    >>> from aeon.transformations.collection.slope import SlopeTransformer
+    >>> from aeon.transformations.collection import SlopeTransformer
     >>> X = np.array([[[4, 6, 10, 12, 8, 6, 5, 5]]])
     >>> s = SlopeTransformer(n_intervals=2)
     >>> res = s.fit_transform(X)
@@ -57,8 +57,7 @@ class SlopeTransformer(BaseCollectionTransformer):
 
         Returns
         -------
-        3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-        collection of time series to transform
+        3D np.ndarray of shape = [n_cases, n_channels, n_intervals]
         """
         # Get information about the dataframe
         n_cases, n_channels, n_timepoints = X.shape
diff --git a/aeon/transformations/collection/_truncate.py b/aeon/transformations/collection/_truncate.py
index eae99acc..ff90c5c2 100644
--- a/aeon/transformations/collection/_truncate.py
+++ b/aeon/transformations/collection/_truncate.py
@@ -1,4 +1,4 @@
-"""Truncation transformer - truncate unequal length panels to lower/upper bounds."""
+"""Truncation transformer - truncate unequal length collections."""
 
 __all__ = ["Truncator"]
 __maintainer__ = []
@@ -11,9 +11,9 @@ from aeon.transformations.collection.base import BaseCollectionTransformer
 class Truncator(BaseCollectionTransformer):
     """Truncate unequal length time series to a lower bounds.
 
-    Truncates all series in panel between lower/upper range bounds. This transformer
-    assumes that all series have the same number of channels (dimensions) and
-    that all channels in a single series are the same length.
+    Truncates all series in collection between lower/upper range bounds. This
+    transformer assumes that all series have the same number of channels (dimensions)
+    and that all channels in a single series are the same length.
 
     Parameters
     ----------
diff --git a/aeon/transformations/collection/base.py b/aeon/transformations/collection/base.py
index 804116a2..2ec30343 100644
--- a/aeon/transformations/collection/base.py
+++ b/aeon/transformations/collection/base.py
@@ -377,20 +377,21 @@ class BaseCollectionTransformer(
         # standard behaviour: no update takes place, new data is ignored
         return self
 
-    def _check_y(self, y, n_cases):
-        if y is None:
-            return None
-        # Check y valid input for collection transformations
-        if not isinstance(y, (pd.Series, np.ndarray)):
-            raise TypeError(
-                f"y must be a np.array or a pd.Series, but found type: {type(y)}"
-            )
-        if isinstance(y, np.ndarray) and y.ndim > 1:
-            raise TypeError(f"y must be 1-dimensional, found {y.ndim} dimensions")
-        # Check matching number of labels
-        n_labels = y.shape[0]
-        if n_cases != n_labels:
-            raise ValueError(
-                f"Mismatch in number of cases. Number in X = {n_cases} nos in y = "
-                f"{n_labels}"
-            )
+
+def _check_y(self, y, n_cases):
+    if y is None:
+        return None
+    # Check y valid input for collection transformations
+    if not isinstance(y, (pd.Series, np.ndarray)):
+        raise TypeError(
+            f"y must be a np.array or a pd.Series, but found type: {type(y)}"
+        )
+    if isinstance(y, np.ndarray) and y.ndim > 1:
+        raise TypeError(f"y must be 1-dimensional, found {y.ndim} dimensions")
+    # Check matching number of labels
+    n_labels = y.shape[0]
+    if n_cases != n_labels:
+        raise ValueError(
+            f"Mismatch in number of cases. Number in X = {n_cases} nos in y = "
+            f"{n_labels}"
+        )
diff --git a/aeon/transformations/collection/convolution_based/_minirocket.py b/aeon/transformations/collection/convolution_based/_minirocket.py
index e413bb16..62cf72b0 100644
--- a/aeon/transformations/collection/convolution_based/_minirocket.py
+++ b/aeon/transformations/collection/convolution_based/_minirocket.py
@@ -96,8 +96,8 @@ class MiniRocket(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-            panel of time series to transform
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
+            Collection of time series to transform
         y : ignored argument for interface compatibility
 
         Returns
@@ -128,8 +128,8 @@ class MiniRocket(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-            panel of time series to transform
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
+            Collection of time series to transform
         y : ignored argument for interface compatibility
 
         Returns
diff --git a/aeon/transformations/collection/convolution_based/_multirocket.py b/aeon/transformations/collection/convolution_based/_multirocket.py
index b42d750e..c3e3f451 100644
--- a/aeon/transformations/collection/convolution_based/_multirocket.py
+++ b/aeon/transformations/collection/convolution_based/_multirocket.py
@@ -105,8 +105,8 @@ class MultiRocket(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-            panel of time series to transform
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
+            Collection of time series to transform
         y : ignored argument for interface compatibility
 
         Returns
@@ -147,8 +147,8 @@ class MultiRocket(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_cases, n_channels, n_timepoints]
-            panel of time series to transform
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
+            Collection of time series to transform.
         y : ignored argument for interface compatibility
 
         Returns
diff --git a/aeon/transformations/collection/feature_based/_tsfresh.py b/aeon/transformations/collection/feature_based/_tsfresh.py
index 3720100a..5ebf8d42 100644
--- a/aeon/transformations/collection/feature_based/_tsfresh.py
+++ b/aeon/transformations/collection/feature_based/_tsfresh.py
@@ -550,19 +550,12 @@ class TSFreshRelevantFeatureExtractor(_TSFreshFeatureExtractor):
 
         Writes to self:
         _is_fitted : flag is set to True.
-        _X : X, coerced copy of X, if remember_data tag is True
-            possibly coerced to inner type or update_data compatible type
-            by reference, when possible
-        model attributes (ending in "_") : dependent on estimator
 
         Parameters
         ----------
-        X : Series or Panel, any supported type
-            Data to be transformed, of python type as follows:
-                Series: pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
-                Panel: pd.DataFrame with 2-level MultiIndex, list of pd.DataFrame,
-                    nested pd.DataFrame, or pd.DataFrame in long/wide format
-        y : Series or Panel, default=None
+        X : 3D np.ndarray of shape (n_cases, n_channels, n_timepoints)
+            collection of time series to transform
+        y : Series, default=None
             Additional data, e.g., labels for transformation
 
         Returns
diff --git a/aeon/transformations/collection/signature_based/__init__.py b/aeon/transformations/collection/signature_based/__init__.py
index 004c08ac..59cc0207 100644
--- a/aeon/transformations/collection/signature_based/__init__.py
+++ b/aeon/transformations/collection/signature_based/__init__.py
@@ -1,4 +1,4 @@
-"""Signature based panel transformations."""
+"""Signature based Collection transformations."""
 
 __all__ = [
     "SignatureTransformer",
diff --git a/docs/api_reference/transformations.rst b/docs/api_reference/transformations.rst
index 2caeaf4f..6319aa55 100644
--- a/docs/api_reference/transformations.rst
+++ b/docs/api_reference/transformations.rst
@@ -46,7 +46,7 @@ Composition
 Sklearn and pandas adapters
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. currentmodule:: aeon.transformations.collection.reduce
+.. currentmodule:: aeon.transformations.collection
 
 .. autosummary::
     :toctree: auto_generated/
@@ -88,7 +88,7 @@ Shapelets, wavelets and convolution
     MiniRocketMultivariateVariable
     MultiRocket
 
-.. currentmodule:: aeon.transformations.collection.dwt
+.. currentmodule:: aeon.transformations.collection
 
 .. autosummary::
     :toctree: auto_generated/
@@ -99,7 +99,7 @@ Shapelets, wavelets and convolution
 Distance-based features
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-.. currentmodule:: aeon.transformations.collection.matrix_profile
+.. currentmodule:: aeon.transformations.collection
 
 .. autosummary::
     :toctree: auto_generated/
@@ -224,7 +224,7 @@ Distance Based
 Slope
 ~~~~~~~~~~~~~~~~~~~~~~
 
-.. currentmodule:: aeon.transformations.collection.slope
+.. currentmodule:: aeon.transformations.collection
 
 .. autosummary::
     :toctree: auto_generated/
@@ -235,7 +235,7 @@ Slope
 Segmentation
 ~~~~~~~~~~~~
 
-.. currentmodule:: aeon.transformations.collection.segment
+.. currentmodule:: aeon.transformations.collection
 
 .. autosummary::
     :toctree: auto_generated/
@@ -250,7 +250,7 @@ Window-based series transforms
 
 These transformers create a series based on a sequence of sliding windows.
 
-.. currentmodule:: aeon.transformations.collection.hog1d
+.. currentmodule:: aeon.transformations.collection
 
 .. autosummary::
     :toctree: auto_generated/
diff --git a/pyproject.toml b/pyproject.toml
index f7385eac..30b0bad4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,7 +49,7 @@ dependencies = [
     "numba>=0.55,<0.61.0",
     "numpy>=1.21.0,<1.27.0",
     "packaging>=20.0",
-    "pandas>=1.5.3,<2.1.0",
+    "pandas>=2.0.0,<2.3.0",
     "scikit-learn>=1.0.0,<1.6.0",
     "scipy>=1.9.0,<1.14.0",
     "typing-extensions>=4.6.0",


Test patch:
diff --git a/aeon/transformations/collection/tests/test_acf.py b/aeon/transformations/collection/tests/test_acf.py
new file mode 100644
index 00000000..9f249a68
--- /dev/null
+++ b/aeon/transformations/collection/tests/test_acf.py
@@ -0,0 +1,14 @@
+"""Test for incorrect input for ARCoefficientTransformer transformer."""
+
+import numpy as np
+import pytest
+
+from aeon.transformations.collection import AutocorrelationFunctionTransformer
+
+
+def test_acf():
+    """Test ACF Transformer exceptions."""
+    X = np.random.random((2, 1, 10))
+    acf = AutocorrelationFunctionTransformer(n_lags=100)
+    with pytest.raises(ValueError, match=r"must be smaller than n_timepoints - 1"):
+        acf.fit_transform(X)
diff --git a/aeon/transformations/collection/tests/test_ar_coefficient.py b/aeon/transformations/collection/tests/test_ar_coefficient.py
new file mode 100644
index 00000000..b9bc5af1
--- /dev/null
+++ b/aeon/transformations/collection/tests/test_ar_coefficient.py
@@ -0,0 +1,28 @@
+"""Test for incorrect input for ARCoefficientTransformer transformer."""
+
+import numpy as np
+import pytest
+
+from aeon.transformations.collection import ARCoefficientTransformer
+from aeon.utils.validation._dependencies import _check_soft_dependencies
+
+
+@pytest.mark.skipif(
+    not _check_soft_dependencies("statsmodels", severity="none"),
+    reason="skip test if required soft dependency statsmodels not available",
+)
+def test_ar_coefficient():
+    """Test AR Coefficient Transformer exceptions."""
+    X = np.random.random((2, 1, 10))
+    ar = ARCoefficientTransformer(order=0)
+    Xt = ar.fit_transform(X)
+    assert Xt.shape[2] == 1
+    ar = ARCoefficientTransformer(order=-10)
+    Xt = ar.fit_transform(X)
+    assert Xt.shape[2] == 1
+    ar = ARCoefficientTransformer(order=100)
+    with pytest.raises(ValueError, match=r"must be smaller than n_timepoints - 1"):
+        ar.fit_transform(X)
+    ar = ARCoefficientTransformer(order=6, min_values=5)
+    Xt = ar.fit_transform(X)
+    assert Xt.shape[2] == 5
diff --git a/aeon/transformations/collection/tests/test_base.py b/aeon/transformations/collection/tests/test_base.py
index 28307b0f..b87d97d4 100644
--- a/aeon/transformations/collection/tests/test_base.py
+++ b/aeon/transformations/collection/tests/test_base.py
@@ -63,6 +63,17 @@ def test_inverse_transform():
     assert_almost_equal(x, x2)
 
 
+def test_raise_inverse_transform():
+    """Test that inverse transform raises NotImplementedError."""
+    d = _Dummy()
+    x, _ = make_example_3d_numpy()
+    d.fit(x)
+    with pytest.raises(
+        NotImplementedError, match="does not implement " "inverse_transform"
+    ):
+        d.inverse_transform(x)
+
+
 class _Dummy(BaseCollectionTransformer):
     """Dummy transformer for testing.
 
diff --git a/aeon/transformations/collection/tests/test_dwt.py b/aeon/transformations/collection/tests/test_dwt.py
index 962094d7..137cfba1 100644
--- a/aeon/transformations/collection/tests/test_dwt.py
+++ b/aeon/transformations/collection/tests/test_dwt.py
@@ -5,7 +5,7 @@ import math
 import numpy as np
 import pytest
 
-from aeon.transformations.collection.dwt import DWTTransformer
+from aeon.transformations.collection import DWTTransformer
 
 
 @pytest.mark.parametrize("bad_num_levels", ["str", 1.2, -1.2, -1, {}])
diff --git a/aeon/transformations/collection/tests/test_hog1d_transformer.py b/aeon/transformations/collection/tests/test_hog1d_transformer.py
index 3f676dbd..50cffb4d 100644
--- a/aeon/transformations/collection/tests/test_hog1d_transformer.py
+++ b/aeon/transformations/collection/tests/test_hog1d_transformer.py
@@ -5,7 +5,21 @@ import numbers
 import numpy as np
 import pytest
 
-from aeon.transformations.collection.hog1d import HOG1DTransformer
+from aeon.transformations.collection._hog1d import HOG1DTransformer
+
+
+def test_hog1d():
+    """Test HOG1D Transformer exceptions."""
+    X = np.random.random((2, 1, 10))
+    hog = HOG1DTransformer(n_bins=0)
+    with pytest.raises(ValueError, match=r"num_bins must have the value of at least 1"):
+        hog.fit_transform(X)
+    hog = HOG1DTransformer(n_bins=0.5)
+    with pytest.raises(TypeError, match=r"must be an 'int'"):
+        hog.fit_transform(X)
+    hog = HOG1DTransformer(scaling_factor="Bob")
+    with pytest.raises(TypeError, match=r"scaling_factor must be a 'number'"):
+        hog.fit_transform(X)
 
 
 @pytest.mark.parametrize("num_bins,corr_n_timepoints", [(4, 8), (8, 16), (12, 24)])
diff --git a/aeon/transformations/collection/tests/test_segment.py b/aeon/transformations/collection/tests/test_segment.py
index 7cf6da20..482b338e 100644
--- a/aeon/transformations/collection/tests/test_segment.py
+++ b/aeon/transformations/collection/tests/test_segment.py
@@ -4,7 +4,7 @@ import numpy as np
 import pandas as pd
 import pytest
 
-from aeon.transformations.collection.segment import (
+from aeon.transformations.collection._segment import (
     IntervalSegmenter,
     RandomIntervalSegmenter,
     _rand_intervals_fixed_n,
diff --git a/aeon/transformations/collection/tests/test_sliding_window_segmenter_transformer.py b/aeon/transformations/collection/tests/test_sliding_window_segmenter_transformer.py
index 42dd3a67..b58ecb4e 100644
--- a/aeon/transformations/collection/tests/test_sliding_window_segmenter_transformer.py
+++ b/aeon/transformations/collection/tests/test_sliding_window_segmenter_transformer.py
@@ -6,7 +6,7 @@ __authors__ = ["mloning"]
 import numpy as np
 import pytest
 
-from aeon.transformations.collection.segment import SlidingWindowSegmenter
+from aeon.transformations.collection import SlidingWindowSegmenter
 
 
 # Check that exception is raised for bad window length.
diff --git a/aeon/transformations/collection/tests/test_slope_transformer.py b/aeon/transformations/collection/tests/test_slope_transformer.py
index c6b0b685..05d02a9a 100644
--- a/aeon/transformations/collection/tests/test_slope_transformer.py
+++ b/aeon/transformations/collection/tests/test_slope_transformer.py
@@ -5,7 +5,7 @@ import math
 import numpy as np
 import pytest
 
-from aeon.transformations.collection.slope import SlopeTransformer
+from aeon.transformations.collection import SlopeTransformer
 
 
 @pytest.mark.parametrize("bad_num_intervals", ["str", 1.2, -1.2, -1, {}, 0])
diff --git a/aeon/transformations/collection/tests/test_tabularizer.py b/aeon/transformations/collection/tests/test_tabularizer.py
index 8132a6ef..89ff29aa 100644
--- a/aeon/transformations/collection/tests/test_tabularizer.py
+++ b/aeon/transformations/collection/tests/test_tabularizer.py
@@ -2,7 +2,7 @@
 
 import numpy as np
 
-from aeon.transformations.collection.reduce import Tabularizer
+from aeon.transformations.collection import Tabularizer
 
 
 def test_tabularizer():
diff --git a/aeon/utils/tests/test_datetime.py b/aeon/utils/tests/test_datetime.py
index 6915478f..fb06be03 100644
--- a/aeon/utils/tests/test_datetime.py
+++ b/aeon/utils/tests/test_datetime.py
@@ -15,6 +15,7 @@ from aeon.utils.datetime import (
     infer_freq,
     set_hier_freq,
 )
+from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 def test_get_freq():
@@ -77,7 +78,7 @@ def test_infer_freq() -> None:
 
     index = pd.date_range(start="2021-01-01", periods=1, freq="M")
     y = pd.Series(index=index, dtype=int)
-    assert infer_freq(y) == "M"
+    assert infer_freq(y) == "ME"
 
     y = pd.DataFrame({"a": 1}, index=pd.date_range(start="2021-01-01", periods=1))
     assert infer_freq(y) == "D"
@@ -85,7 +86,7 @@ def test_infer_freq() -> None:
     y = pd.DataFrame(
         {"a": 1}, index=pd.date_range(start="2021-01-01", periods=1, freq="M")
     )
-    assert infer_freq(y) == "M"
+    assert infer_freq(y) == "ME"
 
 
 def test_set_freq_hier():
@@ -99,6 +100,9 @@ def test_set_freq_hier():
 
     assert get_time_index(y).freq is not None
 
+    if _check_soft_dependencies("pandas>=2.1.0", severity="none"):
+        y.index.freq = None
+
     # Create MultiIndex
     mi = pd.MultiIndex.from_product([[0], y.index], names=["instances", "timepoints"])
     y_group1 = pd.DataFrame(y.values, index=mi, columns=["y"])