Repository: sktime/sktime
Pull request #7474 (2024-12-02 18:07:35)

[DOC] improve formatting of forecasting metrics
Improves formatting of forecasting metric docstrings concerning the `multilevel` and `multioutput` parameters.


Patch:
diff --git a/examples/07_detection_anomaly_changepoints.ipynb b/examples/07_detection_anomaly_changepoints.ipynb
index 127d3fd5d..5fb2bf048 100644
--- a/examples/07_detection_anomaly_changepoints.ipynb
+++ b/examples/07_detection_anomaly_changepoints.ipynb
@@ -870,7 +870,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": null,
    "id": "6fcf2dbd-0afb-4d11-b238-84294e811dba",
    "metadata": {},
    "outputs": [
@@ -905,7 +905,7 @@
     "    linestyle=\"--\",\n",
     ")\n",
     "\n",
-    "for i, cp in enumerate(predicted_change_points):\n",
+    "for i, cp in enumerate(predicted_change_points.values.flatten()):\n",
     "    label = \"Predicted Change Points\" if i == 0 else None\n",
     "    ax.axvline(cp, color=\"tab:green\", linestyle=\"--\", label=label)\n",
     "\n",
diff --git a/extension_templates/detection.py b/extension_templates/detection.py
index fd53118e0..ae97340bd 100644
--- a/extension_templates/detection.py
+++ b/extension_templates/detection.py
@@ -192,8 +192,29 @@ class MyDetector(BaseDetector):
         ----------
         X : pd.DataFrame
             Training data to fit model to time series.
-        y : pd.Series, optional
-            Ground truth labels for training, if detector is supervised.
+
+        y : pd.DataFrame with RangeIndex
+            Known events for training, in ``X``, if detector is supervised.
+
+            Each row ``y`` is a known event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
+
+            * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
 
         Returns
         -------
@@ -221,13 +242,28 @@ class MyDetector(BaseDetector):
 
         Returns
         -------
-        y : pd.Series with RangeIndex
-            Labels for sequence ``X``, in sparse format.
-            Values are ``iloc`` references to indices of ``X``.
+        y : pd.DataFrame with RangeIndex
+            Detected or predicted events.
+
+            Each row ``y`` is a detected or predicted event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
 
             * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
-              the values are integer indices of the changepoints/anomalies.
-            * If ``task`` is "segmentation", the values are ``pd.Interval`` objects.
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
         """
 
         # implement here
diff --git a/sktime/detection/adapters/_pyod.py b/sktime/detection/adapters/_pyod.py
index 4db8de43a..387b211e4 100644
--- a/sktime/detection/adapters/_pyod.py
+++ b/sktime/detection/adapters/_pyod.py
@@ -102,7 +102,7 @@ class PyODDetector(BaseDetector):
             Y_val_np = Y_np
 
         Y_loc = np.where(Y_np)
-        Y = pd.Series(Y_val_np[Y_loc], index=X.index[Y_loc])
+        Y = pd.Series(Y_val_np[Y_loc])
 
         return Y
 
diff --git a/sktime/detection/base/_base.py b/sktime/detection/base/_base.py
index 5ceed0c54..d4cbe125c 100644
--- a/sktime/detection/base/_base.py
+++ b/sktime/detection/base/_base.py
@@ -140,8 +140,29 @@ class BaseDetector(BaseEstimator):
         ----------
         X : pd.DataFrame, pd.Series or np.ndarray
             Training data to fit model to (time series).
-        y : pd.Series, optional
-            Ground truth labels for training if detector is supervised.
+
+        y : pd.DataFrame with RangeIndex, optional.
+            Known events for traininmg, in ``X``, if detector is supervised.
+
+            Each row ``y`` is a known event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
+
+            * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
 
         Returns
         -------
@@ -205,6 +226,14 @@ class BaseDetector(BaseEstimator):
     def predict(self, X):
         """Create labels on test/deployment data.
 
+        This method returns a list-like type specific to the detection task,
+        e.g., segments for segmentation, anomalies for anomaly detection.
+
+        The encoding varies by task and learning_type (tags), see below.
+
+        For returns that are type consistent across tasks, see
+        ``predict_points`` and ``predict_segments``.
+
         Parameters
         ----------
         X : pd.DataFrame, pd.Series or np.ndarray
@@ -212,14 +241,28 @@ class BaseDetector(BaseEstimator):
 
         Returns
         -------
-        y : pd.Series with RangeIndex
-            Labels for sequence ``X``, in sparse format.
-            Values are ``iloc`` references to indices of ``X``.
-            The index is always a ``RangeIndex``.
+        y : pd.DataFrame with RangeIndex
+            Detected or predicted events.
+
+            Each row ``y`` is a detected or predicted event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
 
             * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
-              the values are integer indices of the changepoints/anomalies.
-            * If ``task`` is "segmentation", the values are ``pd.Interval`` objects.
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
         """
         self.check_is_fitted()
 
@@ -229,6 +272,9 @@ class BaseDetector(BaseEstimator):
 
         y = self._predict(X=X_inner)
 
+        # deal with legacy return format with intervals in index
+        y = self._coerce_to_df(y, columns=["ilocs"])
+
         return y
 
     def transform(self, X):
@@ -257,9 +303,10 @@ class BaseDetector(BaseEstimator):
             * If ``task`` is "segmentation", the values are integer labels of the
               segments. Possible labels are integers starting from 0.
         """
-        y = self.predict(X)
-        y_dense = self.sparse_to_dense(y, pd.RangeIndex(len(X)))
-        return pd.DataFrame(y_dense)
+        y_sparse = self.predict(X)
+        y_dense = self.sparse_to_dense(y_sparse, pd.RangeIndex(len(X)))
+        y_dense = self._coerce_to_df(y_dense, columns=["labels"])
+        return y_dense
 
     def transform_scores(self, X):
         """Return scores for predicted labels on test/deployment data.
@@ -361,17 +408,53 @@ class BaseDetector(BaseEstimator):
         ----------
         X : pd.DataFrame, pd.Series or np.ndarray
             Training data to update model with, time series.
-        y : pd.Series, optional
-            Ground truth labels for training if detector is supervised.
+        y : pd.DataFrame with RangeIndex, optional.
+            Known events for training, in ``X``, if detector is supervised.
+
+            Each row ``y`` is a known event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
+
+            * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
 
         Returns
         -------
-        Y : pd.Series
-            Labels for sequence X exact format depends on detection type.
+        y : pd.DataFrame with RangeIndex
+            Detected or predicted events.
 
-        Notes
-        -----
-        Updates fitted model that updates attributes ending in "_".
+            Each row ``y`` is a detected or predicted event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
+
+            * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
         """
         X_inner = self._check_X(X)
 
@@ -391,18 +474,54 @@ class BaseDetector(BaseEstimator):
         ----------
         X : pd.DataFrame, pd.Series or np.ndarray
             Data to be transformed
-        y : pd.Series or np.ndarray, optional (default=None)
-            Target values of data to be predicted.
+
+        y : pd.DataFrame with RangeIndex, optional.
+            Known events for training, in ``X``, if detector is supervised.
+
+            Each row ``y`` is a known event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
+
+            * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
 
         Returns
         -------
-        y : pd.Series with RangeIndex
-            Labels for sequence ``X``, in sparse format.
-            Values are ``iloc`` references to indices of ``X``.
+        y : pd.DataFrame with RangeIndex
+            Detected or predicted events.
+
+            Each row ``y`` is a detected or predicted event.
+            Can have the following columns:
+
+            * ``"ilocs"`` - always. Values encode where/when the event takes place,
+              via ``iloc`` references to indices of ``X``,
+              or ranges ot indices of ``X``, as below.
+            * ``"label"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation with labels, or segment clustering.
+
+            The meaning of entries in the ``"ilocs"`` column and ``"labels"``
+            column describe the event in a given row as follows:
 
             * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
-              the values are integer indices of the changepoints/anomalies.
-            * If ``task`` is "segmentation", the values are ``pd.Interval`` objects.
+              ``"ilocs"`` contains the iloc index at which the event takes place.
+            * If ``task`` is ``"segmentation"``, ``"ilocs"`` contains left-closed
+              intervals of iloc based segments, interpreted as the range
+              of indices over which the event takes place.
+
+            Labels (if present) in the ``"labels"`` column indicate the type of event.
         """
         # Non-optimized default implementation; override when a better
         # method is possible for a given algorithm.
@@ -419,7 +538,7 @@ class BaseDetector(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, optional (default=None)
             Target values of data to be predicted.
 
         Returns
@@ -440,8 +559,41 @@ class BaseDetector(BaseEstimator):
             * If ``task`` is "segmentation", the values are integer labels of the
               segments. Possible labels are integers starting from 0.
         """
-        y = self.fit_predict(X, y=y, Y=Y)
-        return self.sparse_to_dense(y, index=X.index)
+        y_sparse = self.fit_predict(X, y=y, Y=Y)
+        y_dense = self.sparse_to_dense(y_sparse, index=X.index)
+        y_dense = self._coerce_to_df(y_dense, columns=["labels"])
+        return y_dense
+
+    def _coerce_to_df(self, y, columns=None):
+        """Coerce output to a DataFrame.
+
+        Also deals with the following downwards cases:
+
+        * IntervalIndex containing segments -> DataFrame with "ilocs" column
+        """
+        if not isinstance(y, (pd.Series, pd.DataFrame)):
+            y = pd.DataFrame(y, columns=columns, dtype="int64")
+        if isinstance(y.index, pd.IntervalIndex):
+            if isinstance(y, pd.Series):
+                y = pd.DataFrame(y.index, columns=columns)
+            elif isinstance(y, pd.DataFrame):
+                y_index = y.index
+                y_index = pd.DataFrame(y_index, columns=columns)
+                y = y.reset_index(drop=True)
+                y = pd.concat([y_index, y], axis=1)
+
+        if not isinstance(y, pd.DataFrame):
+            y = pd.DataFrame(y, columns=columns, dtype="int64")
+
+        return y
+
+    def _coerce_intervals_to_values(self, y):
+        if not isinstance(y, (pd.Series, pd.DataFrame)):
+            y = pd.Series(y, dtype="int64")
+        if isinstance(y.index, pd.IntervalIndex):
+            if isinstance(y, pd.Series):
+                y = pd.Series(y.index)
+        return y
 
     def _check_X(self, X):
         """Check input data.
@@ -586,6 +738,10 @@ class BaseDetector(BaseEstimator):
     def predict_segments(self, X):
         """Predict segments on test/deployment data.
 
+        The main difference to ``predict`` is that this method always returns
+        a ``pd.DataFrame`` with segments of interest, even if the task is not
+        segmentation.
+
         Parameters
         ----------
         X : pd.DataFrame
@@ -593,30 +749,44 @@ class BaseDetector(BaseEstimator):
 
         Returns
         -------
-        y : pd.Series with IntervalIndex
-            A series with an index of intervals. Each interval is the range of a
-            segment and the corresponding value is the label of the segment.
-            Values are ``iloc`` references to indices of ``X``.
+        y : pd.DataFrame with RangeIndex
+
+            ``pd.DataFrame`` with the following columns:
+
+            * ``"ilocs"`` - always. Values are left-closed intervals with
+              left/right values being ``iloc`` references to indices of ``X``,
+              signifying segments.
+            * ``"labels"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation, or segment clustering.
+
+            The meaning of segments in the ``"ilocs"`` column and ``"labels"``
+            column is as follows:
 
             * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
               the intervals are intervals between changepoints/anomalies, and
-              the labels are consecutive integers starting from 0.
-            * If ``task`` is "segmentation", the values are segmentation labels.
+              potential labels are consecutive integers starting from 0.
+            * If ``task`` is ``"segmentation"``, the values are segmentation labels.
         """
         self.check_is_fitted()
         X = check_series(X)
 
         task = self.get_tag("task")
         if task in ["anomaly_detection", "change_point_detection"]:
-            return self.change_points_to_segments(
-                self.predict_points(X), start=0, end=len(X)
-            )
+            y_pred_pts = self.predict_points(X)
+            y_pred = self.change_points_to_segments(y_pred_pts, start=0, end=len(X))
         elif task == "segmentation":
-            return self._predict_segments(X)
+            y_pred = self._predict_segments(X)
+
+        y_pred = self._coerce_to_df(y_pred, columns=["ilocs"])
+        return y_pred
 
     def predict_points(self, X):
         """Predict changepoints/anomalies on test/deployment data.
 
+        The main difference to ``predict`` is that this method always returns
+        a ``pd.DataFrame`` with points of interest, even if the task is not
+        anomaly or change point detection.
+
         Parameters
         ----------
         X : pd.DataFrame
@@ -624,24 +794,37 @@ class BaseDetector(BaseEstimator):
 
         Returns
         -------
-        y : pd.Series with RangeIndex
-            Labels for sequence ``X``, in sparse format.
-            Values are ``iloc`` references to indices of ``X``.
+        y : pd.DataFrame with RangeIndex
+
+            ``pd.DataFrame`` with the following columns:
+
+            * ``"ilocs"`` - always. Values are integers, ``iloc``
+              references to indices of ``X``, signifying points of interest.
+            * ``"labels"`` - if the task, by tags, is supervised or semi-supervised
+              segmentation, or anomaly clustering.
+
+            The meaning of segments in the ``"ilocs"`` column and ``"labels"``
+            column is as follows:
 
             * If ``task`` is ``"anomaly_detection"`` or ``"change_point_detection"``,
               the values are integer indices of the changepoints/anomalies.
-            * If ``task`` is "segmentation", the values are consecutive
+            * If ``task`` is ``"segmentation"``, the values are consecutive
               segment boundaries.
+
+            The ``"labels"`` are potential labels for the points of interest.
         """
         self.check_is_fitted()
         X = check_series(X)
 
         task = self.get_tag("task")
         if task in ["anomaly_detection", "change_point_detection"]:
-            return self._predict_points(X)
+            y_pred = self._predict_points(X)
         elif task == "segmentation":
-            segments = pd.DataFrame(self.predict_segments(X))
-            return self.segments_to_change_points(segments)
+            y_pred_seg = pd.DataFrame(self.predict_segments(X))
+            y_pred = self.segments_to_change_points(y_pred_seg)
+
+        y_pred = self._coerce_to_df(y_pred, columns=["ilocs"])
+        return y_pred
 
     def _predict_segments(self, X):
         """Predict segments on test/deployment data.
@@ -688,7 +871,8 @@ class BaseDetector(BaseEstimator):
             * If the index of ``y_sparse`` is not a set of intervals, the values of the
               series should represent the indexes of changepoints/anomalies.
         index : array-like
-            Indices that are to be annotated according to ``y_sparse``.
+            Larger set of indices which contains event indices in ``y_sparse``,
+            to be used as the index of the returned series.
 
         Returns
         -------
@@ -737,6 +921,10 @@ class BaseDetector(BaseEstimator):
         9    1
         dtype: int64
         """
+        if isinstance(y_sparse, pd.DataFrame):
+            y_sparse = y_sparse.iloc[:, 0]
+        if not isinstance(y_sparse, pd.Series):
+            y_sparse = pd.Series(y_sparse, dtype="int64")
         if isinstance(y_sparse.index.dtype, pd.IntervalDtype):
             # Segmentation case
             y_dense = BaseDetector._sparse_segments_to_dense(y_sparse, index)
@@ -827,6 +1015,10 @@ class BaseDetector(BaseEstimator):
               datatype index will be returned. The values of the series will be the
               labels of segments.
         """
+        if isinstance(y_dense, pd.DataFrame):
+            y_sparse = y_dense.iloc[:, 0]
+        if not isinstance(y_dense, pd.Series):
+            y_dense = pd.Series(y_dense, dtype="int64")
         if 0 in y_dense.values:
             # y_dense is a series of change points
             change_points = np.where(y_dense.values != 0)[0]
diff --git a/sktime/detection/bs.py b/sktime/detection/bs.py
index 444a6b70a..052e2234d 100644
--- a/sktime/detection/bs.py
+++ b/sktime/detection/bs.py
@@ -175,7 +175,7 @@ class BinarySegmentation(BaseDetector):
             X, self.threshold, self.min_cp_distance, self.max_iter
         )
         change_points.sort()
-        return pd.Series(X.index[change_points])
+        return pd.Series(change_points, dtype="int64")
 
     @classmethod
     def get_test_params(cls, parameter_set="default"):
diff --git a/sktime/detection/compose/_as_transform.py b/sktime/detection/compose/_as_transform.py
index ea12b8171..ac0559690 100644
--- a/sktime/detection/compose/_as_transform.py
+++ b/sktime/detection/compose/_as_transform.py
@@ -1,4 +1,4 @@
-"""Using an annotation estimator as transformation."""
+"""Using an detection estimator as transformation."""
 
 # copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
 from sktime.datatypes import MTYPE_LIST_SERIES
@@ -8,15 +8,15 @@ __author__ = ["fkiraly"]
 __all__ = ["AnnotatorAsTransformer", "DetectorAsTransformer"]
 
 
-MTYPE_LIST_FOR_ANNOTATORS = MTYPE_LIST_SERIES
+MTYPE_LIST_FOR_DETECTORS = MTYPE_LIST_SERIES
 # override until annotators only support pd.Series
-MTYPE_LIST_FOR_ANNOTATORS = ["pd.Series"]
+MTYPE_LIST_FOR_DETECTORS = ["pd.Series"]
 
 
 class DetectorAsTransformer(BaseTransformer):
     """Use an anomaly, changepoint detector, segmentation estimator as a transformer.
 
-    This adapter is used in coercions, when passing an annotator to a transformer slot.
+    This adapter is used in coercions, when passing an detector to a transformer slot.
 
     The transformation is series-to-primitives, transforming a time series
     into its cluster assignment.
@@ -26,18 +26,18 @@ class DetectorAsTransformer(BaseTransformer):
 
     Parameters
     ----------
-    estimator : sktime annotator, i.e., estimator inheriting from BaseDetector
+    estimator : sktime detector, i.e., estimator inheriting from BaseDetector
         this is a "blueprint" clusterer, state does not change when ``fit`` is called
 
     Attributes
     ----------
-    estimator_ : sktime annotator, clone of ``estimator``
+    estimator_ : sktime detector, clone of ``estimator``
         this clone is fitted in the pipeline when ``fit`` is called
 
     Examples
     --------
-    >>> from sktime.annotation.compose import DetectorAsTransformer
-    >>> from sktime.annotation.stray import STRAY
+    >>> from sktime.detection.compose import DetectorAsTransformer
+    >>> from sktime.detection.stray import STRAY
     >>> from sktime.utils._testing import _make_hierarchical
     >>> X = _make_hierarchical()
     >>> t = DetectorAsTransformer(STRAY())
@@ -58,8 +58,8 @@ class DetectorAsTransformer(BaseTransformer):
         "scitype:instancewise": False,  # is this an instance-wise transform?
         "capability:inverse_transform": False,  # can the transformer inverse transform?
         "univariate-only": False,  # can the transformer handle multivariate X?
-        "X_inner_mtype": MTYPE_LIST_FOR_ANNOTATORS,
-        "y_inner_mtype": MTYPE_LIST_FOR_ANNOTATORS,
+        "X_inner_mtype": MTYPE_LIST_FOR_DETECTORS,
+        "y_inner_mtype": MTYPE_LIST_FOR_DETECTORS,
         "requires_y": False,  # does y need to be passed in fit?
         "fit_is_empty": False,  # is fit empty and can be skipped? Yes = True
         "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
@@ -99,7 +99,7 @@ class DetectorAsTransformer(BaseTransformer):
         -------
         self: reference to self
         """
-        self.estimator_.fit(X=X, Y=y)
+        self.estimator_.fit(X=X, y=y)
         return self
 
     def _transform(self, X, y=None):
@@ -144,7 +144,7 @@ class DetectorAsTransformer(BaseTransformer):
             `create_test_instance` uses the first (or only) dictionary in `params`.
         """
         # imports
-        from sktime.annotation.lof import SubLOF
+        from sktime.detection.lof import SubLOF
 
         params1 = {"estimator": SubLOF.create_test_instance()}
         params2 = {"estimator": SubLOF.create_test_instance()}
diff --git a/sktime/detection/compose/_pipeline.py b/sktime/detection/compose/_pipeline.py
index a633a1c6a..8933569b6 100644
--- a/sktime/detection/compose/_pipeline.py
+++ b/sktime/detection/compose/_pipeline.py
@@ -1,4 +1,4 @@
-"""Pipeline for time series annotators."""
+"""Pipeline for time series detectors."""
 
 from sktime.base import _HeterogenousMetaEstimator
 from sktime.detection.base import BaseDetector
@@ -10,15 +10,15 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
 
     Parameters
     ----------
-    steps : list of sktime transformers and annotation estimators, or
-        list of tuples (str, estimator) of ``sktime`` transformers or annotators.
+    steps : list of sktime transformers and detectors, or
+        list of tuples (str, estimator) of ``sktime`` transformers or detectors.
         The list must contain exactly one forecaster.
         These are "blueprint" transformers resp forecasters,
-        annotator/transformer states do not change when ``fit`` is called.
+        detector/transformer states do not change when ``fit`` is called.
 
     Attributes
     ----------
-    steps_ : list of tuples (str, estimator) of ``sktime`` transformers or annotators
+    steps_ : list of tuples (str, estimator) of ``sktime`` transformers or detectors
         clones of estimators in ``steps`` which are fitted in the pipeline
         is always in (str, estimator) format, even if ``steps`` is just a list
         strings not passed in ``steps`` are replaced by unique generated strings
@@ -29,7 +29,7 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
     --------
     >>> import numpy as np
     >>> import pandas as pd
-    >>> from sktime.annotation.lof import SubLOF
+    >>> from sktime.detection.lof import SubLOF
     >>> from sktime.transformations.series.detrend import Detrender
     >>>
     >>> n = 100
@@ -113,7 +113,7 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
 
     @property
     def estimator_(self):
-        """Return reference to the annotator in the pipeline.
+        """Return reference to the detector in the pipeline.
 
         Valid after _fit.
         """
@@ -123,7 +123,7 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
         """Get list of scityes (str) from names/estimator list."""
         return [scitype(x[1], raise_on_unknown=False) for x in estimators]
 
-    def _get_first_annotator_index(self, estimators):
+    def _get_first_detector_index(self, estimators):
         """Get the index of the first forecaster in the list."""
         sts = self._get_pipeline_scitypes(estimators)
         for i, s in enumerate(sts):
@@ -138,7 +138,7 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
         ----------
         estimators : list of estimators, or list of (name, estimator) pairs
         allow_postproc : bool, optional, default=False
-            whether transformers after the annotator are allowed
+            whether transformers after the detector are allowed
 
         Returns
         -------
@@ -168,11 +168,11 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
         # validate names
         self._check_names(names)
 
-        ann_ind = self._get_first_annotator_index(estimator_tuples)
+        ann_ind = self._get_first_detector_index(estimator_tuples)
 
         if not allow_postproc and ann_ind != len(estimators) - 1:
             TypeError(
-                f"in {self_name}, last estimator must be a time series annotator, "
+                f"in {self_name}, last estimator must be a time series detector, "
                 f"but found a transformer"
             )
 
@@ -243,7 +243,7 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
         comp_dict = {name: comp for (name, comp) in fitted_estimator_tuples}
         return comp_dict
 
-    def _fit(self, X, Y=None):
+    def _fit(self, X, y=None):
         """Fit to training data.
 
         core logic
@@ -252,8 +252,8 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
         ----------
         X : pd.DataFrame
             training data to fit model to, time series
-        Y : pd.Series, optional
-            ground truth annotations for training if annotator is supervised
+        y : pd.Series, optional
+            ground truth detections for training if detector is supervised
 
         Returns
         -------
@@ -278,13 +278,13 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
             # transform X
             for step_idx, name, transformer in self._iter_transformers():
                 t = transformer.clone()
-                X = t.fit_transform(X=X, y=Y)
+                X = t.fit_transform(X=X, y=y)
                 self.steps_[step_idx] = (name, t)
 
-        # fit annotator
-        name, annotator = self.steps_[-1]
-        f = annotator.clone()
-        f.fit(X=X, Y=Y)
+        # fit detector
+        name, detector = self.steps_[-1]
+        f = detector.clone()
+        f.fit(X=X, y=y)
         self.steps_[-1] = (name, f)
 
         return self
@@ -355,7 +355,7 @@ class DetectorPipeline(_HeterogenousMetaEstimator, BaseDetector):
         parameter_set : str, default="default"
             Name of the set of test parameters to return, for use in tests. If no
             special parameters are defined for a value, will return `"default"` set.
-            There are currently no reserved values for annotators.
+            There are currently no reserved values for detectors.
 
         Returns
         -------
diff --git a/sktime/detection/dummy/_dummy_regular_an.py b/sktime/detection/dummy/_dummy_regular_an.py
index c2e63c429..df2939351 100644
--- a/sktime/detection/dummy/_dummy_regular_an.py
+++ b/sktime/detection/dummy/_dummy_regular_an.py
@@ -25,7 +25,7 @@ class DummyRegularAnomalies(BaseDetector):
     >>> from sktime.detection.dummy import DummyRegularAnomalies
     >>> y = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
     >>> d = DummyRegularAnomalies(step_size=3)
-    >>> d.fit_transform(y)
+    >>> yt = d.fit_transform(y)
     """
 
     _tags = {
@@ -61,7 +61,10 @@ class DummyRegularAnomalies(BaseDetector):
         self :
             Reference to self.
         """
-        self.first_index_ = X.index[0]
+        X_index = X.index
+        if isinstance(X_index, pd.DatetimeIndex):
+            X_index = pd.PeriodIndex(X_index)
+        self.first_index_ = X_index[0]
         return self
 
     def _predict(self, X):
@@ -87,6 +90,9 @@ class DummyRegularAnomalies(BaseDetector):
         step_size = self.step_size
 
         X_index = X.index
+        if isinstance(X_index, pd.DatetimeIndex):
+            X_index = pd.PeriodIndex(X_index)
+
         first_index = self.first_index_
 
         offset = X_index - first_index
@@ -97,8 +103,10 @@ class DummyRegularAnomalies(BaseDetector):
             offset = pd.Index(offset)
 
         change_point_indicator = offset % step_size == step_size - 1
-        X_ix_cp = X_index[change_point_indicator]
-        change_points = pd.Series(X_ix_cp)
+
+        X_range = pd.RangeIndex(len(X))
+        X_ix_cp = X_range[change_point_indicator]
+        change_points = pd.Series(X_ix_cp, dtype="int64")
         return change_points
 
     @classmethod
diff --git a/sktime/detection/dummy/_dummy_regular_cp.py b/sktime/detection/dummy/_dummy_regular_cp.py
index a772de456..60d3646a1 100644
--- a/sktime/detection/dummy/_dummy_regular_cp.py
+++ b/sktime/detection/dummy/_dummy_regular_cp.py
@@ -23,7 +23,7 @@ class DummyRegularChangePoints(DummyRegularAnomalies):
     >>> from sktime.detection.dummy import DummyRegularChangePoints
     >>> y = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
     >>> d = DummyRegularChangePoints(step_size=3)
-    >>> d.fit_transform(y)
+    >>> yt = d.fit_transform(y)
     """
 
     # same code except the tag
diff --git a/sktime/performance_metrics/forecasting/_classes.py b/sktime/performance_metrics/forecasting/_classes.py
index 5265e54bd..82a1bf80a 100644
--- a/sktime/performance_metrics/forecasting/_classes.py
+++ b/sktime/performance_metrics/forecasting/_classes.py
@@ -965,22 +965,33 @@ def make_forecasting_scorer(
     func : callable
         Callable to convert to a forecasting scorer class.
         Score function (or loss function) with signature ``func(y, y_pred, **kwargs)``.
+
     name : str, default=None
         Name to use for the forecasting scorer loss class.
+
     greater_is_better : bool, default=False
         If True then maximizing the metric is better.
         If False then minimizing the metric is better.
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     Returns
     -------
@@ -1018,17 +1029,26 @@ class MeanAbsoluteScaledError(_ScaledMetricTags, BaseForecastingErrorMetricFunc)
     ----------
     sp : int, default = 1
         Seasonal periodicity of the data
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1130,17 +1150,26 @@ class MedianAbsoluteScaledError(_ScaledMetricTags, BaseForecastingErrorMetricFun
     ----------
     sp : int, default = 1
         Seasonal periodicity of data.
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1243,17 +1272,26 @@ class MeanSquaredScaledError(_ScaledMetricTags, BaseForecastingErrorMetricFunc):
         Seasonal periodicity of data.
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1355,17 +1393,26 @@ class MedianSquaredScaledError(_ScaledMetricTags, BaseForecastingErrorMetricFunc
         Seasonal periodicity of data.
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1468,17 +1515,25 @@ class MeanAbsoluteError(BaseForecastingErrorMetric):
 
     Parameters
     ----------
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1576,17 +1631,25 @@ class MedianAbsoluteError(BaseForecastingErrorMetricFunc):
 
     Parameters
     ----------
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1707,17 +1770,26 @@ class MeanSquaredError(BaseForecastingErrorMetric):
     ----------
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -1888,6 +1960,174 @@ class MeanSquaredError(BaseForecastingErrorMetric):
         return [params1, params2]
 
 
+class MeanSquaredErrorPercentage(BaseForecastingErrorMetricFunc):
+    r"""Mean Squared Error Percentage (MSE%) and root-MSE% forecasting error metrics.
+
+    Calculates the mean squared error percentage between the true and predicted values.
+    Optionally, the root mean squared error percentage (RMSE%) can be computed by
+    setting ``square_root=True``.
+
+    The Mean Squared Error Percentage (MSE%) is calculated as:
+
+    .. math::
+        \\text{MSE%} = \\frac{ \\frac{1}{n} \\sum_{i=1}^{n} (y_i - \\hat{y}_i)^2 }
+                            { \\sum_{i=1}^{n} \\frac{y_i}{n} }
+
+    where:
+    - \\( y_i \\) are the true values,
+    - \\( \\hat{y}_i \\) are the predicted values,
+    - \\( n \\) is the number of observations.
+
+    If ``square_root`` is set to True,
+    the Root Mean Squared Error Percentage (RMSE%) is computed:
+
+    .. math::
+        \\text{RMSE%} = \\sqrt{ \\text{MSE%} }
+
+    Parameters
+    ----------
+    square_root : bool, default = False
+        Whether to take the square root of the metric
+    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+            (n_outputs,), default='uniform_average'
+        Defines how to aggregate metric for multivariate (multioutput) data.
+        If array-like, values used as weights to average the errors.
+        If 'raw_values', returns a full set of errors in case of multioutput input.
+        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+    multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
+        Defines how to aggregate metric for hierarchical data (with levels).
+        If 'uniform_average' (default), errors are mean-averaged across levels.
+        If 'uniform_average_time', metric is applied to all data, ignoring level index.
+        If 'raw_values', does not average errors across levels, hierarchy is retained.
+    """
+
+    def __init__(
+        self,
+        multioutput="uniform_average",
+        multilevel="uniform_average",
+        square_root=False,
+    ):
+        self.square_root = square_root
+        self.multioutput = multioutput
+
+        super().__init__(multioutput=multioutput, multilevel=multilevel)
+
+    def _evaluate(self, y_true, y_pred, **kwargs):
+        r"""
+        Evaluate the Mean Squared Error Percentage (MSE%) between `y_true` and `y_pred`.
+
+        Parameters
+        ----------
+        y_true : pd.Series or pd.DataFrame
+            Ground truth (actual) target values.
+            Can be a Series or DataFrame for univariate or multivariate forecasts.
+
+        y_pred : pd.Series or pd.DataFrame
+            Forecasted target values.
+            Must have the same shape as `y_true`.
+
+        Returns
+        -------
+        loss : float or pd.Series
+            The calculated Mean Squared Error Percentage.
+            - If `multioutput='raw_values'`, returns a Series with the MSPE for
+            each output.
+            - Otherwise, returns a scalar value representing the aggregated MSPE.
+        """
+        multioutput = self.multioutput
+        raw_values = (y_true - y_pred) ** 2
+        raw_values = self._get_weighted_df(raw_values, **kwargs)
+        num = raw_values.mean()
+        denom = y_true.mean()
+
+        msqe = num / denom
+
+        if self.square_root:
+            msqe = msqe.pow(0.5)
+
+        return self._handle_multioutput(msqe, multioutput)
+
+    def _evaluate_by_index(self, y_true, y_pred, **kwargs):
+        """Return the metric evaluated at each time point.
+
+        private _evaluate_by_index containing core logic, called from evaluate_by_index
+
+        Parameters
+        ----------
+        y_true : time series in sktime compatible pandas based data container format
+            Ground truth (correct) target values
+            y can be in one of the following formats:
+            Series scitype: pd.DataFrame
+            Panel scitype: pd.DataFrame with 2-level row MultiIndex
+            Hierarchical scitype: pd.DataFrame with 3 or more level row MultiIndex
+        y_pred :time series in sktime compatible data container format
+            Forecasted values to evaluate
+            must be of same format as y_true, same indices and columns if indexed
+
+        Returns
+        -------
+        loss : pd.Series or pd.DataFrame
+            Calculated metric, by time point (default=jackknife pseudo-values).
+            pd.Series if self.multioutput="uniform_average" or array-like
+                index is equal to index of y_true
+                entry at index i is metric at time i, averaged over variables
+            pd.DataFrame if self.multioutput="raw_values"
+                index and columns equal to those of y_true
+                i,j-th entry is metric at time i, at variable j
+        """
+        multioutput = self.multioutput
+
+        raw_values_mse = (y_true - y_pred) ** 2
+        raw_values_p = y_true
+
+        # what we need to do is efficiently
+        # compute msqe but using data with the i-th time point removed
+        # msqe[i] = msqe(all data minus i-th time point)
+
+        n = raw_values_mse.shape[0]
+
+        num_mean = raw_values_mse.mean()
+        denom_mean = raw_values_p.mean()
+
+        num_jk = num_mean * (1 + 1 / (n - 1)) - raw_values_mse / (n - 1)
+        denom_jk = denom_mean * (1 + 1 / (n - 1)) - raw_values_p / (n - 1)
+
+        msep_jk = num_jk / denom_jk
+        msep = num_mean / denom_mean
+
+        if self.square_root:
+            msep_jk = msep_jk.pow(0.5)
+            msep = msep.pow(0.5)
+
+        pseudo_values = n * msep - (n - 1) * msep_jk
+        pseudo_values = self._get_weighted_df(pseudo_values, **kwargs)
+
+        return self._handle_multioutput(pseudo_values, multioutput)
+
+    @classmethod
+    def get_test_params(cls, parameter_set="default"):
+        """Return testing parameter settings for the estimator.
+
+        Parameters
+        ----------
+        parameter_set : str, default="default"
+            Name of the set of test parameters to return, for use in tests. If no
+            special parameters are defined for a value, will return ``"default"`` set.
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class
+            Each dict are parameters to construct an "interesting" test instance, i.e.,
+            ``MyClass(**params)`` or ``MyClass(**params[i])`` creates a valid test
+            instance.
+            ``create_test_instance`` uses the first (or only) dictionary in ``params``
+        """
+        params1 = {}
+        params2 = {"square_root": True}
+        return [params1, params2]
+
+
 class MedianSquaredError(BaseForecastingErrorMetricFunc):
     """Median squared error (MdSE) or root median squared error (RMdSE).
 
@@ -1909,17 +2149,26 @@ class MedianSquaredError(BaseForecastingErrorMetricFunc):
     ----------
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2012,17 +2261,25 @@ class GeometricMeanAbsoluteError(BaseForecastingErrorMetricFunc):
 
     Parameters
     ----------
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2089,17 +2346,26 @@ class GeometricMeanSquaredError(BaseForecastingErrorMetricFunc):
         Whether to take the square root of the mean squared error.
         If True, returns root geometric mean squared error (RGMSE)
         If False, returns geometric mean squared error (GMSE)
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2194,7 +2460,7 @@ class GeometricMeanSquaredError(BaseForecastingErrorMetricFunc):
 
 
 class MeanAbsolutePercentageError(BaseForecastingErrorMetricFunc):
-    r"""Mean absolute percentage error (MAPE) or symmetric version.
+    r"""Mean absolute percentage error (MAPE) or symmetric MAPE.
 
     For a univariate, non-hierarchical sample
     of true values :math:`y_1, \dots, y_n` and
@@ -2234,17 +2500,26 @@ class MeanAbsolutePercentageError(BaseForecastingErrorMetricFunc):
     ----------
     symmetric : bool, default = False
         Whether to calculate the symmetric version of the percentage metric
-    multioutput : str or 1D array-like (n_outputs,), default='uniform_average'
-        if str, must be one of {'raw_values', 'uniform_average'}
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
+            (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2410,17 +2685,26 @@ class MedianAbsolutePercentageError(BaseForecastingErrorMetricFunc):
     ----------
     symmetric : bool, default = False
         Whether to calculate the symmetric version of the percentage metric
-    multioutput : str or 1D array-like (n_outputs,), default='uniform_average'
-        if str, must be one of {'raw_values', 'uniform_average'}
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
+            (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2501,7 +2785,7 @@ class MedianAbsolutePercentageError(BaseForecastingErrorMetricFunc):
 
 
 class MeanSquaredPercentageError(BaseForecastingErrorMetricFunc):
-    """Mean squared percentage error (MSPE)  or square root version.
+    """Mean squared percentage error (MSPE), or RMSPE, or symmetric MSPE, RMSPE.
 
     If ``square_root`` is False then calculates MSPE and if ``square_root`` is True
     then calculates root mean squared percentage error (RMSPE). If ``symmetric``
@@ -2524,17 +2808,26 @@ class MeanSquaredPercentageError(BaseForecastingErrorMetricFunc):
         Whether to calculate the symmetric version of the percentage metric
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2620,7 +2913,7 @@ class MeanSquaredPercentageError(BaseForecastingErrorMetricFunc):
 
 
 class MedianSquaredPercentageError(BaseForecastingErrorMetricFunc):
-    """Median squared percentage error (MdSPE)  or square root version.
+    """Median squared percentage error (MdSPE), or RMdSPE, or symmetric MdSPE, RMDsPE.
 
     If ``square_root`` is False then calculates MdSPE and if ``square_root`` is True
     then calculates root median squared percentage error (RMdSPE). If ``symmetric``
@@ -2647,17 +2940,26 @@ class MedianSquaredPercentageError(BaseForecastingErrorMetricFunc):
         Whether to calculate the symmetric version of the percentage metric
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2754,17 +3056,25 @@ class MeanRelativeAbsoluteError(BaseForecastingErrorMetricFunc):
 
     Parameters
     ----------
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2821,17 +3131,25 @@ class MedianRelativeAbsoluteError(BaseForecastingErrorMetricFunc):
 
     Parameters
     ----------
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2889,17 +3207,25 @@ class GeometricMeanRelativeAbsoluteError(BaseForecastingErrorMetricFunc):
 
     Parameters
     ----------
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -2964,17 +3290,26 @@ class GeometricMeanRelativeSquaredError(BaseForecastingErrorMetricFunc):
     ----------
     square_root : bool, default = False
         Whether to take the square root of the metric
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -3094,17 +3429,26 @@ class MeanAsymmetricError(BaseForecastingErrorMetricFunc):
     right_error_penalty : int or float, default=1.0
         An additional multiplicative penalty to apply to error values greater
         than the asymmetric threshold.
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -3232,17 +3576,26 @@ class MeanLinexError(BaseForecastingErrorMetricFunc):
         the reverse is true.
     b : int or float
         Multiplicative penalty to apply to calculated errors.
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     See Also
     --------
@@ -3358,17 +3711,26 @@ class RelativeLoss(BaseForecastingErrorMetricFunc):
     ----------
     relative_loss_function : function
         Function to use in calculation relative loss.
-    multioutput : {'raw_values', 'uniform_average'}  or array-like of shape \
+
+    multioutput : {'raw_values', 'uniform_average'} or array-like of shape \
             (n_outputs,), default='uniform_average'
         Defines how to aggregate metric for multivariate (multioutput) data.
-        If array-like, values used as weights to average the errors.
-        If 'raw_values', returns a full set of errors in case of multioutput input.
-        If 'uniform_average', errors of all outputs are averaged with uniform weight.
+
+        * If array-like, values used as weights to average the errors.
+        * If ``'raw_values'``,
+          returns a full set of errors in case of multioutput input.
+        * If ``'uniform_average'``,
+          errors of all outputs are averaged with uniform weight.
+
     multilevel : {'raw_values', 'uniform_average', 'uniform_average_time'}
         Defines how to aggregate metric for hierarchical data (with levels).
-        If 'uniform_average' (default), errors are mean-averaged across levels.
-        If 'uniform_average_time', metric is applied to all data, ignoring level index.
-        If 'raw_values', does not average errors across levels, hierarchy is retained.
+
+        * If ``'uniform_average'`` (default),
+          errors are mean-averaged across levels.
+        * If ``'uniform_average_time'``,
+          metric is applied to all data, ignoring level index.
+        * If ``'raw_values'``,
+          does not average errors across levels, hierarchy is retained.
 
     References
     ----------


Test patch:
diff --git a/sktime/detection/tests/test_all_detectors.py b/sktime/detection/tests/test_all_detectors.py
index 05b52bbf4..71948219e 100644
--- a/sktime/detection/tests/test_all_detectors.py
+++ b/sktime/detection/tests/test_all_detectors.py
@@ -55,7 +55,8 @@ class TestAllDetectors(DetectorFixtureGenerator, QuickTester):
             n_timepoints=10, estimator_type=estimator.get_tag("distribution_type")
         )
         y_test = estimator.predict(X_test)
-        assert isinstance(y_test, pd.Series)
+        assert isinstance(y_test, pd.DataFrame)
+        assert isinstance(y_test.index, pd.RangeIndex)
 
     def test_transform_output_type(self, estimator_instance):
         """Test output type for the transform method."""
@@ -83,7 +84,10 @@ class TestAllDetectors(DetectorFixtureGenerator, QuickTester):
             estimator_type=estimator_instance.get_tag("distribution_type"),
         )
         y_pred = estimator_instance.predict_points(X_test)
-        assert isinstance(y_pred, pd.Series)
+        assert isinstance(y_pred, pd.DataFrame)
+        assert isinstance(y_pred.index, pd.RangeIndex)
+        assert "ilocs" in y_pred.columns
+        assert pd.api.types.is_integer_dtype(y_pred["ilocs"].dtype), y_pred
 
     def test_predict_segments(self, estimator_instance):
         X_train = make_detection_problem(
@@ -96,10 +100,13 @@ class TestAllDetectors(DetectorFixtureGenerator, QuickTester):
             n_timepoints=10,
             estimator_type=estimator_instance.get_tag("distribution_type"),
         )
-        y_test = estimator_instance.predict_segments(X_test)
-        assert isinstance(y_test, pd.Series)
-        assert isinstance(y_test.index.dtype, pd.IntervalDtype)
-        assert pd.api.types.is_integer_dtype(y_test)
+        y_pred = estimator_instance.predict_segments(X_test)
+        assert isinstance(y_pred, pd.DataFrame)
+        assert isinstance(y_pred.index, pd.RangeIndex)
+        assert "ilocs" in y_pred.columns
+        ilocs_dtype = y_pred["ilocs"].dtype
+        assert isinstance(y_pred["ilocs"].dtype, pd.IntervalDtype)
+        assert pd.api.types.is_integer_dtype(ilocs_dtype.subtype)
 
     def test_detector_tags(self, estimator_class):
         """Check the learning_type and task tags are valid."""
diff --git a/sktime/detection/tests/test_bs.py b/sktime/detection/tests/test_bs.py
index 88c8559f8..23dd5dd3e 100644
--- a/sktime/detection/tests/test_bs.py
+++ b/sktime/detection/tests/test_bs.py
@@ -41,7 +41,7 @@ def test_fit_predict(X, expected_change_points):
     """Tese the fit_predict method for binary segmentation."""
     model = BinarySegmentation(threshold=1, min_cp_distance=1)
     change_points = model.fit_predict(X)
-    assert change_points.values.tolist() == expected_change_points
+    assert change_points.values.flatten().tolist() == expected_change_points
 
 
 @pytest.mark.skipif(
diff --git a/sktime/detection/tests/test_clasp.py b/sktime/detection/tests/test_clasp.py
index e1007cb63..5ebc5d0be 100644
--- a/sktime/detection/tests/test_clasp.py
+++ b/sktime/detection/tests/test_clasp.py
@@ -4,7 +4,6 @@ __author__ = ["patrickzib"]
 __all__ = []
 
 import pytest
-from pandas.api.types import is_interval_dtype
 
 from sktime.datasets import load_gun_point_segmentation
 from sktime.detection.clasp import ClaSPSegmentation
@@ -29,7 +28,7 @@ def test_clasp_sparse():
     found_cps = clasp.predict(ts)
     scores = clasp.predict_scores(ts)
 
-    assert len(found_cps) == 1 and found_cps[0] == 893
+    assert len(found_cps) == 1 and found_cps.ilocs[0] == 893
     assert len(scores) == 1 and scores.iloc[0, 0] > 0.74
 
 
@@ -76,5 +75,4 @@ def test_clasp_predict_segments():
     segments = clasp.predict_segments(ts)
 
     assert len(segments) == 2  # With one change point there should be 2 two segments
-    assert is_interval_dtype(segments.index)
-    assert segments.index[-1].left == 893
+    assert segments.values[-1][0].left == 893
diff --git a/sktime/detection/tests/test_dummy.py b/sktime/detection/tests/test_dummy.py
index d36c7b1fb..b42caba53 100644
--- a/sktime/detection/tests/test_dummy.py
+++ b/sktime/detection/tests/test_dummy.py
@@ -12,8 +12,8 @@ def test_dummy_changepoints():
     y_cp_ds = d.fit_transform(y)
     y_cp_sp = d.fit_predict(y)
 
-    expected_ds = pd.Series([0, 0, 1, 0, 0, 1, 0, 0, 1, 0])
+    expected_ds = pd.DataFrame([0, 0, 1, 0, 0, 1, 0, 0, 1, 0], columns=["labels"])
     assert y_cp_ds.equals(expected_ds)
 
-    expected_sp = pd.Series([2, 5, 8])
+    expected_sp = pd.DataFrame([2, 5, 8], columns=["ilocs"])
     assert y_cp_sp.equals(expected_sp)
diff --git a/sktime/detection/tests/test_hmm.py b/sktime/detection/tests/test_hmm.py
index 71f117161..5fb082772 100644
--- a/sktime/detection/tests/test_hmm.py
+++ b/sktime/detection/tests/test_hmm.py
@@ -43,6 +43,7 @@ def test_hmm_basic_gauss():
     gauss_test = HMM(emi_funcs, transition_matrix)
     gauss_test.fit(gauss_data)
     predicted_labels = gauss_test.predict(gauss_data)
+    predicted_labels = predicted_labels.iloc[:, 0]
     assert len(predicted_labels == labels) >= 0.95 * len(predicted_labels)
 
 
@@ -94,5 +95,6 @@ def test_hmm_behaves_as_expected_on_simple_input():
     obs = asarray([3.7, 3.2, 3.4, 3.6, -5.1, -5.2, -4.9])
     hmm_est = hmm_est.fit(obs)
     labels = hmm_est.predict(obs)
+    labels = labels.values.flatten()
     ground_truth = asarray([0, 0, 0, 0, 1, 1, 1])
     assert array_equal(labels, ground_truth)
diff --git a/sktime/detection/tests/test_hmm_learn.py b/sktime/detection/tests/test_hmm_learn.py
index 6a8460721..c8e611f7b 100644
--- a/sktime/detection/tests/test_hmm_learn.py
+++ b/sktime/detection/tests/test_hmm_learn.py
@@ -27,7 +27,7 @@ def test_GaussianHMM_wrapper():
     hmmlearn_model.fit(X=data)
     sktime_model.fit(X=data)
     hmmlearn_predict = hmmlearn_model.predict(X=data)
-    sktime_predict = sktime_model.predict(X=data)
+    sktime_predict = sktime_model.predict(X=data).values.flatten()
     assert array_equal(hmmlearn_predict, sktime_predict)
 
 
@@ -48,7 +48,7 @@ def test_GMMHMM_wrapper():
     hmmlearn_model.fit(X=data)
     sktime_model.fit(X=data)
     hmmlearn_predict = hmmlearn_model.predict(X=data)
-    sktime_predict = sktime_model.predict(X=data)
+    sktime_predict = sktime_model.predict(X=data).values.flatten()
     assert array_equal(hmmlearn_predict, sktime_predict)
 
 
@@ -69,5 +69,5 @@ def test_PoissonHMM_wrapper():
     hmmlearn_model.fit(X=data)
     sktime_model.fit(X=data)
     hmmlearn_predict = hmmlearn_model.predict(X=data)
-    sktime_predict = sktime_model.predict(X=data)
+    sktime_predict = sktime_model.predict(X=data).values.flatten()
     assert array_equal(hmmlearn_predict, sktime_predict)
diff --git a/sktime/detection/tests/test_lof.py b/sktime/detection/tests/test_lof.py
index 27eb42ddf..4e8f969cd 100644
--- a/sktime/detection/tests/test_lof.py
+++ b/sktime/detection/tests/test_lof.py
@@ -69,7 +69,7 @@ def test_cut_into_intervals(x, interval_size, expected_intervals):
     [
         (
             pd.DataFrame([0, 0, 100, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 100, 0]),
-            pd.Series([2, 7, 13]),
+            pd.DataFrame({"ilocs": [2, 7, 13]}),
         ),
     ],
 )
@@ -77,4 +77,4 @@ def test_predict(X, y_expected):
     model = SubLOF(3, window_size=5, novelty=True)
     model.fit(X)
     y_actual = model.predict(X)
-    pd.testing.assert_series_equal(y_actual, y_expected)
+    pd.testing.assert_frame_equal(y_actual, y_expected)
diff --git a/sktime/tests/_config.py b/sktime/tests/_config.py
index c469604e9..c3b3fd8ac 100644
--- a/sktime/tests/_config.py
+++ b/sktime/tests/_config.py
@@ -268,6 +268,7 @@ EXCLUDED_TESTS = {
         "test_predict_points",
         "test_predict_segments",
         "test_transform_output_type",
+        "test_output_type",
     ],
     "BinarySegmentation": [
         "test_predict_segments",
@@ -290,11 +291,13 @@ EXCLUDED_TESTS = {
         "test_predict_points",
         "test_predict_segments",
         "test_transform_output_type",
+        "test_output_type",
     ],
     "GMMHMM": [
         "test_predict_points",
         "test_predict_segments",
         "test_transform_output_type",
+        "test_output_type",
     ],
 }