Repository: aeon-toolkit/aeon
Pull request #1456 (2024-04-22 15:37:51)

[ENH] `fit_predict` for FreshPRINCE and RotationForest
#### Reference Issues/PRs

#1178
#1116

#### What does this implement/fix? Explain your changes.

Implements a `fit_predict`/`fit_predict_proba` method for FreshPRINCE and RotationForest classifiers/regressors. This should put them in line with other estimators which can output custom train estimates.


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 have added the following labels to this PR based on the changes made: **[ $\\color{#BCAE15}{\\textsf{classification}}$, $\\color{#7E0206}{\\textsf{regression}}$ ]**. Feel free to change these if they do not properly represent the PR.

The [Checks](https://github.com/aeon-toolkit/aeon/pull/1456/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.
    

Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index ce7d77cd..ec46ba67 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -2387,6 +2387,24 @@
       "contributions": [
         "doc"
       ]
+    },
+    {
+      "login": "griegner",
+      "name": "Gabriel Riegner",
+      "avatar_url": "https://avatars.githubusercontent.com/u/54326829?v=4",
+      "profile": "https://github.com/griegner",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "RishavKumarSinha",
+      "name": "Rishav Kumar Sinha",
+      "avatar_url": "https://avatars.githubusercontent.com/u/136772607?s=400&v=4",
+      "profile": "https://github.com/RishavKumarSinha",
+      "contributions": [
+        "doc"
+      ]
     }
   ],
   "commitType": "docs"
diff --git a/.codecov.yml b/.codecov.yml
index 85636690..ba302c13 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -30,3 +30,5 @@ ignore:
   - "docs/"
   - "examples/"
   - "extension_templates/"
+  # specific files
+  - "aeon/registry/_tag_deprecation.py"
diff --git a/aeon/annotation/base/_base.py b/aeon/annotation/base/_base.py
index 1a2a60b3..ab15b19b 100644
--- a/aeon/annotation/base/_base.py
+++ b/aeon/annotation/base/_base.py
@@ -31,11 +31,11 @@ class BaseSeriesAnnotator(BaseEstimator):
 
     Parameters
     ----------
-    fmt : str {"dense", "sparse"}, optional (default="dense")
+    fmt : str {"dense", "sparse"}, default="dense"
         annotation output format:
         * If "sparse", a sub-series of labels for only the outliers in X is returned,
         * If "dense", a series of labels for all values in X is returned.
-    labels : str {"indicator", "score"}, optional (default="indicator")
+    labels : str {"indicator", "score"}, default="indicator"
         annotation output labels:
         * If "indicator", returned values are boolean, indicating whether a value is an
         outlier,
@@ -210,7 +210,7 @@ class BaseSeriesAnnotator(BaseEstimator):
         ----------
         X : pd.DataFrame, pd.Series or np.ndarray
             Data to be transformed
-        Y : pd.Series or np.ndarray, optional (default=None)
+        Y : pd.Series or np.ndarray, default=None
             Target values of data to be predicted.
 
         Returns
diff --git a/aeon/anomaly_detection/_stray.py b/aeon/anomaly_detection/_stray.py
index 26204c54..b0b8e8ae 100644
--- a/aeon/anomaly_detection/_stray.py
+++ b/aeon/anomaly_detection/_stray.py
@@ -27,22 +27,22 @@ class STRAY(BaseTransformer):
 
     Parameters
     ----------
-    alpha : float, optional (default=0.01)
+    alpha : float, default=0.01
         Threshold for determining the cutoff for outliers. Observations are
         considered outliers if they fall in the (1 - alpha) tail of
         the distribution of the nearest-neighbor distances between exemplars.
-    k : int, optional (default=10)
+    k : int, default=10
         Number of neighbours considered.
     knn_algorithm : str {"auto", "ball_tree", "kd_tree", "brute"}, optional
         (default="brute")
         Algorithm used to compute the nearest neighbors, from
         sklearn.neighbors.NearestNeighbors
-    p : float, optional (default=0.5)
+    p : float, default=0.5
         Proportion of possible candidates for outliers. This defines the starting point
         for the bottom up searching algorithm.
-    size_threshold : int, optional (default=50)
+    size_threshold : int, default=50
         Sample size to calculate an emperical threshold.
-    outlier_tail : str {"min", "max"}, optional (default="max")
+    outlier_tail : str {"min", "max"}, default="max"
         Direction of the outlier tail.
 
     Attributes
diff --git a/aeon/base/_base.py b/aeon/base/_base.py
index f96f91d6..a2344979 100644
--- a/aeon/base/_base.py
+++ b/aeon/base/_base.py
@@ -53,7 +53,6 @@ __maintainer__ = []
 __all__ = ["BaseEstimator", "BaseObject"]
 
 import inspect
-import warnings
 from collections import defaultdict
 from copy import deepcopy
 
@@ -765,215 +764,6 @@ class BaseObject(_BaseEstimator):
             return pickle.loads(file.open("_obj").read())
 
 
-class TagAliaserMixin:
-    """
-    Mixin class for tag aliasing and deprecation of old tags.
-
-    To deprecate tags, add the TagAliaserMixin to BaseObject or BaseEstimator.
-    alias_dict contains the deprecated tags, and supports removal and renaming.
-        For removal, add an entry "old_tag_name": ""
-        For renaming, add an entry "old_tag_name": "new_tag_name"
-    deprecate_dict contains the version number of renaming or removal.
-        the keys in deprecate_dict should be the same as in alias_dict.
-        values in deprecate_dict should be strings, the version of removal/renaming.
-
-    The class will ensure that new tags alias old tags and vice versa, during
-    the deprecation period. Informative warnings will be raised whenever the
-    deprecated tags are being accessed.
-
-    When removing tags, ensure to remove the removed tags from this class.
-    If no tags are deprecated anymore (e.g., all deprecated tags are removed/renamed),
-    ensure toremove this class as a parent of BaseObject or BaseEstimator.
-    """
-
-    def __init__(self):
-        super().__init__()
-
-    @classmethod
-    def get_class_tags(cls):
-        """
-        Get class tags from estimator class and all its parent classes.
-
-        Get the tags relating to the class not a particular object.
-
-        Returns
-        -------
-        collected_tags : dict
-            Dictionary of tag name : tag value pairs. Collected from _tags
-            class attribute via nested inheritance. NOT overridden by dynamic
-            tags set by set_tags or mirror_tags.
-
-        See Also
-        --------
-        get_tag : Get a single tag from an object.
-        get_clas_tags : Get all tags from a class.
-        get_tags : Get all tags from an object.
-
-        Examples
-        --------
-        >>> from aeon.classification import DummyClassifier
-        >>> tags = DummyClassifier.get_class_tags()
-        """
-        collected_tags = super().get_class_tags()
-        collected_tags = cls._complete_dict(collected_tags)
-        return collected_tags
-
-    @classmethod
-    def get_class_tag(cls, tag_name, tag_value_default=None):
-        """
-        Get tag value from estimator class (only class tags).
-
-        Parameters
-        ----------
-        tag_name : str
-            Name of tag value.
-        tag_value_default : any type
-            Default/fallback value if tag is not found.
-
-        Returns
-        -------
-        tag_value :
-            Value of the `tag_name` tag in self. If not found, returns
-            `tag_value_default`.
-
-        See Also
-        --------
-        get_tag : Get a single tag from an object.
-        get_clas_tags : Get all tags from a class.
-        get_tags : Get all tags from an object.
-
-        Examples
-        --------
-        >>> from aeon.classification import DummyClassifier
-        >>> DummyClassifier.get_class_tag("capability:multivariate")
-        True
-        """
-        cls._deprecate_tag_warn([tag_name])
-        return super().get_class_tag(
-            tag_name=tag_name, tag_value_default=tag_value_default
-        )
-
-    def get_tags(self):
-        """
-        Get tags from estimator class and dynamic tag overrides.
-
-        Returns
-        -------
-        collected_tags : dict
-            Dictionary of tag name : tag value pairs. Collected from _tags
-            class attribute via nested inheritance and then any overrides
-            and new tags from _tags_dynamic object attribute.
-        """
-        collected_tags = super().get_tags()
-        collected_tags = self._complete_dict(collected_tags)
-        return collected_tags
-
-    def get_tag(self, tag_name, tag_value_default=None, raise_error=True):
-        """
-        Get tag value from estimator class and dynamic tag overrides.
-
-        Parameters
-        ----------
-        tag_name : str
-            Name of tag to be retrieved.
-        tag_value_default : any type, default=None
-            Default/fallback value if tag is not found.
-        raise_error : bool
-            whether a ValueError is raised when the tag is not found.
-
-        Returns
-        -------
-        string or None
-            Value of the `tag_name` tag in self. If not found, returns an error if
-            raise_error is True, otherwise it returns `tag_value_default`.
-
-        Raises
-        ------
-        ValueError if raise_error is True i.e. if tag_name is not in self.get_tags(
-        ).keys()
-        """
-        self._deprecate_tag_warn([tag_name])
-        return super().get_tag(
-            tag_name=tag_name,
-            tag_value_default=tag_value_default,
-            raise_error=raise_error,
-        )
-
-    def set_tags(self, **tag_dict):
-        """
-        Set dynamic tags to given values.
-
-        Parameters
-        ----------
-        tag_dict : dict
-            Dictionary of tag name : tag value pairs.
-
-        Returns
-        -------
-        Self :
-            Reference to self.
-
-        Notes
-        -----
-        Changes object state by settting tag values in tag_dict as dynamic tags
-        in self.
-        """
-        self._deprecate_tag_warn(tag_dict.keys())
-
-        tag_dict = self._complete_dict(tag_dict)
-        super().set_tags(**tag_dict)
-        return self
-
-    @classmethod
-    def _complete_dict(cls, tag_dict):
-        """Add all aliased and aliasing tags to the dictionary."""
-        alias_dict = cls.alias_dict
-        deprecated_tags = set(tag_dict.keys()).intersection(alias_dict.keys())
-        new_tags = set(tag_dict.keys()).intersection(alias_dict.values())
-
-        if len(deprecated_tags) > 0 or len(new_tags) > 0:
-            new_tag_dict = deepcopy(tag_dict)
-            # for all tag strings being set, write the value
-            #   to all tags that could *be aliased by* the string
-            #   and all tags that could be *aliasing* the string
-            # this way we ensure upwards and downwards compatibility
-            for old_tag, new_tag in alias_dict.items():
-                for tag in tag_dict:
-                    if tag == old_tag and new_tag != "":
-                        new_tag_dict[new_tag] = tag_dict[tag]
-                    if tag == new_tag:
-                        new_tag_dict[old_tag] = tag_dict[tag]
-            return new_tag_dict
-        else:
-            return tag_dict
-
-    @classmethod
-    def _deprecate_tag_warn(cls, tags):
-        """
-        Print warning message for tag deprecation.
-
-        Parameters
-        ----------
-        tags : list of str.
-
-        Raises
-        ------
-        DeprecationWarning for each tag in tags that is aliased by cls.alias_dict.
-        """
-        for tag_name in tags:
-            if tag_name in cls.alias_dict.keys():
-                version = cls.deprecate_dict[tag_name]
-                new_tag = cls.alias_dict[tag_name]
-                msg = f"tag {tag_name!r} will be removed in aeon version {version}"
-                if new_tag != "":
-                    msg += (
-                        f" and replaced by {new_tag!r}, please use {new_tag!r} instead"
-                    )
-                else:
-                    msg += ', please remove code that access or sets "{tag_name}"'
-                warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
-
-
 class BaseEstimator(BaseObject):
     """Base class for defining estimators in aeon.
 
diff --git a/aeon/benchmarking/benchmarks.py b/aeon/benchmarking/benchmarks.py
index 977344a1..b8f14787 100644
--- a/aeon/benchmarking/benchmarks.py
+++ b/aeon/benchmarking/benchmarks.py
@@ -38,7 +38,7 @@ class BaseBenchmark:
         ----------
         estimator : BaseEstimator object
             Estimator to add to the benchmark.
-        estimator_id : str, optional (default=None)
+        estimator_id : str, default=None
             Identifier for estimator. If none given then uses estimator's class name.
 
         """
diff --git a/aeon/benchmarking/forecasting.py b/aeon/benchmarking/forecasting.py
index 283271a5..3fcc7f97 100644
--- a/aeon/benchmarking/forecasting.py
+++ b/aeon/benchmarking/forecasting.py
@@ -84,7 +84,7 @@ class ForecastingBenchmark(BaseBenchmark):
             Splitter used for generating validation folds.
         scorers : a list of Callable scoring functions
             Each scoring function output will be included in the results.
-        task_id : str, optional (default=None)
+        task_id : str, default=None
             Identifier for the benchmark task. If none given then uses dataset loader
             name combined with cv_splitter class name.
 
diff --git a/aeon/classification/feature_based/_fresh_prince.py b/aeon/classification/feature_based/_fresh_prince.py
index 17b1ba2c..c9ec5147 100644
--- a/aeon/classification/feature_based/_fresh_prince.py
+++ b/aeon/classification/feature_based/_fresh_prince.py
@@ -9,7 +9,6 @@ __all__ = ["FreshPRINCEClassifier"]
 
 
 import numpy as np
-from sklearn.utils import check_random_state
 
 from aeon.classification.base import BaseClassifier
 from aeon.classification.sklearn import RotationForestClassifier
@@ -117,8 +116,8 @@ class FreshPRINCEClassifier(BaseClassifier):
         Changes state by creating a fitted model that updates attributes
         ending in "_" and sets is_fitted flag to True.
         """
-        self._fit_fresh_prince(X, y)
-
+        X_t = self._fit_fp_shared(X, y)
+        self._rotf.fit(X_t, y)
         return self
 
     def _predict(self, X) -> np.ndarray:
@@ -155,24 +154,18 @@ class FreshPRINCEClassifier(BaseClassifier):
         return self._rotf.predict_proba(self._tsfresh.transform(X))
 
     def _fit_predict(self, X, y) -> np.ndarray:
-        rng = check_random_state(self.random_state)
-        return np.array(
-            [
-                self.classes_[int(rng.choice(np.flatnonzero(prob == prob.max())))]
-                for prob in self._fit_predict_proba(X, y)
-            ]
-        )
+        X_t = self._fit_fp_shared(X, y)
+        return self._rotf.fit_predict(X_t, y)
 
     def _fit_predict_proba(self, X, y) -> np.ndarray:
-        Xt = self._fit_fresh_prince(X, y, save_rotf_data=True)
-        return self._rotf._get_train_probs(Xt, y)
+        X_t = self._fit_fp_shared(X, y)
+        return self._rotf.fit_predict_proba(X_t, y)
 
-    def _fit_fresh_prince(self, X, y, save_rotf_data=False):
+    def _fit_fp_shared(self, X, y):
         self.n_cases_, self.n_channels_, self.n_timepoints_ = X.shape
 
         self._rotf = RotationForestClassifier(
             n_estimators=self.n_estimators,
-            save_transformed_data=save_rotf_data,
             n_jobs=self._n_jobs,
             random_state=self.random_state,
         )
@@ -184,10 +177,7 @@ class FreshPRINCEClassifier(BaseClassifier):
             disable_progressbar=self.verbose < 1,
         )
 
-        X_t = self._tsfresh.fit_transform(X, y)
-        self._rotf.fit(X_t, y)
-
-        return X_t
+        return self._tsfresh.fit_transform(X, y)
 
     @classmethod
     def get_test_params(cls, parameter_set="default"):
diff --git a/aeon/classification/shapelet_based/_stc.py b/aeon/classification/shapelet_based/_stc.py
index 1ae9dd5b..cc26921e 100644
--- a/aeon/classification/shapelet_based/_stc.py
+++ b/aeon/classification/shapelet_based/_stc.py
@@ -187,8 +187,8 @@ class ShapeletTransformClassifier(BaseClassifier):
         Changes state by creating a fitted model that updates attributes
         ending in "_".
         """
-        self._fit_stc(X, y)
-
+        X_t = self._fit_stc_shared(X, y)
+        self._estimator.fit(X_t, y)
         return self
 
     def _predict(self, X) -> np.ndarray:
@@ -243,13 +243,15 @@ class ShapeletTransformClassifier(BaseClassifier):
         )
 
     def _fit_predict_proba(self, X, y) -> np.ndarray:
-        Xt = self._fit_stc(X, y, save_rotf_data=True)
+        X_t = self._fit_stc_shared(X, y)
 
         if (isinstance(self.estimator, RotationForestClassifier)) or (
             self.estimator is None
         ):
-            return self._estimator._get_train_probs(Xt, y)
+            return self._estimator.fit_predict_proba(X_t, y)
         else:
+            self._estimator.fit(X_t, y)
+
             m = getattr(self._estimator, "predict_proba", None)
             if not callable(m):
                 raise ValueError("Estimator must have a predict_proba method.")
@@ -269,14 +271,14 @@ class ShapeletTransformClassifier(BaseClassifier):
 
             return cross_val_predict(
                 estimator,
-                X=Xt,
+                X=X_t,
                 y=y,
                 cv=cv_size,
                 method="predict_proba",
                 n_jobs=self._n_jobs,
             )
 
-    def _fit_stc(self, X, y, save_rotf_data=False):
+    def _fit_stc_shared(self, X, y):
         self.n_cases_, self.n_channels_, self.n_timepoints_ = X.shape
 
         if self.time_limit_in_minutes > 0:
@@ -304,9 +306,6 @@ class ShapeletTransformClassifier(BaseClassifier):
             self.random_state,
         )
 
-        if isinstance(self._estimator, RotationForestClassifier):
-            self._estimator.save_transformed_data = save_rotf_data
-
         m = getattr(self._estimator, "n_jobs", None)
         if m is not None:
             self._estimator.n_jobs = self._n_jobs
@@ -315,11 +314,7 @@ class ShapeletTransformClassifier(BaseClassifier):
         if m is not None and self.time_limit_in_minutes > 0:
             self._estimator.time_limit_in_minutes = self._classifier_limit_in_minutes
 
-        Xt = self._transformer.fit_transform(X, y)
-
-        self._estimator.fit(Xt, y)
-
-        return Xt
+        return self._transformer.fit_transform(X, y)
 
     @classmethod
     def get_test_params(cls, parameter_set="default"):
diff --git a/aeon/classification/sklearn/_rotation_forest_classifier.py b/aeon/classification/sklearn/_rotation_forest_classifier.py
index ed1f23fb..7667576f 100644
--- a/aeon/classification/sklearn/_rotation_forest_classifier.py
+++ b/aeon/classification/sklearn/_rotation_forest_classifier.py
@@ -4,10 +4,11 @@ A Rotation Forest aeon implementation for continuous values only. Fits sklearn
 conventions.
 """
 
-__maintainer__ = []
+__maintainer__ = ["MatthewMiddlehurst"]
 __all__ = ["RotationForestClassifier"]
 
 import time
+import warnings
 
 import numpy as np
 import pandas as pd
@@ -52,8 +53,10 @@ class RotationForestClassifier(BaseEstimator):
     contract_max_n_estimators : int, default=500
         Max number of estimators to build when ``time_limit_in_minutes`` is set.
     save_transformed_data : bool, default=False
-        Save the data transformed in fit in ``transformed_data_`` for use in
-        ``_get_train_probs``.
+        Save the data transformed in fit.
+
+        Deprecated and will be removed in v0.8.0. Use fit_predict and fit_predict_proba
+        to generate train estimates instead. transformed_data_ will also be removed.
     n_jobs : int, default=1
         The number of jobs to run in parallel for both ``fit`` and ``predict``.
         `-1` means using all processors.
@@ -73,22 +76,9 @@ class RotationForestClassifier(BaseEstimator):
         The number of train cases in the training set.
     n_atts_ : int
         The number of attributes in the training set.
-    transformed_data_ : list of shape (n_estimators) of ndarray
-        The transformed training dataset for all classifiers. Only saved when
-        ``save_transformed_data`` is `True`.
     estimators_ : list of shape (n_estimators) of BaseEstimator
         The collections of estimators trained in fit.
 
-    See Also
-    --------
-    ShapeletTransformClassifier: A shapelet-based classifier using Rotation Forest.
-
-    Notes
-    -----
-    For the Java version, see
-    `tsml <https://github.com/uea-machine-learning/tsml/blob/master/src/main/java
-    /weka/classifiers/meta/RotationForest.java>`_.
-
     References
     ----------
     .. [1] Rodriguez, Juan José, Ludmila I. Kuncheva, and Carlos J. Alonso. "Rotation
@@ -119,7 +109,7 @@ class RotationForestClassifier(BaseEstimator):
         base_estimator=None,
         time_limit_in_minutes=0.0,
         contract_max_n_estimators=500,
-        save_transformed_data=False,
+        save_transformed_data="deprecated",
         n_jobs=1,
         random_state=None,
     ):
@@ -130,10 +120,18 @@ class RotationForestClassifier(BaseEstimator):
         self.base_estimator = base_estimator
         self.time_limit_in_minutes = time_limit_in_minutes
         self.contract_max_n_estimators = contract_max_n_estimators
-        self.save_transformed_data = save_transformed_data
         self.n_jobs = n_jobs
         self.random_state = random_state
 
+        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.10.0
+        self.save_transformed_data = save_transformed_data
+        if save_transformed_data != "deprecated":
+            warnings.warn(
+                "the save_transformed_data parameter is deprecated and will be"
+                "removed in v0.10.0. transformed_data_ will also be removed.",
+                stacklevel=2,
+            )
+
         super().__init__()
 
     def fit(self, X, y):
@@ -153,108 +151,12 @@ class RotationForestClassifier(BaseEstimator):
 
         Notes
         -----
-        Changes state by creating a fitted model that updates attributes
-        ending in "_".
+        Changes state by creating a fitted model that updates attributes ending in "_".
         """
-        if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
-            X = np.reshape(X, (X.shape[0], -1))
-        elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
-            X = X.to_numpy()
-        elif not isinstance(X, np.ndarray) or len(X.shape) > 2:
-            raise ValueError(
-                "RotationForestClassifier is not a time series classifier. "
-                "A valid sklearn input such as a 2d numpy array is required."
-                "Sparse input formats are currently not supported."
-            )
-        X, y = self._validate_data(X=X, y=y, ensure_min_samples=2)
-
-        self._n_jobs = check_n_jobs(self.n_jobs)
-
-        self.n_cases_, self.n_atts_ = X.shape
-        self.classes_ = np.unique(y)
-        self.n_classes_ = self.classes_.shape[0]
-        self._class_dictionary = {}
-        for index, classVal in enumerate(self.classes_):
-            self._class_dictionary[classVal] = index
-
-        # escape if only one class seen
-        if self.n_classes_ == 1:
-            self._is_fitted = True
-            return self
-
-        time_limit = self.time_limit_in_minutes * 60
-        start_time = time.time()
-        train_time = 0
-
-        if self.base_estimator is None:
-            self._base_estimator = DecisionTreeClassifier(criterion="entropy")
-
-        # remove useless attributes
-        self._useful_atts = ~np.all(X[1:] == X[:-1], axis=0)
-        X = X[:, self._useful_atts]
-
-        self._n_atts = X.shape[1]
-
-        # normalise attributes
-        self._min = X.min(axis=0)
-        self._ptp = X.max(axis=0) - self._min
-        X = (X - self._min) / self._ptp
-
-        X_cls_split = [X[np.where(y == i)] for i in self.classes_]
-
-        rng = check_random_state(self.random_state)
-
-        if time_limit > 0:
-            self._n_estimators = 0
-            self.estimators_ = []
-            self._pcas = []
-            self._groups = []
-            self.transformed_data_ = []
-
-            while (
-                train_time < time_limit
-                and self._n_estimators < self.contract_max_n_estimators
-            ):
-                fit = Parallel(n_jobs=self._n_jobs, prefer="threads")(
-                    delayed(self._fit_estimator)(
-                        X,
-                        X_cls_split,
-                        y,
-                        check_random_state(rng.randint(np.iinfo(np.int32).max)),
-                    )
-                    for _ in range(self._n_jobs)
-                )
-
-                estimators, pcas, groups, transformed_data = zip(*fit)
-
-                self.estimators_ += estimators
-                self._pcas += pcas
-                self._groups += groups
-                self.transformed_data_ += transformed_data
-
-                self._n_estimators += self._n_jobs
-                train_time = time.time() - start_time
-        else:
-            self._n_estimators = self.n_estimators
-
-            fit = Parallel(n_jobs=self._n_jobs, prefer="threads")(
-                delayed(self._fit_estimator)(
-                    X,
-                    X_cls_split,
-                    y,
-                    check_random_state(rng.randint(np.iinfo(np.int32).max)),
-                )
-                for _ in range(self._n_estimators)
-            )
-
-            self.estimators_, self._pcas, self._groups, self.transformed_data_ = zip(
-                *fit
-            )
-
-        self._is_fitted = True
+        self._fit_rotf(X, y)
         return self
 
-    def predict(self, X):
+    def predict(self, X) -> np.ndarray:
         """Predict for all cases in X. Built on top of predict_proba.
 
         Parameters
@@ -275,7 +177,7 @@ class RotationForestClassifier(BaseEstimator):
             ]
         )
 
-    def predict_proba(self, X):
+    def predict_proba(self, X) -> np.ndarray:
         """Probability estimates for each class for all cases in X.
 
         Parameters
@@ -331,43 +233,65 @@ class RotationForestClassifier(BaseEstimator):
         )
         return output
 
-    def _get_train_probs(self, X, y):
-        if not self._is_fitted:
-            raise NotFittedError(
-                f"This instance of {self.__class__.__name__} has not "
-                f"been fitted yet; please call `fit` first."
-            )
-        if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
-            X = np.reshape(X, (X.shape[0], -1))
-        elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
-            X = X.to_numpy()
-        elif not isinstance(X, np.ndarray) or len(X.shape) > 2:
-            raise ValueError(
-                "RotationForestClassifier is not a time series classifier. "
-                "A valid sklearn input such as a 2d numpy array is required."
-                "Sparse input formats are currently not supported."
-            )
-        X = self._validate_data(X=X, reset=False)
+    def fit_predict(self, X, y) -> np.ndarray:
+        """Fit a forest of trees and estimate predictions of the input.
 
-        # handle the single-class-label case
-        if len(self._class_dictionary) == 1:
-            return np.repeat([[1]], len(X), axis=0)
+        fit_predict produces prediction estimates using just the train data. The
+        output is found using out-of-bag (OOB) estimates from the forest.
 
-        n_cases, n_atts = X.shape
+        Parameters
+        ----------
+        X : 2d ndarray or DataFrame of shape = [n_cases, n_attributes]
+            The training data.
+        y : array-like, shape = [n_cases]
+            The class labels.
 
-        if n_cases != self.n_cases_ or n_atts != self.n_atts_:
-            raise ValueError(
-                "n_cases, n_atts mismatch. X should be the same as the training "
-                "data used in fit for generating train probabilities."
-            )
+        Returns
+        -------
+        y : array-like, shape = [n_cases]
+            Predicted class labels.
+
+        Notes
+        -----
+        Changes state by creating a fitted model that updates attributes ending in "_".
+        """
+        rng = check_random_state(self.random_state)
+        return np.array(
+            [
+                self.classes_[int(rng.choice(np.flatnonzero(prob == prob.max())))]
+                for prob in self.fit_predict_proba(X, y)
+            ]
+        )
+
+    def fit_predict_proba(self, X, y) -> np.ndarray:
+        """Fit a forest of trees and estimate probabilities of the input.
+
+        fit_predict produces prediction probability estimates using just the train
+        data. The output is found using out-of-bag (OOB) estimates from the forest.
 
-        if not self.save_transformed_data:
-            raise ValueError("Currently only works with saved transform data from fit.")
+        Parameters
+        ----------
+        X : 2d ndarray or DataFrame of shape = [n_cases, n_attributes]
+            The training data.
+        y : array-like, shape = [n_cases]
+            The class labels.
+
+        Returns
+        -------
+        y : array-like, shape = [n_cases, n_classes_]
+            Predicted probabilities using the ordering in classes_.
+
+        Notes
+        -----
+        Changes state by creating a fitted model that updates attributes ending in "_".
+        """
+        X_t = self._fit_rotf(X, y, save_transformed_data=True)
 
         rng = check_random_state(self.random_state)
 
         p = Parallel(n_jobs=self._n_jobs, prefer="threads")(
             delayed(self._train_probas_for_estimator)(
+                X_t,
                 y,
                 i,
                 check_random_state(rng.randint(np.iinfo(np.int32).max)),
@@ -377,12 +301,12 @@ class RotationForestClassifier(BaseEstimator):
         y_probas, oobs = zip(*p)
 
         results = np.sum(y_probas, axis=0)
-        divisors = np.zeros(n_cases)
+        divisors = np.zeros(self.n_cases_)
         for oob in oobs:
             for inst in oob:
                 divisors[inst] += 1
 
-        for i in range(n_cases):
+        for i in range(self.n_cases_):
             results[i] = (
                 np.ones(self.n_classes_) * (1 / self.n_classes_)
                 if divisors[i] == 0
@@ -391,7 +315,106 @@ class RotationForestClassifier(BaseEstimator):
 
         return results
 
-    def _fit_estimator(self, X, X_cls_split, y, rng):
+    def _fit_rotf(self, X, y, save_transformed_data=False):
+        if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
+            X = np.reshape(X, (X.shape[0], -1))
+        elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
+            X = X.to_numpy()
+        elif not isinstance(X, np.ndarray) or len(X.shape) > 2:
+            raise ValueError(
+                "RotationForestClassifier is not a time series classifier. "
+                "A valid sklearn input such as a 2d numpy array is required."
+                "Sparse input formats are currently not supported."
+            )
+        X, y = self._validate_data(X=X, y=y, ensure_min_samples=2)
+
+        self._n_jobs = check_n_jobs(self.n_jobs)
+
+        self.n_cases_, self.n_atts_ = X.shape
+        self.classes_ = np.unique(y)
+        self.n_classes_ = self.classes_.shape[0]
+        self._class_dictionary = {}
+        for index, classVal in enumerate(self.classes_):
+            self._class_dictionary[classVal] = index
+
+        # escape if only one class seen
+        if self.n_classes_ == 1:
+            self._is_fitted = True
+            return self
+
+        time_limit = self.time_limit_in_minutes * 60
+        start_time = time.time()
+        train_time = 0
+
+        if self.base_estimator is None:
+            self._base_estimator = DecisionTreeClassifier(criterion="entropy")
+
+        # remove useless attributes
+        self._useful_atts = ~np.all(X[1:] == X[:-1], axis=0)
+        X = X[:, self._useful_atts]
+
+        self._n_atts = X.shape[1]
+
+        # normalise attributes
+        self._min = X.min(axis=0)
+        self._ptp = X.max(axis=0) - self._min
+        X = (X - self._min) / self._ptp
+
+        X_cls_split = [X[np.where(y == i)] for i in self.classes_]
+
+        rng = check_random_state(self.random_state)
+
+        if time_limit > 0:
+            self._n_estimators = 0
+            self.estimators_ = []
+            self._pcas = []
+            self._groups = []
+            X_t = []
+
+            while (
+                train_time < time_limit
+                and self._n_estimators < self.contract_max_n_estimators
+            ):
+                fit = Parallel(n_jobs=self._n_jobs, prefer="threads")(
+                    delayed(self._fit_estimator)(
+                        X,
+                        X_cls_split,
+                        y,
+                        check_random_state(rng.randint(np.iinfo(np.int32).max)),
+                        save_transformed_data,
+                    )
+                    for _ in range(self._n_jobs)
+                )
+
+                estimators, pcas, groups, transformed_data = zip(*fit)
+
+                self.estimators_ += estimators
+                self._pcas += pcas
+                self._groups += groups
+                X_t += transformed_data
+
+                self._n_estimators += self._n_jobs
+                train_time = time.time() - start_time
+        else:
+            self._n_estimators = self.n_estimators
+
+            fit = Parallel(n_jobs=self._n_jobs, prefer="threads")(
+                delayed(self._fit_estimator)(
+                    X,
+                    X_cls_split,
+                    y,
+                    check_random_state(rng.randint(np.iinfo(np.int32).max)),
+                    save_transformed_data,
+                )
+                for _ in range(self._n_estimators)
+            )
+
+            self.estimators_, self._pcas, self._groups, X_t = zip(*fit)
+
+        self._is_fitted = True
+        return X_t
+
+    def _fit_estimator(self, X, X_cls_split, y, rng, save_transformed_data):
         groups = self._generate_groups(rng)
         pcas = []
 
@@ -446,7 +469,7 @@ class RotationForestClassifier(BaseEstimator):
         tree = _clone_estimator(self._base_estimator, random_state=rng)
         tree.fit(X_t, y)
 
-        return tree, pcas, groups, X_t if self.save_transformed_data else None
+        return tree, pcas, groups, X_t if save_transformed_data else None
 
     def _predict_proba_for_estimator(self, X, clf, pcas, groups):
         X_t = np.concatenate(
@@ -468,7 +491,7 @@ class RotationForestClassifier(BaseEstimator):
 
         return probas
 
-    def _train_probas_for_estimator(self, y, idx, rng):
+    def _train_probas_for_estimator(self, X_t, y, idx, rng):
         indices = range(self.n_cases_)
         subsample = rng.choice(self.n_cases_, size=self.n_cases_)
         oob = [n for n in indices if n not in subsample]
@@ -478,8 +501,8 @@ class RotationForestClassifier(BaseEstimator):
             return [results, oob]
 
         clf = _clone_estimator(self._base_estimator, rng)
-        clf.fit(self.transformed_data_[idx][subsample], y[subsample])
-        probas = clf.predict_proba(self.transformed_data_[idx][oob])
+        clf.fit(X_t[idx][subsample], y[subsample])
+        probas = clf.predict_proba(X_t[idx][oob])
 
         if probas.shape[1] != self.n_classes_:
             new_probas = np.zeros((probas.shape[0], self.n_classes_))
diff --git a/aeon/clustering/_kernel_k_means.py b/aeon/clustering/_kernel_k_means.py
index 747604fc..c4ba615a 100644
--- a/aeon/clustering/_kernel_k_means.py
+++ b/aeon/clustering/_kernel_k_means.py
@@ -44,7 +44,7 @@ class TimeSeriesKernelKMeans(BaseClusterer):
         convergence.
     verbose: bool, default=False
         Verbosity mode.
-    n_jobs : int or None, optional (default=None)
+    n_jobs : int or None, default=None
         The number of jobs to run in parallel for GAK cross-similarity matrix
         computations.
         ``None`` means 1 unless in a :obj:`joblib.parallel_backend` context.
diff --git a/aeon/datasets/_data_loaders.py b/aeon/datasets/_data_loaders.py
index d646682a..745749ae 100644
--- a/aeon/datasets/_data_loaders.py
+++ b/aeon/datasets/_data_loaders.py
@@ -493,10 +493,10 @@ def _load_tsc_dataset(
     Parameters
     ----------
     name : string, file name to load from
-    split: None or one of "TRAIN", "TEST", optional (default=None)
+    split: None or one of "TRAIN", "TEST", default=None
         Whether to load the train or test instances of the problem.
         By default it loads both train and test instances (in a single container).
-    return_X_y: bool, optional (default=True)
+    return_X_y: bool, default=True
         If True, returns (features, target) separately instead of a single
         dataframe with columns for features and the target.
     return_data_type : str, optional, default = None
diff --git a/aeon/datasets/_single_problem_loaders.py b/aeon/datasets/_single_problem_loaders.py
index 488802e6..cad9d0cb 100644
--- a/aeon/datasets/_single_problem_loaders.py
+++ b/aeon/datasets/_single_problem_loaders.py
@@ -51,7 +51,7 @@ def load_gunpoint(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Raises
@@ -97,7 +97,7 @@ def load_osuleaf(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Raises
@@ -143,7 +143,7 @@ def load_italy_power_demand(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Raises
@@ -196,7 +196,7 @@ def load_unit_test(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure containing series, should be either "numpy2d" or "numpy3d".
 
     Raises
@@ -245,7 +245,7 @@ def load_arrow_head(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Raises
@@ -293,7 +293,7 @@ def load_acsf1(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Raises
@@ -342,7 +342,7 @@ def load_basic_motions(split=None, return_X_y=True, return_type="numpy3d"):
     return_X_y: bool, default=True
         If True, returns (features, target) separately instead of as single data
         structure.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be "numpy3d" or "np-list".
 
     Raises
@@ -430,10 +430,10 @@ def load_japanese_vowels(split=None, return_X_y=True, return_type="np-list"):
 
     Parameters
     ----------
-    split: None or one of "TRAIN", "TEST", optional (default=None)
+    split: None or one of "TRAIN", "TEST", default=None
         Whether to load the train or test instances of the problem. By default it
         loads both train and test instances into a single array.
-    return_X_y: bool, optional (default=True)
+    return_X_y: bool, default=True
         If True, returns (features, target) separately instead of a single
         dataframe with columns for features and the target.
     return_type: string, default="np-list"
@@ -510,7 +510,7 @@ def load_longley(y_name="TOTEMP"):
 
     Parameters
     ----------
-    y_name: str, optional (default="TOTEMP")
+    y_name: str, default="TOTEMP"
         Name of target variable (y)
 
     Returns
@@ -1029,14 +1029,14 @@ def load_covid_3month(split=None, return_X_y=True, return_type="numpy3d"):
 
     Parameters
     ----------
-    split: None or str{"train", "test"}, optional (default=None)
+    split: None or str{"train", "test"}, default=None
         Whether to load the train or test partition of the problem. By
         default, it loads both.
-    return_X_y: bool, optional (default=True)
+    return_X_y: bool, default=True
         If True, returns (features, target) separately instead of a single
         dataframe with columns for
         features and the target.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Returns
@@ -1084,14 +1084,14 @@ def load_cardano_sentiment(split=None, return_X_y=True, return_type="numpy3d"):
 
     Parameters
     ----------
-    split: None or str{"train", "test"}, optional (default=None)
+    split: None or str{"train", "test"}, default=None
         Whether to load the train or test partition of the problem. By
         default, it loads both.
-    return_X_y: bool, optional (default=True)
+    return_X_y: bool, default=True
         If True, returns (features, target) separately instead of a single
         dataframe with columns for
         features and the target.
-    return_type: string, optional (default="numpy3d")
+    return_type: string, default="numpy3d"
         Data structure to use for time series, should be either "numpy2d" or "numpy3d".
 
     Returns
diff --git a/aeon/forecasting/ardl.py b/aeon/forecasting/ardl.py
index 2492040e..ad0a85c6 100644
--- a/aeon/forecasting/ardl.py
+++ b/aeon/forecasting/ardl.py
@@ -306,7 +306,7 @@ class ARDL(_StatsModelsAdapter):
             if self.get_tag("y_input_type")=="univariate":
                 guaranteed to have a single column/variable
             A 1-d endogenous response variable. The dependent variable.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
             Exogenous variables to include in the model. Either a DataFrame or
@@ -392,10 +392,10 @@ class ARDL(_StatsModelsAdapter):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
 
@@ -442,10 +442,10 @@ class ARDL(_StatsModelsAdapter):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
diff --git a/aeon/forecasting/arima.py b/aeon/forecasting/arima.py
index b385533a..228fee49 100644
--- a/aeon/forecasting/arima.py
+++ b/aeon/forecasting/arima.py
@@ -44,94 +44,94 @@ class AutoARIMA(_PmdArimaAdapter):
 
     Parameters
     ----------
-    start_p : int, optional (default=2)
+    start_p : int, default=2
         The starting value of p, the order (or number of time lags)
         of the auto-regressive ("AR") model. Must be a positive integer.
-    d : int, optional (default=None)
+    d : int, default=None
         The order of first-differencing. If None (by default), the value will
         automatically be selected based on the results of the test (i.e.,
         either the Kwiatkowski–Phillips–Schmidt–Shin, Augmented Dickey-Fuller
         or the Phillips–Perron test will be conducted to find the most probable
         value). Must be a positive integer or None. Note that if d is None,
         the runtime could be significantly longer.
-    start_q : int, optional (default=2)
+    start_q : int, default=2
         The starting value of q, the order of the moving-average ("MA") model.
         Must be a positive integer.
-    max_p : int, optional (default=5)
+    max_p : int, default=5
         The maximum value of p, inclusive. Must be a positive integer greater
         than or equal to start_p.
-    max_d : int, optional (default=2)
+    max_d : int, default=2
         The maximum value of d, or the maximum number of non-seasonal
         differences. Must be a positive integer greater than or equal to d.
-    max_q : int, optional (default=5)
+    max_q : int, default=5
         he maximum value of q, inclusive. Must be a positive integer greater
         than start_q.
-    start_P : int, optional (default=1)
+    start_P : int, default=1
         The starting value of P, the order of the auto-regressive portion of
         the seasonal model.
-    D : int, optional (default=None)
+    D : int, default=None
         The order of the seasonal differencing. If None (by default, the value
         will automatically be selected based on the results of the
         seasonal_test. Must be a positive integer or None.
-    start_Q : int, optional (default=1)
+    start_Q : int, default=1
         The starting value of Q, the order of the moving-average portion of
         the seasonal model.
-    max_P : int, optional (default=2)
+    max_P : int, default=2
         The maximum value of P, inclusive. Must be a positive integer greater
         than start_P.
-    max_D : int, optional (default=1)
+    max_D : int, default=1
         The maximum value of D. Must be a positive integer greater than D.
-    max_Q : int, optional (default=2)
+    max_Q : int, default=2
         The maximum value of Q, inclusive. Must be a positive integer greater
         than start_Q.
-    max_order : int, optional (default=5)
+    max_order : int, default=5
         Maximum value of p+q+P+Q if model selection is not stepwise. If the
         sum of p and q is >= max_order, a model will not be fit with those
         parameters, but will progress to the next combination. Default is 5.
         If max_order is None, it means there are no constraints on maximum
         order.
-    sp : int, optional (default=1)
+    sp : int, default=1
         The period for seasonal differencing, sp refers to the number of
         periods in each season. For example, sp is 4 for quarterly data, 12
         for monthly data, or 1 for annual (non-seasonal) data. Default is 1.
         Note that if sp == 1 (i.e., is non-seasonal), seasonal will be set to
         False. For more information on setting this parameter, see Setting sp.
         (link to http://alkaline-ml.com/pmdarima/tips_and_tricks.html#period)
-    seasonal : bool, optional (default=True)
+    seasonal : bool, default=True
         Whether to fit a seasonal ARIMA. Default is True. Note that if
         seasonal is True and sp == 1, seasonal will be set to False.
-    stationary : bool, optional (default=False)
+    stationary : bool, default=False
         Whether the time-series is stationary and d should be set to zero.
-    information_criterion : str, optional (default='aic')
+    information_criterion : str, default='aic'
         The information criterion used to select the best ARIMA model. One of
         pmdarima.arima.auto_arima.VALID_CRITERIA, ('aic', 'bic', 'hqic',
         'oob').
-    alpha : float, optional (default=0.05)
+    alpha : float, default=0.05
         Level of the test for testing significance.
-    test : str, optional (default='kpss')
+    test : str, default='kpss'
         Type of unit root test to use in order to detect stationarity if
         stationary is False and d is None.
-    seasonal_test : str, optional (default='ocsb')
+    seasonal_test : str, default='ocsb'
         This determines which seasonal unit root test is used if seasonal is
         True and D is None.
-    stepwise : bool, optional (default=True)
+    stepwise : bool, default=True
         Whether to use the stepwise algorithm outlined in Hyndman and
         Khandakar (2008) to identify the optimal model parameters. The
         stepwise algorithm can be significantly faster than fitting all (or a
         random subset of) hyper-parameter combinations and is less likely to
         over-fit the model.
-    n_jobs : int, optional (default=1)
+    n_jobs : int, default=1
         The number of models to fit in parallel in the case of a grid search
         (stepwise=False). Default is 1, but -1 can be used to designate "as
         many as possible".
-    start_params : array-like, optional (default=None)
+    start_params : array-like, default=None
         Starting parameters for ARMA(p,q). If None, the default is given by
         ARMA._fit_start_params.
-    trend : str, optional (default=None)
+    trend : str, default=None
         The trend parameter. If with_intercept is True, trend will be used. If
         with_intercept is False, the trend will be set to a no- intercept
         value.
-    method : str, optional (default='lbfgs')
+    method : str, default='lbfgs'
         The ``method`` determines which solver from ``scipy.optimize``
         is used, and it can be chosen from among the following strings:
 
@@ -148,19 +148,19 @@ class AutoARIMA(_PmdArimaAdapter):
         with the exception of the basin-hopping solver. Each
         solver has several optional arguments that are not the same across
         solvers. These can be passed as **fit_kwargs
-    maxiter : int, optional (default=50)
+    maxiter : int, default=50
         The maximum number of function evaluations.
-    offset_test_args : dict, optional (default=None)
+    offset_test_args : dict, default=None
         The args to pass to the constructor of the offset (d) test.
         See pmdarima.arima.stationarity for more details.
-    seasonal_test_args : dict, optional (default=None)
+    seasonal_test_args : dict, default=None
         The args to pass to the constructor of the seasonal offset (D) test.
         See pmdarima.arima.seasonality for more details.
-    suppress_warnings : bool, optional (default=False)
+    suppress_warnings : bool, default=False
         Many warnings might be thrown inside of statsmodels. If
         suppress_warnings is True, all of the warnings coming from ARIMA will
         be squelched.
-    error_action : str, optional (default='warn')
+    error_action : str, default='warn'
         If unable to fit an ARIMA due to stationarity issues, whether to warn
         ('warn'), raise the ValueError ('raise') or ignore ('ignore'). Note
         that the default behavior is to warn, and fits that fail will be
@@ -168,23 +168,23 @@ class AutoARIMA(_PmdArimaAdapter):
         ARIMA and SARIMAX models hit bugs periodically that can cause an
         otherwise healthy parameter combination to fail for reasons not
         related to pmdarima.
-    trace : bool, optional (default=False)
+    trace : bool, default=False
         Whether to print status on the fits. A value of False will print no
         debugging information. A value of True will print some. Integer values
         exceeding 1 will print increasing amounts of debug information at each
         fit.
-    random : bool, optional (default='False')
+    random : bool, default='False'
         Similar to grid searches, auto_arima provides the capability to
         perform a "random search" over a hyper-parameter space. If random is
         True, rather than perform an exhaustive search or stepwise search,
         only n_fits ARIMA models will be fit (stepwise must be False for this
         option to do anything).
-    random_state : int, long or numpy RandomState, optional (default=None)
+    random_state : int, long or numpy RandomState, default=None
         The PRNG for when random=True. Ensures replicable testing and results.
-    n_fits : int, optional (default=10)
+    n_fits : int, default=10
         If random is True and a "random search" is going to be performed,
         n_iter is the number of ARIMA models to be fit.
-    out_of_sample_size : int, optional (default=0)
+    out_of_sample_size : int, default=0
         The number of examples from the tail of the time series to hold out
         and use as validation examples. The model will not be fit on these
         samples, but the observations will be added into the model's ``endog``
@@ -195,28 +195,28 @@ class AutoARIMA(_PmdArimaAdapter):
             > Fit on: [0, 1, 2, 3, 4]
             > Score on: [5, 6]
             > Append [5, 6] to end of self.arima_res_.data.endog values
-    scoring : str, optional (default='mse')
+    scoring : str, default='mse'
         If performing validation (i.e., if out_of_sample_size > 0), the metric
         to use for scoring the out-of-sample data. One of ('mse', 'mae')
-    scoring_args : dict, optional (default=None)
+    scoring_args : dict, default=None
         A dictionary of key-word arguments to be passed to the scoring metric.
-    with_intercept : bool, optional (default=True)
+    with_intercept : bool, default=True
         Whether to include an intercept term.
-    update_pdq : bool, optional (default=True)
+    update_pdq : bool, default=True
         whether to update pdq parameters in update
         True: model is refit on all data seen so far, potentially updating p,d,q
         False: model updates only ARIMA coefficients via likelihood, as in pmdarima
     Further arguments to pass to the SARIMAX constructor:
-    - time_varying_regression : boolean, optional (default=False)
+    - time_varying_regression : boolean, default=False
         Whether or not coefficients on the exogenous regressors are allowed
         to vary over time.
-    - enforce_stationarity : boolean, optional (default=True)
+    - enforce_stationarity : boolean, default=True
         Whether or not to transform the AR parameters to enforce
         stationarity in the auto-regressive component of the model.
-        - enforce_invertibility : boolean, optional (default=True)
+        - enforce_invertibility : boolean, default=True
         Whether or not to transform the MA parameters to enforce
         invertibility in the moving average component of the model.
-    - simple_differencing : boolean, optional (default=False)
+    - simple_differencing : boolean, default=False
         Whether or not to use partially conditional maximum likelihood
         estimation for seasonal ARIMA models. If True, differencing is
         performed prior to estimation, which discards the first
@@ -224,20 +224,20 @@ class AutoARIMA(_PmdArimaAdapter):
         state-space formulation. If False, the full SARIMAX model is
         put in state-space form so that all datapoints can be used in
         estimation. Default is False.
-    - measurement_error: boolean, optional (default=False)
+    - measurement_error: boolean, default=False
         Whether or not to assume the endogenous observations endog were
         measured with error. Default is False.
-    - mle_regression : boolean, optional (default=True)
+    - mle_regression : boolean, default=True
         Whether or not to use estimate the regression coefficients for the
         exogenous variables as part of maximum likelihood estimation or
         through the Kalman filter (i.e. recursive least squares). If
         time_varying_regression is True, this must be set to False.
         Default is True.
-    - hamilton_representation : boolean, optional (default=False)
+    - hamilton_representation : boolean, default=False
         Whether or not to use the Hamilton representation of an ARMA
         process (if True) or the Harvey representation (if False).
         Default is False.
-    - concentrate_scale : boolean, optional (default=False)
+    - concentrate_scale : boolean, default=False
         Whether or not to concentrate the scale (variance of the error
         term) out of the likelihood. This reduces the number of parameters
         estimated by maximum likelihood by one, but standard errors will
@@ -426,7 +426,7 @@ class AutoARIMA(_PmdArimaAdapter):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -518,7 +518,7 @@ class ARIMA(_PmdArimaAdapter):
 
     Parameters
     ----------
-    order : iterable or array-like, shape=(3,), optional (default=(1, 0, 0))
+    order : iterable or array-like, shape=(3,), default=(1, 0, 0)
         The (p,d,q) order of the model for the number of AR parameters,
         differences, and MA parameters to use. ``p`` is the order (number of
         time lags) of the auto-regressive model, and is a non-negative integer.
@@ -526,7 +526,7 @@ class ARIMA(_PmdArimaAdapter):
         had past values subtracted), and is a non-negative integer. ``q`` is
         the order of the moving-average model, and is a non-negative integer.
         Default is an AR(1) model: (1,0,0).
-    seasonal_order : array-like, shape=(4,), optional (default=(0, 0, 0, 0))
+    seasonal_order : array-like, shape=(4,), default=(0, 0, 0, 0)
         The (P,D,Q,s) order of the seasonal component of the model for the
         AR parameters, differences, MA parameters, and periodicity. ``D`` must
         be an integer indicating the integration order of the process, while
@@ -536,10 +536,10 @@ class ARIMA(_PmdArimaAdapter):
         integer giving the periodicity (number of periods in season), often it
         is 4 for quarterly data or 12 for monthly data. Default is no seasonal
         effect.
-    start_params : array-like, optional (default=None)
+    start_params : array-like, default=None
         Starting parameters for ``ARMA(p,q)``.  If None, the default is given
         by ``ARMA._fit_start_params``.
-    method : str, optional (default='lbfgs')
+    method : str, default='lbfgs'
         The ``method`` determines which solver from ``scipy.optimize``
         is used, and it can be chosen from among the following strings:
 
@@ -556,12 +556,12 @@ class ARIMA(_PmdArimaAdapter):
         with the exception of the basin-hopping solver. Each
         solver has several optional arguments that are not the same across
         solvers. These can be passed as **fit_kwargs
-    maxiter : int, optional (default=50)
+    maxiter : int, default=50
         The maximum number of function evaluations. Default is 50
-    suppress_warnings : bool, optional (default=False)
+    suppress_warnings : bool, default=False
         Many warnings might be thrown inside of statsmodels. If
         ``suppress_warnings`` is True, all of these warnings will be squelched.
-    out_of_sample_size : int, optional (default=0)
+    out_of_sample_size : int, default=0
         The number of examples from the tail of the time series to hold out
         and use as validation examples. The model will not be fit on these
         samples, but the observations will be added into the model's ``endog``
@@ -573,7 +573,7 @@ class ARIMA(_PmdArimaAdapter):
             > Fit on: [0, 1, 2, 3, 4]
             > Score on: [5, 6]
             > Append [5, 6] to end of self.arima_res_.data.endog values
-    scoring : str or callable, optional (default='mse')
+    scoring : str or callable, default='mse'
         If performing validation (i.e., if ``out_of_sample_size`` > 0), the
         metric to use for scoring the out-of-sample data:
 
@@ -588,27 +588,27 @@ class ARIMA(_PmdArimaAdapter):
         maximizing metric (such as ``sklearn.metrics.r2_score``), it is the
         user's responsibility to wrap the function such that it returns a
         negative value for minimizing.
-    scoring_args : dict, optional (default=None)
+    scoring_args : dict, default=None
         A dictionary of key-word arguments to be passed to the
         ``scoring`` metric.
-    trend : str or None, optional (default=None)
+    trend : str or None, default=None
         The trend parameter. If ``with_intercept`` is True, ``trend`` will be
         used. If ``with_intercept`` is False, the trend will be set to a no-
         intercept value. If None and ``with_intercept``, 'c' will be used as
         a default.
-    with_intercept : bool, optional (default=True)
+    with_intercept : bool, default=True
         Whether to include an intercept term. Default is True.
     Further arguments to pass to the SARIMAX constructor:
-    - time_varying_regression : boolean, optional (default=False)
+    - time_varying_regression : boolean, default=False
         Whether or not coefficients on the exogenous regressors are allowed
         to vary over time.
-    - enforce_stationarity : boolean, optional (default=True)
+    - enforce_stationarity : boolean, default=True
         Whether or not to transform the AR parameters to enforce
         stationarity in the auto-regressive component of the model.
-        - enforce_invertibility : boolean, optional (default=True)
+        - enforce_invertibility : boolean, default=True
         Whether or not to transform the MA parameters to enforce
         invertibility in the moving average component of the model.
-    - simple_differencing : boolean, optional (default=False)
+    - simple_differencing : boolean, default=False
         Whether or not to use partially conditional maximum likelihood
         estimation for seasonal ARIMA models. If True, differencing is
         performed prior to estimation, which discards the first
@@ -616,20 +616,20 @@ class ARIMA(_PmdArimaAdapter):
         state-space formulation. If False, the full SARIMAX model is
         put in state-space form so that all datapoints can be used in
         estimation. Default is False.
-    - measurement_error: boolean, optional (default=False)
+    - measurement_error: boolean, default=False
         Whether or not to assume the endogenous observations endog were
         measured with error. Default is False.
-    - mle_regression : boolean, optional (default=True)
+    - mle_regression : boolean, default=True
         Whether or not to use estimate the regression coefficients for the
         exogenous variables as part of maximum likelihood estimation or
         through the Kalman filter (i.e. recursive least squares). If
         time_varying_regression is True, this must be set to False.
         Default is True.
-    - hamilton_representation : boolean, optional (default=False)
+    - hamilton_representation : boolean, default=False
         Whether or not to use the Hamilton representation of an ARMA
         process (if True) or the Harvey representation (if False).
         Default is False.
-    - concentrate_scale : boolean, optional (default=False)
+    - concentrate_scale : boolean, default=False
         Whether or not to concentrate the scale (variance of the error
         term) out of the likelihood. This reduces the number of parameters
         estimated by maximum likelihood by one, but standard errors will
diff --git a/aeon/forecasting/base/_base.py b/aeon/forecasting/base/_base.py
index ac04f6f5..37769add 100644
--- a/aeon/forecasting/base/_base.py
+++ b/aeon/forecasting/base/_base.py
@@ -301,12 +301,12 @@ class BaseForecaster(BaseEstimator):
             For further details:
                 on usage, see forecasting examples/forecasting
                 on specification of formats, examples/datasets
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same abstract type (Series, Panel, or Hierarchical) as y
             if self.get_tag("X-y-must-have-same-index"), X.index must contain y.index
             there are no restrictions on number of columns (unlike for y).
-        fh : int, list, np.array or ForecastingHorizon, optional (default=None)
+        fh : int, list, np.array or ForecastingHorizon, default=None
             The forecasting horizon encoding the time stamps to forecast at.
             if self.get_tag("requires-fh-in-fit"), must be passed, not optional.
 
@@ -365,10 +365,10 @@ class BaseForecaster(BaseEstimator):
 
         Parameters
         ----------
-        fh : int, list, np.array or ForecastingHorizon, optional (default=None)
+        fh : int, list, np.array or ForecastingHorizon, default=None
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional.
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same abstract type (Series, Panel, or Hierarchical) as y in fit
             if self.get_tag("X-y-must-have-same-index"), X.index must contain fh.index
@@ -443,7 +443,7 @@ class BaseForecaster(BaseEstimator):
         fh : int, list, np.array or ForecastingHorizon (not optional)
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same abstract type (Series, Panel, or Hierarchical) as y in fit
             if self.get_tag("X-y-must-have-same-index"),
@@ -506,11 +506,11 @@ class BaseForecaster(BaseEstimator):
         fh : int, list, np.array or ForecastingHorizon (not optional)
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same abstract type (Series, Panel, or Hierarchical) as y in fit
             if self.get_tag("X-y-must-have-same-index"), must contain fh.index
-        alpha : float or list of float of unique values, optional (default=[0.05, 0.95])
+        alpha : float or list of float of unique values, default=[0.05, 0.95]
             A probability or list of, at which quantile forecasts are computed.
 
         Returns
@@ -582,7 +582,7 @@ class BaseForecaster(BaseEstimator):
         fh : int, list, np.array or ForecastingHorizon (not optional)
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional.
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same abstract type (Series, Panel, or Hierarchical) as y in fit
             if self.get_tag("X-y-must-have-same-index"), must contain fh.index.
@@ -652,12 +652,12 @@ class BaseForecaster(BaseEstimator):
         fh : int, list, np.array or ForecastingHorizon (not optional)
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same abstract type (Series, Panel, or Hierarchical) as y in fit
             if self.get_tag("X-y-must-have-same-index"),
                 X.index must contain fh.index and y.index both
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
@@ -813,12 +813,12 @@ class BaseForecaster(BaseEstimator):
                 if self.get_tag("y_input_type")=="both": no restrictions apply
             For further details:
                 See  examples/forecasting, or examples/datasets,
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series to fit to
             Should be of same type (Series, Panel, or Hierarchical) as y
             if self.get_tag("X-y-must-have-same-index"), X.index must contain y.index
             there are no restrictions on number of columns (unlike for y)
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -897,15 +897,15 @@ class BaseForecaster(BaseEstimator):
             default = ExpandingWindowSplitter with `initial_window=1` and defaults
                 = individual data points in y/X are added and forecast one-by-one,
                 `initial_window = 1`, `step_length = 1` and `fh = 1`
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
             Exogeneous time series for updating and forecasting
             Should be of same abstract type (Series, Panel, or Hierarchical) as y
             if self.get_tag("X-y-must-have-same-index"),
                 X.index must contain y.index and fh.index both
             there are no restrictions on number of columns (unlike for y).
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             Whether model parameters should be updated in each update step.
-        reset_forecaster : bool, optional (default=True)
+        reset_forecaster : bool, default=True
             If True, will not change the state of the forecaster,
                 i.e., update/predict sequence is run with a copy,
                 and cutoff, model parameters, data memory of self do not change
@@ -996,15 +996,15 @@ class BaseForecaster(BaseEstimator):
                     y must have 2 or more columns
                 if self.get_tag("y_input_type")=="both": no restrictions apply
             For further details see  examples/forecasting, or examples/datasets.
-        fh : int, list, np.array or ForecastingHorizon, optional (default=None)
+        fh : int, list, np.array or ForecastingHorizon, default=None
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional.
-        X : time series in aeon compatible format, optional (default=None)
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series for updating and forecasting
             Should be of same abstract type (Series, Panel, or Hierarchical) as y
             if self.get_tag("X-y-must-have-same-index"),
                 X.index must contain y.index and fh.index both.
-        update_params : bool, optional (default=False)
+        update_params : bool, default=False
 
         Returns
         -------
@@ -1155,7 +1155,7 @@ class BaseForecaster(BaseEstimator):
         X : pd.DataFrame, or 2D np.array, default=None
             Exogeneous time series to score.
             if self.get_tag("X-y-must-have-same-index"), X.index must contain y.index
-        fh : int, list, array-like or ForecastingHorizon, optional (default=None)
+        fh : int, list, array-like or ForecastingHorizon, default=None
             The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -1241,9 +1241,9 @@ class BaseForecaster(BaseEstimator):
 
         Parameters
         ----------
-        y : pd.Series, pd.DataFrame, or np.ndarray (1D or 2D), optional (default=None)
+        y : pd.Series, pd.DataFrame, or np.ndarray (1D or 2D), default=None
             Time series to check.
-        X : pd.DataFrame, or 2D np.array, optional (default=None)
+        X : pd.DataFrame, or 2D np.array, default=None
             Exogeneous time series.
 
         Returns
@@ -1479,7 +1479,7 @@ class BaseForecaster(BaseEstimator):
         ----------
         y : pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
             Endogenous time series
-        X : pd.DataFrame or 2D np.ndarray, optional (default=None)
+        X : pd.DataFrame or 2D np.ndarray, default=None
             Exogeneous time series
         """
         if y is not None:
@@ -1762,11 +1762,11 @@ class BaseForecaster(BaseEstimator):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -1790,10 +1790,10 @@ class BaseForecaster(BaseEstimator):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
 
@@ -1829,10 +1829,10 @@ class BaseForecaster(BaseEstimator):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -1901,10 +1901,10 @@ class BaseForecaster(BaseEstimator):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        coverage : float or list, optional (default=0.95)
+        coverage : float or list, default=0.95
            nominal coverage(s) of predictive interval(s)
 
         Returns
@@ -1974,10 +1974,10 @@ class BaseForecaster(BaseEstimator):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        alpha : list of float, optional (default=[0.5])
+        alpha : list of float, default=[0.5]
             A list of probabilities at which quantile forecasts are computed.
 
         Returns
@@ -2049,10 +2049,10 @@ class BaseForecaster(BaseEstimator):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
@@ -2141,10 +2141,10 @@ class BaseForecaster(BaseEstimator):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        marginal : bool, optional (default=True)
+        marginal : bool, default=True
             whether returned distribution is marginal by time index
 
         Returns
@@ -2207,16 +2207,16 @@ class BaseForecaster(BaseEstimator):
                     y must have 2 or more columns
                 if self.get_tag("y_input_type")=="both": no restrictions apply
             For further details see  examples/forecasting, or examples/datasets
-        cv : temporal cross-validation generator, optional (default=None)
-        X : time series in aeon compatible format, optional (default=None)
+        cv : temporal cross-validation generator, default=None
+        X : time series in aeon compatible format, default=None
                 Exogeneous time series for updating and forecasting
             Should be of same abstract type (Series, Panel, or Hierarchical) as y
             if self.get_tag("X-y-must-have-same-index"),
                 X.index must contain y.index and fh.index both
             there are no restrictions on number of columns (unlike for y)
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated in each update step
-        reset_forecaster : bool, optional (default=True)
+        reset_forecaster : bool, default=True
             if True, will not change the state of the forecaster,
                 i.e., update/predict sequence is run with a copy,
                 and cutoff, model parameters, data memory of self do not change
diff --git a/aeon/forecasting/base/_delegate.py b/aeon/forecasting/base/_delegate.py
index 5d1abac8..30548fb5 100644
--- a/aeon/forecasting/base/_delegate.py
+++ b/aeon/forecasting/base/_delegate.py
@@ -54,11 +54,11 @@ class _DelegatedForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -84,10 +84,10 @@ class _DelegatedForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -123,9 +123,9 @@ class _DelegatedForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -165,7 +165,7 @@ class _DelegatedForecaster(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
@@ -200,7 +200,7 @@ class _DelegatedForecaster(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -229,12 +229,12 @@ class _DelegatedForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
@@ -264,10 +264,10 @@ class _DelegatedForecaster(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        marginal : bool, optional (default=True)
+        marginal : bool, default=True
             whether returned distribution is marginal by time index
 
         Returns
diff --git a/aeon/forecasting/base/_fh.py b/aeon/forecasting/base/_fh.py
index ad2f1a04..ea14d0d4 100644
--- a/aeon/forecasting/base/_fh.py
+++ b/aeon/forecasting/base/_fh.py
@@ -192,7 +192,7 @@ class ForecastingHorizon:
     ----------
     values : pd.Index, pd.TimedeltaIndex, np.array, list, pd.Timedelta, or int
         Values of forecasting horizon
-    is_relative : bool, optional (default=None)
+    is_relative : bool, default=None
         - If True, a relative ForecastingHorizon is created:
                 values are relative to end of training series.
         - If False, an absolute ForecastingHorizon is created:
@@ -200,7 +200,7 @@ class ForecastingHorizon:
         - if None, the flag is determined automatically:
             relative, if values are of supported relative index type
             absolute, if not relative and values of supported absolute index type
-    freq : str, pd.Index, pandas offset, or aeon forecaster, optional (default=None)
+    freq : str, pd.Index, pandas offset, or aeon forecaster, default=None
         object carrying frequency information on values
         ignored unless values is without inferrable freq
 
@@ -333,7 +333,7 @@ class ForecastingHorizon:
             - If None, determined automatically: same as self.is_relative
             - If True, values are relative to end of training series.
             - If False, values are absolute.
-        freq : str, optional (default=None)
+        freq : str, default=None
             Frequency string
 
         Returns
@@ -447,7 +447,7 @@ class ForecastingHorizon:
 
         Parameters
         ----------
-        cutoff : pd.Period, pd.Timestamp, int, or pd.Index, optional (default=None)
+        cutoff : pd.Period, pd.Timestamp, int, or pd.Index, default=None
             Cutoff value required to convert a relative forecasting
             horizon to an absolute one (and vice versa).
             If pd.Index, last/latest value is considered the cutoff
@@ -485,7 +485,7 @@ class ForecastingHorizon:
         ----------
         start : pd.Period, pd.Timestamp, int
             Start value returned as zero.
-        cutoff : pd.Period, pd.Timestamp, int, or pd.Index, optional (default=None)
+        cutoff : pd.Period, pd.Timestamp, int, or pd.Index, default=None
             Cutoff value required to convert a relative forecasting
             horizon to an absolute one (and vice versa).
             If pd.Index, last/latest value is considered the cutoff
@@ -538,7 +538,7 @@ class ForecastingHorizon:
 
         Parameters
         ----------
-        cutoff : pd.Period, pd.Timestamp, int, optional (default=None)
+        cutoff : pd.Period, pd.Timestamp, int, default=None
             Cutoff value required to convert a relative forecasting
             horizon to an absolute one (and vice versa).
 
@@ -556,7 +556,7 @@ class ForecastingHorizon:
 
         Parameters
         ----------
-        cutoff : pd.Period, pd.Timestamp, int, optional (default=None)
+        cutoff : pd.Period, pd.Timestamp, int, default=None
             Cutoff value is required to convert a relative forecasting
             horizon to an absolute one (and vice versa).
 
@@ -599,7 +599,7 @@ class ForecastingHorizon:
 
         Parameters
         ----------
-        cutoff : pd.Period, pd.Timestamp, int, optional (default=None)
+        cutoff : pd.Period, pd.Timestamp, int, default=None
             Cutoff value used to check if forecasting horizon is purely
             out-of-sample.
 
@@ -616,10 +616,10 @@ class ForecastingHorizon:
 
         Parameters
         ----------
-        cutoff : pd.Period, pd.Timestamp, int, optional (default=None)
+        cutoff : pd.Period, pd.Timestamp, int, default=None
             Cutoff value required to convert a relative forecasting
             horizon to an absolute one and vice versa.
-        from_cutoff : bool, optional (default=True)
+        from_cutoff : bool, default=True
             - If True, zero-based relative to cutoff.
             - If False, zero-based relative to first value in forecasting
             horizon.
@@ -665,7 +665,7 @@ def _to_relative(fh: ForecastingHorizon, cutoff=None) -> ForecastingHorizon:
     Parameters
     ----------
     fh : ForecastingHorizon
-    cutoff : pd.Period, pd.Timestamp, int, optional (default=None)
+    cutoff : pd.Period, pd.Timestamp, int, default=None
         Cutoff value required to convert a relative forecasting
         horizon to an absolute one (and vice versa).
 
@@ -752,7 +752,7 @@ def _check_cutoff(cutoff, index):
 
     Parameters
     ----------
-    cutoff : pd.Period, pd.Timestamp, int, optional (default=None)
+    cutoff : pd.Period, pd.Timestamp, int, default=None
         Cutoff value is required to convert a relative forecasting
         horizon to an absolute one and vice versa.
     index : pd.PeriodIndex or pd.DataTimeIndex
diff --git a/aeon/forecasting/base/adapters/_fbprophet.py b/aeon/forecasting/base/adapters/_fbprophet.py
index 2f62c517..f983f8d9 100644
--- a/aeon/forecasting/base/adapters/_fbprophet.py
+++ b/aeon/forecasting/base/adapters/_fbprophet.py
@@ -56,9 +56,9 @@ class _ProphetAdapter(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -220,7 +220,7 @@ class _ProphetAdapter(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon, default = y.index (in-sample forecast)
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
            nominal coverage(s) of predictive interval(s)
diff --git a/aeon/forecasting/base/adapters/_pmdarima.py b/aeon/forecasting/base/adapters/_pmdarima.py
index 0d8e310f..4fb74b3b 100644
--- a/aeon/forecasting/base/adapters/_pmdarima.py
+++ b/aeon/forecasting/base/adapters/_pmdarima.py
@@ -34,9 +34,9 @@ class _PmdArimaAdapter(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list, np.array or ForecastingHorizon, optional (default=None)
+        fh : int, list, np.array or ForecastingHorizon, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -56,7 +56,7 @@ class _PmdArimaAdapter(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -240,7 +240,7 @@ class _PmdArimaAdapter(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon, default = y.index (in-sample forecast)
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
            nominal coverage(s) of predictive interval(s)
diff --git a/aeon/forecasting/base/adapters/_statsforecast.py b/aeon/forecasting/base/adapters/_statsforecast.py
index dc0fb093..68ab7b70 100644
--- a/aeon/forecasting/base/adapters/_statsforecast.py
+++ b/aeon/forecasting/base/adapters/_statsforecast.py
@@ -49,11 +49,11 @@ class _StatsForecastAdapter(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -80,10 +80,10 @@ class _StatsForecastAdapter(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -203,7 +203,7 @@ class _StatsForecastAdapter(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon, default = y.index (in-sample forecast)
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
            nominal coverage(s) of predictive interval(s)
diff --git a/aeon/forecasting/base/adapters/_statsmodels.py b/aeon/forecasting/base/adapters/_statsmodels.py
index eddc0fce..9bef36de 100644
--- a/aeon/forecasting/base/adapters/_statsmodels.py
+++ b/aeon/forecasting/base/adapters/_statsmodels.py
@@ -37,9 +37,9 @@ class _StatsModelsAdapter(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -86,7 +86,7 @@ class _StatsModelsAdapter(BaseForecaster):
             The forecasters horizon with the steps ahead to to predict.
             Default is one-step ahead forecast,
             i.e. np.array([1])
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
diff --git a/aeon/forecasting/base/adapters/_tbats.py b/aeon/forecasting/base/adapters/_tbats.py
index 56d3c899..c6c7bd29 100644
--- a/aeon/forecasting/base/adapters/_tbats.py
+++ b/aeon/forecasting/base/adapters/_tbats.py
@@ -84,9 +84,9 @@ class _TbatsAdapter(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables (ignored)
 
         Returns
@@ -110,9 +110,9 @@ class _TbatsAdapter(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables (ignored)
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -253,7 +253,7 @@ class _TbatsAdapter(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon, default = y.index (in-sample forecast)
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Ignored, passed for interface compatibility
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
            nominal coverage(s) of predictive interval(s)
diff --git a/aeon/forecasting/bats.py b/aeon/forecasting/bats.py
index 6d5a3e3e..569ac205 100644
--- a/aeon/forecasting/bats.py
+++ b/aeon/forecasting/bats.py
@@ -40,36 +40,36 @@ class BATS(_TbatsAdapter):
 
     Parameters
     ----------
-    use_box_cox: bool or None, optional (default=None)
+    use_box_cox: bool or None, default=None
         If Box-Cox transformation of original series should be applied.
         When None both cases shall be considered and better is selected by AIC.
-    box_cox_bounds: tuple, shape=(2,), optional (default=(0, 1))
+    box_cox_bounds: tuple, shape=(2,), default=(0, 1)
         Minimal and maximal Box-Cox parameter values.
-    use_trend: bool or None, optional (default=None)
+    use_trend: bool or None, default=None
         Indicates whether to include a trend or not.
         When None both cases shall be considered and better is selected by AIC.
-    use_damped_trend: bool or None, optional (default=None)
+    use_damped_trend: bool or None, default=None
         Indicates whether to include a damping parameter in the trend or not.
         Applies only when trend is used.
         When None both cases shall be considered and better is selected by AIC.
-    sp: Iterable or array-like of floats, optional (default=None)
+    sp: Iterable or array-like of floats, default=None
         Abbreviation of "seasonal periods". The length of each of the periods
         (amount of observations in each period). Accepts int and float values here.
         When None or empty array, non-seasonal model shall be fitted.
-    use_arma_errors: bool, optional (default=True)
+    use_arma_errors: bool, default=True
         When True BATS will try to improve the model by modelling residuals with ARMA.
         Best model will be selected by AIC.
         If False, ARMA residuals modeling will not be considered.
-    show_warnings: bool, optional (default=True)
+    show_warnings: bool, default=True
         If warnings should be shown or not.
         Also see Model.warnings variable that contains all model related warnings.
-    n_jobs: int, optional (default=None)
+    n_jobs: int, default=None
         How many jobs to run in parallel when fitting BATS model.
         When not provided BATS shall try to utilize all available cpu cores.
-    multiprocessing_start_method: str, optional (default='spawn')
+    multiprocessing_start_method: str, default='spawn'
         How threads should be started. See also:
         https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
-    context: abstract.ContextInterface, optional (default=None)
+    context: abstract.ContextInterface, default=None
         For advanced users only. Provide this to override default behaviors
 
     See Also
diff --git a/aeon/forecasting/compose/_bagging.py b/aeon/forecasting/compose/_bagging.py
index 74e70f12..4432f98f 100644
--- a/aeon/forecasting/compose/_bagging.py
+++ b/aeon/forecasting/compose/_bagging.py
@@ -143,11 +143,11 @@ class BaggingForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -208,10 +208,10 @@ class BaggingForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -241,7 +241,7 @@ class BaggingForecaster(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
             A list of probabilities at which quantile forecasts are computed.
@@ -267,9 +267,9 @@ class BaggingForecaster(BaseForecaster):
         ----------
         y : pd.Series, pd.DataFrame, or np.array
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogeneous data
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
diff --git a/aeon/forecasting/compose/_column_ensemble.py b/aeon/forecasting/compose/_column_ensemble.py
index 954d7f15..5f08e40e 100644
--- a/aeon/forecasting/compose/_column_ensemble.py
+++ b/aeon/forecasting/compose/_column_ensemble.py
@@ -170,9 +170,9 @@ class ColumnEnsembleForecaster(_HeterogenousEnsembleForecaster):
         ----------
         y : pd.DataFrame
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
@@ -259,10 +259,10 @@ class ColumnEnsembleForecaster(_HeterogenousEnsembleForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -289,7 +289,7 @@ class ColumnEnsembleForecaster(_HeterogenousEnsembleForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
@@ -330,7 +330,7 @@ class ColumnEnsembleForecaster(_HeterogenousEnsembleForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -365,12 +365,12 @@ class ColumnEnsembleForecaster(_HeterogenousEnsembleForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
diff --git a/aeon/forecasting/compose/_hierarchy_ensemble.py b/aeon/forecasting/compose/_hierarchy_ensemble.py
index 854ee6f6..c41894b1 100644
--- a/aeon/forecasting/compose/_hierarchy_ensemble.py
+++ b/aeon/forecasting/compose/_hierarchy_ensemble.py
@@ -164,9 +164,9 @@ class HierarchyEnsembleForecaster(_HeterogenousEnsembleForecaster):
         ----------
         y : pd-multiindex
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
@@ -365,10 +365,10 @@ class HierarchyEnsembleForecaster(_HeterogenousEnsembleForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
diff --git a/aeon/forecasting/compose/_pipeline.py b/aeon/forecasting/compose/_pipeline.py
index 8cdd51e1..fdd1d18c 100644
--- a/aeon/forecasting/compose/_pipeline.py
+++ b/aeon/forecasting/compose/_pipeline.py
@@ -448,7 +448,7 @@ class ForecastingPipeline(_Pipeline):
         ----------
         y : pd.Series, pd.DataFrame
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
         X : pd.DataFrame, required
             Exogenous variables are ignored
@@ -508,7 +508,7 @@ class ForecastingPipeline(_Pipeline):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
@@ -543,7 +543,7 @@ class ForecastingPipeline(_Pipeline):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -572,12 +572,12 @@ class ForecastingPipeline(_Pipeline):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
@@ -607,10 +607,10 @@ class ForecastingPipeline(_Pipeline):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        marginal : bool, optional (default=True)
+        marginal : bool, default=True
             whether returned distribution is marginal by time index
 
         Returns
@@ -637,7 +637,7 @@ class ForecastingPipeline(_Pipeline):
         ----------
         y : pd.Series
         X : pd.DataFrame, required
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
 
         Returns
         -------
@@ -935,9 +935,9 @@ class TransformedTargetForecaster(_Pipeline):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -968,7 +968,7 @@ class TransformedTargetForecaster(_Pipeline):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -992,8 +992,8 @@ class TransformedTargetForecaster(_Pipeline):
         Parameters
         ----------
         y : pd.Series
-        X : pd.DataFrame, optional (default=None)
-        update_params : bool, optional (default=True)
+        X : pd.DataFrame, default=None
+        update_params : bool, default=True
 
         Returns
         -------
@@ -1071,7 +1071,7 @@ class TransformedTargetForecaster(_Pipeline):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
@@ -1109,7 +1109,7 @@ class TransformedTargetForecaster(_Pipeline):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -1242,7 +1242,7 @@ class ForecastX(BaseForecaster):
         ----------
         y : time series in aeon compatible format
             Target time series to which to fit the forecaster
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
         X : time series in aeon compatible format, optional, default=None
             Exogenous time series to which to fit the forecaster
@@ -1344,7 +1344,7 @@ class ForecastX(BaseForecaster):
             Target time series to which to fit the forecaster
         X : time series in aeon compatible format, optional, default=None
             Exogenous time series to which to fit the forecaster
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
 
         Returns
         -------
@@ -1366,10 +1366,10 @@ class ForecastX(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        coverage : float or list, optional (default=0.95)
+        coverage : float or list, default=0.95
            nominal coverage(s) of predictive interval(s)
 
         Returns
@@ -1401,10 +1401,10 @@ class ForecastX(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        alpha : list of float, optional (default=[0.5])
+        alpha : list of float, default=[0.5]
             A list of probabilities at which quantile forecasts are computed.
 
         Returns
@@ -1430,10 +1430,10 @@ class ForecastX(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
@@ -1470,10 +1470,10 @@ class ForecastX(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        marginal : bool, optional (default=True)
+        marginal : bool, default=True
             whether returned distribution is marginal by time index
 
         Returns
diff --git a/aeon/forecasting/compose/_reduce.py b/aeon/forecasting/compose/_reduce.py
index e1bd485b..2644f021 100644
--- a/aeon/forecasting/compose/_reduce.py
+++ b/aeon/forecasting/compose/_reduce.py
@@ -74,7 +74,7 @@ def _sliding_window_transform(
         Window length for transformed feature variables
     fh : ForecastingHorizon
         Forecasting horizon for transformed target variable
-    X : pd.DataFrame, optional (default=None)
+    X : pd.DataFrame, default=None
         Exogenous series.
     transformers: list of transformers (default = None)
         A suitable list of transformers that allows for using an en-bloc approach with
@@ -446,9 +446,9 @@ class _DirectReducer(_Reducer):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
              The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -565,7 +565,7 @@ class _DirectReducer(_Reducer):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         return_pred_int : bool
         alpha : float or array-like
@@ -653,9 +653,9 @@ class _MultioutputReducer(_Reducer):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
              The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -688,7 +688,7 @@ class _MultioutputReducer(_Reducer):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         return_pred_int : bool
         alpha : float or array-like
@@ -754,9 +754,9 @@ class _RecursiveReducer(_Reducer):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
              The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -857,7 +857,7 @@ class _RecursiveReducer(_Reducer):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         return_pred_int : bool
         alpha : float or array-like
@@ -984,9 +984,9 @@ class _DirRecReducer(_Reducer):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
              The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -1043,7 +1043,7 @@ class _DirRecReducer(_Reducer):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         return_pred_int : bool
         alpha : float or array-like
@@ -1098,7 +1098,7 @@ class DirectTabularRegressionForecaster(_DirectReducer):
     ----------
     estimator : Estimator
         A tabular regression estimator as provided by scikit-learn.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix.
     """
@@ -1148,7 +1148,7 @@ class MultioutputTabularRegressionForecaster(_MultioutputReducer):
     ----------
     estimator : Estimator
         A tabular regression estimator as provided by scikit-learn.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix.
     """
@@ -1166,7 +1166,7 @@ class RecursiveTabularRegressionForecaster(_RecursiveReducer):
     ----------
     estimator : Estimator
         A tabular regression estimator as provided by scikit-learn.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix.
     transformers: list of transformers (default = None)
@@ -1231,7 +1231,7 @@ class DirRecTabularRegressionForecaster(_DirRecReducer):
     ----------
     estimator : sklearn estimator object
         Tabular regressor.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix
     """
@@ -1249,7 +1249,7 @@ class DirectTimeSeriesRegressionForecaster(_DirectReducer):
     ----------
     estimator : Estimator
         A time-series regression estimator as provided by aeon.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix.
     """
@@ -1267,7 +1267,7 @@ class MultioutputTimeSeriesRegressionForecaster(_MultioutputReducer):
     ----------
     estimator : Estimator
         A time-series regression estimator as provided by aeon.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix.
     """
@@ -1285,7 +1285,7 @@ class RecursiveTimeSeriesRegressionForecaster(_RecursiveReducer):
     ----------
     estimator : Estimator
         A time-series regression estimator as provided by aeon.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix.
     """
@@ -1310,7 +1310,7 @@ class DirRecTimeSeriesRegressionForecaster(_DirRecReducer):
     ----------
     estimator : aeon estimator object
         Time-series regressor.
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         The length of the sliding window used to transform the series into
         a tabular matrix
     """
@@ -1339,12 +1339,12 @@ def make_reduction(
     estimator : an estimator instance
         Either a tabular regressor from scikit-learn or a time series regressor from
         aeon.
-    strategy : str, optional (default="recursive")
+    strategy : str, default="recursive"
         The strategy to generate forecasts. Must be one of "direct", "recursive" or
         "multioutput".
-    window_length : int, optional (default=10)
+    window_length : int, default=10
         Window length used in sliding window transformation.
-    scitype : str, optional (default="infer")
+    scitype : str, default="infer"
         Legacy argument for downwards compatibility, should not be used.
         `make_reduction` will automatically infer the correct type of `estimator`.
         This internal inference can be force-overridden by the `scitype` argument.
@@ -2166,11 +2166,11 @@ class RecursiveReductionForecaster(BaseForecaster, _ReducerMixin):
         y : pd.DataFrame
             pd.DataFrame, pd-multiindex, or pd_multiindex_hier
             Time series to which to fit the forecaster.
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : pd.DataFrame optional (default=None)
+        X : pd.DataFrame default=None
             pd.DataFrame, pd-multiindex, or pd_multiindex_hier
             Exogeneous time series to fit to.
 
@@ -2226,10 +2226,10 @@ class RecursiveReductionForecaster(BaseForecaster, _ReducerMixin):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             pd.DataFrame, pd-multiindex, or pd_multiindex_hier
             Exogeneous time series for the forecast
 
diff --git a/aeon/forecasting/compose/_stack.py b/aeon/forecasting/compose/_stack.py
index 0ca3a472..344ebf93 100644
--- a/aeon/forecasting/compose/_stack.py
+++ b/aeon/forecasting/compose/_stack.py
@@ -31,7 +31,7 @@ class StackingForecaster(_HeterogenousEnsembleForecaster):
         The regressor can also be a sklearn.Pipeline().
     random_state : int, RandomState instance or None, default=None
         Used to set random_state of the default regressor.
-    n_jobs : int or None, optional (default=None)
+    n_jobs : int or None, default=None
         The number of jobs to run in parallel for fit. None means 1 unless
         in a joblib.parallel_backend context.
         -1 means using all processors.
@@ -82,9 +82,9 @@ class StackingForecaster(_HeterogenousEnsembleForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -132,7 +132,7 @@ class StackingForecaster(_HeterogenousEnsembleForecaster):
         ----------
         y : pd.Series
         X : pd.DataFrame
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
 
         Returns
         -------
@@ -151,7 +151,7 @@ class StackingForecaster(_HeterogenousEnsembleForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
diff --git a/aeon/forecasting/conformal.py b/aeon/forecasting/conformal.py
index 63f559eb..cb119150 100644
--- a/aeon/forecasting/conformal.py
+++ b/aeon/forecasting/conformal.py
@@ -53,7 +53,7 @@ class ConformalIntervals(BaseForecaster):
             Caveat: this does not give frequentist but conformal predictive intervals
         "conformal": as in Stankeviciute et al, but with H=1,
             i.e., no Bonferroni correction under number of indices in the horizon
-    initial_window : float, int or None, optional (default=max(10, 0.1*len(y)))
+    initial_window : float, int or None, default=max(10, 0.1*len(y))
         Defines the size of the initial training window
         If float, should be between 0.0 and 1.0 and represent the proportion
         of the dataset to include for the initial window for the train split.
@@ -188,7 +188,7 @@ class ConformalIntervals(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -271,10 +271,10 @@ class ConformalIntervals(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
-        alpha : list of float, optional (default=[0.5])
+        alpha : list of float, default=[0.5]
             A list of probabilities at which quantile forecasts are computed.
 
         Returns
@@ -339,7 +339,7 @@ class ConformalIntervals(BaseForecaster):
             aeon compatible exogeneous time series to use in forecasts
         forecaster : aeon compatible forecaster
             forecaster to use in computing the sliding residuals
-        initial_window : float, int or None, optional (default=max(10, 0.1*len(y)))
+        initial_window : float, int or None, default=max(10, 0.1*len(y))
             Defines the size of the initial training window
             If float, should be between 0.0 and 1.0 and represent the proportion
             of the dataset to include for the initial window for the train split.
diff --git a/aeon/forecasting/croston.py b/aeon/forecasting/croston.py
index e3016d31..e1ea36de 100644
--- a/aeon/forecasting/croston.py
+++ b/aeon/forecasting/croston.py
@@ -83,9 +83,9 @@ class Croston(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
@@ -131,9 +131,9 @@ class Croston(BaseForecaster):
 
         Parameters
         ----------
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
diff --git a/aeon/forecasting/dynamic_factor.py b/aeon/forecasting/dynamic_factor.py
index 9ee6e00c..fa03da15 100644
--- a/aeon/forecasting/dynamic_factor.py
+++ b/aeon/forecasting/dynamic_factor.py
@@ -196,7 +196,7 @@ class DynamicFactor(_StatsModelsAdapter):
             The forecasters horizon with the steps ahead to to predict.
             Default is one-step ahead forecast,
             i.e. np.array([1])
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
@@ -238,7 +238,7 @@ class DynamicFactor(_StatsModelsAdapter):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -344,7 +344,7 @@ class DynamicFactor(_StatsModelsAdapter):
         ----------
         y:pd.Series
           Target time series to which forcaster is fit.
-        X:pd.DataFrame , optional (default=None)
+        X:pd.DataFrame , default=None
           Exogenous variables
         """
         from statsmodels.tsa.statespace.dynamic_factor import (
diff --git a/aeon/forecasting/ets.py b/aeon/forecasting/ets.py
index 7ba505b9..62fa9351 100644
--- a/aeon/forecasting/ets.py
+++ b/aeon/forecasting/ets.py
@@ -393,7 +393,7 @@ class AutoETS(_StatsModelsAdapter):
             The forecasters horizon with the steps ahead to to predict.
             Default is one-step ahead forecast,
             i.e. np.array([1])
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
         **simulate_kwargs : see statsmodels ETSResults.get_prediction
 
@@ -428,7 +428,7 @@ class AutoETS(_StatsModelsAdapter):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to predict from.
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
diff --git a/aeon/forecasting/model_selection/_split.py b/aeon/forecasting/model_selection/_split.py
index a0ed79c4..b253c8b4 100644
--- a/aeon/forecasting/model_selection/_split.py
+++ b/aeon/forecasting/model_selection/_split.py
@@ -539,7 +539,7 @@ class BaseSplitter(BaseObject):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -554,7 +554,7 @@ class BaseSplitter(BaseObject):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -693,7 +693,7 @@ class CutoffSplitter(BaseSplitter):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -714,7 +714,7 @@ class CutoffSplitter(BaseSplitter):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -942,7 +942,7 @@ class BaseWindowSplitter(BaseSplitter):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -962,7 +962,7 @@ class BaseWindowSplitter(BaseSplitter):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -1032,11 +1032,11 @@ class SlidingWindowSplitter(BaseWindowSplitter):
         Forecasting horizon
     window_length : int or timedelta or pd.DateOffset
         Window length
-    step_length : int or timedelta or pd.DateOffset, optional (default=1)
+    step_length : int or timedelta or pd.DateOffset, default=1
         Step length between windows
-    initial_window : int or timedelta or pd.DateOffset, optional (default=None)
+    initial_window : int or timedelta or pd.DateOffset, default=None
         Window length of first window
-    start_with_window : bool, optional (default=True)
+    start_with_window : bool, default=True
         - If True, starts with full window.
         - If False, starts with empty window.
 
@@ -1099,11 +1099,11 @@ class ExpandingWindowSplitter(BaseWindowSplitter):
 
     Parameters
     ----------
-    fh : int, list or np.array, optional (default=1)
+    fh : int, list or np.array, default=1
         Forecasting horizon
-    initial_window : int or timedelta or pd.DateOffset, optional (default=10)
+    initial_window : int or timedelta or pd.DateOffset, default=10
         Window length of initial training fold. If =0, initial training fold is empty.
-    step_length : int or timedelta or pd.DateOffset, optional (default=1)
+    step_length : int or timedelta or pd.DateOffset, default=1
         Step length between windows
 
     Examples
@@ -1216,7 +1216,7 @@ class SingleWindowSplitter(BaseSplitter):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -1235,7 +1235,7 @@ class SingleWindowSplitter(BaseSplitter):
 
         Parameters
         ----------
-        y : pd.Series or pd.Index, optional (default=None)
+        y : pd.Series or pd.Index, default=None
             Time series to split
 
         Returns
@@ -1293,9 +1293,9 @@ def temporal_train_test_split(
     ----------
     y : pd.Series
         Target series
-    X : pd.DataFrame, optional (default=None)
+    X : pd.DataFrame, default=None
         Exogenous data
-    test_size : float, int or None, optional (default=None)
+    test_size : float, int or None, default=None
         If float, should be between 0.0 and 1.0 and represent the proportion
         of the dataset to include in the test split. If int, represents the
         relative number of test samples. If None, the value is set to the
diff --git a/aeon/forecasting/model_selection/_tune.py b/aeon/forecasting/model_selection/_tune.py
index 313e6cd2..1a9f4c50 100644
--- a/aeon/forecasting/model_selection/_tune.py
+++ b/aeon/forecasting/model_selection/_tune.py
@@ -129,9 +129,9 @@ class BaseGridSearch(_DelegatedForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -266,10 +266,10 @@ class BaseGridSearch(_DelegatedForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -327,27 +327,27 @@ class ForecastingGridSearchCV(BaseGridSearch):
         "no_update" = neither tuning parameters nor inner estimator are updated
     param_grid : dict or list of dictionaries
         Model tuning parameters of the forecaster to evaluate
-    scoring: function, optional (default=None)
+    scoring: function, default=None
         Function to score models for evaluation of optimal parameters. If None,
         then mean_absolute_percentage_error is used.
-    n_jobs: int, optional (default=None)
+    n_jobs: int, default=None
         Number of jobs to run in parallel if backend either "loky",
         "multiprocessing" or "threading".
         None means 1 unless in a joblib.parallel_backend context.
         -1 means using all processors.
-    refit: bool, optional (default=True)
+    refit: bool, default=True
         True = refit the forecaster with the best parameters on the entire data in fit
         False = best forecaster remains fitted on the last fold in cv
-    verbose: int, optional (default=0)
+    verbose: int, default=0
     return_n_best_forecasters: int, default=1
         In case the n best forecaster should be returned, this value can be set
         and the n best forecasters will be assigned to n_best_forecasters_
-    pre_dispatch: str, optional (default='2*n_jobs').
+    pre_dispatch: str, default='2*n_jobs'
         Controls the number of jobs that get dispatched during parallel execution when
         using the "loky", "threading", or "multiprocessing" backend.
-    error_score: numeric value or the str 'raise', optional (default=np.nan)
+    error_score: numeric value or the str 'raise', default=np.nan
         The test score returned when a forecaster fails to be fitted.
-    return_train_score: bool, optional (default=False)
+    return_train_score: bool, default=False
     backend : {"dask", "loky", "multiprocessing", "threading"}, by default "loky".
         Runs parallel evaluate if specified and `strategy` is set as "refit".
         - "loky", "multiprocessing" and "threading": uses `joblib` Parallel loops
@@ -356,7 +356,7 @@ class ForecastingGridSearchCV(BaseGridSearch):
         "threading" is unlikely to see speed ups due to the GIL and the serialization
         backend (`cloudpickle`) for "dask" and "loky" is generally more robust than the
         standard `pickle` library used in "multiprocessing".
-    pre_dispatch: str, optional (default='2*n_jobs').
+    pre_dispatch: str, default='2*n_jobs'
         Controls the number of jobs that get dispatched during parallel execution when
         using the "loky", "threading", or "multiprocessing" backend.
     error_score : "raise" or numeric, default=np.nan
@@ -589,22 +589,22 @@ class ForecastingRandomizedSearchCV(BaseGridSearch):
     n_iter : int, default=10
         Number of parameter settings that are sampled. n_iter trades
         off runtime vs quality of the solution.
-    scoring: function, optional (default=None)
+    scoring: function, default=None
         Function to score models for evaluation of optimal parameters. If None,
         then mean_absolute_percentage_error is used.
-    n_jobs: int, optional (default=None)
+    n_jobs: int, default=None
         Number of jobs to run in parallel if backend either "loky",
         "multiprocessing" or "threading".
         None means 1 unless in a joblib.parallel_backend context.
         -1 means using all processors.
-    refit: bool, optional (default=True)
+    refit: bool, default=True
         True = refit the forecaster with the best parameters on the entire data in fit
         False = best forecaster remains fitted on the last fold in cv
-    verbose: int, optional (default=0)
+    verbose: int, default=0
     return_n_best_forecasters: int, default=1
         In case the n best forecaster should be returned, this value can be set
         and the n best forecasters will be assigned to n_best_forecasters_
-    pre_dispatch: str, optional (default='2*n_jobs').
+    pre_dispatch: str, default='2*n_jobs'
         Controls the number of jobs that get dispatched during parallel execution when
         using the "loky", "threading", or "multiprocessing" backend.
     random_state : int, RandomState instance or None, default=None
diff --git a/aeon/forecasting/naive.py b/aeon/forecasting/naive.py
index d0edd218..d31265af 100644
--- a/aeon/forecasting/naive.py
+++ b/aeon/forecasting/naive.py
@@ -318,7 +318,7 @@ class NaiveForecaster(_BaseWindowForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         """
         y_pred = super()._predict(fh=fh, X=X)
@@ -347,9 +347,9 @@ class NaiveForecaster(_BaseWindowForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
-        alpha : float or list of float, optional (default=0.5)
+        alpha : float or list of float, default=0.5
             A probability or list of, at which quantile forecasts are computed.
 
         Returns
@@ -392,9 +392,9 @@ class NaiveForecaster(_BaseWindowForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             If True, return the covariance matrix.
             If False, return the marginal variance.
 
@@ -636,9 +636,9 @@ class NaiveVariance(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        alpha : float or list of float, optional (default=0.5)
+        alpha : float or list of float, default=0.5
             A probability or list of, at which quantile forecasts are computed.
 
         Returns
@@ -678,9 +678,9 @@ class NaiveVariance(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             If True, return the covariance matrix.
             If False, return the marginal variance.
 
diff --git a/aeon/forecasting/online_learning/_online_ensemble.py b/aeon/forecasting/online_learning/_online_ensemble.py
index 04889504..669c8099 100644
--- a/aeon/forecasting/online_learning/_online_ensemble.py
+++ b/aeon/forecasting/online_learning/_online_ensemble.py
@@ -15,7 +15,7 @@ class OnlineEnsembleForecaster(EnsembleForecaster):
     ----------
     ensemble_algorithm : ensemble algorithm
     forecasters : list of (str, estimator) tuples
-    n_jobs : int or None, optional (default=None)
+    n_jobs : int or None, default=None
         The number of jobs to run in parallel for fit. None means 1 unless
         in a joblib.parallel_backend context.
         -1 means using all processors.
@@ -42,9 +42,9 @@ class OnlineEnsembleForecaster(EnsembleForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -67,7 +67,7 @@ class OnlineEnsembleForecaster(EnsembleForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
         """
         fh = np.arange(len(y)) + 1
@@ -83,7 +83,7 @@ class OnlineEnsembleForecaster(EnsembleForecaster):
         ----------
         y : pd.Series
         X : pd.DataFrame
-        update_params : bool, optional (default=False)
+        update_params : bool, default=False
 
         Returns
         -------
diff --git a/aeon/forecasting/reconcile.py b/aeon/forecasting/reconcile.py
index 51f2daad..d62b61d5 100644
--- a/aeon/forecasting/reconcile.py
+++ b/aeon/forecasting/reconcile.py
@@ -121,7 +121,7 @@ class ReconcilerForecaster(BaseForecaster):
         ----------
         y : pd.DataFrame
             Time series to which to fit the forecaster.
-        fh : ForecastingHorizon or None, optional (default=None)
+        fh : ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
         X : pd.DataFrame, default=None
             Exogenous variables for the base forecaster
@@ -189,9 +189,9 @@ class ReconcilerForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : ForecastingHorizon or None, optional (default=None)
+        fh : ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             Exogeneous time series for the forecast
 
         Returns
@@ -240,7 +240,7 @@ class ReconcilerForecaster(BaseForecaster):
             Time series to which to fit the forecaster.
         X : pd.DataFrame, default=None
             Exogenous variables based to the base forecaster
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -293,10 +293,10 @@ class ReconcilerForecaster(BaseForecaster):
 
         Parameters
         ----------
-        shrink:  bool, optional (default=False)
+        shrink:  bool, default=False
             Shrink the off diagonal elements of the sample covariance matrix.
             according to the method in [2]_
-        diag_only: bool, optional (default=False)
+        diag_only: bool, default=False
             Remove the off-diagonal elements of the sample covariance matrix.
 
         Returns
diff --git a/aeon/forecasting/squaring_residuals.py b/aeon/forecasting/squaring_residuals.py
index 6e3cb492..361eb5cb 100644
--- a/aeon/forecasting/squaring_residuals.py
+++ b/aeon/forecasting/squaring_residuals.py
@@ -137,11 +137,11 @@ class SquaringResiduals(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -209,10 +209,10 @@ class SquaringResiduals(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
 
@@ -250,10 +250,10 @@ class SquaringResiduals(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -282,7 +282,7 @@ class SquaringResiduals(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
@@ -327,9 +327,9 @@ class SquaringResiduals(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             If True, return the covariance matrix.
             If False, return the marginal variance.
 
diff --git a/aeon/forecasting/stream/_update.py b/aeon/forecasting/stream/_update.py
index 42219882..27c012a4 100644
--- a/aeon/forecasting/stream/_update.py
+++ b/aeon/forecasting/stream/_update.py
@@ -86,11 +86,11 @@ class UpdateRefitsEvery(_DelegatedForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -129,9 +129,9 @@ class UpdateRefitsEvery(_DelegatedForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -267,11 +267,11 @@ class UpdateEvery(_DelegatedForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -310,9 +310,9 @@ class UpdateEvery(_DelegatedForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -438,9 +438,9 @@ class DontUpdate(_DelegatedForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
diff --git a/aeon/forecasting/structural.py b/aeon/forecasting/structural.py
index 64041a61..9184ed06 100644
--- a/aeon/forecasting/structural.py
+++ b/aeon/forecasting/structural.py
@@ -282,7 +282,7 @@ class UnobservedComponents(_StatsModelsAdapter):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables.
         """
         from statsmodels.tsa.statespace.structural import (
@@ -339,7 +339,7 @@ class UnobservedComponents(_StatsModelsAdapter):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon, default = y.index (in-sample forecast)
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
            nominal coverage(s) of predictive interval(s)
@@ -410,7 +410,7 @@ class UnobservedComponents(_StatsModelsAdapter):
             time-invariant this can be any number. If the model is
             time-varying, then this number must be less than or equal to the
             number of observations.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables.
         measurement_shocks : array_like, optional
             If specified, these are the shocks to the measurement equation,
diff --git a/aeon/forecasting/tbats.py b/aeon/forecasting/tbats.py
index ffd823b4..6b1bd00d 100644
--- a/aeon/forecasting/tbats.py
+++ b/aeon/forecasting/tbats.py
@@ -42,36 +42,36 @@ class TBATS(_TbatsAdapter):
 
     Parameters
     ----------
-    use_box_cox: bool or None, optional (default=None)
+    use_box_cox: bool or None, default=None
         If Box-Cox transformation of original series should be applied.
         When None both cases shall be considered and better is selected by AIC.
-    box_cox_bounds: tuple, shape=(2,), optional (default=(0, 1))
+    box_cox_bounds: tuple, shape=(2,), default=(0, 1)
         Minimal and maximal Box-Cox parameter values.
-    use_trend: bool or None, optional (default=None)
+    use_trend: bool or None, default=None
         Indicates whether to include a trend or not.
         When None both cases shall be considered and better is selected by AIC.
-    use_damped_trend: bool or None, optional (default=None)
+    use_damped_trend: bool or None, default=None
         Indicates whether to include a damping parameter in the trend or not.
         Applies only when trend is used.
         When None both cases shall be considered and better is selected by AIC.
-    sp: Iterable or array-like of floats, optional (default=None)
+    sp: Iterable or array-like of floats, default=None
         Abbreviation of "seasonal periods". The length of each of the periods
         (amount of observations in each period). Accepts int and float values here.
         When None or empty array, non-seasonal model shall be fitted.
-    use_arma_errors: bool, optional (default=True)
+    use_arma_errors: bool, default=True
         When True BATS will try to improve the model by modelling residuals with ARMA.
         Best model will be selected by AIC.
         If False, ARMA residuals modeling will not be considered.
-    show_warnings: bool, optional (default=True)
+    show_warnings: bool, default=True
         If warnings should be shown or not.
         Also see Model.warnings variable that contains all model related warnings.
-    n_jobs: int, optional (default=None)
+    n_jobs: int, default=None
         How many jobs to run in parallel when fitting BATS model.
         When not provided BATS shall try to utilize all available cpu cores.
-    multiprocessing_start_method: str, optional (default='spawn')
+    multiprocessing_start_method: str, default='spawn'
         How threads should be started. See also:
         https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
-    context: abstract.ContextInterface, optional (default=None)
+    context: abstract.ContextInterface, default=None
         For advanced users only. Provide this to override default behaviors
 
     See Also
diff --git a/aeon/forecasting/theta.py b/aeon/forecasting/theta.py
index e6b890d0..e8408a02 100644
--- a/aeon/forecasting/theta.py
+++ b/aeon/forecasting/theta.py
@@ -86,9 +86,9 @@ class ThetaForecaster(ExponentialSmoothing):
     initial_level : float, optional
         The alpha value of the simple exponential smoothing, if the value is
         set then this will be used, otherwise it will be estimated from the data.
-    deseasonalize : bool, optional (default=True)
+    deseasonalize : bool, default=True
         If True, data is seasonally adjusted.
-    sp : int, optional (default=1)
+    sp : int, default=1
         The number of observations that constitute a seasonal period for a
         multiplicative deseasonaliser, which is used if seasonality is detected in the
         training data. Ignored if a deseasonaliser transformer is provided.
@@ -152,9 +152,9 @@ class ThetaForecaster(ExponentialSmoothing):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored
 
         Returns
@@ -192,7 +192,7 @@ class ThetaForecaster(ExponentialSmoothing):
             The forecasters horizon with the steps ahead to to predict.
             Default is
             one-step ahead forecast, i.e. np.array([1]).
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -242,9 +242,9 @@ class ThetaForecaster(ExponentialSmoothing):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        alpha : list of float, optional (default=[0.5])
+        alpha : list of float, default=[0.5]
             A list of probabilities at which quantile forecasts are computed.
 
         Returns
diff --git a/aeon/forecasting/trend.py b/aeon/forecasting/trend.py
index a56ef413..c31e8c53 100644
--- a/aeon/forecasting/trend.py
+++ b/aeon/forecasting/trend.py
@@ -78,7 +78,7 @@ class TrendForecaster(BaseForecaster):
             Target time series with which to fit the forecaster.
         X : pd.DataFrame, default=None
             Exogenous variables are ignored
-        fh : int, list or np.array, optional (default=None)
+        fh : int, list or np.array, default=None
             The forecasters horizon with the steps ahead to to predict.
 
         Returns
@@ -473,9 +473,9 @@ class STLForecaster(BaseForecaster):
         ----------
         y : pd.Series
             Target time series to which to fit the forecaster.
-        fh : int, list, np.array or ForecastingHorizon, optional (default=None)
+        fh : int, list, np.array or ForecastingHorizon, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
 
         Returns
         -------
@@ -533,7 +533,7 @@ class STLForecaster(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
                 Exogenous time series
 
         Returns
@@ -554,9 +554,9 @@ class STLForecaster(BaseForecaster):
         ----------
         y : pd.Series, pd.DataFrame, or np.array
             Target time series to which to fit the forecaster.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogeneous data
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
diff --git a/aeon/forecasting/var.py b/aeon/forecasting/var.py
index f34b7426..a1696a8e 100644
--- a/aeon/forecasting/var.py
+++ b/aeon/forecasting/var.py
@@ -34,12 +34,12 @@ class VAR(_StatsModelsAdapter):
         "ctt" - constant, linear and quadratic trend
         "n" - co constant, no trend
         Note that these are prepended to the columns of the dataset.
-    missing: str, optional (default='none')
+    missing: str, default='none'
         A string specifying if data is missing
-    freq: str, tuple, datetime.timedelta, DateOffset or None, optional (default=None)
+    freq: str, tuple, datetime.timedelta, DateOffset or None, default=None
         A frequency specification for either `dates` or the row labels from
         the endog / exog data.
-    dates: array_like, optional (default=None)
+    dates: array_like, default=None
         An array like object containing dates.
     ic: One of {'aic', 'fpe', 'hqic', 'bic', None} (default=None)
         Information criterion to use for VAR order selection.
@@ -114,9 +114,9 @@ class VAR(_StatsModelsAdapter):
         ----------
         y : pd.DataFrame
             Target time series to which to fit the forecaster.
-        fh : int, list, np.array or ForecastingHorizon, optional (default=None)
+        fh : int, list, np.array or ForecastingHorizon, default=None
             The forecasters horizon with the steps ahead to to predict.
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
 
         Returns
         -------
@@ -146,7 +146,7 @@ class VAR(_StatsModelsAdapter):
             The forecasters horizon with the steps ahead to to predict.
             Default is one-step ahead forecast,
             i.e. np.array([1])
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables are ignored.
 
         Returns
@@ -207,7 +207,7 @@ class VAR(_StatsModelsAdapter):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
diff --git a/aeon/forecasting/varmax.py b/aeon/forecasting/varmax.py
index 04283284..02eb9672 100644
--- a/aeon/forecasting/varmax.py
+++ b/aeon/forecasting/varmax.py
@@ -290,7 +290,7 @@ class VARMAX(_StatsModelsAdapter):
         ----------
         y : array_like
             The observed time-series process :math:`y`, shaped n_obs x k_endog.
-        X : array_like, optional (default=None)
+        X : array_like, default=None
             Array of exogenous regressors, shaped n_obs x k.
 
         Returns
@@ -347,7 +347,7 @@ class VARMAX(_StatsModelsAdapter):
             The forecasters horizon with the steps ahead to to predict.
             Default is one-step ahead forecast,
             i.e. np.array([1])
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous variables.
 
         Returns
diff --git a/aeon/forecasting/vecm.py b/aeon/forecasting/vecm.py
index 896695bc..2c96a880 100644
--- a/aeon/forecasting/vecm.py
+++ b/aeon/forecasting/vecm.py
@@ -125,7 +125,7 @@ class VECM(_StatsModelsAdapter):
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogeneous time series to fit to.
 
         Returns
@@ -162,7 +162,7 @@ class VECM(_StatsModelsAdapter):
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
 
@@ -224,7 +224,7 @@ class VECM(_StatsModelsAdapter):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
diff --git a/aeon/registry/_lookup.py b/aeon/registry/_lookup.py
index 3a96d0d5..73565ccb 100644
--- a/aeon/registry/_lookup.py
+++ b/aeon/registry/_lookup.py
@@ -55,37 +55,37 @@ def all_estimators(
 
     Parameters
     ----------
-    estimator_types: str, list of str, optional (default=None)
+    estimator_types: str, list of str, default=None
         Which kind of estimators should be returned.
         if None, no filter is applied and all estimators are returned.
         if str or list of str, string identifiers define types specified in search
                 only estimators that are of (at least) one of the types are returned
             possible str values are entries of registry.BASE_CLASS_REGISTER (first col)
                 for instance 'classifier', 'regressor', 'transformer', 'forecaster'
-    return_names: bool, optional (default=True)
+    return_names: bool, default=True
         if True, estimator class name is included in the all_estimators()
             return in the order: name, estimator class, optional tags, either as
             a tuple or as pandas.DataFrame columns
         if False, estimator class name is removed from the all_estimators()
             return.
-    filter_tags: dict of (str or list of str), optional (default=None)
+    filter_tags: dict of (str or list of str), default=None
         For a list of valid tag strings, use the registry.all_tags utility.
         subsets the returned estimators as follows:
             each key/value pair is statement in "and"/conjunction
                 key is tag name to sub-set on
                 value str or list of string are tag values
                 condition is "key must be equal to value, or in set(value)"
-    exclude_estimators: str, list of str, optional (default=None)
+    exclude_estimators: str, list of str, default=None
         Names of estimators to exclude.
-    exclude_estimator_types: str, list of str, optional (default=None)
+    exclude_estimator_types: str, list of str, default=None
         Names of estimator types to exclude i.e. "collection_transformer" when you are
         looking for "transformer" classes
-    as_dataframe: bool, optional (default=False)
+    as_dataframe: bool, default=False
         if True, all_estimators will return a pandas.DataFrame with named
             columns for all of the attributes being returned.
         if False, all_estimators will return a list (either a list of
             estimators or a list of tuples, see Returns)
-    return_tags: str or list of str, optional (default=None)
+    return_tags: str or list of str, default=None
         Names of tags to fetch and return each estimator's value of.
         For a list of valid tag strings, use the registry.all_tags utility.
         if str or list of str,
@@ -438,13 +438,13 @@ def all_tags(
 
     Parameters
     ----------
-    estimator_types: string, list of string, optional (default=None)
+    estimator_types: string, list of string, default=None
         Which kind of estimators should be returned.
         - If None, no filter is applied and all estimators are returned.
         - Possible values are 'classifier', 'regressor', 'transformer' and
         'forecaster' to get estimators only of these specific types, or a list of
         these to get the estimators that fit at least one of the types.
-    as_dataframe: bool, optional (default=False)
+    as_dataframe: bool, default=False
                 if False, return is as described below;
                 if True, return is converted into a pandas.DataFrame for pretty
                 display
diff --git a/aeon/registry/_tag_deprecation.py b/aeon/registry/_tag_deprecation.py
new file mode 100644
index 00000000..d5995e18
--- /dev/null
+++ b/aeon/registry/_tag_deprecation.py
@@ -0,0 +1,213 @@
+"""Utility class for deprecating tags."""
+
+import warnings
+from copy import deepcopy
+
+
+class _TagAliaserMixin:
+    """
+    Mixin class for tag aliasing and deprecation of old tags.
+
+    To deprecate tags, add the TagAliaserMixin to BaseObject or BaseEstimator.
+    alias_dict contains the deprecated tags, and supports removal and renaming.
+        For removal, add an entry "old_tag_name": ""
+        For renaming, add an entry "old_tag_name": "new_tag_name"
+    deprecate_dict contains the version number of renaming or removal.
+        the keys in deprecate_dict should be the same as in alias_dict.
+        values in deprecate_dict should be strings, the version of removal/renaming.
+
+    The class will ensure that new tags alias old tags and vice versa, during
+    the deprecation period. Informative warnings will be raised whenever the
+    deprecated tags are being accessed.
+
+    When removing tags, ensure to remove the removed tags from this class.
+    If no tags are deprecated anymore (e.g., all deprecated tags are removed/renamed),
+    ensure toremove this class as a parent of BaseObject or BaseEstimator.
+    """
+
+    def __init__(self):
+        super().__init__()
+
+    @classmethod
+    def get_class_tags(cls):
+        """
+        Get class tags from estimator class and all its parent classes.
+
+        Get the tags relating to the class not a particular object.
+
+        Returns
+        -------
+        collected_tags : dict
+            Dictionary of tag name : tag value pairs. Collected from _tags
+            class attribute via nested inheritance. NOT overridden by dynamic
+            tags set by set_tags or mirror_tags.
+
+        See Also
+        --------
+        get_tag : Get a single tag from an object.
+        get_clas_tags : Get all tags from a class.
+        get_tags : Get all tags from an object.
+
+        Examples
+        --------
+        >>> from aeon.classification import DummyClassifier
+        >>> tags = DummyClassifier.get_class_tags()
+        """
+        collected_tags = super().get_class_tags()
+        collected_tags = cls._complete_dict(collected_tags)
+        return collected_tags
+
+    @classmethod
+    def get_class_tag(cls, tag_name, tag_value_default=None):
+        """
+        Get tag value from estimator class (only class tags).
+
+        Parameters
+        ----------
+        tag_name : str
+            Name of tag value.
+        tag_value_default : any type
+            Default/fallback value if tag is not found.
+
+        Returns
+        -------
+        tag_value :
+            Value of the `tag_name` tag in self. If not found, returns
+            `tag_value_default`.
+
+        See Also
+        --------
+        get_tag : Get a single tag from an object.
+        get_clas_tags : Get all tags from a class.
+        get_tags : Get all tags from an object.
+
+        Examples
+        --------
+        >>> from aeon.classification import DummyClassifier
+        >>> DummyClassifier.get_class_tag("capability:multivariate")
+        True
+        """
+        cls._deprecate_tag_warn([tag_name])
+        return super().get_class_tag(
+            tag_name=tag_name, tag_value_default=tag_value_default
+        )
+
+    def get_tags(self):
+        """
+        Get tags from estimator class and dynamic tag overrides.
+
+        Returns
+        -------
+        collected_tags : dict
+            Dictionary of tag name : tag value pairs. Collected from _tags
+            class attribute via nested inheritance and then any overrides
+            and new tags from _tags_dynamic object attribute.
+        """
+        collected_tags = super().get_tags()
+        collected_tags = self._complete_dict(collected_tags)
+        return collected_tags
+
+    def get_tag(self, tag_name, tag_value_default=None, raise_error=True):
+        """
+        Get tag value from estimator class and dynamic tag overrides.
+
+        Parameters
+        ----------
+        tag_name : str
+            Name of tag to be retrieved.
+        tag_value_default : any type, default=None
+            Default/fallback value if tag is not found.
+        raise_error : bool
+            whether a ValueError is raised when the tag is not found.
+
+        Returns
+        -------
+        string or None
+            Value of the `tag_name` tag in self. If not found, returns an error if
+            raise_error is True, otherwise it returns `tag_value_default`.
+
+        Raises
+        ------
+        ValueError if raise_error is True i.e. if tag_name is not in self.get_tags(
+        ).keys()
+        """
+        self._deprecate_tag_warn([tag_name])
+        return super().get_tag(
+            tag_name=tag_name,
+            tag_value_default=tag_value_default,
+            raise_error=raise_error,
+        )
+
+    def set_tags(self, **tag_dict):
+        """
+        Set dynamic tags to given values.
+
+        Parameters
+        ----------
+        tag_dict : dict
+            Dictionary of tag name : tag value pairs.
+
+        Returns
+        -------
+        Self :
+            Reference to self.
+
+        Notes
+        -----
+        Changes object state by settting tag values in tag_dict as dynamic tags
+        in self.
+        """
+        self._deprecate_tag_warn(tag_dict.keys())
+
+        tag_dict = self._complete_dict(tag_dict)
+        super().set_tags(**tag_dict)
+        return self
+
+    @classmethod
+    def _complete_dict(cls, tag_dict):
+        """Add all aliased and aliasing tags to the dictionary."""
+        alias_dict = cls.alias_dict
+        deprecated_tags = set(tag_dict.keys()).intersection(alias_dict.keys())
+        new_tags = set(tag_dict.keys()).intersection(alias_dict.values())
+
+        if len(deprecated_tags) > 0 or len(new_tags) > 0:
+            new_tag_dict = deepcopy(tag_dict)
+            # for all tag strings being set, write the value
+            #   to all tags that could *be aliased by* the string
+            #   and all tags that could be *aliasing* the string
+            # this way we ensure upwards and downwards compatibility
+            for old_tag, new_tag in alias_dict.items():
+                for tag in tag_dict:
+                    if tag == old_tag and new_tag != "":
+                        new_tag_dict[new_tag] = tag_dict[tag]
+                    if tag == new_tag:
+                        new_tag_dict[old_tag] = tag_dict[tag]
+            return new_tag_dict
+        else:
+            return tag_dict
+
+    @classmethod
+    def _deprecate_tag_warn(cls, tags):
+        """
+        Print warning message for tag deprecation.
+
+        Parameters
+        ----------
+        tags : list of str.
+
+        Raises
+        ------
+        DeprecationWarning for each tag in tags that is aliased by cls.alias_dict.
+        """
+        for tag_name in tags:
+            if tag_name in cls.alias_dict.keys():
+                version = cls.deprecate_dict[tag_name]
+                new_tag = cls.alias_dict[tag_name]
+                msg = f"tag {tag_name!r} will be removed in aeon version {version}"
+                if new_tag != "":
+                    msg += (
+                        f" and replaced by {new_tag!r}, please use {new_tag!r} instead"
+                    )
+                else:
+                    msg += ', please remove code that access or sets "{tag_name}"'
+                warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
diff --git a/aeon/regression/feature_based/_fresh_prince.py b/aeon/regression/feature_based/_fresh_prince.py
index 13a15e14..994e4b4b 100644
--- a/aeon/regression/feature_based/_fresh_prince.py
+++ b/aeon/regression/feature_based/_fresh_prince.py
@@ -4,15 +4,16 @@ Pipeline regressor using the full set of TSFresh features and a RotationForestRe
 regressor.
 """
 
-__maintainer__ = []
+__maintainer__ = ["MatthewMiddlehurst"]
 __all__ = ["FreshPRINCERegressor"]
 
+import warnings
+
 import numpy as np
 
 from aeon.regression.base import BaseRegressor
 from aeon.regression.sklearn import RotationForestRegressor
 from aeon.transformations.collection.feature_based import TSFreshFeatureExtractor
-from aeon.utils.validation.panel import check_X_y
 
 
 class FreshPRINCERegressor(BaseRegressor):
@@ -30,6 +31,11 @@ class FreshPRINCERegressor(BaseRegressor):
         "comprehensive".
     n_estimators : int, default=200
         Number of estimators for the RotationForestRegressor ensemble.
+    save_transformed_data : bool, default="deprecated"
+        Save the data transformed in fit.
+
+        Deprecated and will be removed in v0.8.0. Use fit_predict
+        to generate train estimates instead. transformed_data_ will also be removed.
     verbose : int, default=0
         Level of output printed to the console (for information only)
     n_jobs : int, default=1
@@ -75,7 +81,7 @@ class FreshPRINCERegressor(BaseRegressor):
         self,
         default_fc_parameters="comprehensive",
         n_estimators=200,
-        save_transformed_data=False,
+        save_transformed_data="deprecated",
         verbose=0,
         n_jobs=1,
         chunksize=None,
@@ -84,7 +90,6 @@ class FreshPRINCERegressor(BaseRegressor):
         self.default_fc_parameters = default_fc_parameters
         self.n_estimators = n_estimators
 
-        self.save_transformed_data = save_transformed_data
         self.verbose = verbose
         self.n_jobs = n_jobs
         self.chunksize = chunksize
@@ -93,11 +98,20 @@ class FreshPRINCERegressor(BaseRegressor):
         self.n_cases_ = 0
         self.n_channels_ = 0
         self.n_timepoints_ = 0
-        self.transformed_data_ = []
 
         self._rotf = None
         self._tsfresh = None
 
+        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.10.0
+        self.transformed_data_ = []
+        self.save_transformed_data = save_transformed_data
+        if save_transformed_data != "deprecated":
+            warnings.warn(
+                "the save_transformed_data parameter is deprecated and will be"
+                "removed in v0.10.0. transformed_data_ will also be removed.",
+                stacklevel=2,
+            )
+
         super().__init__()
 
     def _fit(self, X, y):
@@ -120,27 +134,16 @@ class FreshPRINCERegressor(BaseRegressor):
         Changes state by creating a fitted model that updates attributes
         ending in "_" and sets is_fitted flag to True.
         """
-        self.n_cases_, self.n_channels_, self.n_timepoints_ = X.shape
-
-        self._rotf = RotationForestRegressor(
-            n_estimators=self.n_estimators,
-            save_transformed_data=self.save_transformed_data,
-            n_jobs=self._n_jobs,
-            random_state=self.random_state,
-        )
-        self._tsfresh = TSFreshFeatureExtractor(
-            default_fc_parameters=self.default_fc_parameters,
-            n_jobs=self._n_jobs,
-            chunksize=self.chunksize,
-            show_warnings=self.verbose > 1,
-            disable_progressbar=self.verbose < 1,
+        # TODO remove in v0.10.0
+        b = (
+            False
+            if isinstance(self.save_transformed_data, str)
+            else self.save_transformed_data
         )
-
-        X_t = self._tsfresh.fit_transform(X, y)
-        self._rotf.fit(X_t, y)
-
-        if self.save_transformed_data:
-            self.transformed_data_ = X_t
+        self.transformed_data_ = self._fit_fp_shared(X, y)
+        self._rotf.fit(self.transformed_data_, y)
+        if not b:
+            self.transformed_data_ = []
 
         return self
 
@@ -159,27 +162,27 @@ class FreshPRINCERegressor(BaseRegressor):
         """
         return self._rotf.predict(self._tsfresh.transform(X))
 
-    def _get_train_preds(self, X, y) -> np.ndarray:
-        self.check_is_fitted()
-        X, y = check_X_y(X, y, coerce_to_numpy=True)
-
-        n_cases, n_channels, n_timepoints = X.shape
-
-        if (
-            n_cases != self.n_cases_
-            or n_channels != self.n_channels_
-            or n_timepoints != self.n_timepoints_
-        ):
-            raise ValueError(
-                "n_cases, n_channels, n_timepoints mismatch. X should be "
-                "the same as the training data used in fit for generating train "
-                "probabilities."
-            )
+    def _fit_predict(self, X, y):
+        X_t = self._fit_fp_shared(X, y)
+        return self._rotf.fit_predict(X_t, y)
 
-        if not self.save_transformed_data:
-            raise ValueError("Currently only works with saved transform data from fit.")
+    def _fit_fp_shared(self, X, y):
+        self.n_cases_, self.n_channels_, self.n_timepoints_ = X.shape
 
-        return self._rotf._get_train_preds(self.transformed_data_, y)
+        self._rotf = RotationForestRegressor(
+            n_estimators=self.n_estimators,
+            n_jobs=self._n_jobs,
+            random_state=self.random_state,
+        )
+        self._tsfresh = TSFreshFeatureExtractor(
+            default_fc_parameters=self.default_fc_parameters,
+            n_jobs=self._n_jobs,
+            chunksize=self.chunksize,
+            show_warnings=self.verbose > 1,
+            disable_progressbar=self.verbose < 1,
+        )
+
+        return self._tsfresh.fit_transform(X, y)
 
     @classmethod
     def get_test_params(cls, parameter_set="default"):
@@ -211,12 +214,6 @@ class FreshPRINCERegressor(BaseRegressor):
                 "n_estimators": 10,
                 "default_fc_parameters": "minimal",
             }
-        elif parameter_set == "train_estimate":
-            return {
-                "n_estimators": 2,
-                "default_fc_parameters": "minimal",
-                "save_transformed_data": True,
-            }
         else:
             return {
                 "n_estimators": 2,
diff --git a/aeon/regression/sklearn/_rotation_forest_regressor.py b/aeon/regression/sklearn/_rotation_forest_regressor.py
index 20bc5f92..4d4d56c4 100644
--- a/aeon/regression/sklearn/_rotation_forest_regressor.py
+++ b/aeon/regression/sklearn/_rotation_forest_regressor.py
@@ -4,10 +4,11 @@ A Rotation Forest aeon implementation for continuous values only. Fits sklearn
 conventions.
 """
 
-__maintainer__ = []
+__maintainer__ = ["MatthewMiddlehurst"]
 __all__ = ["RotationForestRegressor"]
 
 import time
+import warnings
 
 import numpy as np
 import pandas as pd
@@ -53,8 +54,10 @@ class RotationForestRegressor(BaseEstimator):
     contract_max_n_estimators : int, default=500
         Max number of estimators to build when ``time_limit_in_minutes`` is set.
     save_transformed_data : bool, default=False
-        Save the data transformed in fit in ``transformed_data_`` for use in
-        ``_get_train_probs``.
+        Save the data transformed in fit.
+
+        Deprecated and will be removed in v0.8.0. Use fit_predict
+        to generate train estimates instead. transformed_data_ will also be removed.
     n_jobs : int, default=1
         The number of jobs to run in parallel for both ``fit`` and ``predict``.
         `-1` means using all processors.
@@ -70,16 +73,9 @@ class RotationForestRegressor(BaseEstimator):
         The number of train cases in the training set.
     n_atts_ : int
         The number of attributes in the training set.
-    transformed_data_ : list of shape (n_estimators) of ndarray
-        The transformed training dataset for all regressors. Only saved when
-        ``save_transformed_data`` is `True`.
     estimators_ : list of shape (n_estimators) of BaseEstimator
         The collections of estimators trained in fit.
 
-    See Also
-    --------
-    FreshPRINCERegressor: A feature-based regressor using Rotation Forest.
-
     References
     ----------
     .. [1] Rodriguez, Juan José, Ludmila I. Kuncheva, and Carlos J. Alonso. "Rotation
@@ -112,7 +108,7 @@ class RotationForestRegressor(BaseEstimator):
         base_estimator=None,
         time_limit_in_minutes=0.0,
         contract_max_n_estimators=500,
-        save_transformed_data=False,
+        save_transformed_data="deprecated",
         n_jobs=1,
         random_state=None,
     ):
@@ -123,10 +119,18 @@ class RotationForestRegressor(BaseEstimator):
         self.base_estimator = base_estimator
         self.time_limit_in_minutes = time_limit_in_minutes
         self.contract_max_n_estimators = contract_max_n_estimators
-        self.save_transformed_data = save_transformed_data
         self.n_jobs = n_jobs
         self.random_state = random_state
 
+        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.10.0
+        self.save_transformed_data = save_transformed_data
+        if save_transformed_data != "deprecated":
+            warnings.warn(
+                "the save_transformed_data parameter is deprecated and will be"
+                "removed in v0.10.0. transformed_data_ will also be removed.",
+                stacklevel=2,
+            )
+
         super().__init__()
 
     def fit(self, X, y):
@@ -149,6 +153,90 @@ class RotationForestRegressor(BaseEstimator):
         Changes state by creating a fitted model that updates attributes
         ending in "_".
         """
+        self._fit_rotf(X, y)
+        return self
+
+    def predict(self, X) -> np.ndarray:
+        """Predict for all cases in X.
+
+        Parameters
+        ----------
+        X : 2d ndarray or DataFrame of shape = [n_cases, n_attributes]
+            The data to make predictions for.
+
+        Returns
+        -------
+        y : array-like, shape = [n_cases]
+            Predicted output values.
+        """
+        if not self._is_fitted:
+            raise NotFittedError(
+                f"This instance of {self.__class__.__name__} has not "
+                f"been fitted yet; please call `fit` first."
+            )
+
+        if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
+            X = np.reshape(X, (X.shape[0], -1))
+        elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
+            X = X.to_numpy()
+        elif not isinstance(X, np.ndarray) or len(X.shape) > 2:
+            raise ValueError(
+                "RotationForestRegressor is not a time series regressor. "
+                "A valid sklearn input such as a 2d numpy array is required."
+                "Sparse input formats are currently not supported."
+            )
+        X = self._validate_data(X=X, reset=False)
+
+        # replace missing values with 0 and remove useless attributes
+        X = X[:, self._useful_atts]
+
+        # normalise the data.
+        X = (X - self._min) / self._ptp
+
+        y_preds = Parallel(n_jobs=self._n_jobs, prefer="threads")(
+            delayed(self._predict_for_estimator)(
+                X,
+                self.estimators_[i],
+                self._pcas[i],
+                self._groups[i],
+            )
+            for i in range(self._n_estimators)
+        )
+
+        output = np.sum(y_preds, axis=0) / self._n_estimators
+
+        return output
+
+    def fit_predict(self, X, y) -> np.ndarray:
+        X_t = self._fit_rotf(X, y, save_transformed_data=True)
+
+        rng = check_random_state(self.random_state)
+
+        p = Parallel(n_jobs=self._n_jobs, prefer="threads")(
+            delayed(self._train_preds_for_estimator)(
+                X_t,
+                y,
+                i,
+                check_random_state(rng.randint(np.iinfo(np.int32).max)),
+            )
+            for i in range(self._n_estimators)
+        )
+        y_preds, oobs = zip(*p)
+
+        results = np.sum(y_preds, axis=0)
+        divisors = np.zeros(self.n_cases_)
+        for oob in oobs:
+            for inst in oob:
+                divisors[inst] += 1
+
+        for i in range(self.n_cases_):
+            results[i] = (
+                self._label_average if divisors[i] == 0 else results[i] / divisors[i]
+            )
+
+        return results
+
+    def _fit_rotf(self, X, y, save_transformed_data=False):
         if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
             X = np.reshape(X, (X.shape[0], -1))
         elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
@@ -192,7 +280,7 @@ class RotationForestRegressor(BaseEstimator):
             self.estimators_ = []
             self._pcas = []
             self._groups = []
-            self.transformed_data_ = []
+            X_t = []
 
             while (
                 train_time < time_limit
@@ -203,6 +291,7 @@ class RotationForestRegressor(BaseEstimator):
                         X,
                         y,
                         check_random_state(rng.randint(np.iinfo(np.int32).max)),
+                        save_transformed_data,
                     )
                     for _ in range(self._n_jobs)
                 )
@@ -212,7 +301,7 @@ class RotationForestRegressor(BaseEstimator):
                 self.estimators_ += estimators
                 self._pcas += pcas
                 self._groups += groups
-                self.transformed_data_ += transformed_data
+                X_t += transformed_data
 
                 self._n_estimators += self._n_jobs
                 train_time = time.time() - start_time
@@ -224,123 +313,17 @@ class RotationForestRegressor(BaseEstimator):
                     X,
                     y,
                     check_random_state(rng.randint(np.iinfo(np.int32).max)),
+                    save_transformed_data,
                 )
                 for _ in range(self._n_estimators)
             )
 
-            self.estimators_, self._pcas, self._groups, self.transformed_data_ = zip(
-                *fit
-            )
+            self.estimators_, self._pcas, self._groups, X_t = zip(*fit)
 
         self._is_fitted = True
-        return self
-
-    def predict(self, X):
-        """Predict for all cases in X.
-
-        Parameters
-        ----------
-        X : 2d ndarray or DataFrame of shape = [n_cases, n_attributes]
-            The data to make predictions for.
-
-        Returns
-        -------
-        y : array-like, shape = [n_cases]
-            Predicted output values.
-        """
-        if not self._is_fitted:
-            raise NotFittedError(
-                f"This instance of {self.__class__.__name__} has not "
-                f"been fitted yet; please call `fit` first."
-            )
-
-        if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
-            X = np.reshape(X, (X.shape[0], -1))
-        elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
-            X = X.to_numpy()
-        elif not isinstance(X, np.ndarray) or len(X.shape) > 2:
-            raise ValueError(
-                "RotationForestRegressor is not a time series regressor. "
-                "A valid sklearn input such as a 2d numpy array is required."
-                "Sparse input formats are currently not supported."
-            )
-        X = self._validate_data(X=X, reset=False)
-
-        # replace missing values with 0 and remove useless attributes
-        X = X[:, self._useful_atts]
-
-        # normalise the data.
-        X = (X - self._min) / self._ptp
-
-        y_preds = Parallel(n_jobs=self._n_jobs, prefer="threads")(
-            delayed(self._predict_for_estimator)(
-                X,
-                self.estimators_[i],
-                self._pcas[i],
-                self._groups[i],
-            )
-            for i in range(self._n_estimators)
-        )
-
-        output = np.sum(y_preds, axis=0) / self._n_estimators
-
-        return output
-
-    def _get_train_preds(self, X, y):
-        if not self._is_fitted:
-            raise NotFittedError(
-                f"This instance of {self.__class__.__name__} has not "
-                f"been fitted yet; please call `fit` first."
-            )
-        if isinstance(X, np.ndarray) and len(X.shape) == 3 and X.shape[1] == 1:
-            X = np.reshape(X, (X.shape[0], -1))
-        elif isinstance(X, pd.DataFrame) and len(X.shape) == 2:
-            X = X.to_numpy()
-        elif not isinstance(X, np.ndarray) or len(X.shape) > 2:
-            raise ValueError(
-                "RotationForestRegressor is not a time series regressor. "
-                "A valid sklearn input such as a 2d numpy array is required."
-                "Sparse input formats are currently not supported."
-            )
-        X = self._validate_data(X=X, reset=False)
-
-        n_cases, n_atts = X.shape
-
-        if n_cases != self.n_cases_ or n_atts != self.n_atts_:
-            raise ValueError(
-                "n_cases, n_atts mismatch. X should be the same as the training "
-                "data used in fit for generating train predictions."
-            )
-
-        if not self.save_transformed_data:
-            raise ValueError("Currently only works with saved transform data from fit.")
-
-        rng = check_random_state(self.random_state)
-
-        p = Parallel(n_jobs=self._n_jobs, prefer="threads")(
-            delayed(self._train_preds_for_estimator)(
-                y,
-                i,
-                check_random_state(rng.randint(np.iinfo(np.int32).max)),
-            )
-            for i in range(self._n_estimators)
-        )
-        y_preds, oobs = zip(*p)
-
-        results = np.sum(y_preds, axis=0)
-        divisors = np.zeros(n_cases)
-        for oob in oobs:
-            for inst in oob:
-                divisors[inst] += 1
-
-        for i in range(n_cases):
-            results[i] = (
-                self._label_average if divisors[i] == 0 else results[i] / divisors[i]
-            )
-
-        return results
+        return X_t
 
-    def _fit_estimator(self, X, y, rng):
+    def _fit_estimator(self, X, y, rng, save_transformed_data):
         groups = self._generate_groups(rng)
         pcas = []
 
@@ -385,7 +368,7 @@ class RotationForestRegressor(BaseEstimator):
         tree = _clone_estimator(self._base_estimator, random_state=rng)
         tree.fit(X_t, y)
 
-        return tree, pcas, groups, X_t if self.save_transformed_data else None
+        return tree, pcas, groups, X_t if save_transformed_data else None
 
     def _predict_for_estimator(self, X, clf, pcas, groups):
         X_t = np.concatenate(
@@ -398,7 +381,7 @@ class RotationForestRegressor(BaseEstimator):
 
         return clf.predict(X_t)
 
-    def _train_preds_for_estimator(self, y, idx, rng):
+    def _train_preds_for_estimator(self, X_t, y, idx, rng):
         indices = range(self.n_cases_)
         subsample = rng.choice(self.n_cases_, size=self.n_cases_)
         oob = [n for n in indices if n not in subsample]
@@ -408,8 +391,8 @@ class RotationForestRegressor(BaseEstimator):
             return [results, oob]
 
         clf = _clone_estimator(self._base_estimator, rng)
-        clf.fit(self.transformed_data_[idx][subsample], y[subsample])
-        preds = clf.predict(self.transformed_data_[idx][oob])
+        clf.fit(X_t[idx][subsample], y[subsample])
+        preds = clf.predict(X_t[idx][oob])
 
         for n, pred in enumerate(preds):
             results[oob[n]] += pred
diff --git a/aeon/transformations/collection/segment.py b/aeon/transformations/collection/segment.py
index d2cfeb54..0d1906cd 100644
--- a/aeon/transformations/collection/segment.py
+++ b/aeon/transformations/collection/segment.py
@@ -203,7 +203,7 @@ class RandomIntervalSegmenter(IntervalSegmenter):
 
         Default is "sqrt".
 
-    random_state : int, RandomState instance or None, optional (default=None)
+    random_state : int, RandomState instance or None, default=None
         If int, random_state is the seed used by the random number generator;
         If RandomState instance, random_state is the random number generator;
         If None, the random number generator is the RandomState instance used
diff --git a/aeon/transformations/collection/truncate.py b/aeon/transformations/collection/truncate.py
index b0b3261a..971b1662 100644
--- a/aeon/transformations/collection/truncate.py
+++ b/aeon/transformations/collection/truncate.py
@@ -17,9 +17,10 @@ class TruncationTransformer(BaseCollectionTransformer):
 
     Parameters
     ----------
-    truncated_length : int, optional (default=None) bottom range of the values to
-                truncate can also be used to truncate to a specific length
-                if None, will find the shortest sequence and use instead.
+    truncated_length : int, default=None
+        bottom range of the values to truncate can also be used to truncate
+        to a specific length.
+        if None, will find the shortest sequence and use instead.
 
     Example
     -------
diff --git a/aeon/transformations/compose.py b/aeon/transformations/compose.py
index 9e978f71..5a4e35a1 100644
--- a/aeon/transformations/compose.py
+++ b/aeon/transformations/compose.py
@@ -329,7 +329,7 @@ class TransformerPipeline(_HeterogenousMetaEstimator, BaseTransformer):
         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 inverse transformed
-        y : Series or Panel of type y_inner_type, optional (default=None)
+        y : Series or Panel of type y_inner_type, default=None
             Additional data, e.g., labels for transformation
 
         Returns
@@ -422,7 +422,7 @@ class FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):
     transformer_list : list of (string, transformer) tuples
         List of transformer objects to be applied to the data. The first
         half of each tuple is the name of the transformer.
-    n_jobs : int or None, optional (default=None)
+    n_jobs : int or None, default=None
         Number of jobs to run in parallel.
         ``None`` means 1 unless in a :obj:`joblib.parallel_backend`
         context.
@@ -430,7 +430,7 @@ class FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):
     transformer_weights : dict, optional
         Multiplicative weights for features per transformer.
         Keys are transformer names, values the weights.
-    flatten_transform_index : bool, optional (default=True)
+    flatten_transform_index : bool, default=True
         if True, columns of return DataFrame are flat, by "transformer__variablename"
         if False, columns are MultiIndex (transformer, variablename)
         has no effect if return type is one without column names
@@ -729,7 +729,7 @@ class FitInTransform(BaseTransformer):
         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 inverse transformed
-        y : Series or Panel of type y_inner_type, optional (default=None)
+        y : Series or Panel of type y_inner_type, default=None
             Additional data, e.g., labels for transformation
 
         Returns
@@ -1729,7 +1729,7 @@ class YtoX(BaseTransformer):
         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 inverse transformed
-        y : Series or Panel of type y_inner_type, optional (default=None)
+        y : Series or Panel of type y_inner_type, default=None
             Additional data, e.g., labels for transformation
 
         Returns
diff --git a/aeon/transformations/date.py b/aeon/transformations/date.py
index d44f926c..6dd006bc 100644
--- a/aeon/transformations/date.py
+++ b/aeon/transformations/date.py
@@ -39,7 +39,7 @@ class DateTimeFeatures(BaseTransformer):
 
     Parameters
     ----------
-    ts_freq : str, optional (default="day")
+    ts_freq : str, default="day"
         Restricts selection of items to those with a frequency lower than
         the frequency of the time series given by ts_freq.
         E.g. if monthly data is provided and ts_freq = ("M"), it does not make
@@ -61,14 +61,14 @@ class DateTimeFeatures(BaseTransformer):
         * T - minute
         * S - second
         * L - millisecond
-    feature_scope: str, optional (default="minimal")
+    feature_scope: str, default="minimal"
         Specify how many calendar features you want to be returned.
         E.g., rarely used features like week of quarter will only be returned
         with feature_scope =  "comprehensive".
         * "minimal"
         * "efficient"
         * "comprehensive"
-    manual_selection: str, optional (default=None)
+    manual_selection: str, default=None
         Manual selection of dummys. Notation is child of parent for precise notation.
         Will ignore specified feature_scope, but will still check with warning against
         a specified ts_freq.
diff --git a/aeon/transformations/kalman_filter.py b/aeon/transformations/kalman_filter.py
index 68c76357..998b495d 100644
--- a/aeon/transformations/kalman_filter.py
+++ b/aeon/transformations/kalman_filter.py
@@ -148,29 +148,29 @@ class BaseKalmanFilter:
     ----------
     state_dim : int
         System state feature dimension
-    state_transition : np.ndarray, optional (default=None)
+    state_transition : np.ndarray, default=None
         of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
         State transition matrix, also referred to as F, is a matrix
         which describes the way the underlying series moves
         through successive time periods.
-    process_noise : np.ndarray, optional (default=None)
+    process_noise : np.ndarray, default=None
         of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
         Process noise matrix, also referred to as Q,
         the uncertainty of the dynamic model.
-    measurement_noise : np.ndarray, optional (default=None)
+    measurement_noise : np.ndarray, default=None
         of shape (measurement_dim, measurement_dim) or
         (time_steps, measurement_dim, measurement_dim).
         Measurement noise matrix, also referred to as R,
         represents the uncertainty of the measurements.
-    measurement_function : np.ndarray, optional (default=None)
+    measurement_function : np.ndarray, default=None
         of shape (measurement_dim, state_dim) or
         (time_steps, measurement_dim, state_dim).
         Measurement equation matrix, also referred to as H, adjusts
         dimensions of measurements to match dimensions of state.
-    initial_state : np.ndarray, optional (default=None)
+    initial_state : np.ndarray, default=None
         of shape (state_dim,).
         Initial estimated system state, also referred to as x0.
-    initial_state_covariance : np.ndarray, optional (default=None)
+    initial_state_covariance : np.ndarray, default=None
         of shape (state_dim, state_dim).
         Initial estimated system state covariance, also referred to as P0.
 
@@ -294,7 +294,7 @@ class BaseKalmanFilter:
             The value of corresponding attribute.
         matrix_shape : tuple
             The supposed shape of a single matrix.
-        time_steps : int, optional (default=None)
+        time_steps : int, default=None
         """
         _validate_param_shape(
             param_name=param_name,
@@ -325,42 +325,42 @@ class KalmanFilterTransformer(BaseKalmanFilter, BaseTransformer):
     ----------
     state_dim : int
         System state feature dimension.
-    state_transition : np.ndarray, optional (default=None)
+    state_transition : np.ndarray, default=None
         of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
         State transition matrix, also referred to as `F`, is a matrix
         which describes the way the underlying series moves
         through successive time periods.
-    process_noise : np.ndarray, optional (default=None)
+    process_noise : np.ndarray, default=None
         of shape (state_dim, state_dim) or (time_steps, state_dim, state_dim).
         Process noise matrix, also referred to as `Q`,
         the uncertainty of the dynamic model.
-    measurement_noise : np.ndarray, optional (default=None)
+    measurement_noise : np.ndarray, default=None
         of shape (measurement_dim, measurement_dim) or
         (time_steps, measurement_dim, measurement_dim).
         Measurement noise matrix, also referred to as `R`,
         represents the uncertainty of the measurements.
-    measurement_function : np.ndarray, optional (default=None)
+    measurement_function : np.ndarray, default=None
         of shape (measurement_dim, state_dim) or
         (time_steps, measurement_dim, state_dim).
         Measurement equation matrix, also referred to as `H`, adjusts
         dimensions of measurements to match dimensions of state.
-    initial_state : np.ndarray, optional (default=None)
+    initial_state : np.ndarray, default=None
         of shape (state_dim,).
         Initial estimated system state, also referred to as `X0`.
-    initial_state_covariance : np.ndarray, optional (default=None)
+    initial_state_covariance : np.ndarray, default=None
         of shape (state_dim, state_dim).
         Initial estimated system state covariance, also referred to as `P0`.
-    control_transition : np.ndarray, optional (default=None)
+    control_transition : np.ndarray, default=None
         of shape (state_dim, control_variable_dim) or
         (time_steps, state_dim, control_variable_dim).
         Control transition matrix, also referred to as `G`.
         `control_variable_dim` is the dimension of `control variable`,
         also referred to as `u`.
         `control variable` is an optional parameter for `fit` and `transform` functions.
-    denoising : bool, optional (default=False).
+    denoising : bool, default=False
         This parameter affects `transform`. If False, then `transform` will be inferring
         hidden state. If True, uses `FilterPy` `rts_smoother` for denoising.
-    estimate_matrices : str or list of str, optional (default=None).
+    estimate_matrices : str or list of str, default=None
         Subset of [`state_transition`, `measurement_function`,
         `process_noise`, `measurement_noise`, `initial_state`,
         `initial_state_covariance`]
@@ -568,7 +568,7 @@ class KalmanFilterTransformer(BaseKalmanFilter, BaseTransformer):
             of shape (time_steps, measurement_dim).
             Data (measurements) to be transformed.
             Missing values must be represented as np.NaN or np.nan.
-        y : np.ndarray, optional (default=None).
+        y : np.ndarray, default=None
             of shape (control_variable_dim,) or (time_steps, control_variable_dim).
             `control variable`, also referred to as `u`.
             if 2D, must be same length as X.
diff --git a/aeon/transformations/lag.py b/aeon/transformations/lag.py
index 0d0346c1..137b238c 100644
--- a/aeon/transformations/lag.py
+++ b/aeon/transformations/lag.py
@@ -66,11 +66,11 @@ class Lag(BaseTransformer):
         "original" - only original indices are retained. Will usually create NA.
         "extend" - both original indices and shifted indices are retained.
             Will usually create NA, possibly many, if shifted/original do not intersect.
-    flatten_transform_index : bool, optional (default=True)
+    flatten_transform_index : bool, default=True
         if True, columns of return DataFrame are flat, by "lagname__variablename"
         if False, columns are MultiIndex (lagname, variablename)
         has no effect if return type is one without column names
-    keep_column_names : bool, optional (default=False)
+    keep_column_names : bool, default=False
         has an effect only if `lags` contains only a single element
         if True, ensures that column names of `transform` output are same as in input,
         i.e., not `lag_x__varname` but `varname`. Overrides `flatten_transform_index`.
diff --git a/aeon/transformations/outlier_detection.py b/aeon/transformations/outlier_detection.py
index 6a51a4c3..d83e45e1 100644
--- a/aeon/transformations/outlier_detection.py
+++ b/aeon/transformations/outlier_detection.py
@@ -20,8 +20,8 @@ class HampelFilter(BaseTransformer):
 
     Parameters
     ----------
-    window_length : int, optional (default=10)
-        Lenght of the sliding window
+    window_length : int, default=10
+        Length of the sliding window
     n_sigma : int, optional
         Defines how strong a point must outly to be an "outlier", by default 3
     k : float, optional
diff --git a/aeon/transformations/scaledlogit.py b/aeon/transformations/scaledlogit.py
index 8f959449..80b8ef76 100644
--- a/aeon/transformations/scaledlogit.py
+++ b/aeon/transformations/scaledlogit.py
@@ -159,7 +159,7 @@ class ScaledLogitTransformer(BaseTransformer):
         ----------
         X : 2D np.ndarray
             Data to be inverse transformed
-        y : data of y_inner_type, optional (default=None)
+        y : data of y_inner_type, default=None
             Ignored argument for interface compatibility
 
         Returns
diff --git a/aeon/transformations/series/_acf.py b/aeon/transformations/series/_acf.py
new file mode 100644
index 00000000..e8c7d59b
--- /dev/null
+++ b/aeon/transformations/series/_acf.py
@@ -0,0 +1,136 @@
+"""Auto-correlation transformations.
+
+Module :mod:`aeon.transformations` implements auto-correlation
+transformers.
+"""
+
+__maintainer__ = ["TonyBagnall"]
+__all__ = ["AutoCorrelationTransformer"]
+
+import numpy as np
+from numba import njit
+
+from aeon.transformations.series.base import BaseSeriesTransformer
+
+
+class AutoCorrelationTransformer(BaseSeriesTransformer):
+    """Auto-correlation transformer.
+
+    The autocorrelation function (ACF) measures how correlated a time series is
+    with itself at different lags. The AutocorrelationTransformer returns
+    these values as a series for each lag up to the `n_lags` specified. This transformer
+    intentionally uses a simple implementation without use of FFT and makes minimal
+    adjustments to the ACF. It does not adjust for the mean or variance or trend.
+
+    Parameters
+    ----------
+    adjusted : bool, default=False
+        If True, then denominators for autocovariance are n-k, otherwise n.
+
+    n_lags : int, default=None
+        Number of lags to return autocorrelation for. If None, it sets it to max(1,
+        n_timepoints/4).
+
+    Examples
+    --------
+    >>> from aeon.transformations.series._acf import AutoCorrelationTransformer
+    >>> from aeon.datasets import load_airline
+    >>> y = load_airline()
+    >>> transformer = AutoCorrelationTransformer(n_lags=12)
+    >>> y_hat = transformer.fit_transform(y)
+    """
+
+    _tags = {
+        "X_inner_type": "np.ndarray",
+        "capability:multivariate": True,
+        "fit_is_empty": True,
+    }
+
+    def __init__(
+        self,
+        n_lags=None,
+    ):
+        self.n_lags = n_lags
+        super().__init__(axis=1)
+
+    def _transform(self, X, y=None):
+        """Transform X and return a transformed version.
+
+        private _transform containing the core logic, called from transform
+
+        Parameters
+        ----------
+        X : np.ndarray
+            Data to be transformed, shape (n_channels, n_timepoints)
+        y : ignored argument for interface compatibility
+            Additional data, e.g., labels for transformation
+
+        Returns
+        -------
+        transformed version of X
+        """
+        # statsmodels acf function uses min(10 * np.log10(nobs), nobs - 1)
+        if self.n_lags is None:
+            self._n_lags = int(max(1, X.shape[1] / 4))
+        else:
+            self._n_lags = int(self.n_lags)
+        if self._n_lags < 1:
+            self._n_lags = 1
+        if X.shape[1] - self._n_lags < 3:
+            raise ValueError(
+                f"The number of lags is too large for the length of the "
+                f"series, autocorrelation would be calculated with just"
+                f"{X.shape[1]-self._n_lags} observations."
+            )
+        return self._acf(X, max_lag=self._n_lags)
+
+    @staticmethod
+    @njit(cache=True, fastmath=True)
+    def _acf(X, max_lag):
+        n_channels, length = X.shape
+        X_t = np.zeros((n_channels, max_lag), dtype=float)
+
+        for i in range(0, n_channels):
+            for lag in range(1, max_lag + 1):
+                lag_length = length - lag
+                x1 = X[i][:-lag]
+                x2 = X[i][lag:]
+                s1 = np.sum(x1)
+                s2 = np.sum(x2)
+                m1 = s1 / lag_length
+                m2 = s2 / lag_length
+                ss1 = np.sum(x1 * x1)
+                ss2 = np.sum(x2 * x2)
+                v1 = ss1 - s1 * m1
+                v2 = ss2 - s2 * m2
+                v1_is_zero, v2_is_zero = v1 <= 1e-9, v2 <= 1e-9
+                if v1_is_zero and v2_is_zero:  # Both zero variance,
+                    # so must be 100% correlated
+                    X_t[i][lag - 1] = 1
+                elif v1_is_zero or v2_is_zero:  # One zero variance
+                    # the other not
+                    X_t[i][lag - 1] = 0
+                else:
+                    X_t[i][lag - 1] = np.sum((x1 - m1) * (x2 - m2)) / np.sqrt(v1 * v2)
+        return X_t
+
+    @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`
+        """
+        return [{}, {"n_lags": 1}]
diff --git a/aeon/transformations/summarize.py b/aeon/transformations/summarize.py
index 0c4c8302..1922b832 100644
--- a/aeon/transformations/summarize.py
+++ b/aeon/transformations/summarize.py
@@ -21,7 +21,7 @@ class WindowSummarizer(BaseTransformer):
 
     Parameters
     ----------
-    n_jobs : int, optional (default=-1)
+    n_jobs : int, default=-1
         The number of jobs to run in parallel for applying the window functions.
         ``-1`` means using all processors.
     target_cols : list of str, optional (default = None)
@@ -606,7 +606,7 @@ class SummaryTransformer(BaseTransformer):
     quantiles : str, list, tuple or None, default=(0.1, 0.25, 0.5, 0.75, 0.9)
         Optional list of series quantiles to calculate. If None, no quantiles
         are calculated, and summary_function must be non-None.
-    flatten_transform_index : bool, optional (default=True)
+    flatten_transform_index : bool, default=True
         if True, columns of return DataFrame are flat, by "variablename__feature"
         if False, columns are MultiIndex (variablename__feature)
         has no effect if return type is one without column names
@@ -830,7 +830,7 @@ class FittedParamExtractor(BaseTransformer):
         An aeon estimator to extract features from.
     param_names : str
         Name of parameters to extract from the forecaster.
-    n_jobs : int, optional (default=None)
+    n_jobs : int, default=None
         Number of jobs to run in parallel.
         None means 1 unless in a joblib.parallel_backend context.
         -1 means using all processors.
diff --git a/aeon/transformations/time_since.py b/aeon/transformations/time_since.py
index f70a1845..118a3fb2 100644
--- a/aeon/transformations/time_since.py
+++ b/aeon/transformations/time_since.py
@@ -40,7 +40,7 @@ class TimeSince(BaseTransformer):
         * str: String is converted to datetime or period, depending on the index type, \
             to give the start time.
 
-    to_numeric : string, optional (default=True)
+    to_numeric : string, default=True
         Return the integer number of periods elapsed since `start`; the period
         is defined by the frequency of the data. Converts datetime types to
         pd.Period before calculating time differences.
diff --git a/aeon/utils/datetime.py b/aeon/utils/datetime.py
index 4e7c9887..d84a9a4f 100644
--- a/aeon/utils/datetime.py
+++ b/aeon/utils/datetime.py
@@ -250,7 +250,7 @@ def _get_duration(x, y=None, coerce_to_int=False, unit=None):
     Parameters
     ----------
     x : pd.Index, pd.Timestamp, pd.Period, int
-    y : pd.Timestamp, pd.Period, int, optional (default=None)
+    y : pd.Timestamp, pd.Period, int, default=None
     coerce_to_int : bool
         If True, duration is returned as integer value for given unit
     unit : str
diff --git a/aeon/utils/validation/__init__.py b/aeon/utils/validation/__init__.py
index 53533211..3f34bc61 100644
--- a/aeon/utils/validation/__init__.py
+++ b/aeon/utils/validation/__init__.py
@@ -208,7 +208,7 @@ def check_window_length(
         - If float, the fraction of time points relative to `n_timepoints`.
         - If timedelta, length in corresponding time units
         - If pd.DateOffset, length in corresponding time units following calendar rules
-    n_timepoints: positive int, optional (default=None)
+    n_timepoints: positive int, default=None
         The number of time points to which to apply `window_length` when
         passed as a float (fraction). Will be ignored if `window_length` is
         an integer.
diff --git a/aeon/utils/validation/forecasting.py b/aeon/utils/validation/forecasting.py
index 8519f621..918fdc29 100644
--- a/aeon/utils/validation/forecasting.py
+++ b/aeon/utils/validation/forecasting.py
@@ -49,12 +49,12 @@ def check_y_X(
     Parameters
     ----------
     y : pd.Series
-    X : pd.DataFrame, optional (default=None)
-    allow_empty : bool, optional (default=False)
+    X : pd.DataFrame, default=None
+    allow_empty : bool, default=False
         If True, empty `y` does not raise an error.
-    allow_constant : bool, optional (default=True)
+    allow_constant : bool, default=True
         If True, constant `y` does not raise an error.
-    enforce_index_type : type, optional (default=None)
+    enforce_index_type : type, default=None
         type of time index
 
     Raises
@@ -89,11 +89,11 @@ def check_X(
     Parameters
     ----------
     X : pd.Series, pd.DataFrame, np.ndarray
-    allow_empty : bool, optional (default=False)
+    allow_empty : bool, default=False
         If False, empty `X` raises an error.
-    enforce_index_type : type, optional (default=None)
+    enforce_index_type : type, default=None
         type of time index
-    enforce_univariate : bool, optional (default=False)
+    enforce_univariate : bool, default=False
         If True, multivariate X will raise an error.
 
     Returns
@@ -130,13 +130,13 @@ def check_y(
     Parameters
     ----------
     y : pd.Series
-    allow_empty : bool, optional (default=False)
+    allow_empty : bool, default=False
         If False, empty `y` raises an error.
-    allow_constant : bool, optional (default=True)
+    allow_constant : bool, default=True
         If True, constant `y` does not raise an error.
-    enforce_index_type : type, optional (default=None)
+    enforce_index_type : type, default=None
         type of time index
-    allow_index_names : bool, optional (default=None)
+    allow_index_names : bool, default=None
         If False, names of y.index will be set to None
 
     Returns
@@ -249,7 +249,7 @@ def check_sp(sp, enforce_list=False):
     ----------
     sp : int or [int/float]
         Seasonal periodicity
-    enforce_list : bool, optional (default=False)
+    enforce_list : bool, default=False
         If true, convert sp to list if not list.
 
     Returns
@@ -277,9 +277,9 @@ def check_fh(fh, enforce_relative: bool = False, freq=None):
     ----------
     fh : int, list, np.array, pd.Index or ForecastingHorizon
         Forecasting horizon specifying the time points to predict.
-    enforce_relative : bool, optional (default=False)
+    enforce_relative : bool, default=False
         If True, checks if fh is relative.
-    freq : str, or pd.Index, optional (default=None)
+    freq : str, or pd.Index, default=None
         object carrying frequency information on values
         ignored unless values is without inferrable freq
         Frequency string or pd.Index
diff --git a/aeon/utils/validation/panel.py b/aeon/utils/validation/panel.py
index c331bd49..bd27cc16 100644
--- a/aeon/utils/validation/panel.py
+++ b/aeon/utils/validation/panel.py
@@ -40,15 +40,15 @@ def check_X(
     ----------
     X : pd.DataFrame or np.array
         Input data
-    enforce_univariate : bool, optional (default=False)
+    enforce_univariate : bool, default=False
         Enforce that X is univariate.
-    enforce_min_cases : int, optional (default=1)
+    enforce_min_cases : int, default=1
         Enforce minimum number of instances.
-    enforce_min_columns : int, optional (default=1)
+    enforce_min_columns : int, default=1
         Enforce minimum number of columns (or time-series variables).
-    coerce_to_numpy : bool, optional (default=False)
+    coerce_to_numpy : bool, default=False
         If True, X will be coerced to a 3-dimensional numpy array.
-    coerce_to_pandas : bool, optional (default=False)
+    coerce_to_pandas : bool, default=False
         If True, X will be coerced to a nested pandas DataFrame.
 
     Returns
@@ -132,9 +132,9 @@ def check_y(y, enforce_min_cases=1, coerce_to_numpy=False):
     Parameters
     ----------
     y : pd.Series or np.array
-    enforce_min_cases : int, optional (default=1)
+    enforce_min_cases : int, default=1
         Enforce minimum number of instances.
-    coerce_to_numpy : bool, optional (default=False)
+    coerce_to_numpy : bool, default=False
         If True, y will be coerced to a numpy array.
 
     Returns
@@ -182,15 +182,15 @@ def check_X_y(
     ----------
     X : pd.DataFrame
     y : pd.Series or np.array
-    enforce_univariate : bool, optional (default=False)
+    enforce_univariate : bool, default=False
         Enforce that X is univariate.
-    enforce_min_cases : int, optional (default=1)
+    enforce_min_cases : int, default=1
         Enforce minimum number of instances.
-    enforce_min_columns : int, optional (default=1)
+    enforce_min_columns : int, default=1
         Enforce minimum number of columns (or time-series variables).
-    coerce_to_numpy : bool, optional (default=False)
+    coerce_to_numpy : bool, default=False
         If True, X will be coerced to a 3-dimensional numpy array.
-    coerce_to_pandas : bool, optional (default=False)
+    coerce_to_pandas : bool, default=False
         If True, X will be coerced to a nested pandas DataFrame.
 
     Returns
diff --git a/aeon/utils/validation/series.py b/aeon/utils/validation/series.py
index 45e7d293..4b2a4129 100644
--- a/aeon/utils/validation/series.py
+++ b/aeon/utils/validation/series.py
@@ -315,9 +315,9 @@ def check_time_index(
     ----------
     index : pd.Index or np.array
         Time index
-    allow_empty : bool, optional (default=False)
+    allow_empty : bool, default=False
         If False, empty `index` raises an error.
-    enforce_index_type : type, optional (default=None)
+    enforce_index_type : type, default=None
         type of time index
     var_name : str, default = "input" - variable name printed in error messages
 
diff --git a/extension_templates/annotation.py b/extension_templates/annotation.py
index f57f37e7..9cefd842 100644
--- a/extension_templates/annotation.py
+++ b/extension_templates/annotation.py
@@ -41,11 +41,11 @@ class MySeriesAnnotator(BaseSeriesAnnotator):
 
     Parameters
     ----------
-    fmt : str {"dense", "sparse"}, optional (default="dense")
+    fmt : str {"dense", "sparse"}, default="dense"
         annotation output format:
         * If "sparse", a sub-series of labels for only the outliers in X is returned,
         * If "dense", a series of labels for all values in X is returned.
-    labels : str {"indicator", "score"}, optional (default="indicator")
+    labels : str {"indicator", "score"}, default="indicator"
         annotation output labels:
         * If "indicator", returned values are boolean, indicating whether a value is an
         outlier,
@@ -53,9 +53,9 @@ class MySeriesAnnotator(BaseSeriesAnnotator):
 
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
 
diff --git a/extension_templates/clustering.py b/extension_templates/clustering.py
index 17fdb7e3..d6197ef0 100644
--- a/extension_templates/clustering.py
+++ b/extension_templates/clustering.py
@@ -47,9 +47,9 @@ class MyClusterer(BaseClusterer):
     ----------------
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
 
diff --git a/extension_templates/early_classification.py b/extension_templates/early_classification.py
index 3a05ed04..107dec30 100644
--- a/extension_templates/early_classification.py
+++ b/extension_templates/early_classification.py
@@ -52,9 +52,9 @@ class MyEarlyTimeSeriesClassifier(BaseEarlyClassifier):
     ----------------
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
 
diff --git a/extension_templates/forecasting.py b/extension_templates/forecasting.py
index 5b01f509..72898e2b 100644
--- a/extension_templates/forecasting.py
+++ b/extension_templates/forecasting.py
@@ -56,9 +56,9 @@ class MyForecaster(BaseForecaster):
     ----------
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
     est : aeon.estimator, BaseEstimator descendant
@@ -209,11 +209,11 @@ class MyForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X :  aeon time series object, optional (default=None)
+        X :  aeon time series object, default=None
             guaranteed to be of an mtype in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -251,10 +251,10 @@ class MyForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : aeon time series object, optional (default=None)
+        X : aeon time series object, default=None
             guaranteed to be of an mtype in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
 
@@ -296,10 +296,10 @@ class MyForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X :  aeon time series object, optional (default=None)
+        X :  aeon time series object, default=None
             guaranteed to be of an mtype in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -351,7 +351,7 @@ class MyForecaster(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X :  aeon time series object, optional (default=None)
+        X :  aeon time series object, default=None
             guaranteed to be of an mtype in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
@@ -394,7 +394,7 @@ class MyForecaster(BaseForecaster):
         ----------
         fh : guaranteed to be ForecastingHorizon
             The forecasting horizon with the steps ahead to to predict.
-        X :  aeon time series object, optional (default=None)
+        X :  aeon time series object, default=None
             guaranteed to be of an mtype in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
         coverage : list of float (guaranteed not None and floats in [0,1] interval)
@@ -434,13 +434,13 @@ class MyForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X :  aeon time series object, optional (default=None)
+        X :  aeon time series object, default=None
             guaranteed to be of an mtype in self.get_tag("X_inner_type")
             Exogeneous time series for the forecast
-        cov : bool, optional (default=False)
+        cov : bool, default=False
             if True, computes covariance matrix forecast.
             if False, computes marginal variance forecasts.
 
@@ -483,12 +483,12 @@ class MyForecaster(BaseForecaster):
         fh : int, list, np.array or ForecastingHorizon (not optional)
             The forecasting horizon encoding the time stamps to forecast at.
             if has not been passed in fit, must be passed, not optional
-        X : aeon time series object, optional (default=None)
+        X : aeon time series object, default=None
                 Exogeneous time series for the forecast
             Should be of same scitype (Series, Panel, or Hierarchical) as y in fit
             if self.get_tag("X-y-must-have-same-index"),
                 X.index must contain fh.index and y.index both
-        marginal : bool, optional (default=True)
+        marginal : bool, default=True
             whether returned distribution is marginal by time index
 
         Returns
diff --git a/extension_templates/forecasting_simple.py b/extension_templates/forecasting_simple.py
index e95a877c..c4246163 100644
--- a/extension_templates/forecasting_simple.py
+++ b/extension_templates/forecasting_simple.py
@@ -53,9 +53,9 @@ class MyForecaster(BaseForecaster):
     ----------
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
     """
@@ -139,11 +139,11 @@ class MyForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -179,10 +179,10 @@ class MyForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
diff --git a/extension_templates/transformer.py b/extension_templates/transformer.py
index b6fb373b..a62d3428 100644
--- a/extension_templates/transformer.py
+++ b/extension_templates/transformer.py
@@ -56,9 +56,9 @@ class MyTransformer(BaseTransformer):
     ----------
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
     est : aeon.estimator, BaseEstimator descendant
@@ -365,7 +365,7 @@ class MyTransformer(BaseTransformer):
         X : Series or Panel of mtype X_inner_type
             if X_inner_type is list, _inverse_transform must support all types in it
             Data to be inverse transformed
-        y : Series or Panel of mtype y_inner_type, optional (default=None)
+        y : Series or Panel of mtype y_inner_type, default=None
             Additional data, e.g., labels for transformation
 
         Returns
diff --git a/extension_templates/transformer_simple.py b/extension_templates/transformer_simple.py
index 81f64783..493ad1d6 100644
--- a/extension_templates/transformer_simple.py
+++ b/extension_templates/transformer_simple.py
@@ -56,9 +56,9 @@ class MyTransformer(BaseTransformer):
     ----------
     parama : int
         descriptive explanation of parama
-    paramb : string, optional (default='default')
+    paramb : string, default='default'
         descriptive explanation of paramb
-    paramc : boolean, optional (default= whether paramb is not the default)
+    paramc : boolean, default= whether paramb is not the default
         descriptive explanation of paramc
     and so on
     """


Test patch:
diff --git a/aeon/classification/sklearn/tests/test_rotation_forest_classifier.py b/aeon/classification/sklearn/tests/test_rotation_forest_classifier.py
index 233f8a53..94d9c3ab 100644
--- a/aeon/classification/sklearn/tests/test_rotation_forest_classifier.py
+++ b/aeon/classification/sklearn/tests/test_rotation_forest_classifier.py
@@ -1,28 +1,48 @@
 """Rotation Forest test code."""
 
 import numpy as np
+from sklearn.metrics import accuracy_score
 
 from aeon.classification.sklearn import RotationForestClassifier
 from aeon.datasets import load_unit_test
 
 
 def test_contracted_rotf():
-    """Test of RotF contracting and train estimate on unit test data."""
-    # load unit test data
+    """Test of RotF contracting and train estimate on test data."""
     X_train, y_train = load_unit_test(split="train", return_type="numpy2d")
+    X_test, y_test = load_unit_test(split="test", return_type="numpy2d")
 
     rotf = RotationForestClassifier(
         time_limit_in_minutes=5,
         contract_max_n_estimators=5,
-        save_transformed_data=True,
         random_state=0,
     )
     rotf.fit(X_train, y_train)
+    assert len(rotf.estimators_) > 0
+
+    y_pred = rotf.predict(X_test)
+    assert isinstance(y_pred, np.ndarray)
+    assert len(y_pred) == len(y_test)
+
+    acc = accuracy_score(y_test, y_pred)
+    np.testing.assert_almost_equal(acc, 0.909, decimal=4)
+
+
+def test_rotf_fit_predict():
+    """Test of RotF fit_predict on test data."""
+    X_train, y_train = load_unit_test(split="train", return_type="numpy2d")
+
+    rotf = RotationForestClassifier(
+        n_estimators=5,
+        random_state=0,
+    )
 
+    y_proba = rotf.fit_predict_proba(X_train, y_train)
+    assert isinstance(y_proba, np.ndarray)
+    assert y_proba.shape == (len(y_train), 2)
     assert len(rotf.estimators_) > 0
+    assert rotf._is_fitted
 
-    # test train estimate
-    train_proba = rotf._get_train_probs(X_train, y_train)
-    assert isinstance(train_proba, np.ndarray)
-    assert train_proba.shape == (len(X_train), 2)
-    np.testing.assert_almost_equal(train_proba.sum(axis=1), 1, decimal=4)
+    y_proba = rotf.predict_proba(X_train)
+    assert isinstance(y_proba, np.ndarray)
+    assert y_proba.shape == (len(y_train), 2)
diff --git a/aeon/datasets/tests/test_load_forecasting.py b/aeon/datasets/tests/test_load_forecasting.py
index 6a293bc6..7f004380 100644
--- a/aeon/datasets/tests/test_load_forecasting.py
+++ b/aeon/datasets/tests/test_load_forecasting.py
@@ -38,8 +38,8 @@ def test_forecasting_data_loaders(dataset):
 
     dataset: dictionary with values to assert against should contain:
         'columns' : list with column names in correct order,
-        'len_y'   : lenght of the y series (int),
-        'len_X'   : lenght of the X series/dataframe (int),
+        'len_y'   : length of the y series (int),
+        'len_X'   : length of the X series/dataframe (int),
         'data_types_X' : dictionary with column name keys and dtype as value,
         'data_type_y'  : dtype if y column (string)
         'data'    : tuple with y series and X series/dataframe if one is not
diff --git a/aeon/regression/sklearn/tests/test_rotation_forest_regressor.py b/aeon/regression/sklearn/tests/test_rotation_forest_regressor.py
index 67c8cabb..bd253e20 100644
--- a/aeon/regression/sklearn/tests/test_rotation_forest_regressor.py
+++ b/aeon/regression/sklearn/tests/test_rotation_forest_regressor.py
@@ -8,29 +8,42 @@ from aeon.regression.sklearn import RotationForestRegressor
 
 
 def test_contracted_rotf():
-    """Test of RotF contracting and train estimate on unit test data."""
-    # load unit test data
+    """Test of RotF contracting and train estimate on test data."""
     X_train, y_train = load_covid_3month(split="train", return_type="numpy2d")
+    X_test, y_test = load_covid_3month(split="test", return_type="numpy2d")
 
     rotf = RotationForestRegressor(
         time_limit_in_minutes=5,
         contract_max_n_estimators=5,
-        save_transformed_data=True,
         random_state=0,
     )
-    rotf.fit(X_train, y_train)
 
+    rotf.fit(X_train, y_train)
     assert len(rotf.estimators_) > 0
 
-    # test train estimate
-    train_preds = rotf._get_train_preds(X_train, y_train)
-    assert isinstance(train_preds, np.ndarray)
-    assert train_preds.shape[0] == len(X_train)
-
-    X_test, y_test = load_covid_3month(split="test", return_type="numpy2d")
-
     y_pred = rotf.predict(X_test)
+    assert isinstance(y_pred, np.ndarray)
+    assert len(y_pred) == len(y_test)
 
     mse = mean_squared_error(y_test, y_pred)
-
     np.testing.assert_almost_equal(mse, 0.002, decimal=4)
+
+
+def test_rotf_fit_predict():
+    """Test of RotF fit_predict on test data."""
+    X_train, y_train = load_covid_3month(split="train", return_type="numpy2d")
+
+    rotf = RotationForestRegressor(
+        n_estimators=5,
+        random_state=0,
+    )
+
+    y_pred = rotf.fit_predict(X_train, y_train)
+    assert isinstance(y_pred, np.ndarray)
+    assert len(y_pred) == len(y_train)
+    assert len(rotf.estimators_) > 0
+    assert rotf._is_fitted
+
+    y_pred = rotf.predict(X_train)
+    assert isinstance(y_pred, np.ndarray)
+    assert len(y_pred) == len(y_train)
diff --git a/aeon/testing/mock_estimators/_mock_forecasters.py b/aeon/testing/mock_estimators/_mock_forecasters.py
index a2a8eabb..96925ac5 100644
--- a/aeon/testing/mock_estimators/_mock_forecasters.py
+++ b/aeon/testing/mock_estimators/_mock_forecasters.py
@@ -178,11 +178,11 @@ class MockUnivariateForecasterLogger(BaseForecaster, _MockEstimatorMixin):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -207,10 +207,10 @@ class MockUnivariateForecasterLogger(BaseForecaster, _MockEstimatorMixin):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -247,9 +247,9 @@ class MockUnivariateForecasterLogger(BaseForecaster, _MockEstimatorMixin):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -276,7 +276,7 @@ class MockUnivariateForecasterLogger(BaseForecaster, _MockEstimatorMixin):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
             A list of probabilities at which quantile forecasts are computed.
@@ -356,11 +356,11 @@ class MockForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             Required (non-optional) here if self.get_tag("requires-fh-in-fit")==True
             Otherwise, if not passed in _fit, guaranteed to be passed in _predict
-        X : optional (default=None)
+        X : default=None
             guaranteed to be of a type in self.get_tag("X_inner_type")
             Exogeneous time series to fit to.
 
@@ -384,10 +384,10 @@ class MockForecaster(BaseForecaster):
 
         Parameters
         ----------
-        fh : guaranteed to be ForecastingHorizon or None, optional (default=None)
+        fh : guaranteed to be ForecastingHorizon or None, default=None
             The forecasting horizon with the steps ahead to to predict.
             If not passed in _fit, guaranteed to be passed here
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
 
         Returns
@@ -425,9 +425,9 @@ class MockForecaster(BaseForecaster):
             if self.get_tag("y_input_type")=="multivariate":
                 guaranteed to have 2 or more columns
             if self.get_tag("y_input_type")=="both": no restrictions apply
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
-        update_params : bool, optional (default=True)
+        update_params : bool, default=True
             whether model parameters should be updated
 
         Returns
@@ -453,7 +453,7 @@ class MockForecaster(BaseForecaster):
         ----------
         fh : int, list, np.array or ForecastingHorizon
             Forecasting horizon
-        X : pd.DataFrame, optional (default=None)
+        X : pd.DataFrame, default=None
             Exogenous time series
         alpha : list of float (guaranteed not None and floats in [0,1] interval)
             A list of probabilities at which quantile forecasts are computed.
diff --git a/aeon/transformations/series/tests/test_acf.py b/aeon/transformations/series/tests/test_acf.py
new file mode 100644
index 00000000..40214275
--- /dev/null
+++ b/aeon/transformations/series/tests/test_acf.py
@@ -0,0 +1,73 @@
+"""Test ACF series transformer."""
+
+import numpy as np
+import pytest
+from numpy.testing import assert_array_almost_equal
+
+from aeon.transformations.series._acf import AutoCorrelationTransformer
+
+
+def test_acf():
+    """Test ACF series transformer returned size."""
+    n_lags = 3
+    acf = AutoCorrelationTransformer(n_lags=n_lags)
+    # Output format, should return an array length n_timepoints-lags for univariate
+    # and multivariate time series
+    x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    if len(x) - n_lags < 3:
+        with pytest.raises(ValueError, match="The number of lags is too large"):
+            y = acf.fit_transform(x)
+    y = acf.fit_transform(x)
+    assert y.shape == (1, n_lags)
+    y = acf.fit_transform(x, axis=0)
+    assert y.shape == (n_lags, 1)
+    # Test with axis = 0
+
+
+TEST_DATA = [
+    np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+    np.array([-4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5]),
+    np.array([1.0, 5.0, 3.0, 4.0, 2.0, 1.0, 3.0, 4.0, 5.0, 2.0]),
+]
+EXPECTED_RESULTS = [
+    np.array([1.0, 1.0, 1.0, 1.0]),
+    np.array([1.0, 1.0, 1.0, 1.0]),
+    np.array([-0.18797908, -0.22454436, -0.70898489, -0.11094004]),
+]
+
+
+def test_acf_against_expected():
+    """Test ACF series transformer against expected results."""
+    acf = AutoCorrelationTransformer(n_lags=4)
+    for i in range(len(TEST_DATA)):
+        xt = acf.fit_transform(TEST_DATA[i])
+        xt = xt.squeeze()
+        assert_array_almost_equal(xt, EXPECTED_RESULTS[i], decimal=5)
+
+
+NORMED_TEST_DATA = [
+    np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+    np.array([-4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5]),
+    np.array([1.0, 5.0, 3.0, 4.0, 2.0, 1.0, 3.0, 4.0, 5.0, 2.0]),
+]
+NORMED_EXPECTED_RESULTS = [
+    np.array([1.0, 1.0, 1.0, 1.0]),
+    np.array([1.0, 1.0, 1.0, 1.0]),
+    np.array([-0.18797908, -0.22454436, -0.70898489, -0.11094004]),
+]
+
+
+def test_multivariate():
+    """Test multiple univariate calls the same as a multivariate one."""
+    x1 = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    x2 = np.array([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
+    x3 = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
+    n_lags = 3
+    acf = AutoCorrelationTransformer(n_lags=n_lags)
+    y1 = acf.fit_transform(x1)
+    y2 = acf.fit_transform(x2)
+    y3 = acf.fit_transform(x3)
+    c2 = np.vstack([y1, y2, y3])
+    combined = np.vstack([x1, x2, x3])
+    c3 = acf.fit_transform(combined)
+    assert np.allclose(c2, c3)
diff --git a/aeon/visualisation/series/tests/test_series_plotting.py b/aeon/visualisation/series/tests/test_series_plotting.py
index 221ec64d..6d5f8f28 100644
--- a/aeon/visualisation/series/tests/test_series_plotting.py
+++ b/aeon/visualisation/series/tests/test_series_plotting.py
@@ -107,19 +107,15 @@ def test_plot_series_multiple_series():
     reason="skip test if required soft dependency not available",
 )
 @pytest.mark.parametrize("series_to_plot", invalid_input_types)
-def test_plot_series_invalid_input_type_raises_error(series_to_plot, valid_data_types):
+def test_plot_series_invalid_input_type_raises_error(series_to_plot):
     """Tests whether plot_series raises error for invalid input types."""
     series_type = type(series_to_plot)
 
     if not isinstance(series_to_plot, (pd.Series, pd.DataFrame)):
-        match = (
-            rf"input must be a one of {valid_data_types}, but found type: {series_type}"
-        )
-        with pytest.raises((TypeError), match=re.escape(match)):
+        with pytest.raises((TypeError), match=f"found type: {series_type}"):
             _plot_series(series_to_plot)
     else:
-        match = "input must be univariate, but found 2 variables."
-        with pytest.raises(ValueError, match=match):
+        with pytest.raises(ValueError, match="input must be univariate"):
             _plot_series(series_to_plot)