Repository: sktime/sktime
Pull request #6155 (2024-03-17 19:41:53)

[ENH] added test params to `RNNNetwork` on 3429
#### What does this implement/fix? Explain your changes.
<!--
A clear and concise description of what you have implemented.
-->

Implemented the standard 'get_test_params' class method with the appropriate docstring and applicable parameters.

Added a couple test params for `RNNNetwork` contributing towards #3429. One test param that covers the default set and another that covers the 'units' parameter.

One thing I noticed when going through the docstring for the `RNNNetwork` class is that there seems to be a missing reference [1] inside.

This is my first pull request ever so please leave me some feedback! :) 

#### PR checklist
<!--
Please go through the checklist below. Please feel free to remove points if they are not applicable.
-->

##### For all contributions
- [ ] I've added myself to the [list of contributors](https://github.com/sktime/sktime/blob/main/CONTRIBUTORS.md) with any new badges I've earned :-)
  How to: add yourself to the [all-contributors file](https://github.com/sktime/sktime/blob/main/.all-contributorsrc) in the `sktime` root directory (not the `CONTRIBUTORS.md`). Common badges: `code` - fixing a bug, or adding code logic. `doc` - writing or improving documentation or docstrings. `bug` - reporting or diagnosing a bug (get this plus `code` if you also fixed the bug in the PR).`maintenance` - CI, test framework, release.
  See here for [full badge reference](https://allcontributors.org/docs/en/emoji-key)
- [ ] Optionally, I've added myself and possibly others to the [CODEOWNERS](https://github.com/sktime/sktime/blob/main/CODEOWNERS) file - do this if you want to become the owner or maintainer of an estimator you added.
  See here for further details on the [algorithm maintainer role](https://www.sktime.net/en/latest/get_involved/governance.html#algorithm-maintainers).
- [x] The PR title starts with either [ENH], [MNT], [DOC], or [BUG]. [BUG] - bugfix, [MNT] - CI, test framework, [ENH] - adding or improving code, [DOC] - writing or improving documentation or docstrings.

##### For new estimators
- [ ] I've added the estimator to the API reference - in `docs/source/api_reference/taskname.rst`, follow the pattern.
- [ ] I've added one or more illustrative usage examples to the docstring, in a pydocstyle compliant `Examples` section.
- [ ] If the estimator relies on a soft dependency, I've set the `python_dependencies` tag and ensured
  dependency isolation, see the [estimator dependencies guide](https://www.sktime.net/en/latest/developer_guide/dependencies.html#adding-a-soft-dependency).

<!--
Thanks for contributing!
-->



Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 81384dc23..e9ae4e968 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -2607,6 +2607,17 @@
         "code"
       ]
     },
+    {
+      "login": "ShreeshaM07",
+      "name": "Shreesha M",
+      "avatar_url": "https://avatars.githubusercontent.com/u/120820143?s=400&v=4",
+      "profile": "https://github.com/ShreeshaM07",
+      "contributions": [
+        "bug",
+        "code",
+        "test"
+      ]
+    },
     {
       "login": "geetu040",
       "name": "Armaghan",
@@ -2645,6 +2656,26 @@
         "maintenance"
       ]
     },
+    {
+      "login": "ianspektor",
+      "name": "Ian Spektor",
+      "avatar_url": "https://avatars.githubusercontent.com/u/49082859?v=4",
+      "profile": "https://github.com/ianspektor",
+      "contributions": [
+        "code",
+        "doc"
+      ]
+    },
+    {
+      "login": "javiber",
+      "name": "Javier Berneche",
+      "avatar_url": "https://avatars.githubusercontent.com/u/3588715?v=4",
+      "profile": "https://github.com/javiber",
+      "contributions": [
+        "code",
+        "doc"
+      ]
+    },
     {
       "login": "fnhirwa",
       "name": "Felix Hirwa Nshuti",
@@ -2654,6 +2685,15 @@
         "code",
         "maintenance"
       ]
+    },
+    {
+      "login": "vandit98",
+      "name": "Vandit Tyagi",
+      "avatar_url": "https://avatars.githubusercontent.com/u/91458535?v=4",
+      "profile": "https://github.com/vandit98",
+      "contributions": [
+        "doc"
+      ]
     }
   ]
 }
diff --git a/.github/workflows/update_contributors.yml b/.github/workflows/update_contributors.yml
index 9f9a91c2d..be2743678 100644
--- a/.github/workflows/update_contributors.yml
+++ b/.github/workflows/update_contributors.yml
@@ -1,7 +1,9 @@
 name: Update Contributors
 
 on:
-  push:
+  pull_request:
+    branches:
+      - main
     paths:
       - '.all-contributorsrc'
 
diff --git a/docs/source/api_reference/transformations.rst b/docs/source/api_reference/transformations.rst
index 569762e95..1a2388973 100644
--- a/docs/source/api_reference/transformations.rst
+++ b/docs/source/api_reference/transformations.rst
@@ -148,6 +148,7 @@ Shapelets, wavelets, and convolution
 
     ShapeletTransform
     RandomShapeletTransform
+    ShapeletTransformPyts
 
 .. currentmodule:: sktime.transformations.panel.rocket
 
@@ -631,6 +632,20 @@ These transformers subset `X` by time points (`pandas` index or index level) or
     ColumnSelect
     IndexSubset
 
+Adapters to other frameworks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Generic framework adapters that expose other frameworks in the ``sktime`` interface.
+
+.. currentmodule:: sktime.transformations.series.temporian
+
+.. autosummary::
+    :toctree: auto_generated/
+    :template: class.rst
+
+    TemporianTransformer
+
+
 Panel transformers
 ------------------
 
diff --git a/docs/source/developer_guide.rst b/docs/source/developer_guide.rst
index 2b3cbaea7..90ef47026 100644
--- a/docs/source/developer_guide.rst
+++ b/docs/source/developer_guide.rst
@@ -14,7 +14,7 @@ Welcome to sktime's developer guide!
 
 New developers should:
 
-* sign up to the developer Discord (see link in README) and say hello in the ``#contributors`` channel
+* sign up to the developer Discord (see link in README) and say hello in the ``#intro`` channel
 * install a development version of ``sktime``, see :ref:`installation`
 * set up CI tests locally and ensure they know how to check them remotely, see :ref:`continuous_integration`
 * get familiar with the git workflow (:ref:`git_workflow`) and coding standards (:ref:`coding_standards`)
diff --git a/docs/source/get_involved/contributing.rst b/docs/source/get_involved/contributing.rst
index 705e81dfe..e7eb30c94 100644
--- a/docs/source/get_involved/contributing.rst
+++ b/docs/source/get_involved/contributing.rst
@@ -24,7 +24,7 @@ and people who are looking to learn and develop their skills.
 
 Recommended steps for first time contributors, or to get started with regular contributions:
 
-1. Say hello (in the lobby on `Discord`_)!
+1. Say hello (in the ``#intro`` channel on `Discord`_)!
 2. Get set up for development, see `instructions in the developer guide <https://www.sktime.net/en/stable/developer_guide.html>`_.
 3. Pick a good first issue to work on, see a collection `in this summary issue <https://github.com/sktime/sktime/issues/1147>`_ , or from `this list <https://github.com/sktime/sktime/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22>`_
    suggestion: pick something small with simple content to learn the "process"
diff --git a/pyproject.toml b/pyproject.toml
index d8897bac4..258f292fc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -108,6 +108,7 @@ all_extras = [
   "statsmodels>=0.12.1",
   'stumpy>=1.5.1; python_version < "3.11"',
   'tbats>=1.1; python_version < "3.12"',
+  'temporian>=0.7.0; python_version < "3.12" and sys_platform != "win32"',
   'tensorflow<2.17,>=2; python_version < "3.12"',
   'tsfresh>=0.17; python_version < "3.12"',
   'tslearn<0.7.0,!=0.6.0,>=0.5.2; python_version < "3.11"',
@@ -145,6 +146,7 @@ all_extras_pandas2 = [
   "statsmodels>=0.12.1",
   'stumpy>=1.5.1; python_version < "3.11"',
   'tbats>=1.1; python_version < "3.12"',
+  'temporian>=0.7.0; python_version < "3.12" and sys_platform != "win32"',
   'tensorflow<2.17,>=2; python_version < "3.12"',
   'tsfresh>=0.17; python_version < "3.12"',
   'tslearn<0.7.0,!=0.6.0,>=0.5.2; python_version < "3.11"',
@@ -210,6 +212,7 @@ transformations = [
   "pykalman-bardo<0.10,>=0.9.7",
   "statsmodels<0.15,>=0.12.1",
   'stumpy<1.13,>=1.5.1; python_version < "3.12"',
+  'temporian>=0.7.0; python_version < "3.12" and sys_platform != "win32"',
   'tsfresh<0.21,>=0.17; python_version < "3.12"',
 ]
 
diff --git a/sktime/classification/deep_learning/resnet.py b/sktime/classification/deep_learning/resnet.py
index f85fcf7f0..44c3ef4c5 100644
--- a/sktime/classification/deep_learning/resnet.py
+++ b/sktime/classification/deep_learning/resnet.py
@@ -55,8 +55,8 @@ class ResNetClassifier(BaseDeepClassifier):
     >>> from sktime.classification.deep_learning.resnet import ResNetClassifier
     >>> from sktime.datasets import load_unit_test
     >>> X_train, y_train = load_unit_test(split="train")
-    >>> clf = ResNetClassifier(n_epochs=20, bacth_size=4) # doctest: +SKIP
-    >>> clf.fit(X_train, Y_train) # doctest: +SKIP
+    >>> clf = ResNetClassifier(n_epochs=20) # doctest: +SKIP
+    >>> clf.fit(X_train, y_train) # doctest: +SKIP
     ResNetClassifier(...)
     """
 
diff --git a/sktime/classification/deep_learning/rnn.py b/sktime/classification/deep_learning/rnn.py
index ab651853f..e5b6b0815 100644
--- a/sktime/classification/deep_learning/rnn.py
+++ b/sktime/classification/deep_learning/rnn.py
@@ -47,6 +47,15 @@ class SimpleRNNClassifier(BaseDeepClassifier):
     ----------
     ..[1] benchmark forecaster in M4 forecasting competition:
     https://github.com/Mcompetitions/M4-methods
+
+    Examples
+    --------
+    >>> from sktime.classification.deep_learning.rnn import SimpleRNNClassifier
+    >>> from sktime.datasets import load_unit_test
+    >>> X_train, y_train = load_unit_test(split="train")
+    >>> clf = SimpleRNNClassifier(n_epochs=20,batch_size=20) # doctest: +SKIP
+    >>> clf.fit(X_train, y_train) # doctest: +SKIP
+    ResNetClassifier(...)
     """
 
     _tags = {
@@ -121,7 +130,7 @@ class SimpleRNNClassifier(BaseDeepClassifier):
         )(output_layer)
 
         self.optimizer_ = (
-            keras.optimizers.RMSprop(lr=0.001)
+            keras.optimizers.RMSprop(learning_rate=0.001)
             if self.optimizer is None
             else self.optimizer
         )
diff --git a/sktime/forecasting/compose/_fallback.py b/sktime/forecasting/compose/_fallback.py
index d2382066c..7b9f867c1 100644
--- a/sktime/forecasting/compose/_fallback.py
+++ b/sktime/forecasting/compose/_fallback.py
@@ -8,7 +8,7 @@ If the active forecaster fails during prediction, it proceeds to the next. This
 a robust forecasting mechanism by providing fallback options.
 """
 
-__author__ = ["ninedigits"]
+__author__ = ["ninedigits", "RikStarmans"]
 __all__ = ["FallbackForecaster"]
 
 from sktime.base import _HeterogenousMetaEstimator
@@ -107,7 +107,7 @@ class FallbackForecaster(_HeterogenousMetaEstimator, _DelegatedForecaster):
     """
 
     _tags = {
-        "authors": ["ninedigits"],
+        "authors": ["ninedigits", "RikStarmans"],
         "maintainers": ["ninedigits"],
         "handles-missing-data": True,
         "scitype:y": "both",
diff --git a/sktime/networks/rnn.py b/sktime/networks/rnn.py
index 836bef650..f63ff822d 100644
--- a/sktime/networks/rnn.py
+++ b/sktime/networks/rnn.py
@@ -74,3 +74,29 @@ class RNNNetwork(BaseDeepNetwork):
         )(input_layer)
 
         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.
+            Reserved values for classifiers:
+                "results_comparison" - used for identity testing in some classifiers
+                    should contain parameter settings comparable to "TSC bakeoff"
+
+        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 = {"units": 5}
+        return [params1, params2]
diff --git a/sktime/regression/deep_learning/mlp.py b/sktime/regression/deep_learning/mlp.py
index 5c0f26d75..354daca74 100644
--- a/sktime/regression/deep_learning/mlp.py
+++ b/sktime/regression/deep_learning/mlp.py
@@ -48,6 +48,17 @@ class MLPRegressor(BaseDeepRegressor):
     .. [1] Wang et. al, Time series classification from
     scratch with deep neural networks: A strong baseline,
     International joint conference on neural networks (IJCNN), 2017.
+
+    Examples
+    --------
+    >>> from sktime.datasets import load_unit_test
+    >>> from sktime.regression.deep_learning.mlp import MLPRegressor
+    >>> X_train, y_train = load_unit_test(return_X_y=True, split="train")
+    >>> X_test, y_test = load_unit_test(return_X_y=True, split="test")
+    >>> regressor = MLPRegressor() # doctest: +SKIP
+    >>> regressor.fit(X_train, y_train) # doctest: +SKIP
+    MLPRegressor(...)
+    >>> y_pred = regressor.predict(X_test) # doctest: +SKIP
     """
 
     _tags = {
diff --git a/sktime/regression/deep_learning/rnn.py b/sktime/regression/deep_learning/rnn.py
index 1ea5ed4bd..7d40c7ce1 100644
--- a/sktime/regression/deep_learning/rnn.py
+++ b/sktime/regression/deep_learning/rnn.py
@@ -47,6 +47,15 @@ class SimpleRNNRegressor(BaseDeepRegressor):
     ----------
     ..[1] benchmark forecaster in M4 forecasting competition:
     https://github.com/Mcompetitions/M4-methods
+
+    Examples
+    --------
+    >>> from sktime.regression.deep_learning.rnn import SimpleRNNRegressor
+    >>> from sktime.datasets import load_unit_test
+    >>> X_train, Y_train = load_unit_test(split="train")
+    >>> clf = SimpleRNNRegressor(n_epochs=20, batch_size=4) # doctest: +SKIP
+    >>> clf.fit(X_train, Y_train) # doctest: +SKIP
+    SimpleRNNRegressor(...)
     """
 
     _tags = {
@@ -117,7 +126,7 @@ class SimpleRNNRegressor(BaseDeepRegressor):
         )(output_layer)
 
         self.optimizer_ = (
-            keras.optimizers.RMSprop(lr=0.001)
+            keras.optimizers.RMSprop(learning_rate=0.001)
             if self.optimizer is None
             else self.optimizer
         )
diff --git a/sktime/transformations/panel/shapelet_transform/__init__.py b/sktime/transformations/panel/shapelet_transform/__init__.py
new file mode 100644
index 000000000..d6e504484
--- /dev/null
+++ b/sktime/transformations/panel/shapelet_transform/__init__.py
@@ -0,0 +1,14 @@
+"""Shapelet Transformers."""
+__all__ = [
+    "ShapeletTransform",
+    "RandomShapeletTransform",
+    "ShapeletTransformPyts",
+]
+
+from sktime.transformations.panel.shapelet_transform._shapelet_transform import (
+    RandomShapeletTransform,
+    ShapeletTransform,
+)
+from sktime.transformations.panel.shapelet_transform._shapelet_transform_pyts import (
+    ShapeletTransformPyts,
+)
diff --git a/sktime/transformations/panel/shapelet_transform.py b/sktime/transformations/panel/shapelet_transform/_shapelet_transform.py
similarity index 100%
rename from sktime/transformations/panel/shapelet_transform.py
rename to sktime/transformations/panel/shapelet_transform/_shapelet_transform.py
diff --git a/sktime/transformations/panel/shapelet_transform/_shapelet_transform_pyts.py b/sktime/transformations/panel/shapelet_transform/_shapelet_transform_pyts.py
new file mode 100644
index 000000000..3f40d621e
--- /dev/null
+++ b/sktime/transformations/panel/shapelet_transform/_shapelet_transform_pyts.py
@@ -0,0 +1,196 @@
+"""Shapelet transformer, from pyts."""
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+
+__author__ = ["Abhay-Lejith"]
+__all__ = ["ShapeletTransformPyts"]
+
+from sktime.base.adapters._pyts import _PytsAdapter
+from sktime.transformations.base import BaseTransformer
+
+
+class ShapeletTransformPyts(_PytsAdapter, BaseTransformer):
+    """Shapelet Transform, from ``pyts``.
+
+    Direct interface to ``pyts.transformation.ShapeletTransform``.
+
+    The Shapelet Transform algorithm extracts shapelets from a data set of time series
+    and returns the distances between the shapelets and the time series. A shapelet is
+    defined as a subset of a time series, that is a set of values from consecutive time
+    points. The distance between a shapelet and a time series is defined as the minimum
+    of the distances between this shapelet and all the shapelets of same length
+    extracted from this time series. The most discriminative shapelets are selected.
+    Two criteria are made available: mutual information and F-scores.
+
+    Parameters
+    ----------
+    n_shapelets : int or 'auto' (default = 'auto')
+        The number of shapelets to keep. If 'auto', `n_timestamps // 2`
+        shapelets are considered, where `n_timestamps` is the number of
+        time points in the dataset. Note that there might be a smaller
+        number of shapelets if fewer than ``n_shapelets`` shapelets have been
+        extracted during the search.
+
+    criterion : 'mutual_info' or 'anova' (default = 'mutual_info')
+        Criterion to perform the selection of the shapelets.
+        'mutual_info' uses the mutual information, while 'anova' use
+        the ANOVA F-value.
+
+    window_sizes : array-like or 'auto '(default = 'auto')
+        Size of the sliding windows. If 'auto', the range for the
+        window sizes is determined automatically.
+        Otherwise, all the elements must be either integers or floats.
+        In the latter case, each element represents the percentage
+        of the size of each time series and must be between 0 and 1; the size
+        of the sliding windows will be computed as
+        ``np.ceil(window_sizes * n_timestamps)``.
+
+    window_steps : None or array-like (default = None)
+        Step of the sliding windows. If None, each ``window_step`` is equal
+        to 1. Otherwise, all the elements must be either integers or floats.
+        In the latter case, each element represents the percentage of the size
+        of each time series and must be between 0 and 1; the step of the
+        sliding windows will be computed as
+        ``np.ceil(window_steps * n_timestamps)``.
+        Must be None if ``window_sizes='auto'``.
+
+    remove_similar : bool (default = True)
+        If True, self-similar shapelets are removed, keeping only
+        the non-self-similar shapelets with the highest scores. Two
+        shapelets are considered to be self-similar if they are
+        taken from the the same time series and have at least one
+        overlapping index.
+
+    sort : bool (default = False)
+        If True, shapelets are sorted in descending order according to
+        their associated scores. If False, the order is undefined.
+
+    verbose : int (default = 0)
+        Verbosity level when fitting: if non zero, progress messages are
+        printed. Above 50, the output is sent to stdout.
+        The frequency of the messages increases with the verbosity level.
+
+    random_state : int, RandomState instance or None (default = None)
+        If int, random_state is the seed used by the random number generator;
+        If RandomState instance, random_state is the random number generator;
+        If None, the random number generator is the RandomState instance used
+        by ``np.random``. Only used if ``window_sizes='auto'`` in order to
+        subsample the dataset to find the best range or if
+        ``criterion=='mutual_info'`` to add small noise to the data.
+
+    n_jobs : None or int (default = None)
+        The number of jobs to run in parallel for ``fit``.
+        If -1, then the number of jobs is set to the number of cores.
+
+    Attributes
+    ----------
+    shapelets_ : array, shape = (n_shapelets,)
+        The array with the selected shapelets.
+
+    indices_ : array, shape = (n_shapelets, 3)
+        The indices for the corresponding shapelets in the training set.
+        The first column consists of the indices of the samples.
+        The second column consists of the starting indices (included)
+        of the shapelets. The third column consists of the ending indices
+        (excluded) of the shapelets.
+
+    scores_ : array, shape = (n_shapelets,)
+        The scores associated to the shapelets. The higher, the more
+        discriminant.
+        If ``criterion='mutual_info'``, mutual information scores are reported.
+        If ``criterion='anova'``, F-scores are reported.
+
+    window_range_ : None or tuple
+        Range of the window sizes if ``window_sizes='auto'``. None otherwise.
+
+    References
+    ----------
+    .. [1] J. Lines, L. M. Davis, J. Hills and A. Bagnall, "A Shapelet
+           Transform for Time Series Classification". Data Mining and Knowledge
+           Discovery, 289-297 (2012).
+
+    Examples
+    --------
+    >>> from sktime.transformations.panel.shapelet_transform import (
+    ...     ShapeletTransformPyts
+    ... )
+    >>> from sktime.datasets import load_unit_test
+    >>> X_train, y_train = load_unit_test(split="train") # doctest: +SKIP
+    >>> stp = ShapeletTransformPyts() # doctest: +SKIP
+    >>> stp.fit(X_train,y_train) # doctest: +SKIP
+    >>> stp.transform(X_train) # doctest: +SKIP
+    """
+
+    _tags = {
+        # packaging info
+        # --------------
+        "authors": "Abhay-Lejith",
+        "python_dependencies": "pyts",
+        # univariate-only controls whether internal X can be univariate/multivariate
+        # if True (only univariate), always applies vectorization over variables
+        "univariate-only": True,
+        "scitype:transform-input": "Series",
+        "scitype:transform-output": "Primitives",
+        "scitype:instancewise": False,
+        "fit_is_empty": False,
+        "y_inner_mtype": "numpy1D",
+        "requires_y": True,
+    }
+
+    _estimator_attr = "_pyts_shapelet_transform"
+
+    def _get_pyts_class(self):
+        """Get pyts class.
+
+        should import and return pyts class
+        """
+        from pyts.transformation.shapelet_transform import ShapeletTransform
+
+        return ShapeletTransform
+
+    def __init__(
+        self,
+        n_shapelets="auto",
+        criterion="mutual_info",
+        window_sizes="auto",
+        window_steps=None,
+        remove_similar=True,
+        sort=False,
+        verbose=0,
+        random_state=None,
+        n_jobs=None,
+    ):
+        self.n_shapelets = n_shapelets
+        self.criterion = criterion
+        self.window_sizes = window_sizes
+        self.window_steps = window_steps
+        self.remove_similar = remove_similar
+        self.sort = sort
+        self.verbose = verbose
+        self.random_state = random_state
+        self.n_jobs = n_jobs
+
+        super().__init__()
+
+    @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 = {"criterion": "anova", "n_shapelets": 10}
+        params2 = {"window_sizes": [3]}
+        return [params1, params2]
diff --git a/sktime/transformations/series/summarize.py b/sktime/transformations/series/summarize.py
index 8347e2289..86d000ad7 100644
--- a/sktime/transformations/series/summarize.py
+++ b/sktime/transformations/series/summarize.py
@@ -473,38 +473,37 @@ def _window_feature(Z, summarizer=None, window=None, bfill=False):
             if bfill is False:
                 feat = Z.apply(
                     lambda x: getattr(
-                        x.shift(lag).rolling(
-                            window=window_length, min_periods=window_length
-                        ),
+                        x.rolling(window=window_length, min_periods=window_length),
                         summarizer,
-                    )()
+                    )().shift(lag)
                 )
             else:
                 feat = Z.apply(
                     lambda x: getattr(
-                        x.shift(lag)
-                        .bfill()
-                        .rolling(window=window_length, min_periods=window_length),
+                        x.rolling(window=window_length, min_periods=window_length),
                         summarizer,
                     )()
+                    .shift(lag)
+                    .bfill()
                 )
     else:
-        if bfill is False:
-            feat = Z.shift(lag)
-        else:
-            feat = Z.shift(lag).bfill()
         if isinstance(Z, pd.core.groupby.generic.SeriesGroupBy) and callable(
             summarizer
         ):
-            feat = feat.rolling(window_length).apply(summarizer, raw=True)
+            feat = Z.rolling(window_length).apply(summarizer, raw=True)
         elif not isinstance(Z, pd.core.groupby.generic.SeriesGroupBy) and callable(
             summarizer
         ):
-            feat = feat.apply(
+            feat = Z.apply(
                 lambda x: x.rolling(
                     window=window_length, min_periods=window_length
                 ).apply(summarizer, raw=True)
             )
+        if bfill is False:
+            feat = Z.shift(lag)
+        else:
+            feat = Z.shift(lag).bfill()
+
         feat = pd.DataFrame(feat)
     if bfill is True:
         feat = feat.bfill()
diff --git a/sktime/transformations/series/temporian.py b/sktime/transformations/series/temporian.py
new file mode 100644
index 000000000..01ddf472a
--- /dev/null
+++ b/sktime/transformations/series/temporian.py
@@ -0,0 +1,154 @@
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+
+"""Implementation of TemporianTransformer. Contributed by the Temporian team."""
+
+__author__ = ["ianspektor", "javiber"]
+
+from sktime.transformations.base import BaseTransformer
+
+
+class TemporianTransformer(BaseTransformer):
+    """Applies a Temporian function to the input time series.
+
+    This transformer applies a Temporian [1]_ function to the input time series.
+
+    The conversion from sktime's internal representation to Temporian's EventSet [2]_
+    and back is handled automatically by the transformer.
+
+    Parameters
+    ----------
+    function : Callable[[temporian.EventSet], temporian.EventSet]
+        Temporian function to apply to the input time series. The function must receive
+        and return a single Temporian EventSet, and can apply an arbitrary number of
+        Temporian operators to its input.
+    compile : bool, default=False
+        If True, the function will be compiled using Temporian's @tp.compile [3]_
+        decorator, which can lead to significant speedups by optimizing the graph of
+        operations.
+
+    Examples
+    --------
+    >>> from sktime.datasets import load_airline
+    >>> from sktime.transformations.series.temporian import TemporianTransformer
+    >>> import temporian as tp  # doctest: +SKIP
+    >>>
+    >>> def function(evset):  # doctest: +SKIP
+    ...     return evset.simple_moving_average(tp.duration.days(3 * 365))  \
+        # doctest: +SKIP
+    >>> transformer = TemporianTransformer(function=function)  # doctest: +SKIP
+    >>> X = load_airline()  # doctest: +SKIP
+    >>> X_averaged = transformer.fit_transform(X)  # doctest: +SKIP
+
+    References
+    ----------
+    .. [1] https://temporian.readthedocs.io/en/stable/
+    .. [2] https://temporian.readthedocs.io/en/stable/reference/temporian/EventSet/
+    .. [3] https://temporian.readthedocs.io/en/stable/reference/temporian/compile/
+    """
+
+    _tags = {
+        # packaging info
+        # --------------
+        "authors": ["ianspektor", "javiber"],
+        "maintainers": ["ianspektor", "javiber"],
+        "python_dependencies": ["temporian"],
+        "python_version": ">=3.8",
+        # estimator tags
+        # --------------
+        "univariate-only": False,
+        "scitype:transform-input": "Series",
+        "scitype:transform-output": "Series",
+        "scitype:instancewise": True,
+        "scitype:transform-labels": "None",
+        "X_inner_mtype": "pd.DataFrame",
+        "fit_is_empty": True,
+        "capability:inverse_transform": False,
+        "capability:unequal_length": True,
+        "handles-missing-data": False,
+    }
+
+    def __init__(self, function, compile=False):
+        self.function = function
+        self.compile = compile
+
+        super().__init__()
+
+    def _transform(self, X, y=None):
+        """Transform X and return a transformed version.
+
+        private _transform containing core logic, called from transform
+
+        Parameters
+        ----------
+        X : pd.DataFrame
+            if self.get_tag("univariate-only")==True:
+                guaranteed to have a single column
+            if self.get_tag("univariate-only")==False: no restrictions apply
+        y : None, present only for interface compatibility
+
+        Returns
+        -------
+        transformed version of X
+        """
+        import temporian as tp
+
+        X_noindex = X.reset_index(drop=False)
+        X_noindex.columns = X_noindex.columns.astype(str)
+        timestamps_col = X_noindex.columns[0]
+        evset = tp.from_pandas(X_noindex, timestamps=timestamps_col)
+
+        function = tp.compile(self.function) if self.compile else self.function
+
+        res = function(evset)
+        if not isinstance(res, tp.EventSet):
+            raise TypeError(
+                f"Expected return type to be an EventSet but received a {type(res)}"
+            )
+
+        # Test that the sampling was not modified, otherwise the conversion back to
+        # pandas and the set_index would not work as expected
+        try:
+            res.check_same_sampling(evset)
+        except ValueError as exc:
+            raise ValueError(
+                "The resulting EventSet must have the same sampling as the input. "
+                "Visit our docs for more info "
+                "https://temporian.readthedocs.io/en/stable/user_guide/#sampling"
+            ) from exc
+
+        res = tp.to_pandas(res, timestamps=False)
+        res = res.set_index(X.index)
+
+        return res
+
+    @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.
+            There are currently no reserved values for transformers.
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class
+            Each dict are parameters to construct an "interesting" test instance, i.e.,
+            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
+            `create_test_instance` uses the first (or only) dictionary in `params`
+        """
+        return [
+            {"function": _test_function_single_op, "compile": False},
+            {"function": _test_function_many_ops, "compile": True},
+        ]
+
+
+def _test_function_single_op(evset):
+    return evset + 1
+
+
+def _test_function_many_ops(evset):
+    return evset.simple_moving_average(10).lag(10).resample(evset)


Test patch:
diff --git a/sktime/tests/_config.py b/sktime/tests/_config.py
index 144c8adcd..20d1665dd 100644
--- a/sktime/tests/_config.py
+++ b/sktime/tests/_config.py
@@ -34,8 +34,6 @@ EXCLUDE_ESTIMATORS = [
     # DL classifier suspected to cause hangs and memouts, see #4610
     "FCNClassifier",
     "MACNNClassifier",
-    "SimpleRNNClassifier",
-    "SimpleRNNRegressor",
     "EditDist",
     "CNNClassifier",
     "FCNClassifier",
diff --git a/sktime/tests/test_softdeps.py b/sktime/tests/test_softdeps.py
index c83d6ada8..7f3d100b2 100644
--- a/sktime/tests/test_softdeps.py
+++ b/sktime/tests/test_softdeps.py
@@ -53,6 +53,7 @@ EXCEPTED_FROM_NO_DEP_CHECK = []
 EXCEPTED_FROM_GET_PARAMS_CHECK = [
     "PyODAnnotator",  # adapters always require soft dep. Here: pyod
     "HCrystalBallAdapter",  # adapters always require soft dep. Here: hcrystalball
+    "TemporianTransformer",  # adapters always require soft dep. Here: temporian
 ]
 
 
diff --git a/sktime/transformations/panel/shapelet_transform/tests/__init__.py b/sktime/transformations/panel/shapelet_transform/tests/__init__.py
new file mode 100644
index 000000000..37034d8e4
--- /dev/null
+++ b/sktime/transformations/panel/shapelet_transform/tests/__init__.py
@@ -0,0 +1,1 @@
+"""Shapelet transform unit tests."""
diff --git a/sktime/transformations/panel/tests/test_shapelet_transform.py b/sktime/transformations/panel/shapelet_transform/tests/test_shapelet_transform.py
similarity index 100%
rename from sktime/transformations/panel/tests/test_shapelet_transform.py
rename to sktime/transformations/panel/shapelet_transform/tests/test_shapelet_transform.py
diff --git a/sktime/transformations/panel/shapelet_transform/tests/test_shapelet_transform_pyts.py b/sktime/transformations/panel/shapelet_transform/tests/test_shapelet_transform_pyts.py
new file mode 100644
index 000000000..23384c840
--- /dev/null
+++ b/sktime/transformations/panel/shapelet_transform/tests/test_shapelet_transform_pyts.py
@@ -0,0 +1,40 @@
+"""ShapeletTransformPyts test code."""
+import numpy as np
+import pytest
+from numpy import testing
+
+from sktime.datasets import load_unit_test
+from sktime.tests.test_switch import run_test_for_class
+from sktime.transformations.panel.shapelet_transform import ShapeletTransformPyts
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(ShapeletTransformPyts),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_stp_on_unit_test():
+    """Test of ShapeletTransformPyts on unit test data."""
+    # load unit test data
+    X_train, y_train = load_unit_test(split="train")
+    indices = np.random.RandomState(0).choice(len(y_train), 5, replace=False)
+
+    # fit the shapelet transform
+    stp = ShapeletTransformPyts(n_shapelets=5, random_state=0)
+    stp.fit(X_train.iloc[indices], y_train[indices])
+
+    # assert transformed data is the same
+    data = stp.transform(X_train.iloc[indices])
+    testing.assert_array_almost_equal(
+        data, shapelet_transform_unit_test_data, decimal=4
+    )
+
+
+shapelet_transform_unit_test_data = np.array(
+    [
+        [176.0, 14.866069, 17.262677, 0.000000, 7.0],
+        [0.0, 51.908573, 82.197932, 65.517173, 15.0],
+        [223.0, 30.413813, 0.000000, 17.262677, 7.0],
+        [1.0, 61.159627, 71.515733, 59.502101, 19.0],
+        [32.0, 0.000000, 30.413813, 14.866069, 0.0],
+    ]
+)
diff --git a/sktime/transformations/series/tests/test_temporian.py b/sktime/transformations/series/tests/test_temporian.py
new file mode 100644
index 000000000..7a7138d10
--- /dev/null
+++ b/sktime/transformations/series/tests/test_temporian.py
@@ -0,0 +1,225 @@
+"""Tests for TemporianTransformer."""
+
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file).
+
+__author__ = ["ianspektor", "javiber"]
+
+from datetime import datetime
+from unittest.mock import patch
+
+import numpy as np
+import pandas as pd
+import pytest
+
+from sktime.datasets import load_solar
+from sktime.datatypes import get_examples
+from sktime.tests.test_switch import run_test_for_class
+from sktime.transformations.series.temporian import TemporianTransformer
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_flat_univariate():
+    """Tests basic function works on flat (non-indexed) univariate time series."""
+    X = load_solar()[0:32]
+
+    def function(evset):
+        return evset["solar_gen"] + 1
+
+    transformer = TemporianTransformer(function=function)
+    X_transformed = transformer.fit_transform(X=X)
+
+    pd.testing.assert_series_equal(X_transformed, X + 1)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_panel():
+    """Tests basic function works on Panels."""
+
+    def function(evset):
+        return evset + 1
+
+    examples = get_examples("pd-multiindex", as_scitype="Panel").values()
+    assert len(examples) > 0
+
+    for X in examples:
+        transformer = TemporianTransformer(function=function)
+        X_transformed = transformer.fit_transform(X=X)
+        pd.testing.assert_frame_equal(X_transformed, X + 1)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_hier():
+    """Tests basic function works on Hierarchical."""
+
+    def function(evset):
+        return evset + 1
+
+    examples = get_examples("pd_multiindex_hier", as_scitype="Hierarchical").values()
+    assert len(examples) > 0
+
+    for X in examples:
+        transformer = TemporianTransformer(function=function)
+        X_transformed = transformer.fit_transform(X=X)
+        pd.testing.assert_frame_equal(X_transformed, X + 1)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_dtypes():
+    """Tests basic function works on all dtypes."""
+
+    def function(evset):
+        return evset + 1
+
+    X = pd.DataFrame(
+        {
+            str(dtype): np.zeros(10).astype(dtype)
+            for dtype in [
+                np.int32,
+                np.int64,
+                np.float32,
+                np.float64,
+            ]
+        }
+    )
+    transformer = TemporianTransformer(function=function)
+    X_transformed = transformer.fit_transform(X=X)
+    pd.testing.assert_frame_equal(X_transformed, X + 1)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_compiled():
+    """Tests compiling the function."""
+    import temporian as tp
+
+    def function(evset):
+        return evset + 1
+
+    X = get_examples("pd.Series", as_scitype="Series")[0]
+
+    true_compile = tp.compile
+
+    with patch.object(tp, "compile") as compile_patch:
+        compile_patch.side_effect = true_compile
+        transformer = TemporianTransformer(function=function, compile=True)
+        X_transformed = transformer.fit_transform(X=X)
+        pd.testing.assert_series_equal(X_transformed, X + 1)
+        compile_patch.assert_called_once_with(function)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_compile_already_compiled():
+    """Tests compiling doesn't fail if the function was compiled already."""
+    import temporian as tp
+
+    @tp.compile
+    def function(evset):
+        return evset + 1
+
+    X = get_examples("pd.Series", as_scitype="Series")[0]
+
+    true_compile = tp.compile
+
+    with patch.object(tp, "compile") as compile_patch:
+        compile_patch.side_effect = true_compile
+        transformer = TemporianTransformer(function, compile=True)
+        X_transformed = transformer.fit_transform(X=X)
+        pd.testing.assert_series_equal(X_transformed, X + 1)
+        compile_patch.assert_called_once_with(function)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_multiple_output():
+    """Tests errors when functions return incorrect types."""
+
+    def function(evset):
+        split = datetime(1950, 7, 16)
+        return evset.before(split), evset.after(split)
+
+    X = load_solar()
+
+    transformer = TemporianTransformer(function=function)
+    with pytest.raises(
+        TypeError, match="Expected return type to be an EventSet but received"
+    ):
+        _ = transformer.fit_transform(X=X)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_multiple_input():
+    """Tests errors on incorrectly defined functions."""
+
+    def function(evset, incorrect_param):
+        return evset + 1
+
+    X = load_solar()
+
+    transformer = TemporianTransformer(function=function)
+    with pytest.raises(TypeError, match="missing 1 required positional"):
+        _ = transformer.fit_transform(X=X)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_change_sampling():
+    """Tests error when changing the sampling."""
+    import temporian as tp
+
+    # in this dataset each row is a period of 1/2 hour
+    # lagging for one hour would remove the first two rows thus changing the sampling
+    X = load_solar()
+
+    def function(evset):
+        return evset.lag(tp.duration.hours(1))
+
+    transformer = TemporianTransformer(function=function, compile=True)
+    with pytest.raises(
+        ValueError,
+        match="The resulting EventSet must have the same sampling as the input",
+    ):
+        _ = transformer.fit_transform(X=X)
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(TemporianTransformer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_complex_function():
+    import temporian as tp
+
+    # in this dataset each row is a period of 1/2 hour
+
+    def function(evset):
+        return evset.simple_moving_average(tp.duration.days(1)).resample(evset)
+
+    X = load_solar()
+
+    transformer = TemporianTransformer(function=function, compile=True)
+    result = transformer.fit_transform(X=X)
+    assert result.iloc[0] == X.iloc[0]
+    assert np.allclose(result.iloc[-1], X.iloc[-48:].mean())
diff --git a/sktime/transformations/series/tests/test_window_summarizer.py b/sktime/transformations/series/tests/test_window_summarizer.py
index 0d0306798..7cb16aadf 100644
--- a/sktime/transformations/series/tests/test_window_summarizer.py
+++ b/sktime/transformations/series/tests/test_window_summarizer.py
@@ -76,6 +76,13 @@ kwargs_custom = {
         count_gt100: [[3, 2]],
     }
 }
+
+kwargs_negative_lags = {
+    "lag_feature": {
+        "mean": [[-1, 1], [-1, 2]],
+    }
+}
+
 # Generate named and unnamed y
 y_train.name = None
 y_train_named = y_train.copy()
@@ -94,6 +101,20 @@ Xtmvar_none = ["GNPDEFL_lag_3", "GNPDEFL_lag_6", "GNP", "UNEMP", "ARMED", "POP"]
 @pytest.mark.parametrize(
     "kwargs, column_names, y, target_cols, truncate",
     [
+        (
+            kwargs_negative_lags,
+            ["a_mean_-1_1", "a_mean_-1_2"],
+            y_pd,
+            None,
+            None,
+        ),
+        (
+            kwargs_negative_lags,
+            ["y_mean_-1_1", "y_mean_-1_2"],
+            y_hierarchical,
+            None,
+            None,
+        ),
         (
             kwargs,
             ["y_lag_1", "y_mean_1_3", "y_mean_1_12", "y_std_1_4"],