Repository: sktime/sktime

Issue:
[ENH] Adapter to TSBootstrap
**Is your feature request related to a problem? Please describe.**
Add a adapter  to TSBootstrap see astrogilda/tsbootstrap#45



Discussion:

Resolving patch:
diff --git a/sktime/transformations/bootstrap/__init__.py b/sktime/transformations/bootstrap/__init__.py
--- a/sktime/transformations/bootstrap/__init__.py
+++ b/sktime/transformations/bootstrap/__init__.py
@@ -8,6 +8,7 @@
     "STLBootstrapTransformer",
     "MovingBlockBootstrapTransformer",
     "SplitterBootstrapTransformer",
+    "TSBootstrapAdapter",
 ]
 
 from sktime.transformations.bootstrap._mbb import (
@@ -15,3 +16,4 @@
     STLBootstrapTransformer,
 )
 from sktime.transformations.bootstrap._splitterboot import SplitterBootstrapTransformer
+from sktime.transformations.bootstrap._tsbootstrap import TSBootstrapAdapter
diff --git a/sktime/transformations/bootstrap/_tsbootstrap.py b/sktime/transformations/bootstrap/_tsbootstrap.py
new file mode 100644
--- /dev/null
+++ b/sktime/transformations/bootstrap/_tsbootstrap.py
@@ -0,0 +1,134 @@
+"""Bootstrapping adapter for tsbootstrap."""
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+
+__author__ = ["benheid"]
+
+import pandas as pd
+
+from sktime.transformations.base import BaseTransformer
+
+
+class TSBootstrapAdapter(BaseTransformer):
+    """Adapter for TSBootstrap.
+
+    The bootstrap samples will be returned as a Panel with the first level
+    being integer index of the synthetic sample.
+
+    For hierarchical data, the bootstrap index will be added as the index
+    at integer position -2.
+
+    Parameters
+    ----------
+    bootstrap : bootstrap from tsbootstrap
+        The splitter used for the bootstrap splitting.
+    include_actual : bool, default=False
+        Whether to include the actual data in the output.
+
+    Examples
+    --------
+    >>> from sktime.datasets import load_airline
+    >>> from sktime.transformations.bootstrap import TSBootstrapAdapter
+    >>> from tsbootstrap import (
+    ...    MovingBlockBootstrap,
+    ...    MovingBlockBootstrapConfig
+    ... ) # doctest: +SKIP
+    >>> y = load_airline()
+    >>> config = MovingBlockBootstrapConfig(10, n_bootstraps=10)  # doctest: +SKIP
+    >>> bootstrap = TSBootstrapAdapter(MovingBlockBootstrap(config))  # doctest: +SKIP
+    >>> result = bootstrap.fit_transform(y)  # doctest: +SKIP
+    """
+
+    _tags = {
+        "scitype:transform-input": "Series",
+        "scitype:transform-output": "Panel",
+        "scitype:instancewise": True,  # is this an instance-wise transform?
+        "X_inner_mtype": "pd.DataFrame",  # which mtypes do _fit/_predict support for X?
+        "y_inner_mtype": "None",  # which mtypes do _fit/_predict support for y?
+        "capability:inverse_transform": False,
+        "skip-inverse-transform": True,  # is inverse-transform skipped when called?
+        "univariate-only": False,  # can the transformer handle multivariate X?
+        "handles-missing-data": True,  # can estimator handle missing data?
+        "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
+        "enforce_index_type": None,  # index type that needs to be enforced in X/y
+        "fit_is_empty": True,  # is fit empty and can be skipped? Yes = True
+        "transform-returns-same-time-index": False,
+        "python_dependencies": ["tsbootstrap>=0.1.0"],
+    }
+
+    def __init__(
+        self,
+        bootstrap,
+        include_actual=False,
+    ):
+        self.bootstrap = bootstrap
+        self.include_actual = include_actual
+        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 : Series or Panel of mtype X_inner_mtype
+            if X_inner_mtype is list, _transform must support all types in it
+            Data to be transformed
+        y : Series or Panel of mtype y_inner_mtype, default=None
+            Additional data, e.g., labels for transformation
+
+        Returns
+        -------
+        transformed version of X
+        """
+        bootstrapped_samples = self.bootstrap.bootstrap(X, test_ratio=0)
+
+        bootstrapped_samples = [pd.DataFrame(sample) for sample in bootstrapped_samples]
+
+        boostrapped_df = pd.concat(
+            bootstrapped_samples,
+            keys=[f"synthetic_{i}" for i in range(len(bootstrapped_samples))],
+        )
+
+        _X = X.copy()
+        if self.include_actual:
+            _X.index = pd.MultiIndex.from_product([["actual"], range(len(X))])
+            return pd.concat([_X, boostrapped_df], axis=0)
+        else:
+            return boostrapped_df
+
+    @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`
+        """
+        from sktime.utils.validation._dependencies import _check_soft_dependencies
+
+        deps = cls.get_class_tag("python_dependencies")
+        _check_soft_dependencies(deps, severity="error")
+
+        from tsbootstrap import BlockBootstrap, MovingBlockBootstrap
+
+        params = [
+            {"bootstrap": BlockBootstrap(n_bootstraps=10)},
+            {
+                "bootstrap": MovingBlockBootstrap(n_bootstraps=10, block_length=4),
+                "include_actual": True,
+            },
+        ]
+
+        return params


Test patch:
diff --git a/sktime/tests/test_softdeps.py b/sktime/tests/test_softdeps.py
--- a/sktime/tests/test_softdeps.py
+++ b/sktime/tests/test_softdeps.py
@@ -53,6 +53,7 @@
 EXCEPTED_FROM_GET_PARAMS_CHECK = [
     "PyODAnnotator",  # adapters always require soft dep. Here: pyod
     "HCrystalBallAdapter",  # adapters always require soft dep. Here: hcrystalball
+    "TSBootstrapAdapter",  # adapters always require soft dep. Here: tsbootstrap
     "TemporianTransformer",  # adapters always require soft dep. Here: temporian
 ]