Repository: sktime/sktime
Pull request #6292 (2024-04-12 21:07:47)

[ENH] TBATS test parameters to cover doc example
This adds one test parameter set to the `TBATS` forecaster to cover the doc example.


Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 7695c0f52..5f567c577 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -2756,6 +2756,15 @@
         "doc"
       ]
     },
+    {
+      "login": "ssabarwal",
+      "name": "Shlok Sabarwal",
+      "avatar_url": "https://gravatar.com/avatar/cbdbaac712ae282d730cd3028e862d45?s=400&d=robohash&r=x",
+      "prifle": "https://www.github.com/shlok191/",
+      "contributions": [
+        "code"
+      ]
+    },
     {
       "login": "mobley-trent",
       "name": "Eddy Oyieko",
diff --git a/README.md b/README.md
index 9d74745e2..8181992d5 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ Questions and feedback are extremely welcome! We strongly believe in the value o
 | :woman_technologist: **Usage Questions**          | [GitHub Discussions] · [Stack Overflow] |
 | :speech_balloon: **General Discussion**        | [GitHub Discussions] |
 | :factory: **Contribution & Development** | `dev-chat` channel · [Discord] |
-| :globe_with_meridians: **Community collaboration session** | [Discord] - Fridays 3 pm UTC, dev/meet-ups channel |
+| :globe_with_meridians: **Meet-ups and collaboration sessions** | [Discord] - Fridays 4 pm UTC, dev/meet-ups channel |
 
 [github issue tracker]: https://github.com/sktime/sktime/issues
 [github discussions]: https://github.com/sktime/sktime/discussions
diff --git a/docs/source/api_reference.rst b/docs/source/api_reference.rst
index 9dc6307d5..122ea2138 100644
--- a/docs/source/api_reference.rst
+++ b/docs/source/api_reference.rst
@@ -8,8 +8,11 @@ Welcome to the API reference for ``sktime``.
 
 The API reference provides a technical manual.
 It describes the classes and functions included in sktime.
+
 For a scientific manual, see the :ref:`user_guide`.
 
+For a list of object and estimator tags, see :ref:`tags_ref`.
+
 .. include:: includes/api_css.rst
 
 .. toctree::
@@ -32,4 +35,5 @@ For a scientific manual, see the :ref:`user_guide`.
     api_reference/deployment
     api_reference/utils
     api_reference/exceptions
+    api_reference/tags
     api_reference/pipeline
diff --git a/docs/source/api_reference/tags.rst b/docs/source/api_reference/tags.rst
new file mode 100644
index 000000000..37de75a25
--- /dev/null
+++ b/docs/source/api_reference/tags.rst
@@ -0,0 +1,112 @@
+.. _tags_ref:
+
+Object and estimator tags
+=========================
+
+Every first-class object in ``sktime``
+is tagged with a set of tags that describe its properties and capabilities,
+or control its behavior.
+
+Tags are key-value pairs, where the key is a string with the name of the tag.
+The value of the tag can have arbitrary type, and describes a property, capability,
+or controls behaviour of the object, depending on the value.
+
+For instance, a forecaster may have the tag ``capability:pred_int: True`` if it can
+make probabilistic predictions.
+Users can find all forecasters that can make probabilistic predictions by filtering
+for this tag.
+
+This API reference lists all tags available in ``sktime``, and key utilities
+for their usage.
+
+
+Inspecting tags, retrieving by tags
+-----------------------------------
+
+* to get the tags of an object, use the ``get_tags`` method.
+  An object's tags can depend on its hyper-parameters.
+* to get the tags of a class, use the ``get_tags`` method of the class.
+  A class's tags are static and do not depend on its hyper-parameters.
+  By default, class tags that may vary for instances take the most "capable" value,
+  in the case of capabilities.
+* to programmatically retrieve all tags available in ``sktime``
+  or for a particular type of object, at runtime, use the ``registry.all_tags`` utility
+* to programmatically retrieve all objects or estimators in ``sktime``,
+  filtered for values of tags, use the ``registry.all_estimators`` utility
+
+
+.. currentmodule:: sktime.registry
+
+.. autosummary::
+    :toctree: auto_generated/
+    :template: function.rst
+
+    all_tags
+    all_estimators
+
+
+General tags, packaging
+-----------------------
+
+This section lists tags that are general and apply to all objects in ``sktime``.
+These tags are typically used for typing, packaging and documentation purposes.
+
+.. currentmodule:: sktime.registry._tags
+
+.. autosummary::
+    :toctree: auto_generated/
+    :template: function.rst
+    :nosignatures:
+
+    object_type
+    maintainers
+    authors
+    python_version
+    python_dependencies
+    python_dependencies_alias
+    requires_cython
+
+
+.. _forecaster_tags:
+
+Forecaster tags
+---------------
+
+This section lists tags applying to forecasters.
+These tags are used to describe capabilities, properties, and behavior of forecasters.
+
+The list also includes some developer facing tags that are used to
+control internal behavior of the forecaster.
+
+.. currentmodule:: sktime.registry._tags
+
+.. autosummary::
+    :toctree: auto_generated/
+    :template: function.rst
+    :nosignatures:
+
+    capability__exogeneous
+    capability__insample
+    capability__pred_int
+    capability__pred_int__insample
+    capability__missing_values
+
+
+.. _panel_tags:
+
+Tags for classifiers, regressors, clustering
+--------------------------------------------
+
+.. currentmodule:: sktime.registry._tags
+
+.. autosummary::
+    :toctree: auto_generated/
+    :template: function.rst
+    :nosignatures:
+
+    capability__multivariate
+    capability__missing_values
+    capability__unequal_length
+    capability__feature_importance
+    capability__contractable
+    capability__train_estimate
diff --git a/pyproject.toml b/pyproject.toml
index 306802c43..03e5f0853 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -207,7 +207,7 @@ regression = [
 transformations = [
   'esig<0.10,>=0.9.7; python_version < "3.11"',
   "filterpy<1.5,>=1.4.5",
-  "holidays>=0.29,<0.47",
+  "holidays>=0.29,<0.48",
   "mne>=1.5,<1.7",
   'numba<0.60,>=0.53',
   "pycatch22>=0.4,<0.4.4",
diff --git a/sktime/clustering/k_means/_k_means_tslearn.py b/sktime/clustering/k_means/_k_means_tslearn.py
index 62cb2ff5c..af37ec34a 100644
--- a/sktime/clustering/k_means/_k_means_tslearn.py
+++ b/sktime/clustering/k_means/_k_means_tslearn.py
@@ -107,7 +107,7 @@ class TimeSeriesKMeansTslearn(_TslearnAdapter, BaseClusterer):
         # estimator type
         # --------------
         "capability:multivariate": True,
-        "capability:unequal_length": True,
+        "capability:unequal_length": False,
     }
 
     # defines the name of the attribute containing the tslearn estimator
diff --git a/sktime/clustering/k_shapes.py b/sktime/clustering/k_shapes.py
index 0167087f8..aa53222e3 100755
--- a/sktime/clustering/k_shapes.py
+++ b/sktime/clustering/k_shapes.py
@@ -57,7 +57,7 @@ class TimeSeriesKShapes(_TslearnAdapter, BaseClusterer):
         # estimator type
         # --------------
         "capability:multivariate": True,
-        "capability:unequal_length": True,
+        "capability:unequal_length": False,
     }
 
     # defines the name of the attribute containing the tslearn estimator
diff --git a/sktime/forecasting/base/adapters/_neuralforecast.py b/sktime/forecasting/base/adapters/_neuralforecast.py
index 4de57629b..a33a6f233 100644
--- a/sktime/forecasting/base/adapters/_neuralforecast.py
+++ b/sktime/forecasting/base/adapters/_neuralforecast.py
@@ -2,15 +2,18 @@
 """Implements adapter for NeuralForecast models."""
 import abc
 import functools
+from copy import deepcopy
+from inspect import signature
 from typing import List, Literal, Optional, Union
 
 import numpy as np
 import pandas
 
 from sktime.forecasting.base import BaseForecaster, ForecastingHorizon
+from sktime.utils.warnings import warn
 
 __all__ = ["_NeuralForecastAdapter"]
-__author__ = ["yarnabrina", "geetu040"]
+__author__ = ["yarnabrina", "geetu040", "pranavvp16"]
 
 
 class _NeuralForecastAdapter(BaseForecaster):
@@ -134,16 +137,81 @@ class _NeuralForecastAdapter(BaseForecaster):
         - custom model name (``alias``) - used from ``algorithm_name``
         """
 
+    def _get_valid_parameters(self: "_NeuralForecastAdapter") -> dict:
+        """Get valid parameters for the underlying NeuralForecast algorithm class.
+
+        Returns
+        -------
+        dict
+            valid list of arguments for the underlying algorithm class
+        """
+        from pytorch_lightning import Trainer
+
+        # get valid init parameters from the model class and Trainer
+        model_class = self.algorithm_class
+        trainer_params = list(signature(Trainer.__init__).parameters.keys())
+        valid_parameters = list(signature(model_class.__init__).parameters.keys())
+        valid_parameters += trainer_params
+
+        sktime_parameters = self.algorithm_parameters
+        default_parameters = self.get_param_defaults()
+
+        valid_parameters = set(valid_parameters)
+        unsupported_parameters = set(sktime_parameters.keys()) - valid_parameters
+
+        # iterate through neuralforecast parameters and check for non default values
+        user_parameters = [
+            key
+            for key in sktime_parameters
+            if sktime_parameters[key] != default_parameters[key]
+        ]
+
+        # filter out unsupported parameters
+        sktime_trainer_params = set(self.algorithm_parameters["trainer_kwargs"])
+        invalid_trainer_params = sktime_trainer_params - set(trainer_params)
+
+        filter_params = deepcopy(self.algorithm_parameters)
+        for invalid_param in invalid_trainer_params:
+            warn(
+                f"Keyword argument '{invalid_param}' will be omitted as it is"
+                f" not found in the __init__ method "
+                f"from {Trainer}. "
+                f"Check your pytorch_lightning version "
+                f"to find out the right API parameters.",
+                obj=self,
+                stacklevel=2,
+            )
+            filter_params["trainer_kwargs"].pop(invalid_param)
+
+        for unsupported_param in unsupported_parameters:
+            if unsupported_param in user_parameters:
+                warn(
+                    f"Keyword argument '{unsupported_param}' will be omitted as it is"
+                    f" not found in the __init__ method "
+                    f"from {self.algorithm_class}. "
+                    f"Check your neuralforecast version "
+                    f"to find out the right API parameters.",
+                    obj=self,
+                    stacklevel=2,
+                )
+            filter_params.pop(unsupported_param)
+
+        return filter_params
+
     def _instantiate_model(self: "_NeuralForecastAdapter", fh: ForecastingHorizon):
         """Instantiate the model."""
         exogenous_parameters = (
             {"futr_exog_list": self.futr_exog_list} if self.needs_X else {}
         )
 
+        # filter params according to neuralforecast version
+        params = self._get_valid_parameters()
+        trainer_kwargs = params.pop("trainer_kwargs")
         algorithm_instance = self.algorithm_class(
             fh,
             alias=self.algorithm_name,
-            **self.algorithm_parameters,
+            **params,
+            **trainer_kwargs,
             **exogenous_parameters,
         )
 
diff --git a/sktime/forecasting/naive.py b/sktime/forecasting/naive.py
index 74a6b7233..c1314efd5 100644
--- a/sktime/forecasting/naive.py
+++ b/sktime/forecasting/naive.py
@@ -357,7 +357,6 @@ class NaiveForecaster(_BaseWindowForecaster):
         lagger = Lag(1, keep_column_names=True, freq=freq)
 
         expected_index = fh.to_absolute(cutoff).to_pandas()
-
         if strategy == "last" and sp == 1:
             y_old = lagger.fit_transform(_y)
             y_new = pd.DataFrame(index=expected_index, columns=[0], dtype="float64")
diff --git a/sktime/forecasting/neuralforecast.py b/sktime/forecasting/neuralforecast.py
index f7de9b950..fbf556853 100644
--- a/sktime/forecasting/neuralforecast.py
+++ b/sktime/forecasting/neuralforecast.py
@@ -6,7 +6,7 @@ from typing import List, Literal, Optional, Union
 from sktime.forecasting.base.adapters._neuralforecast import _NeuralForecastAdapter
 from sktime.utils.validation._dependencies import _check_soft_dependencies
 
-__author__ = ["yarnabrina", "geetu040"]
+__author__ = ["yarnabrina", "geetu040", "pranavvp16"]
 
 
 class NeuralForecastRNN(_NeuralForecastAdapter):
@@ -96,6 +96,10 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
         whether ``TimeSeriesDataLoader`` drops last non-full batch
     trainer_kwargs : dict (default=None)
         keyword trainer arguments inherited from PyTorch Lighning's trainer [6]_
+    optimizer : pytorch optimizer (default=None) [7]_
+        optimizer to use for training, if passed with None defaults to Adam
+    optimizer_kwargs : dict (default=None) [8]_
+        dict of parameters to pass to the user defined optimizer
 
     Notes
     -----
@@ -147,6 +151,8 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
     .. [5] https://nixtlaverse.nixtla.io/neuralforecast/losses.pytorch.html
     .. [6]
     https://lightning.ai/docs/pytorch/stable/api/pytorch_lightning.trainer.trainer.Trainer.html#lightning.pytorch.trainer.trainer.Trainer
+    .. [7] https://pytorch.org/docs/stable/optim.html
+    .. [8] https://pytorch.org/docs/stable/generated/torch.optim.Adam.html#torch.optim.Adam
     """  # noqa: E501
 
     _tags = {
@@ -194,6 +200,8 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
         num_workers_loader=0,
         drop_last_loader=False,
         trainer_kwargs: Optional[dict] = None,
+        optimizer=None,
+        optimizer_kwargs: dict = None,
     ):
         self.input_size = input_size
         self.inference_input_size = inference_input_size
@@ -218,6 +226,8 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
         self.random_seed = random_seed
         self.num_workers_loader = num_workers_loader
         self.drop_last_loader = drop_last_loader
+        self.optimizer = optimizer
+        self.optimizer_kwargs = optimizer_kwargs
         self.trainer_kwargs = trainer_kwargs
 
         super().__init__(
@@ -297,7 +307,9 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
             "random_seed": self.random_seed,
             "num_workers_loader": self.num_workers_loader,
             "drop_last_loader": self.drop_last_loader,
-            **self._trainer_kwargs,
+            "optimizer": self.optimizer,
+            "optimizer_kwargs": self.optimizer_kwargs,
+            "trainer_kwargs": self._trainer_kwargs,
         }
 
     @classmethod
@@ -324,6 +336,7 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
 
         try:
             _check_soft_dependencies("neuralforecast", severity="error")
+            _check_soft_dependencies("torch", severity="error")
         except ModuleNotFoundError:
             params = [
                 {
@@ -346,6 +359,7 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
             ]
         else:
             from neuralforecast.losses.pytorch import SMAPE, QuantileLoss
+            from torch.optim import Adam
 
             params = [
                 {
@@ -366,6 +380,8 @@ class NeuralForecastRNN(_NeuralForecastAdapter):
                     "max_steps": 4,
                     "val_check_steps": 2,
                     "trainer_kwargs": {"logger": False},
+                    "optimizer": Adam,
+                    "optimizer_kwargs": {"lr": 0.001},
                 },
             ]
 
@@ -456,6 +472,10 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
         whether `TimeSeriesDataLoader` drops last non-full batch
     trainer_kwargs : dict (default=None)
         keyword trainer arguments inherited from PyTorch Lighning's trainer [6]_
+    optimizer : pytorch optimizer (default=None) [7]_
+        optimizer to use for training, if passed with None defaults to Adam
+    optimizer_kwargs : dict (default=None) [8]_
+        dict of parameters to pass to the user defined optimizer
 
     Notes
     -----
@@ -503,6 +523,8 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
     .. [4] https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
     .. [5] https://nixtlaverse.nixtla.io/neuralforecast/losses.pytorch.html
     .. [6] https://lightning.ai/docs/pytorch/stable/api/pytorch_lightning.trainer.trainer.Trainer.html#lightning.pytorch.trainer.trainer.Trainer
+    .. [7] https://pytorch.org/docs/stable/optim.html
+    .. [8] https://pytorch.org/docs/stable/generated/torch.optim.Adam.html#torch.optim.Adam
     """  # noqa: E501
 
     _tags = {
@@ -549,6 +571,8 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
         num_workers_loader=0,
         drop_last_loader=False,
         trainer_kwargs: Optional[dict] = None,
+        optimizer=None,
+        optimizer_kwargs: dict = None,
     ):
         self.input_size = input_size
         self.inference_input_size = inference_input_size
@@ -572,6 +596,8 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
         self.random_seed = random_seed
         self.num_workers_loader = num_workers_loader
         self.drop_last_loader = drop_last_loader
+        self.optimizer = optimizer
+        self.optimizer_kwargs = optimizer_kwargs
         self.trainer_kwargs = trainer_kwargs
 
         super().__init__(
@@ -649,7 +675,9 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
             "random_seed": self.random_seed,
             "num_workers_loader": self.num_workers_loader,
             "drop_last_loader": self.drop_last_loader,
-            **self._trainer_kwargs,
+            "optimizer": self.optimizer,
+            "optimizer_kwargs": self.optimizer_kwargs,
+            "trainer_kwargs": self._trainer_kwargs,
         }
 
     @classmethod
@@ -672,6 +700,7 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
 
         try:
             _check_soft_dependencies("neuralforecast", severity="error")
+            _check_soft_dependencies("torch", severity="error")
         except ModuleNotFoundError:
             params = [
                 {
@@ -694,6 +723,7 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
             ]
         else:
             from neuralforecast.losses.pytorch import SMAPE, QuantileLoss
+            from torch.optim import Adam
 
             params = [
                 {
@@ -714,6 +744,8 @@ class NeuralForecastLSTM(_NeuralForecastAdapter):
                     "max_steps": 4,
                     "val_check_steps": 2,
                     "trainer_kwargs": {"logger": False},
+                    "optimizer": Adam,
+                    "optimizer_kwargs": {"lr": 0.001},
                 },
             ]
 
diff --git a/sktime/forecasting/tbats.py b/sktime/forecasting/tbats.py
index 91dc4257c..cdf2510f7 100644
--- a/sktime/forecasting/tbats.py
+++ b/sktime/forecasting/tbats.py
@@ -153,4 +153,12 @@ class TBATS(_TbatsAdapter):
             "use_arma_errors": True,
             "n_jobs": 2,
         }
-        return [params1, params2]
+        params3 = {
+            "use_box_cox": False,
+            "use_trend": False,
+            "use_damped_trend": False,
+            "sp": 3,
+            "use_arma_errors": False,
+            "n_jobs": 1,
+        }
+        return [params1, params2, params3]
diff --git a/sktime/networks/lstmfcn.py b/sktime/networks/lstmfcn.py
index b2b80e5c4..991b5c472 100644
--- a/sktime/networks/lstmfcn.py
+++ b/sktime/networks/lstmfcn.py
@@ -123,3 +123,43 @@ class LSTMFCNNetwork(BaseDeepNetwork):
         output_layer = keras.layers.concatenate([x, y])
 
         return input_layer, output_layer
+
+    @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`
+        """
+        params = [
+            # Advanced model version
+            {
+                "kernel_sizes": (8, 5, 3),  # Keep standard kernel sizes
+                "filter_sizes": (128, 256, 128),  # Keep standard kernel counts
+                "lstm_size": 8,
+                "dropout": 0.25,  # Maintain lower dropout rate for attention model
+                "attention": True,
+            },
+            # Simpler model version
+            {
+                "kernel_sizes": (4, 2, 1),  # Reduce kernel sizes
+                "filter_sizes": (32, 64, 32),  # Reduc filter sizes for cheaper model
+                "lstm_size": 8,  # Keeping LSTM output size fixed
+                "dropout": 0.75,  # Maintain higher dropout rate for non attention model
+                "attention": False,
+            },
+            {},
+        ]
+
+        return params
diff --git a/sktime/registry/_lookup.py b/sktime/registry/_lookup.py
index 4952ffc57..1adbcf7b6 100644
--- a/sktime/registry/_lookup.py
+++ b/sktime/registry/_lookup.py
@@ -48,7 +48,7 @@ def all_estimators(
     return_tags=None,
     suppress_import_stdout=True,
 ):
-    """Get a list of all estimators from sktime.
+    """List all estimators or objects in sktime, by scitype or tag.
 
     This function crawls the module and gets all classes that inherit
     from sktime's and sklearn's base classes.
@@ -156,6 +156,7 @@ def all_estimators(
         "plotting",
         "_split",
         "test_split",
+        "registry",
     )
 
     ROOT = str(Path(__file__).parent.parent)  # sktime package root directory
@@ -274,7 +275,7 @@ def all_tags(
     estimator_types=None,
     as_dataframe=False,
 ):
-    """Get a list of all tags from sktime.
+    """List all tags in sktime, for objects of a certain type.
 
     All objects in ``sktime`` are tagged with a set of :term``tag``s.
     This function allows to list all tags, optionally filtered by
diff --git a/sktime/registry/_tags.py b/sktime/registry/_tags.py
index 22933dc08..cdbee7d27 100644
--- a/sktime/registry/_tags.py
+++ b/sktime/registry/_tags.py
@@ -38,23 +38,742 @@ ESTIMATOR_TAG_LIST - list of string
 check_tag_is_valid(tag_name, tag_value) - checks whether tag_value is valid for tag_name
 """
 
-__author__ = ["fkiraly", "victordremov"]
+import inspect
+import sys
 
 import pandas as pd
 
+from sktime.base import BaseObject
+from sktime.registry._base_classes import BASE_CLASS_REGISTER
+
+
+class _BaseTag(BaseObject):
+    """Base class for all tags."""
+
+    _tags = {
+        "object_type": "tag",
+        "tag_name": "fill_this_in",  # name of the tag used in the _tags dictionary
+        "parent_type": "object",  # scitype of the parent object, str or list of str
+        "tag_type": "str",  # type of the tag value
+        "short_descr": "describe the tag here",  # short tag description, max 80 chars
+        "user_facing": True,  # whether the tag is user-facing
+    }
+
+
+# General tags, for all objects
+# -----------------------------
+
+
+class object_type(_BaseTag):
+    """Scientific type of the object.
+
+    Typing tag for all objects in ``sktime``.
+
+    - String name: ``"object_type"``
+    - Public metadata tag
+    - Values:  string or list of strings
+    - Example: ``"forecaster"``
+    - Example 2: ``["transformer", "clusterer"]`` (polymorphic object)
+    - Default: ``"object"``
+
+    In ``sktime``, every object has a scientific type (scitype),
+    determining the type of object and unified interface,
+    e.g., forecaster, time series classifier, time series regressor.
+
+    The ``object_type`` tag of an object is a string, or list of strings,
+    specifying the scitpye of the object.
+    For instance, a forecaster has scitype `"forecaster"`.
+
+    In case of a list, the object is polymorphic, and can assume (class),
+    or simultaneously satisfy different interfaces (object).
+
+    Valid scitypes are defined in ``sktime.registry.BASE_CLASS_SCITYPE_LIST``,
+    or ``sktime.registry.BASE_CLASS_REGISTER``.
+
+    The full list of scitypes in the current version is:
+    """
+
+    _tags = {
+        "tag_name": "object_type",
+        "parent_type": "object",
+        "tag_type": "str",
+        "short_descr": "type of object: estimator, transformer, regressor, etc",
+        "user_facing": True,
+    }
+
+
+# dynamically add a pretty printd list of scitypes to the docstring
+for name, _, desc in BASE_CLASS_REGISTER:
+    object_type.__doc__ += f'\n    - ``"{name}"``: {desc}'
+
+
+class maintainers(_BaseTag):
+    """Current maintainers of the object, GitHub IDs.
+
+    Part of packaging metadata for the object.
+
+    - String name: ``"maintainers"``
+    - Public metadata tag
+    - Values:  string or list of strings
+    - Example: ``["benheid", "fkiraly", "yarnabrina"]``
+    - Example 2: ``"yarnabrina"``
+    - Default: ``"sktime developers"``
+
+    The ``maintainers`` tag of an object is a string or list of strings,
+    each string being a GitHub handle of a maintainer of the object.
+
+    Maintenance extends to the specific class in ``sktime`` only,
+    and not interfaced packages or dependencies.
+
+    Maintainers should be tagged on issues and PRs related to the object,
+    and have rights and responsibilities in accordance with
+    the ``sktime`` governance model, see :ref:`algorithm-maintainers`.
+
+    To find an algorithm's maintainer, use ``get_tag("maintainers")`` on the object,
+    or use the search function in the
+    `estimator overview <https://www.sktime.net/en/stable/estimator_overview.html>`_.
+
+    In case of classes not owned by specific algorithm maintainers,
+    the tag defaults to the ``sktime`` core team.
+
+    Maintainers are given prominent visibility in the object's metadata,
+    and in automatically generated documentation.
+    """
+
+    _tags = {
+        "tag_name": "maintainers",
+        "parent_type": "object",
+        "tag_type": ("list", "str"),
+        "short_descr": "current maintainers of the object, each maintainer a GitHub handle",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+class authors(_BaseTag):
+    """Authors of the object, GitHub IDs.
+
+    Part of packaging metadata for the object.
+
+    - String name: ``"authors"``
+    - Public metadata tag
+    - Values:  string or list of strings
+    - Example: ``["benheid", "fkiraly", "yarnabrina"]``
+    - Example 2: ``"fkiraly"``
+    - Default: ``"sktime developers"``
+
+    The ``authors`` tag of an object is a string or list of strings,
+    each string being a GitHub handle of an author of the object.
+
+    Authors are credited for the original implementation of the object,
+    and contributions to the object.
+
+    In case of light wrappers around third or second party packages,
+    author credits should include authors of the wrapped object.
+
+    Authors are not necessarily maintainers of the object,
+    and do not need to be tagged on issues and PRs related to the object.
+
+    Authors are given prominent visibility in the object's metadata,
+    and in automatically generated documentation.
+
+    To find an algorithm's authors, use ``get_tag("authors")`` on the object,
+    or use the search function in the
+    `estimator overview <https://www.sktime.net/en/stable/estimator_overview.html>`_.
+    """
+
+    _tags = {
+        "tag_name": "authors",
+        "parent_type": "object",
+        "tag_type": ("list", "str"),
+        "short_descr": "list of authors of the object, each author a GitHub handle",
+        "user_facing": True,
+    }
+
+
+class python_version(_BaseTag):
+    """Python version requirement specifier for the object (PEP 440).
+
+    Part of packaging metadata for the object.
+
+    - String name: ``"python_version"``
+    - Private tag, developer and framework facing
+    - Values: PEP 440 compliant version specifier
+    - Example: ``">=3.10"``
+    - Default: no restriction
+
+    ``sktime`` manages objects and estimators like mini-packages,
+    with their own dependencies and compatibility requirements.
+    Dependencies are specified in the tags:
+
+    - ``"python_version"``: Python version specifier (PEP 440) for the object,
+    - ``"python_dependencies"``: list of required Python packages (PEP 440)
+    - ``"python_dependencies_alias"``: alias for package names,
+      if different from import names
+    - ``"env_marker"``: environment marker for the object (PEP 508)
+    - ``"requires_cython"``: whether the object requires a C compiler present
+
+    The ``python_version`` tag of an object is a PEP 440 compliant version specifier
+    string, specifying python version compatibility of the object.
+
+    The tag is used in packaging metadata for the object,
+    and is used internally to check compatibility of the object with
+    the build environment, to raise informative error messages.
+
+    Developers can use ``_check_python_version`` from ``skbase.utils.dependencies``
+    to check compatibility of the python constraint of the object
+    with the current build environment, or
+    ``_check_estimator_deps`` to check compatibility of the object
+    (including further checks) with the current build environment.
+    """
+
+    _tags = {
+        "tag_name": "python_version",
+        "parent_type": "object",
+        "tag_type": "str",
+        "short_descr": "python version specifier (PEP 440) for estimator, or None = all versions ok",  # noqa: E501
+        "user_facing": False,
+    }
+
+
+class python_dependencies(_BaseTag):
+    """Python package dependency requirement specifiers for the object (PEP 440).
+
+    Part of packaging metadata for the object.
+
+    - String name: ``"python_dependencies"``
+    - Private tag, developer and framework facing
+    - Values: str or list of str, each str a PEP 440 compliant dependency specifier
+    - Example: ``"numpy>=1.20.0"``
+    - Example 2: ``["numpy>=1.20.0", "pandas>=1.3.0"]``
+    - Default: no requirements beyond ``sktime`` core dependencies (``None``)
+
+    ``sktime`` manages objects and estimators like mini-packages,
+    with their own dependencies and compatibility requirements.
+    Dependencies are specified in the tags:
+
+    - ``"python_version"``: Python version specifier (PEP 440) for the object,
+    - ``"python_dependencies"``: list of required Python packages (PEP 440)
+    - ``"python_dependencies_alias"``: alias for package names,
+      if different from import names
+    - ``"env_marker"``: environment marker for the object (PEP 508)
+    - ``"requires_cython"``: whether the object requires a C compiler present
+
+    The ``python_dependencies`` tag of an object is string or list of strings,
+    each string a PEP 440 compliant version specifier,
+    specifying python dependency requirements of the object.
+
+    The tag is used in packaging metadata for the object,
+    and is used internally to check compatibility of the object with
+    the build environment, to raise informative error messages.
+
+    Developers should note that package names in PEP 440 specifier strings
+    are identical with the package names used in ``pip install`` commands,
+    which in general is not the same as the import name of the package,
+    e.g., ``"scikit-learn"`` and not ``"sklearn"``.
+
+    Developers can use ``_check_soft_dependencies`` from ``skbase.utils.dependencies``
+    to check compatibility of the python constraint of the object
+    with the current build environment, or
+    ``_check_estimator_deps`` to check compatibility of the object
+    (including further checks) with the current build environment.
+    """
+
+    _tags = {
+        "tag_name": "python_dependencies",
+        "parent_type": "object",
+        "tag_type": ("list", "str"),
+        "short_descr": "python dependencies of estimator as str or list of str (PEP 440)",  # noqa: E501
+        "user_facing": False,
+    }
+
+
+class python_dependencies_alias(_BaseTag):
+    """Alias for Python package dependency names for the object.
+
+    Part of packaging metadata for the object.
+
+    - String name: ``"python_dependencies_alias"``
+    - Private tag, developer and framework facing
+    - Values: dict of str, key = PEP 440 package name, value = import name
+    - Example: ``{"scikit-learn": "sklearn"}``
+    - Example 2: ``{"dtw-python": "dtw", "scikit-learn": "sklearn"}``
+    - Default: no aliases (``None``)
+
+    ``sktime`` manages objects and estimators like mini-packages,
+    with their own dependencies and compatibility requirements.
+    Dependencies are specified in the tags:
+
+    - ``"python_version"``: Python version specifier (PEP 440) for the object,
+    - ``"python_dependencies"``: list of required Python packages (PEP 440)
+    - ``"python_dependencies_alias"``: alias for package names,
+      if different from import names
+    - ``"env_marker"``: environment marker for the object (PEP 508)
+    - ``"requires_cython"``: whether the object requires a C compiler present
+
+    The ``python_dependencies_alias`` tag of an object is dict,
+    providing import name aliases for package names in the ``python_dependencies`` tag,
+    if the package name differs from the import name.
+
+    The tag is used in packaging metadata for the object,
+    and is used internally to check compatibility of the object with
+    the build environment, to raise informative error messages.
+
+    This tag is required if the package name of a dependency is different
+    from the import name of the package, e.g., ``"dtw-python"`` and ``"dtw"``.
+    If not set, the package name is assumed to be identical with the import name.
+
+    Developers should note that elements of this ``dict`` are not passed on
+    via field inheritance, unlike the tags themselves.
+    Hence, if multiple aliases are required, they need to be set in the same tag.
+    """
+
+    _tags = {
+        "tag_name": "python_dependencies_alias",
+        "parent_type": "object",
+        "tag_type": "dict",
+        "short_descr": "alias for package names in python_dependencies, key-value pairs are package name, import name",  # noqa: E501
+        "user_facing": False,
+    }
+
+
+class requires_cython(_BaseTag):
+    """Whether the object requires a C compiler present, such as libomp, gcc.
+
+    Part of packaging metadata for the object.
+
+    - String name: ``"requires_cython"``
+    - Private tag, developer and framework facing
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    ``sktime`` manages objects and estimators like mini-packages,
+    with their own dependencies and compatibility requirements.
+    Dependencies are specified in the tags:
+
+    - ``"python_version"``: Python version specifier (PEP 440) for the object,
+    - ``"python_dependencies"``: list of required Python packages (PEP 440)
+    - ``"python_dependencies_alias"``: alias for package names,
+      if different from import names
+    - ``"env_marker"``: environment marker for the object (PEP 508)
+    - ``"requires_cython"``: whether the object requires a C compiler present
+
+    The ``requires_cython`` tag of an object is a boolean,
+    specifying whether the object requires a C compiler present.
+    True means that a C compiler is required, False means it is not required.
+
+    The tag is used in packaging metadata for the object,
+    and primarily in the continuous integration and testing setup of the ``sktime``
+    package, which ensures that objects with this tag are
+    tested in specific environments with a C compiler present.
+
+    It is not used in user facing checks, error messages,
+    or recommended build processes otherwise.
+    """
+
+    _tags = {
+        "tag_name": "requires_cython",
+        "parent_type": "object",
+        "tag_type": "bool",
+        "short_descr": "whether the object requires a C compiler present such as libomp, gcc",  # noqa: E501
+        "user_facing": False,
+    }
+
+
+# Estimator tags
+# --------------
+
+# These tags are applicable to a wide range of objects,
+# most tags in this group apply to estimators
+
+# "capability:missing_values" is same as "handles-missing-data" tag.
+# They are kept distinct intentionally for easier TSC refactoring.
+# Will be merged after refactor completion.
+
+
+class capability__missing_values(_BaseTag):
+    """Capability: the estimator can handle missing data, e.g,, NaNs.
+
+    - String name: ``"capability:missing_values"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+    - Alias: ``handles-missing-data``  (forecasters, transformations)
+
+    If the tag is ``True``, the estimator can handle missing data,
+    e.g., NaNs in input data.
+
+    This applies to main and secondary input data where applicable,
+    e.g., ``X`` in ``fit`` of transformations and classifieres, or ``y`` in forecasters,
+    but not to target labels
+    in the case of labelling of entire time series, such as in
+    classification or regression.
+
+    If the tag is ``False``, the estimator cannot handle missing data,
+    and will raise an error if missing data is encountered.
+    """
+
+    _tags = {
+        "tag_name": "capability:missing_values",
+        "parent_type": "object",
+        "tag_type": "bool",
+        "short_descr": "can the estimator handle missing data (NA, np.nan) in inputs?",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+class capability__feature_importance(_BaseTag):
+    """Capability: the estimator can provide feature importance.
+
+    - String name: ``"capability:feature_importance"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    If the tag is ``True``, the estimator can produce feature importances.
+
+    Feature importances are queriable by the fitted parameter interface
+    via ``get_fitted_params``, after calling ``fit`` of the respective estimator.
+
+    If the tag is ``False``, the estimator does not produce feature importances.
+    The method ``get_fitted_params`` can be called,
+    but the list of fitted parameters will not contain feature importances.
+    """
+
+    _tags = {
+        "tag_name": "capability:feature_importance",
+        "parent_type": "estimator",
+        "tag_type": "bool",
+        "short_descr": "Can the estimator provide feature importance?",
+        "user_facing": True,
+    }
+
+
+class capability__contractable(_BaseTag):
+    """Capability: the estimator can be asked to satisfy a maximum time contract.
+
+    To avoid confusion, users should note that the literature term "contractable"
+    unusually derives its meaning from "contract", i.e., a time contract,
+    and not from "contraction", i.e., reducing the size of something.
+
+    - String name: ``"capability:contractable"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    If the tag is ``True``, the estimator can be contracted in time,
+    by using some of its parameters, to limit the maximum time spent in fitting.
+
+    Currently, there is no unified naming or selection of parameters controlling
+    the contract time setting, as this can apply to different parts of the algorithm.
+    Users should consult the documentation of the specific estimator for details.
+
+    If the tag is ``False``, the estimator does not support a contract time setting.
+    """
+
+    _tags = {
+        "tag_name": "capability:contractable",
+        "parent_type": "estimator",
+        "tag_type": "bool",
+        "short_descr": "contract time setting, does the estimator support limiting max fit time?",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+class capability__train_estimate(_BaseTag):
+    """Capability: the algorithm can estimate its performance on the training set.
+
+    - String name: ``"capability:train_estimate"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    If the tag is ``True``, the estimator can estimate its performance on
+    the training set.
+
+    More precisely, this tag describes algorithms that, when calling ``fit``,
+    produce and store an estimate of their own statistical performance,
+    e.g., via out-of-bag estimates, or cross-validation.
+
+    Training performance estimates are queriable by the fitted parameter interface
+    via ``get_fitted_params``, after calling ``fit`` of the respective estimator.
+
+    If the tag is ``False``, the estimator does not produce
+    training performance estimates.
+    The method ``get_fitted_params`` can be called,
+    but the list of fitted parameters will not contain training performance estimates.
+    """
+
+    _tags = {
+        "tag_name": "capability:train_estimate",
+        "parent_type": "estimator",
+        "tag_type": "bool",
+        "short_descr": "can the estimator estimate its performance on the training set?",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+# Forecasters
+# -----------
+
+
+class capability__exogeneous(_BaseTag):
+    """Capability: the forecaster can use exogeneous data.
+
+    The tag is currently named ``ignores-exogeneous-X``, and will be renamed.
+
+    ``False`` = does use exogeneous data, ``True`` = does not use exogeneous data.
+
+    - String name: ``"ignores-exogeneous-X"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+    - Alias: ``capability:exogeneous`` (currently not used)
+
+    Exogeneous data are additional time series,
+    that can be used to improve forecasting accuracy.
+
+    If the forecaster uses exogeneous data (``ignore-exogeneous-X=False``),
+    the ``X`` parmameter in ``fit``, ``predict``, and other methods
+    can be used to pass exogeneous data to the forecaster.
+
+    If the ``X-y-must-have-same-index`` tag is ``True``,
+    then such data must always have an index that contains that of the target series,
+    i.e., ``y`` in ``fit``, or the indices specified by ``fh`` in ``predict``.
+
+    If the tag is ``False``, the forecaster does not make use of exogeneous data.
+    ``X`` parameters can still be passed to methods, to ensure a uniform interface,
+    but the data will be ignored,
+    i.e., not used in the internal logic of the forecaster.
+
+    """
+
+    _tags = {
+        "tag_name": "ignores-exogeneous-X",
+        "parent_type": "forecaster",
+        "tag_type": "bool",
+        "short_descr": "does forecaster make use of exogeneous data?",
+        "user_facing": True,
+    }
+
+
+class capability__insample(_BaseTag):
+    """Capability: the forecaster can make in-sample predictions.
+
+    - String name: ``"capability:insample"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    If the tag is ``True``, the forecaster can make in-sample predictions,
+    i.e., predict the target series for time points that are part of the training set.
+
+    In-sample predictions are useful for model evaluation,
+    and for making predictions for the training set itself.
+
+    Mechanically, in-sample predictions are made by calling the ``predict`` method
+    and specifying a forecasting horizon ``fh`` such that at least one index
+    is queried that is equal or earlier to the latest index in the training set,
+    i.e., any data previously passed in ``fit`` or ``update``.
+
+    If the tag is ``False``, the forecaster cannot make in-sample predictions,
+    and will raise an error if an in-sample prediction is attempted.
+    """
+
+    _tags = {
+        "tag_name": "capability:insample",
+        "parent_type": "forecaster",
+        "tag_type": "bool",
+        "short_descr": "can the forecaster make in-sample predictions?",
+        "user_facing": True,
+    }
+
+
+class capability__pred_int(_BaseTag):
+    """Capability: the forecaster can make probabilistic or interval forecasts.
+
+    - String name: ``"capability:pred_int"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    ``sktime`` supports a range of ways to make probabilistic type forecasts,
+    via the following methods of any forecaster:
+
+    * ``predict_interval``: prediction intervals
+    * ``predict_quantiles``: quantile forecasts
+    * ``predict_var``: variance forecasts
+    * ``predict_proba``: distribution forecasts
+
+    If the ``capability:pred_int`` tag is ``True``, the forecaster can make
+    probabilistic type forecasts using all of the above methods.
+
+    Even if the forecaster natively implements only one of the above methods,
+    all are available to the user:
+
+    * interval and quantile forecasts are of equivalent information,
+      with intervals of a coverage assumed symmetric
+    * a forecaster with available distribution forecasts obtains
+      prediction intervals and quantile forecasts from the distribution
+    * a forecaster with available variance forecasts assumes a normal distribution
+      around the ``predict`` output as mean,
+      and derives prediction intervals and quantiles
+      from that normal distribution
+    * a forecaster with available interval or quantile uses the IQR to
+      to derive variance forecasts under normality assumptions.
+      Users should note that this may lead to a distribution forecast which
+      is not consistent with interval or quantile forecasts.
+
+    If the tag is ``False``, the forecaster cannot make probabilistic forecasts,
+    and will raise an error if a probabilistic forecast is attempted.
+    """
+
+    _tags = {
+        "tag_name": "capability:pred_int",
+        "parent_type": "forecaster",
+        "tag_type": "bool",
+        "short_descr": "does the forecaster implement predict_interval or predict_quantiles?",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+class capability__pred_int__insample(_BaseTag):
+    """Capability: the forecaster can make in-sample probabilistic forecasts.
+
+    Only relevant if the ``capability:pred_int`` tag is ``True``,
+    i.e., the forecaster can make probabilistic forecasts.
+
+    - String name: ``"capability:pred_int:insample"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``False``
+    - Default: ``True``
+
+    If the tag is ``True``, the forecaster can make
+    its probabilistic forecasts in-sample, i.e.,
+    ``predict_interval``, ``predict_quantiles``, ``predict_var``,
+    or ``predict_proba`` can be called with a forecasting horizon ``fh``
+    that includes in-sample indices, i.e., indices that are not later than
+    the latest index in the training set.
+
+    If the tag ``capability:pred_int`` is ``False``,
+    then the tag ``capability:pred_int:insample`` is irrelevant,
+    as the forecaster cannot make probabilistic forecasts at all.
+    In such a case, the tag ``capability:pred_int:insample`` should be ignored.
+
+    If the tag ``capability:pred_int`` is ``True``,
+    and is the tag ``capability:pred_int:insample`` is ``False``,
+    the forecaster can make probabilistic forecasts that are out-of-sample,
+    but cannot make in-sample probabilistic forecasts,
+    and will raise an error if an in-sample probabilistic forecast is attempted.
+    """
+
+    _tags = {
+        "tag_name": "capability:pred_int:insample",
+        "parent_type": "forecaster",
+        "tag_type": "bool",
+        "short_descr": "can the forecaster make in-sample predictions in predict_interval/quantiles?",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+# Panel data related tags
+# -----------------------
+
+# tags related to panel data, typically:
+# classification, regression, clustering, and transformations
+
+
+class capability__multivariate(_BaseTag):
+    """Capability: the estimator can handle multivariate time series.
+
+    - String name: ``"capability:multivariate"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    If the tag is ``True``, the estimator can handle multivariate time series,
+    for its main input data, i.e., the ``X`` parameter in ``fit`` of classifiers,
+    regressors, clusterers, ordinary transformers, and pairwise transformers.
+
+    If the tag is ``False``, the estimator can only handle univariate time series,
+    and will broadcast to variables (ordinary transformers), or raise an error (others).
+
+    This condition is specific to the main input data representation,
+    target data (e.g., classifier or transformation ``y``) are not considered.
+
+    The condition is also specific to the data type used, in terms of how
+    being "multivariate" is represented.
+    For instance, a ``pandas`` based time series specification is considered
+    multivariate if it has more than one column.
+    """
+
+    _tags = {
+        "tag_name": "capability:multivariate",
+        "parent_type": [
+            "classifier",
+            "clusterer",
+            "early_classifier",
+            "param_est",
+            "regressor",
+            "transformer-pairwise",
+            "transformer-pairwise-panel",
+        ],
+        "tag_type": "bool",
+        "short_descr": "can the estimator be applied to time series with 2 or more variables?",  # noqa: E501
+        "user_facing": True,
+    }
+
+
+class capability__unequal_length(_BaseTag):
+    """Capability: the estimator can handle unequal length time series.
+
+    - String name: ``"capability:unequal_length"``
+    - Public capability tag
+    - Values: boolean, ``True`` / ``False``
+    - Example: ``True``
+    - Default: ``False``
+
+    Tag applicable to estimators which can accept panel data,
+    i.e., collections of time series.
+
+    If the tag is ``True``, the estimator can handle panels of time series
+    with unequal index set, including panels of time series with
+    unequal length.
+
+    It should be noted that the capability implied by the tag is
+    strictly more general than the capability implied by the name of the tag,
+    as panels of time series of equal length can have unequal index sets.
+
+    If the tag is ``False``, the estimator requires all time series in the panel
+    to have equal length and index set, and will otherwise raise an error.
+    """
+
+    _tags = {
+        "tag_name": "capability:unequal_length",
+        "parent_type": [
+            "classifier",
+            "clusterer",
+            "early_classifier",
+            "regressor",
+            "transformer",
+            "transformer-pairwise-panel",
+        ],
+        "tag_type": "bool",
+        "short_descr": "can the estimator handle unequal length time series?",
+        "user_facing": True,
+    }
+
+
 ESTIMATOR_TAG_REGISTER = [
-    (
-        "object_type",
-        "object",
-        "str",
-        "type of object: estimator, transformer, regressor, etc",
-    ),
-    (
-        "ignores-exogeneous-X",
-        "forecaster",
-        "bool",
-        "does forecaster ignore exogeneous data (X)?",
-    ),
     (
         "univariate-only",
         "transformer",
@@ -221,30 +940,12 @@ ESTIMATOR_TAG_REGISTER = [
         "bool",
         "whether inverse_transform is expected to be an exact inverse to transform",
     ),
-    (
-        "capability:pred_int",
-        "forecaster",
-        "bool",
-        "does the forecaster implement predict_interval or predict_quantiles?",
-    ),
     (
         "capability:pred_var",
         "forecaster",
         "bool",
         "does the forecaster implement predict_variance?",
     ),
-    (
-        "capability:insample",
-        "forecaster",
-        "bool",
-        "can the forecaster make in-sample predictions?",
-    ),
-    (
-        "capability:pred_int:insample",
-        "forecaster",
-        "bool",
-        "can the forecaster make in-sample predictions in predict_interval/quantiles?",
-    ),
     (
         "capability:predict_proba",
         "classifier",
@@ -252,53 +953,6 @@ ESTIMATOR_TAG_REGISTER = [
         "does the classifier implement a non-default predict_proba, "
         "i.e., not just 0/1 probabilities obtained from predict?",
     ),
-    (
-        "capability:feature_importance",
-        "classifier",
-        "bool",
-        "Can the estimator provide feature importance?",
-    ),
-    (
-        "capability:multivariate",
-        [
-            "classifier",
-            "early_classifier",
-            "param_est",
-            "regressor",
-            "transformer-pairwise",
-            "transformer-pairwise-panel",
-        ],
-        "bool",
-        "can the classifier classify time series with 2 or more variables?",
-    ),
-    (
-        "capability:unequal_length",
-        [
-            "classifier",
-            "early_classifier",
-            "regressor",
-            "transformer",
-            "transformer-pairwise-panel",
-        ],
-        "bool",
-        "can the estimator handle unequal length time series?",
-    ),
-    # "capability:missing_values" is same as "handles-missing-data" tag.
-    # They are kept distinct intentionally for easier TSC refactoring.
-    # Will be merged after refactor completion.
-    (
-        "capability:missing_values",
-        [
-            "classifier",
-            "early_classifier",
-            "param_est",
-            "regressor",
-            "transformer-pairwise",
-            "transformer-pairwise-panel",
-        ],
-        "bool",
-        "can the classifier handle missing data (NA, np.nan) in inputs?",
-    ),
     (
         "capability:unequal_length:removes",
         "transformer",
@@ -311,18 +965,6 @@ ESTIMATOR_TAG_REGISTER = [
         "bool",
         "is the transformer result guaranteed to have no missing values?",
     ),
-    (
-        "capability:train_estimate",
-        "classifier",
-        "bool",
-        "can the classifier estimate its performance on the training set?",
-    ),
-    (
-        "capability:contractable",
-        "classifier",
-        "bool",
-        "contract time setting, does the estimator support limiting max fit time?",
-    ),
     (
         "capability:multithreading",
         ["classifier", "early_classifier"],
@@ -407,31 +1049,6 @@ ESTIMATOR_TAG_REGISTER = [
         "bool",
         "whether inner _evaluate can deal with multilevel (Panel/Hierarchical)",
     ),
-    (
-        "python_version",
-        "estimator",
-        "str",
-        "python version specifier (PEP 440) for estimator, or None = all versions ok",
-    ),
-    (
-        "python_dependencies",
-        "estimator",
-        ("list", "str"),
-        "python dependencies of estimator as str or list of str",
-    ),
-    (
-        "python_dependencies_alias",
-        "estimator",
-        "dict",
-        "should be provided if import name differs from package name, \
-        key-value pairs are package name, import name",
-    ),
-    (
-        "requires_cython",
-        "estimator",
-        "bool",
-        "whether the estimator requires a C compiler present such as libomp, gcc",
-    ),
     (
         "remember_data",
         ["forecaster", "transformer"],
@@ -522,20 +1139,24 @@ ESTIMATOR_TAG_REGISTER = [
         "bool",
         "can the estimator handle multioutput data?",
     ),
-    (
-        "maintainers",
-        "object",
-        ("list", "str"),
-        "list of current maintainers of the object, each maintainer a GitHub handle",
-    ),
-    (
-        "authors",
-        "object",
-        ("list", "str"),
-        "list of authors of the object, each author a GitHub handle",
-    ),
 ]
 
+# construct the tag register from all classes in this module
+tag_clses = inspect.getmembers(sys.modules[__name__], inspect.isclass)
+for _, cl in tag_clses:
+    # skip the base class
+    if cl.__name__ == "_BaseTag" or not issubclass(cl, _BaseTag):
+        continue
+
+    cl_tags = cl.get_class_tags()
+
+    tag_name = cl_tags["tag_name"]
+    parent_type = cl_tags["parent_type"]
+    tag_type = cl_tags["tag_type"]
+    short_descr = cl_tags["short_descr"]
+
+    ESTIMATOR_TAG_REGISTER.append((tag_name, parent_type, tag_type, short_descr))
+
 ESTIMATOR_TAG_TABLE = pd.DataFrame(ESTIMATOR_TAG_REGISTER)
 ESTIMATOR_TAG_LIST = ESTIMATOR_TAG_TABLE[0].tolist()
 
diff --git a/sktime/utils/seasonality.py b/sktime/utils/seasonality.py
index e91856798..8e5ce1769 100644
--- a/sktime/utils/seasonality.py
+++ b/sktime/utils/seasonality.py
@@ -111,12 +111,8 @@ def _pivot_sp(df, sp, anchor=None, freq=None, anchor_side="start"):
     if freq is None and hasattr(anchor.index, "freq"):
         freq = anchor.index.freq
 
-    if isinstance(df.index, pd.DatetimeIndex):
-        df = df.copy()
-        df.index = df.index.to_period(freq=freq)
-        was_datetime = True
-    else:
-        was_datetime = False
+    # Make df to period index if it datetime index
+    df, was_datetime = _make_period_index_df(df, freq)
 
     if isinstance(anchor.index, pd.DatetimeIndex):
         aix = anchor.index.to_period(freq=freq)
@@ -124,11 +120,14 @@ def _pivot_sp(df, sp, anchor=None, freq=None, anchor_side="start"):
         aix = anchor.index
     aix_int = aix.astype("int64")
 
+    if pd.api.types.is_integer_dtype(anchor.index) or len(anchor) <= 1:
+        period_len = 1
+        period_len_int = 1
+    else:
+        period_len = anchor.index[1] - anchor.index[0]
+        period_len_int = aix_int[1] - aix_int[0]
+
     if not isinstance(df.index, pd.PeriodIndex):
-        if pd.api.types.is_integer_dtype(anchor.index) and len(anchor) <= 1:
-            period_len = 1
-        else:
-            period_len = anchor.index[1] - anchor.index[0]
         if anchor_side == "start":
             ix = (df.index - anchor.index[0]) / period_len
         else:
@@ -145,8 +144,8 @@ def _pivot_sp(df, sp, anchor=None, freq=None, anchor_side="start"):
     df = pd.DataFrame(df)
     df_pivot = pd.pivot_table(
         data=df,
-        index=ix // sp,  # Upper level
-        columns=ix % sp,  # Lower level
+        index=ix // (sp * period_len_int),  # Upper level
+        columns=ix % (sp * period_len_int),  # Lower level
         dropna=False,
     )
 
@@ -159,13 +158,25 @@ def _pivot_sp(df, sp, anchor=None, freq=None, anchor_side="start"):
     df_pivot.index = pivot_ix
 
     if was_datetime:
-        df_pivot.index = df_pivot.index.to_timestamp()
+        df_pivot.index = df_pivot.index.to_timestamp(
+            freq=anchor.index.freq
+        ).tz_localize(anchor.index.tz)
 
     df_pivot.columns = df_pivot.columns.droplevel(0)
 
     return df_pivot
 
 
+def _make_period_index_df(df, freq):
+    if isinstance(df.index, pd.DatetimeIndex):
+        df = df.copy()
+        df.index = df.index.to_period(freq=freq)
+        was_datetime = True
+    else:
+        was_datetime = False
+    return df, was_datetime
+
+
 def _unpivot_sp(df, template=None):
     """Unpivot DataFrame with multivariate-by-seasonal-offset, invert _pivot_sp.
 
@@ -193,24 +204,36 @@ def _unpivot_sp(df, template=None):
             freq = template.freq
         else:
             freq = template
-
+    else:
+        freq = None
+    if isinstance(df.index, pd.DatetimeIndex):
+        tz = df.index.tz
+    else:
+        tz = None
+    df_copied, _ = _make_period_index_df(template, freq)
+    ix = df_copied.index.astype("int64")
     df_melt = df.melt(ignore_index=False)
 
+    if len(ix) <= 1:
+        period_len_int = 1
+    else:
+        period_len_int = ix[1] - ix[0]
+
     offset = df_melt[df_melt.columns[0]]
     if isinstance(df_melt.index, pd.DatetimeIndex):
         a = df_melt.index.to_period(freq=freq)
-        res = a + offset
+        res = a + offset // period_len_int
         df_melt.index = res
         was_datetime = True
     else:
-        df_melt.index = df_melt.index + offset
+        df_melt.index = df_melt.index + offset // period_len_int
         was_datetime = False
     df_melt = df_melt.drop(columns=df_melt.columns[0])
     df_melt = df_melt.sort_index()
     df_melt = df_melt.dropna()
 
     if was_datetime:
-        df_melt.index = df_melt.index.to_timestamp()
+        df_melt.index = df_melt.index.to_timestamp(template.index.freq).tz_localize(tz)
 
     if template is not None:
         if hasattr(template, "columns"):


Test patch:
diff --git a/sktime/forecasting/tests/test_naive.py b/sktime/forecasting/tests/test_naive.py
index 8b39121d4..96df16d64 100644
--- a/sktime/forecasting/tests/test_naive.py
+++ b/sktime/forecasting/tests/test_naive.py
@@ -403,6 +403,22 @@ def test_naive_predict_interval_against_R_naive(strategy, sp, lower, upper):
     pd.testing.assert_frame_equal(y_pred_ints, expected)
 
 
+@pytest.mark.parametrize("freq", ["2D", "W", "W-TUE", "M"])
+def test_naive_sp_greater_1_not_nan(freq):
+    sample_dates = pd.date_range(start="2001-01-01", periods=30, freq=freq)
+    sample_values = np.random.default_rng(seed=0).random(size=len(sample_dates))
+
+    sample_dataset = pd.Series(sample_values, index=sample_dates)
+
+    model = NaiveForecaster(sp=2)
+    model.fit(sample_dataset)
+
+    predictions: pd.Series = model.predict(fh=[1, 2, 3])
+    null_predictions_count = predictions.isna().sum()
+
+    assert null_predictions_count == 0
+
+
 def test_insample_with_numpy_input():
     """Test insample prediction with numpy input."""
     y = np.random.random(1000)
diff --git a/sktime/utils/_testing/scenarios_clustering.py b/sktime/utils/_testing/scenarios_clustering.py
index 42921d3a7..c687432a8 100644
--- a/sktime/utils/_testing/scenarios_clustering.py
+++ b/sktime/utils/_testing/scenarios_clustering.py
@@ -7,7 +7,11 @@ __author__ = ["fkiraly"]
 
 __all__ = ["scenarios_clustering"]
 
+from inspect import isclass
+
 from sktime.base import BaseObject
+from sktime.registry import scitype
+from sktime.utils._testing.hierarchical import _make_hierarchical
 from sktime.utils._testing.panel import _make_panel_X, make_clustering_problem
 from sktime.utils._testing.scenarios import TestScenario
 
@@ -43,11 +47,52 @@ class ClustererTestScenario(TestScenario, BaseObject):
 
         return super().get_args(key=key, obj=obj, deepcopy_args=deepcopy_args)
 
+    def is_applicable(self, obj):
+        """Check whether scenario is applicable to obj.
+
+        Parameters
+        ----------
+        obj : class or object to check against scenario
+
+        Returns
+        -------
+        applicable: bool
+            True if self is applicable to obj, False if not
+        """
+
+        def get_tag(obj, tag_name):
+            if isclass(obj):
+                return obj.get_class_tag(tag_name)
+            else:
+                return obj.get_tag(tag_name)
+
+        # applicable only if obj inherits from BaseClassifier, BaseEarlyClassifier or
+        #   BaseRegressor. currently we test both classifiers and regressors using these
+        #   scenarios
+        if scitype(obj) != "clusterer":
+            return False
+
+        # if X is multivariate, applicable only if can handle multivariate
+        is_multivariate = not self.get_tag("X_univariate")
+        if is_multivariate and not get_tag(obj, "capability:multivariate"):
+            return False
+
+        # if X is unequal length, applicable only if can handle unequal length
+        is_unequal_length = self.get_tag("X_unequal_length")
+        if is_unequal_length and not get_tag(obj, "capability:unequal_length"):
+            return False
+
+        return True
+
 
 class ClustererFitPredict(ClustererTestScenario):
     """Fit/predict with panel Xmake_clustering_problem."""
 
-    _tags = {"X_univariate": True, "is_enabled": True}
+    _tags = {
+        "X_univariate": True,
+        "X_unequal_length": False,
+        "is_enabled": True,
+    }
 
     @property
     def args(self):
@@ -59,6 +104,39 @@ class ClustererFitPredict(ClustererTestScenario):
     default_method_sequence = ["fit", "predict"]
 
 
+class ClustererFitPredictUnequalLength(ClustererTestScenario):
+    """Fit/predict with univariate panel X, unequal length series."""
+
+    _tags = {
+        "X_univariate": True,
+        "X_unequal_length": True,
+        "is_enabled": True,
+    }
+
+    @property
+    def args(self):
+        X_unequal_length = _make_hierarchical(
+            hierarchy_levels=(10,),
+            min_timepoints=10,
+            max_timepoints=15,
+            random_state=RAND_SEED,
+        )
+        X_unequal_length_test = _make_hierarchical(
+            hierarchy_levels=(5,),
+            min_timepoints=10,
+            max_timepoints=15,
+            random_state=RAND_SEED,
+        )
+        return {
+            "fit": {"X": X_unequal_length},
+            "predict": {"X": X_unequal_length_test},
+        }
+
+    default_method_sequence = ["fit", "predict", "predict_proba", "decision_function"]
+    default_arg_sequence = ["fit", "predict", "predict", "predict"]
+
+
 scenarios_clustering = [
     ClustererFitPredict,
+    ClustererFitPredictUnequalLength,
 ]