Repository: aeon-toolkit/aeon (python)

Issue:
[ENH] `Differencer` series transformer
### Describe the feature or idea you want to propose

The previous transformer for finding series difference (i.e. first-order differences, second-order differences etc.) is deprecated as it was overcomplicated and used the old interface.

It would be good to have a simpler version in the new interface.

### Describe your proposed solution

A differencer extending `BaseSeriesTransformer`.

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

_No response_

Pull request:
[ENH] add a difference transformer to series transformations
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at our
contribution guide: https://www.aeon-toolkit.org/en/latest/contributing.html.

Feel free to delete sections of this template if they do not apply to your PR,
avoid submitting a blank template or empty sections.
If you are a new contributor, do not delete this template without a suitable
replacement or reason. If in doubt, ask for help. We're here to help!

Please be aware that we are a team of volunteers so patience is
necessary when waiting for a review or reply. There may not be a quick turnaround for
reviews during slow periods. While we value all contributions big or small, pull
requests which do not follow our guidelines may be closed.
-->

#### Reference Issues/PRs
fixes #1553 

<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->

#### What does this implement/fix? Explain your changes.
This implements a transformer that computes the n-order differences of a time series. 

The input time series is expected to have a NumPy inner-type, and the transformation is performed using numpy.diff(). 

To preserve the original shape of the series along the time axis, the first order element(s) of the output are filled with NaN.



<!--
A clear and concise description of what you have implemented.
-->

#### Does your contribution introduce a new dependency? If yes, which one?
No. It only depends on NumPy.

<!--
If your contribution does add a dependency, we may suggest adding it as an
optional/soft dependency to keep external dependencies of the core aeon package
to a minimum.
-->

#### Any other comments?
No.

<!--
Any other information that is important to this PR or helpful for reviewers.
-->

### PR checklist

<!--
Please go through the checklist below. Please feel free to remove points if they are
not applicable. To check a box, replace the space inside the square brackets with an
'x' i.e. [x].
-->

##### For all contributions
- [x] I've added myself to the [list of contributors](https://github.com/aeon-toolkit/aeon/blob/main/.all-contributorsrc). Alternatively, you can use the [@all-contributors](https://allcontributors.org/docs/en/bot/usage) bot to do this for you **after** the PR has been merged.
- [x] The PR title starts with either [ENH], [MNT], [DOC], [BUG], [REF], [DEP] or [GOV] indicating whether the PR topic is related to enhancement, maintenance, documentation, bugs, refactoring, deprecation or governance.

##### For new estimators and functions
- [ ] I've added the estimator/function to the online [API documentation](https://www.aeon-toolkit.org/en/latest/api_reference.html).
- [ ] (OPTIONAL) I've added myself as a `__maintainer__` at the top of relevant files and want to be contacted regarding its maintenance. Unmaintained files may be removed. This is for the full file, and you should not add yourself if you are just making minor changes or do not want to help maintain its contents.

##### For developers with write access
- [ ] (OPTIONAL) I've updated aeon's [CODEOWNERS](https://github.com/aeon-toolkit/aeon/blob/main/CODEOWNERS) to receive notifications about future changes to these files.


<!--
Thanks for contributing!
-->


Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index c7094563..ce303bd1 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -2710,6 +2710,16 @@
       "contributions": [
         "doc"
       ]
+    },
+    {
+      "login": "TinaJin0228",
+      "name": "Tina Jin",
+      "avatar_url": "https://avatars.githubusercontent.com/TinaJin0228",
+      "profile": "https://github.com/TinaJin0228",
+      "contributions": [
+        "code",
+        "doc"
+      ]
     }
   ],
   "commitType": "docs"
diff --git a/aeon/benchmarking/metrics/anomaly_detection/_continuous.py b/aeon/benchmarking/metrics/anomaly_detection/_continuous.py
index ce84ee33..ade8c102 100644
--- a/aeon/benchmarking/metrics/anomaly_detection/_continuous.py
+++ b/aeon/benchmarking/metrics/anomaly_detection/_continuous.py
@@ -18,12 +18,16 @@ import numpy as np
 from sklearn.metrics import auc, f1_score, precision_recall_curve
 from sklearn.metrics import roc_auc_score as _roc_auc_score
 
+from aeon.benchmarking.metrics.anomaly_detection._range_metrics import (
+    range_f_score,
+    range_precision,
+    range_recall,
+)
 from aeon.benchmarking.metrics.anomaly_detection._util import check_y
 from aeon.benchmarking.metrics.anomaly_detection.thresholding import (
     top_k_points_threshold,
     top_k_ranges_threshold,
 )
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 def roc_auc_score(y_true: np.ndarray, y_score: np.ndarray) -> float:
@@ -142,9 +146,8 @@ def f_score_at_k_ranges(
     finding a threshold on the scoring that produces at least k anomalous ranges. If `k`
     is not specified, the number of anomalies within the ground truth is used as `k`.
 
-    This implementation uses the community package
-    `prts <https://pypi.org/project/prts/>`_ as a soft-dependency to compute the
-    range-based F-score.
+    This implementation uses the aeon's implementation from `_range_metrics.py` to
+    compute the range-based F-score.
 
     Parameters
     ----------
@@ -167,12 +170,6 @@ def f_score_at_k_ranges(
     aeon.benchmarking.metrics.anomaly_detection.thresholding.top_k_ranges_threshold
         Function used to find the threshold.
     """
-    _check_soft_dependencies(
-        "prts", obj="f_score_at_k_ranges", suppress_import_stdout=True
-    )
-
-    from prts import ts_fscore
-
     y_true, y_pred = check_y(y_true, y_score, force_y_pred_continuous=True)
     if np.unique(y_score).shape[0] == 1:
         warnings.warn(
@@ -183,7 +180,8 @@ def f_score_at_k_ranges(
 
     threshold = top_k_ranges_threshold(y_true, y_score, k)
     y_pred = y_score >= threshold
-    return ts_fscore(y_true, y_pred, p_alpha=1, r_alpha=1, cardinality="reciprocal")
+    # Corrected p_alpha to 1 to match original prts behavior expectation
+    return range_f_score(y_true, y_pred, p_alpha=1, r_alpha=1, cardinality="reciprocal")
 
 
 def rp_rr_auc_score(
@@ -199,8 +197,6 @@ def rp_rr_auc_score(
 
     Computes the area under the precision recall curve when using the range-based
     precision and range-based recall metric introduced by Tatbul et al. at NeurIPS 2018
-    [1]_. This implementation uses the community package
-    `prts <https://pypi.org/project/prts/>`_ as a soft-dependency.
 
     This metric only considers the top-k predicted anomaly ranges within the scoring by
     finding a threshold on the scoring that produces at least k anomalous ranges. If `k`
@@ -242,12 +238,6 @@ def rp_rr_auc_score(
        1920–30. 2018.
        http://papers.nips.cc/paper/7462-precision-and-recall-for-time-series.pdf.
     """
-    _check_soft_dependencies(
-        "prts", obj="f_score_at_k_ranges", suppress_import_stdout=True
-    )
-
-    from prts import ts_precision, ts_recall
-
     y_true, y_pred = check_y(y_true, y_score, force_y_pred_continuous=True)
     if np.unique(y_score).shape[0] == 1:
         warnings.warn(
@@ -279,10 +269,10 @@ def rp_rr_auc_score(
     precisions = np.zeros_like(thresholds)
     for i, threshold in enumerate(thresholds):
         y_pred = (y_score >= threshold).astype(np.int64)
-        recalls[i] = ts_recall(
+        recalls[i] = range_recall(
             y_true, y_pred, alpha=r_alpha, cardinality=cardinality, bias=bias
         )
-        precisions[i] = ts_precision(
+        precisions[i] = range_precision(
             y_true, y_pred, alpha=p_alpha, cardinality=cardinality, bias=bias
         )
     # first sort by recall, then by precision to break ties
diff --git a/aeon/benchmarking/metrics/anomaly_detection/_range_metrics.py b/aeon/benchmarking/metrics/anomaly_detection/_range_metrics.py
index 7143100c..6308cd9b 100644
--- a/aeon/benchmarking/metrics/anomaly_detection/_range_metrics.py
+++ b/aeon/benchmarking/metrics/anomaly_detection/_range_metrics.py
@@ -30,10 +30,6 @@ def range_precision(
     predicted continuous anomaly range the overlap size, position, and cardinality is
     considered. For more details, please refer to the paper [1]_.
 
-    The `alpha` parameter for the existence reward was removed. Because precision
-    emphasizes prediction quality, there is no need for an existence reward and this
-    value should always be set to 0.
-
     Parameters
     ----------
     y_true : np.ndarray
@@ -41,7 +37,7 @@ def range_precision(
     y_pred : np.ndarray
         Anomaly scores for each point of the time series of shape (n_instances,).
     alpha : float
-        DEPRECATED. Default is 0 = no existence reward.
+        Default is 0 = no existence reward.
     cardinality : {'reciprocal', 'one', 'udf_gamma'}
         Cardinality type.
     bias : {'flat', 'front', 'middle', 'back'}
@@ -60,13 +56,6 @@ def range_precision(
        1920–30. 2018.
        http://papers.nips.cc/paper/7462-precision-and-recall-for-time-series.pdf.
     """
-    if alpha != 0:
-        warnings.warn(
-            "The alpha parameter should not be used in range precision. This "
-            "parameter is removed in 1.3.0.",
-            stacklevel=2,
-            category=FutureWarning,
-        )
     y_true, y_pred = check_y(y_true, y_pred, force_y_pred_continuous=False)
     if np.unique(y_pred).shape[0] == 1:
         warnings.warn(
@@ -160,9 +149,6 @@ def range_f_score(
     its optimal value at 1 and its worst value at 0. This implementation uses the
     range-based precision and range-based recall as basis.
 
-    The `p_alpha` parameter for the potential existance reward in the calculation of
-    range-based precision was removed. `p_alpha` should always be set to 0, anyway.
-
     Parameters
     ----------
     y_true : np.ndarray
@@ -173,7 +159,7 @@ def range_f_score(
         F-score beta determines the weight of recall in the combined score.
         beta < 1 lends more weight to precision, while beta > 1 favors recall.
     p_alpha : float
-        DEPRECATED. Default is 0 = no existence reward for precision.
+        Default is 0 = no existence reward for precision.
     r_alpha : float
         Weight of the existence reward. If 0: no existence reward, if 1: only
         existence reward.
@@ -197,14 +183,6 @@ def range_f_score(
        1920–30. 2018.
        http://papers.nips.cc/paper/7462-precision-and-recall-for-time-series.pdf.
     """
-    if p_alpha != 0:
-        warnings.warn(
-            "The p_alpha parameter should not be used. This parameter is removed "
-            "in 1.3.0.",
-            stacklevel=2,
-            category=FutureWarning,
-        )
-
     y_true, y_pred = check_y(y_true, y_pred, force_y_pred_continuous=False)
     if np.unique(y_pred).shape[0] == 1:
         warnings.warn(
@@ -216,7 +194,9 @@ def range_f_score(
     y_pred_ranges = _binary_to_ranges(y_pred)
     y_true_ranges = _binary_to_ranges(y_true)
 
-    precision = _ts_precision(y_pred_ranges, y_true_ranges, cardinality, p_bias)
+    precision = _ts_precision(
+        y_pred_ranges, y_true_ranges, cardinality, p_bias, p_alpha
+    )
     recall = _ts_recall(y_pred_ranges, y_true_ranges, cardinality, r_bias, r_alpha)
 
     if precision + recall > 0:
diff --git a/aeon/benchmarking/metrics/anomaly_detection/_range_ts_metrics.py b/aeon/benchmarking/metrics/anomaly_detection/_range_ts_metrics.py
index 12ad2c0e..1791d367 100644
--- a/aeon/benchmarking/metrics/anomaly_detection/_range_ts_metrics.py
+++ b/aeon/benchmarking/metrics/anomaly_detection/_range_ts_metrics.py
@@ -233,7 +233,7 @@ def _binary_to_ranges(binary_sequence):
     "Please use range_precision instead.",
     category=FutureWarning,
 )
-def ts_precision(y_pred, y_real, gamma="one", bias_type="flat"):
+def ts_precision(y_pred, y_real, gamma="one", bias_type="flat", alpha=0.0):
     """
     Calculate Precision for time series anomaly detection.
 
@@ -317,10 +317,12 @@ def ts_precision(y_pred, y_real, gamma="one", bias_type="flat"):
     flat_y_pred = _flatten_ranges(y_pred_ranges)
     flat_y_real = _flatten_ranges(y_real_ranges)
 
-    return _ts_precision(flat_y_pred, flat_y_real, gamma, bias_type)
+    return _ts_precision(flat_y_pred, flat_y_real, gamma, bias_type, alpha=alpha)
 
 
-def _ts_precision(y_pred_ranges, y_real_ranges, gamma="one", bias_type="flat"):
+def _ts_precision(
+    y_pred_ranges, y_real_ranges, gamma="one", bias_type="flat", alpha=0.0
+):
     """
     Implement range-based precision for time series anomaly detection.
 
@@ -371,13 +373,20 @@ def _ts_precision(y_pred_ranges, y_real_ranges, gamma="one", bias_type="flat"):
         overlap_reward = _calculate_overlap_reward_precision(
             pred_range, overlap_set, bias_type
         )
+
+        existence_reward = 1.0 if overlap_set else 0.0
         gamma_value = _gamma_select(cardinality, gamma)
-        total_overlap_reward += gamma_value * overlap_reward
+
+        score_i = alpha * existence_reward + (1 - alpha) * (
+            gamma_value * overlap_reward
+        )
+
+        total_overlap_reward += score_i
+
         total_cardinality += 1
 
-    precision = (
-        total_overlap_reward / total_cardinality if total_cardinality > 0 else 0.0
-    )
+    precision = total_overlap_reward / total_cardinality if total_cardinality else 0.0
+
     return precision
 
 
@@ -609,7 +618,7 @@ def ts_fscore(
        Processing Systems (NeurIPS 2018), Montréal, Canada.
        http://papers.nips.cc/paper/7462-precision-and-recall-for-time-series.pdf
     """
-    precision = ts_precision(y_pred, y_real, gamma, p_bias)
+    precision = ts_precision(y_pred, y_real, gamma, p_bias, alpha=p_alpha)
     recall = ts_recall(y_pred, y_real, gamma, r_bias, r_alpha)
 
     if precision + recall > 0:
diff --git a/aeon/forecasting/__init__.py b/aeon/forecasting/__init__.py
index de203a0b..7a331f69 100644
--- a/aeon/forecasting/__init__.py
+++ b/aeon/forecasting/__init__.py
@@ -1,13 +1,13 @@
 """Forecasters."""
 
 __all__ = [
-    "DummyForecaster",
+    "NaiveForecaster",
     "BaseForecaster",
     "RegressionForecaster",
     "ETSForecaster",
 ]
 
-from aeon.forecasting._dummy import DummyForecaster
 from aeon.forecasting._ets import ETSForecaster
+from aeon.forecasting._naive import NaiveForecaster
 from aeon.forecasting._regression import RegressionForecaster
 from aeon.forecasting.base import BaseForecaster
diff --git a/aeon/forecasting/_dummy.py b/aeon/forecasting/_naive.py
similarity index 60%
rename from aeon/forecasting/_dummy.py
rename to aeon/forecasting/_naive.py
index 7525b6cc..8b7deede 100644
--- a/aeon/forecasting/_dummy.py
+++ b/aeon/forecasting/_naive.py
@@ -1,24 +1,24 @@
-"""DummyForecaster always predicts the last value seen in training."""
+"""Naive Forecaster."""
 
 from aeon.forecasting.base import BaseForecaster
 
 
-class DummyForecaster(BaseForecaster):
-    """Dummy forecaster always predicts the last value seen in training."""
+class NaiveForecaster(BaseForecaster):
+    """Naive forecaster that always predicts the last value seen in training."""
 
     def __init__(self):
-        """Initialize DummyForecaster."""
+        """Initialize NaiveForecaster."""
         self.last_value_ = None
         super().__init__(horizon=1, axis=1)
 
     def _fit(self, y, exog=None):
-        """Fit dummy forecaster."""
+        """Fit Naive forecaster."""
         y = y.squeeze()
         self.last_value_ = y[-1]
         return self
 
     def _predict(self, y=None, exog=None):
-        """Predict using dummy forecaster."""
+        """Predict using Naive forecaster."""
         return self.last_value_
 
     def _forecast(self, y, exog=None):
diff --git a/aeon/forecasting/base.py b/aeon/forecasting/base.py
index cf2db8d8..23950fa0 100644
--- a/aeon/forecasting/base.py
+++ b/aeon/forecasting/base.py
@@ -4,6 +4,9 @@ A simplified first base class for forecasting models.
 
 """
 
+__maintainer__ = ["TonyBagnall"]
+__all__ = ["BaseForecaster"]
+
 from abc import abstractmethod
 
 import numpy as np
diff --git a/aeon/segmentation/_ggs.py b/aeon/segmentation/_ggs.py
index 0a1bb615..85b08d27 100644
--- a/aeon/segmentation/_ggs.py
+++ b/aeon/segmentation/_ggs.py
@@ -503,12 +503,10 @@ class GreedyGaussianSegmenter(BaseSegmenter):
             dimension of X. The numerical values represent distinct segments
             labels for each of the data points.
         """
-        self.change_points_ = self.ggs.find_change_points(X)
+        change_points_ = self.ggs.find_change_points(X)
 
         labels = np.zeros(X.shape[0], dtype=np.int32)
-        for i, (start, stop) in enumerate(
-            zip(self.change_points_[:-1], self.change_points_[1:])
-        ):
+        for i, (start, stop) in enumerate(zip(change_points_[:-1], change_points_[1:])):
             labels[start:stop] = i
         return labels
 
diff --git a/aeon/segmentation/_igts.py b/aeon/segmentation/_igts.py
index 1bd673b3..dd9aa399 100644
--- a/aeon/segmentation/_igts.py
+++ b/aeon/segmentation/_igts.py
@@ -106,7 +106,7 @@ class _IGTS:
     """
     Information Gain based Temporal Segmentation (GTS).
 
-    GTS is a n unsupervised method for segmenting multivariate time series
+    GTS is an unsupervised method for segmenting multivariate time series
     into non-overlapping segments by locating change points that for which
     the information gain is maximized.
 
@@ -127,18 +127,13 @@ class _IGTS:
 
     Parameters
     ----------
-    k_max: int, default=10
+    k_max : int, default=10
         Maximum number of change points to find. The number of segments is thus k+1.
-    step: : int, default=5
+    step : int, default=5
         Step size, or stride for selecting candidate locations of change points.
         Fox example a `step=5` would produce candidates [0, 5, 10, ...]. Has the same
         meaning as `step` in `range` function.
 
-    Attributes
-    ----------
-    intermediate_results_: list of `ChangePointResult`
-        Intermediate segmentation results for each k value, where k=1, 2, ..., k_max
-
     Notes
     -----
     Based on the work from [1]_.
@@ -366,9 +361,9 @@ class InformationGainSegmenter(BaseSegmenter):
             The numerical values represent distinct segment labels for each of the
             data points.
         """
-        self.change_points_ = self._igts.find_change_points(X)
-        self.intermediate_results_ = self._igts.intermediate_results_
-        return self.to_clusters(self.change_points_[1:-1], X.shape[0])
+        change_points_ = self._igts.find_change_points(X)
+        # self.intermediate_results_ = self._igts.intermediate_results_
+        return self.to_clusters(change_points_[1:-1], X.shape[0])
 
     def __repr__(self) -> str:
         """Return a string representation of the estimator."""
diff --git a/aeon/transformations/series/__init__.py b/aeon/transformations/series/__init__.py
index 8b71ba9f..01bb5faa 100644
--- a/aeon/transformations/series/__init__.py
+++ b/aeon/transformations/series/__init__.py
@@ -21,6 +21,7 @@ __all__ = [
     "SIVSeriesTransformer",
     "PCASeriesTransformer",
     "WarpingSeriesTransformer",
+    "DifferenceTransformer",
 ]
 
 from aeon.transformations.series._acf import (
@@ -32,6 +33,7 @@ from aeon.transformations.series._bkfilter import BKFilter
 from aeon.transformations.series._boxcox import BoxCoxTransformer
 from aeon.transformations.series._clasp import ClaSPTransformer
 from aeon.transformations.series._dft import DFTSeriesTransformer
+from aeon.transformations.series._diff import DifferenceTransformer
 from aeon.transformations.series._dobin import Dobin
 from aeon.transformations.series._exp_smoothing import ExpSmoothingSeriesTransformer
 from aeon.transformations.series._gauss import GaussSeriesTransformer
diff --git a/aeon/transformations/series/_diff.py b/aeon/transformations/series/_diff.py
new file mode 100644
index 00000000..221987b7
--- /dev/null
+++ b/aeon/transformations/series/_diff.py
@@ -0,0 +1,92 @@
+import numpy as np
+
+from aeon.transformations.series.base import BaseSeriesTransformer
+
+__maintainer__ = ["TinaJin0228"]
+__all__ = ["DifferenceTransformer"]
+
+
+class DifferenceTransformer(BaseSeriesTransformer):
+    """
+    Calculates the n-th order difference of a time series.
+
+    Transforms a time series X into a series Y representing the difference
+    calculated `order` times.
+
+    The time series are supposed to be all in rows,
+    with shape (n_channels, n_timepoints)
+
+    - Order 1: Y[t] = X[t] - X[t-1]
+    - Order 2: Y[t] = (X[t] - X[t-1]) - (X[t-1] - X[t-2]) = X[t] - 2*X[t-1] + X[t-2]
+    - ... and so on.
+
+    The transformed series will be shorter than the input series by `order`
+    elements along the time axis.
+
+    Parameters
+    ----------
+    order : int, default=1
+        The order of differencing. Must be a positive integer.
+
+    Notes
+    -----
+    This transformer assumes the input series does not contain NaN values where
+    the difference needs to be computed.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from aeon.transformations.series._diff import DifferenceTransformer
+    >>> X1 = np.array([[1, 3, 2, 5, 4, 7, 6, 9, 8, 10]]) # Shape (1, 10)
+    >>> dt = DifferenceTransformer()
+    >>> Xt1 = dt.fit_transform(X1)
+    >>> print(Xt1) # Shape (1, 9)
+    [[ 2 -1  3 -1  3 -1  3 -1  2]]
+
+    >>> X2 = np.array([[1, 3, 2, 5, 4, 7, 6, 9, 8, 10]]) # Shape (1, 10)
+    >>> dt2 = DifferenceTransformer(order=2)
+    >>> Xt2 = dt2.fit_transform(X2)
+    >>> print(Xt2) # Shape (1, 8)
+    [[-3  4 -4  4 -4  4 -4  3]]
+
+    >>> X3 = np.array([[1, 2, 3, 4, 5], [5, 4, 3, 2, 1]]) # Shape (2, 5)
+    >>> dt = DifferenceTransformer()
+    >>> Xt3 = dt.fit_transform(X3)
+    >>> print(Xt3) # Shape (2, 4)
+    [[ 1  1  1  1]
+     [-1 -1 -1 -1]]
+    """
+
+    _tags = {
+        "capability:multivariate": True,
+        "X_inner_type": "np.ndarray",
+        "fit_is_empty": True,
+    }
+
+    def __init__(self, order=1):
+        self.order = order
+        super().__init__(axis=1)
+
+    def _transform(self, X, y=None):
+        """
+        Perform the n-th order differencing transformation.
+
+        Parameters
+        ----------
+        X : Time series to transform. With shape (n_channels, n_timepoints).
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        Xt : np.ndarray
+        """
+        if not isinstance(self.order, int) or self.order < 1:
+            raise ValueError(
+                f"`order` must be a positive integer, but got {self.order}"
+            )
+
+        diff_X = np.diff(X, n=self.order, axis=1)
+
+        Xt = diff_X
+
+        return Xt
diff --git a/examples/forecasting/forecasting.ipynb b/examples/forecasting/forecasting.ipynb
index 0e0b4ac7..5d98504f 100644
--- a/examples/forecasting/forecasting.ipynb
+++ b/examples/forecasting/forecasting.ipynb
@@ -193,9 +193,9 @@
    "cell_type": "code",
    "source": [
     "# Fit then predict\n",
-    "from aeon.forecasting import DummyForecaster\n",
+    "from aeon.forecasting import NaiveForecaster\n",
     "\n",
-    "d = DummyForecaster()\n",
+    "d = NaiveForecaster()\n",
     "print(d.get_tag(\"y_inner_type\"))\n",
     "d.fit(y)\n",
     "p = d.predict()\n",


Test patch:
diff --git a/aeon/benchmarking/metrics/anomaly_detection/tests/test_ad_metrics.py b/aeon/benchmarking/metrics/anomaly_detection/tests/test_ad_metrics.py
index fd14aa39..e2e5364e 100644
--- a/aeon/benchmarking/metrics/anomaly_detection/tests/test_ad_metrics.py
+++ b/aeon/benchmarking/metrics/anomaly_detection/tests/test_ad_metrics.py
@@ -20,9 +20,8 @@ from aeon.benchmarking.metrics.anomaly_detection._range_metrics import (
     range_recall,
 )
 from aeon.testing.data_generation import make_example_1d_numpy
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
-pr_metrics = [pr_auc_score]
+pr_metrics = [pr_auc_score, rp_rr_auc_score]
 range_metrics = [
     range_roc_auc_score,
     range_pr_auc_score,
@@ -32,6 +31,7 @@ range_metrics = [
 other_metrics = [
     roc_auc_score,
     f_score_at_k_points,
+    f_score_at_k_ranges,
 ]
 continuous_metrics = [*pr_metrics, *other_metrics, *range_metrics]
 binary_metrics = [
@@ -40,21 +40,6 @@ binary_metrics = [
     range_recall,
 ]
 
-if _check_soft_dependencies("prts", severity="none"):
-    pr_metrics.append(rp_rr_auc_score)
-    other_metrics.extend(
-        [
-            f_score_at_k_ranges,
-            rp_rr_auc_score,
-        ]
-    )
-    continuous_metrics.extend(
-        [
-            rp_rr_auc_score,
-            f_score_at_k_ranges,
-        ]
-    )
-
 metrics = [*pr_metrics, *range_metrics, *other_metrics]
 
 
@@ -148,10 +133,6 @@ def test_edge_cases_pr_metrics(metric):
     assert score <= 0.2, f"{metric.__name__}(y_true, y_inverted)={score} is not <= 0.2"
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_range_based_f1():
     """Test range-based F1 score."""
     y_pred = np.array([0, 1, 1, 0])
@@ -160,10 +141,6 @@ def test_range_based_f1():
     np.testing.assert_almost_equal(result, 0.66666, decimal=4)
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_range_based_precision():
     """Test range-based precision."""
     y_pred = np.array([0, 1, 1, 0])
@@ -172,10 +149,6 @@ def test_range_based_precision():
     assert result == 0.5
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_range_based_recall():
     """Test range-based recall."""
     y_pred = np.array([0, 1, 1, 0])
@@ -184,10 +157,6 @@ def test_range_based_recall():
     assert result == 1
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_rf1_value_error():
     """Test range-based F1 score raises ValueError on binary predictions."""
     y_pred = np.array([0, 0.2, 0.7, 0])
@@ -211,10 +180,6 @@ def test_pr_curve_auc():
 #     np.testing.assert_almost_equal(result, 0.8333, decimal=4)
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_range_based_p_range_based_r_curve_auc():
     """Test range-based precision-recall curve AUC."""
     y_pred = np.array([0, 0.1, 1.0, 0.5, 0.1, 0])
@@ -223,10 +188,6 @@ def test_range_based_p_range_based_r_curve_auc():
     np.testing.assert_almost_equal(result, 0.9792, decimal=4)
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_range_based_p_range_based_r_auc_perfect_hit():
     """Test range-based precision-recall curve AUC with perfect hit."""
     y_pred = np.array([0, 0, 0.5, 0.5, 0, 0])
@@ -235,10 +196,6 @@ def test_range_based_p_range_based_r_auc_perfect_hit():
     np.testing.assert_almost_equal(result, 1.0000, decimal=4)
 
 
-@pytest.mark.skipif(
-    not _check_soft_dependencies("prts", severity="none"),
-    reason="required soft dependency prts not available",
-)
 def test_f_score_at_k_ranges():
     """Test range-based F1 score at k ranges."""
     y_pred = np.array([0.4, 0.1, 1.0, 0.5, 0.1, 0, 0.4, 0.5])
diff --git a/aeon/forecasting/tests/test_base.py b/aeon/forecasting/tests/test_base.py
index 22c15901..bd4e26a6 100644
--- a/aeon/forecasting/tests/test_base.py
+++ b/aeon/forecasting/tests/test_base.py
@@ -4,12 +4,12 @@ import numpy as np
 import pandas as pd
 import pytest
 
-from aeon.forecasting import DummyForecaster
+from aeon.forecasting import NaiveForecaster
 
 
 def test_base_forecaster():
     """Test base forecaster functionality."""
-    f = DummyForecaster()
+    f = NaiveForecaster()
     y = np.random.rand(50)
     f.fit(y)
     p1 = f.predict()
@@ -26,7 +26,7 @@ def test_base_forecaster():
 
 def test_convert_y():
     """Test y conversion in forecasting base."""
-    f = DummyForecaster()
+    f = NaiveForecaster()
     y = np.random.rand(50)
     with pytest.raises(ValueError, match="Input axis should be 0 or 1"):
         f._convert_y(y, axis=2)
diff --git a/aeon/segmentation/tests/test_igts.py b/aeon/segmentation/tests/test_igts.py
index 7103dca3..0aaa0f2d 100644
--- a/aeon/segmentation/tests/test_igts.py
+++ b/aeon/segmentation/tests/test_igts.py
@@ -51,5 +51,3 @@ def test_InformationGainSegmenter(multivariate_mean_shift):
             [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3], dtype=int
         ),
     )
-    assert igts.change_points_ == [0, 5, 10, 15, 20]
-    assert len(igts.intermediate_results_) == 3
diff --git a/aeon/testing/testing_config.py b/aeon/testing/testing_config.py
index 3e47dcf0..fe7f8d00 100644
--- a/aeon/testing/testing_config.py
+++ b/aeon/testing/testing_config.py
@@ -55,8 +55,6 @@ EXCLUDED_TESTS = {
     "LeftSTAMPi": ["check_anomaly_detector_output"],
     # missed in legacy testing, changes state in predict/transform
     "FLUSSSegmenter": ["check_non_state_changing_method"],
-    "InformationGainSegmenter": ["check_non_state_changing_method"],
-    "GreedyGaussianSegmenter": ["check_non_state_changing_method"],
     "ClaSPSegmenter": ["check_non_state_changing_method"],
     "HMMSegmenter": ["check_non_state_changing_method"],
     "RSTSF": ["check_non_state_changing_method"],
diff --git a/aeon/transformations/series/tests/test_diff.py b/aeon/transformations/series/tests/test_diff.py
new file mode 100644
index 00000000..9f54fccf
--- /dev/null
+++ b/aeon/transformations/series/tests/test_diff.py
@@ -0,0 +1,37 @@
+"""Tests for Difference transformation."""
+
+import numpy as np
+
+from aeon.transformations.series._diff import DifferenceTransformer
+
+
+def test_diff():
+    """Tests basic first and second order differencing."""
+    X = np.array([[1.0, 4.0, 9.0, 16.0, 25.0, 36.0]])
+
+    dt1 = DifferenceTransformer(order=1)
+    Xt1 = dt1.fit_transform(X)
+    expected1 = np.array([[3.0, 5.0, 7.0, 9.0, 11.0]])
+
+    np.testing.assert_allclose(
+        Xt1, expected1, equal_nan=True, err_msg="Value mismatch for order 1"
+    )
+
+    dt2 = DifferenceTransformer(order=2)
+    Xt2 = dt2.fit_transform(X)
+    expected2 = np.array([[2.0, 2.0, 2.0, 2.0]])
+
+    np.testing.assert_allclose(
+        Xt2, expected2, equal_nan=True, err_msg="Value mismatch for order 2"
+    )
+
+    Y = np.array([[1, 2, 3, 4], [5, 3, 1, 8]])
+
+    Yt1 = dt1.fit_transform(Y)
+    expected3 = np.array([[1, 1, 1], [-2, -2, 7]])
+    np.testing.assert_allclose(
+        Yt1,
+        expected3,
+        equal_nan=True,
+        err_msg="Value mismatch for order 1,multivariate",
+    )