We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[DOC] Transformers missing from API Reference
#### Describe the issue linked to the documentation

<!--
Tell us about the confusion introduced in the documentation.
-->

There have been transformers that have recently been improved but have not been added the API Reference. 

For example, STLTransformer, CLaSPTransformer, etc.

#### Suggest a potential alternative/fix

<!--
Tell us how we could improve the documentation in this regard.
-->

These need to be added to the documentation. 

In the future we'll have to try and be good about following our [reviewer guide](https://www.sktime.org/en/stable/reviewer_guide.html) and waiting to merge until things are added to docs. Otherwise, it is hard for users to know what functionality exists.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sktime/transformations/series/difference.py`
Content:
```
1 #!/usr/bin/env python3 -u
2 # -*- coding: utf-8 -*-
3 # copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
4 """Class to iteratively apply differences to a time series."""
5 __author__ = ["Ryan Kuhns"]
6 __all__ = ["Differencer"]
7 
8 import numpy as np
9 import pandas as pd
10 from sklearn.utils import check_array
11 
12 from sktime.forecasting.base import ForecastingHorizon
13 from sktime.transformations.base import _SeriesToSeriesTransformer
14 from sktime.utils.validation import is_int
15 from sktime.utils.validation.series import check_series
16 
17 
18 def _check_lags(lags):
19     msg = " ".join(
20         [
21             "`lags` should be provided as a positive integer scaler, or",
22             "a list, tuple or np.ndarray of positive integers,"
23             f"but found {type(lags)}.",
24         ]
25     )
26     non_positive_msg = "`lags` should be positive integers."
27     if isinstance(lags, int):
28         if lags <= 0:
29             raise ValueError(non_positive_msg)
30         lags = check_array([lags], ensure_2d=False)
31     elif isinstance(lags, (list, tuple, np.ndarray)):
32         if not all([is_int(lag) for lag in lags]):
33             raise TypeError(msg)
34         lags = check_array(lags, ensure_2d=False)
35         if (lags <= 0).any():
36             raise ValueError(non_positive_msg)
37     else:
38         raise TypeError(msg)
39 
40     return lags
41 
42 
43 def _diff_transform(Z, lags):
44     Zt = Z.copy()
45 
46     if len(lags) == 0:
47         return Zt
48 
49     else:
50         for lag in lags:
51             Zt = Zt.diff(lag)
52         return Zt
53 
54 
55 def _inverse_diff(Z, lag):
56     for i in range(lag):
57         Z.iloc[i::lag] = Z.iloc[i::lag].cumsum()
58 
59     return Z
60 
61 
62 class Differencer(_SeriesToSeriesTransformer):
63     """Apply iterative differences to a timeseries.
64 
65     The transformation works for univariate and multivariate timeseries. However,
66     the multivariate case applies the same differencing to every series.
67 
68     Difference transformations are applied at the specified lags in the order
69     provided.
70 
71     For example, given a timeseries with monthly periodicity, using lags=[1, 12]
72     corresponds to applying a standard first difference to handle trend, and
73     followed by a seasonal difference (at lag 12) to attempt to account for
74     seasonal dependence.
75 
76     To provide a higher-order difference at the same lag list the lag multiple
77     times. For example, lags=[1, 1] takes iterative first differences like may
78     be needed for a series that is integrated of order 2.
79 
80     Parameters
81     ----------
82     lags : int or array-like, default = 1
83         The lags used to difference the data.
84         If a single `int` value is
85 
86     drop_na : bool, default = True
87         Whether the differencer should drop the initial observations that
88         contain missing values as a result of the differencing operation(s).
89 
90     Attributes
91     ----------
92     lags : int or array-like
93         Lags used to perform the differencing of the input series.
94 
95     drop_na : bool
96         Stores whether the Differencer drops the initial observations that contain
97         missing values as a result of the differencing operation(s).
98 
99     Example
100     -------
101     >>> from sktime.transformations.series.difference import Differencer
102     >>> from sktime.datasets import load_airline
103     >>> y = load_airline()
104     >>> transformer = Differencer(lags=[1, 12])
105     >>> y_transform = transformer.fit_transform(y)
106     """
107 
108     _tags = {
109         "fit-in-transform": False,
110         "transform-returns-same-time-index": False,
111         "univariate-only": False,
112     }
113 
114     def __init__(self, lags=1, drop_na=True):
115         self.lags = lags
116         self.drop_na = drop_na
117         self._Z = None
118         self._lags = None
119         self._cumulative_lags = None
120         self._prior_cum_lags = None
121         self._prior_lags = None
122         super(Differencer, self).__init__()
123 
124     def _check_inverse_transform_index(self, Z):
125         """Check fitted series contains indices needed in inverse_transform."""
126         first_idx = Z.index.min()
127         orig_first_idx, orig_last_idx = self._Z.index.min(), self._Z.index.max()
128 
129         is_contained_by_fitted_z = False
130         is_future = False
131 
132         if first_idx < orig_first_idx:
133             msg = [
134                 "Some indices of `Z` are prior to timeseries used in `fit`.",
135                 "Reconstruction via `inverse_transform` is not possible.",
136             ]
137             raise ValueError(" ".join(msg))
138 
139         elif Z.index.difference(self._Z.index).shape[0] == 0:
140             is_contained_by_fitted_z = True
141 
142         elif first_idx > orig_last_idx:
143             is_future = True
144 
145         pad_z_inv = self.drop_na or is_future
146 
147         cutoff = Z.index[0] if pad_z_inv else Z.index[self._cumulative_lags[-1]]
148         fh = ForecastingHorizon(np.arange(-1, -(self._cumulative_lags[-1] + 1), -1))
149         index = fh.to_absolute(cutoff).to_pandas()
150         index_diff = index.difference(self._Z.index)
151 
152         if index_diff.shape[0] != 0 and not is_contained_by_fitted_z:
153             msg = [
154                 f"Inverse transform requires indices {index}",
155                 "to have been stored in `fit()`,",
156                 f"but the indices {index_diff} were not found.",
157             ]
158             raise ValueError(" ".join(msg))
159 
160         return is_contained_by_fitted_z, pad_z_inv
161 
162     def _fit(self, Z, X=None):
163         """Logic used by fit method on `Z`.
164 
165         Parameters
166         ----------
167         Z : pd.Series or pd.DataFrame
168             A timeseries to apply the specified transformation on.
169 
170         Returns
171         -------
172         self
173         """
174         self._lags = _check_lags(self.lags)
175         self._prior_lags = np.roll(self._lags, shift=1)
176         self._prior_lags[0] = 0
177         self._cumulative_lags = self._lags.cumsum()
178         self._prior_cum_lags = np.zeros_like(self._cumulative_lags)
179         self._prior_cum_lags[1:] = self._cumulative_lags[:-1]
180         self._Z = Z.copy()
181         return self
182 
183     def _transform(self, Z, X=None):
184         """Logic used by `transform` to apply transformation to `Z`.
185 
186         Differences are applied at lags specified in `lags`.
187 
188         Parameters
189         ----------
190         Z : pd.Series or pd.DataFrame
191             The timeseries to apply the specified transformation on.
192 
193         Returns
194         -------
195         Zt : pd.Series or pd.DataFrame
196             The transformed timeseries.
197         """
198         Zt = _diff_transform(Z, self._lags)
199         if self.drop_na:
200             Zt = Zt.iloc[self._cumulative_lags[-1] :]
201         return Zt
202 
203     def _inverse_transform(self, Z, X=None):
204         """Logic used by `inverse_transform` to reverse transformation on  `Z`.
205 
206         Parameters
207         ----------
208         Z : pd.Series or pd.DataFrame
209             A time series to reverse the transformation on.
210 
211         Returns
212         -------
213         Z_inv : pd.Series or pd.DataFrame
214             The reconstructed timeseries after the transformation has been reversed.
215         """
216         is_df = isinstance(Z, pd.DataFrame)
217         is_contained_by_fit_z, pad_z_inv = self._check_inverse_transform_index(Z)
218 
219         # If `Z` is entirely contained in fitted `_Z` we can just return
220         # the values from the timeseires stored in `fit` as a shortcut
221         if is_contained_by_fit_z:
222             Z_inv = self._Z.loc[Z.index, :] if is_df else self._Z.loc[Z.index]
223 
224         else:
225             Z_inv = Z.copy()
226             for i, lag_info in enumerate(
227                 zip(self._lags[::-1], self._prior_cum_lags[::-1])
228             ):
229                 lag, prior_cum_lag = lag_info
230                 _lags = self._lags[::-1][i + 1 :]
231                 _transformed = _diff_transform(self._Z, _lags)
232 
233                 # Determine index values for initial values needed to reverse
234                 # the differencing for the specified lag
235                 if pad_z_inv:
236                     cutoff = Z_inv.index[0]
237                 else:
238                     cutoff = Z_inv.index[prior_cum_lag + lag]
239                 fh = ForecastingHorizon(np.arange(-1, -(lag + 1), -1))
240                 index = fh.to_absolute(cutoff).to_pandas()
241 
242                 if is_df:
243                     prior_n_timepoint_values = _transformed.loc[index, :]
244                 else:
245                     prior_n_timepoint_values = _transformed.loc[index]
246                 if pad_z_inv:
247                     Z_inv = pd.concat([prior_n_timepoint_values, Z_inv])
248                 else:
249                     Z_inv.update(prior_n_timepoint_values)
250 
251                 Z_inv = _inverse_diff(Z_inv, lag)
252 
253         if pad_z_inv:
254             Z_inv = Z_inv.loc[Z.index, :] if is_df else Z_inv.loc[Z.index]
255 
256         return Z_inv
257 
258     def fit(self, Z, X=None):
259         """Fit the transformation on input series `Z`.
260 
261         Parameters
262         ----------
263         Z : pd.Series or pd.DataFrame
264             A time series to apply the specified transformation on.
265 
266         Returns
267         -------
268         self
269         """
270         Z = check_series(Z)
271 
272         self._fit(Z, X=X)
273 
274         self._is_fitted = True
275         return self
276 
277     def transform(self, Z, X=None):
278         """Return transformed version of input series `Z`.
279 
280         Parameters
281         ----------
282         Z : pd.Series or pd.DataFrame
283             A time series to apply the specified transformation on.
284 
285         Returns
286         -------
287         Zt : pd.Series or pd.DataFrame
288             Transformed version of input series `Z`.
289         """
290         self.check_is_fitted()
291         Z = check_series(Z)
292 
293         Zt = self._transform(Z, X=X)
294 
295         return Zt
296 
297     def inverse_transform(self, Z, X=None):
298         """Reverse transformation on input series `Z`.
299 
300         Parameters
301         ----------
302         Z : pd.Series or pd.DataFrame
303             A time series to reverse the transformation on.
304 
305         Returns
306         -------
307         Z_inv : pd.Series or pd.DataFrame
308             The reconstructed timeseries after the transformation has been reversed.
309         """
310         self.check_is_fitted()
311         Z = check_series(Z)
312 
313         Z_inv = self._inverse_transform(Z, X=X)
314 
315         return Z_inv
316
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sktime/transformations/series/difference.py b/sktime/transformations/series/difference.py
--- a/sktime/transformations/series/difference.py
+++ b/sktime/transformations/series/difference.py
@@ -96,8 +96,8 @@
         Stores whether the Differencer drops the initial observations that contain
         missing values as a result of the differencing operation(s).
 
-    Example
-    -------
+    Examples
+    --------
     >>> from sktime.transformations.series.difference import Differencer
     >>> from sktime.datasets import load_airline
     >>> y = load_airline()

{"golden_diff": "diff --git a/sktime/transformations/series/difference.py b/sktime/transformations/series/difference.py\n--- a/sktime/transformations/series/difference.py\n+++ b/sktime/transformations/series/difference.py\n@@ -96,8 +96,8 @@\n         Stores whether the Differencer drops the initial observations that contain\n         missing values as a result of the differencing operation(s).\n \n-    Example\n-    -------\n+    Examples\n+    --------\n     >>> from sktime.transformations.series.difference import Differencer\n     >>> from sktime.datasets import load_airline\n     >>> y = load_airline()\n", "issue": "[DOC] Transformers missing from API Reference\n#### Describe the issue linked to the documentation\r\n\r\n<!--\r\nTell us about the confusion introduced in the documentation.\r\n-->\r\n\r\nThere have been transformers that have recently been improved but have not been added the API Reference. \r\n\r\nFor example, STLTransformer, CLaSPTransformer, etc.\r\n\r\n#### Suggest a potential alternative/fix\r\n\r\n<!--\r\nTell us how we could improve the documentation in this regard.\r\n-->\r\n\r\nThese need to be added to the documentation. \r\n\r\nIn the future we'll have to try and be good about following our [reviewer guide](https://www.sktime.org/en/stable/reviewer_guide.html) and waiting to merge until things are added to docs. Otherwise, it is hard for users to know what functionality exists.\n", "before_files": [{"content": "#!/usr/bin/env python3 -u\n# -*- coding: utf-8 -*-\n# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)\n\"\"\"Class to iteratively apply differences to a time series.\"\"\"\n__author__ = [\"Ryan Kuhns\"]\n__all__ = [\"Differencer\"]\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.utils import check_array\n\nfrom sktime.forecasting.base import ForecastingHorizon\nfrom sktime.transformations.base import _SeriesToSeriesTransformer\nfrom sktime.utils.validation import is_int\nfrom sktime.utils.validation.series import check_series\n\n\ndef _check_lags(lags):\n    msg = \" \".join(\n        [\n            \"`lags` should be provided as a positive integer scaler, or\",\n            \"a list, tuple or np.ndarray of positive integers,\"\n            f\"but found {type(lags)}.\",\n        ]\n    )\n    non_positive_msg = \"`lags` should be positive integers.\"\n    if isinstance(lags, int):\n        if lags <= 0:\n            raise ValueError(non_positive_msg)\n        lags = check_array([lags], ensure_2d=False)\n    elif isinstance(lags, (list, tuple, np.ndarray)):\n        if not all([is_int(lag) for lag in lags]):\n            raise TypeError(msg)\n        lags = check_array(lags, ensure_2d=False)\n        if (lags <= 0).any():\n            raise ValueError(non_positive_msg)\n    else:\n        raise TypeError(msg)\n\n    return lags\n\n\ndef _diff_transform(Z, lags):\n    Zt = Z.copy()\n\n    if len(lags) == 0:\n        return Zt\n\n    else:\n        for lag in lags:\n            Zt = Zt.diff(lag)\n        return Zt\n\n\ndef _inverse_diff(Z, lag):\n    for i in range(lag):\n        Z.iloc[i::lag] = Z.iloc[i::lag].cumsum()\n\n    return Z\n\n\nclass Differencer(_SeriesToSeriesTransformer):\n    \"\"\"Apply iterative differences to a timeseries.\n\n    The transformation works for univariate and multivariate timeseries. However,\n    the multivariate case applies the same differencing to every series.\n\n    Difference transformations are applied at the specified lags in the order\n    provided.\n\n    For example, given a timeseries with monthly periodicity, using lags=[1, 12]\n    corresponds to applying a standard first difference to handle trend, and\n    followed by a seasonal difference (at lag 12) to attempt to account for\n    seasonal dependence.\n\n    To provide a higher-order difference at the same lag list the lag multiple\n    times. For example, lags=[1, 1] takes iterative first differences like may\n    be needed for a series that is integrated of order 2.\n\n    Parameters\n    ----------\n    lags : int or array-like, default = 1\n        The lags used to difference the data.\n        If a single `int` value is\n\n    drop_na : bool, default = True\n        Whether the differencer should drop the initial observations that\n        contain missing values as a result of the differencing operation(s).\n\n    Attributes\n    ----------\n    lags : int or array-like\n        Lags used to perform the differencing of the input series.\n\n    drop_na : bool\n        Stores whether the Differencer drops the initial observations that contain\n        missing values as a result of the differencing operation(s).\n\n    Example\n    -------\n    >>> from sktime.transformations.series.difference import Differencer\n    >>> from sktime.datasets import load_airline\n    >>> y = load_airline()\n    >>> transformer = Differencer(lags=[1, 12])\n    >>> y_transform = transformer.fit_transform(y)\n    \"\"\"\n\n    _tags = {\n        \"fit-in-transform\": False,\n        \"transform-returns-same-time-index\": False,\n        \"univariate-only\": False,\n    }\n\n    def __init__(self, lags=1, drop_na=True):\n        self.lags = lags\n        self.drop_na = drop_na\n        self._Z = None\n        self._lags = None\n        self._cumulative_lags = None\n        self._prior_cum_lags = None\n        self._prior_lags = None\n        super(Differencer, self).__init__()\n\n    def _check_inverse_transform_index(self, Z):\n        \"\"\"Check fitted series contains indices needed in inverse_transform.\"\"\"\n        first_idx = Z.index.min()\n        orig_first_idx, orig_last_idx = self._Z.index.min(), self._Z.index.max()\n\n        is_contained_by_fitted_z = False\n        is_future = False\n\n        if first_idx < orig_first_idx:\n            msg = [\n                \"Some indices of `Z` are prior to timeseries used in `fit`.\",\n                \"Reconstruction via `inverse_transform` is not possible.\",\n            ]\n            raise ValueError(\" \".join(msg))\n\n        elif Z.index.difference(self._Z.index).shape[0] == 0:\n            is_contained_by_fitted_z = True\n\n        elif first_idx > orig_last_idx:\n            is_future = True\n\n        pad_z_inv = self.drop_na or is_future\n\n        cutoff = Z.index[0] if pad_z_inv else Z.index[self._cumulative_lags[-1]]\n        fh = ForecastingHorizon(np.arange(-1, -(self._cumulative_lags[-1] + 1), -1))\n        index = fh.to_absolute(cutoff).to_pandas()\n        index_diff = index.difference(self._Z.index)\n\n        if index_diff.shape[0] != 0 and not is_contained_by_fitted_z:\n            msg = [\n                f\"Inverse transform requires indices {index}\",\n                \"to have been stored in `fit()`,\",\n                f\"but the indices {index_diff} were not found.\",\n            ]\n            raise ValueError(\" \".join(msg))\n\n        return is_contained_by_fitted_z, pad_z_inv\n\n    def _fit(self, Z, X=None):\n        \"\"\"Logic used by fit method on `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A timeseries to apply the specified transformation on.\n\n        Returns\n        -------\n        self\n        \"\"\"\n        self._lags = _check_lags(self.lags)\n        self._prior_lags = np.roll(self._lags, shift=1)\n        self._prior_lags[0] = 0\n        self._cumulative_lags = self._lags.cumsum()\n        self._prior_cum_lags = np.zeros_like(self._cumulative_lags)\n        self._prior_cum_lags[1:] = self._cumulative_lags[:-1]\n        self._Z = Z.copy()\n        return self\n\n    def _transform(self, Z, X=None):\n        \"\"\"Logic used by `transform` to apply transformation to `Z`.\n\n        Differences are applied at lags specified in `lags`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            The timeseries to apply the specified transformation on.\n\n        Returns\n        -------\n        Zt : pd.Series or pd.DataFrame\n            The transformed timeseries.\n        \"\"\"\n        Zt = _diff_transform(Z, self._lags)\n        if self.drop_na:\n            Zt = Zt.iloc[self._cumulative_lags[-1] :]\n        return Zt\n\n    def _inverse_transform(self, Z, X=None):\n        \"\"\"Logic used by `inverse_transform` to reverse transformation on  `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to reverse the transformation on.\n\n        Returns\n        -------\n        Z_inv : pd.Series or pd.DataFrame\n            The reconstructed timeseries after the transformation has been reversed.\n        \"\"\"\n        is_df = isinstance(Z, pd.DataFrame)\n        is_contained_by_fit_z, pad_z_inv = self._check_inverse_transform_index(Z)\n\n        # If `Z` is entirely contained in fitted `_Z` we can just return\n        # the values from the timeseires stored in `fit` as a shortcut\n        if is_contained_by_fit_z:\n            Z_inv = self._Z.loc[Z.index, :] if is_df else self._Z.loc[Z.index]\n\n        else:\n            Z_inv = Z.copy()\n            for i, lag_info in enumerate(\n                zip(self._lags[::-1], self._prior_cum_lags[::-1])\n            ):\n                lag, prior_cum_lag = lag_info\n                _lags = self._lags[::-1][i + 1 :]\n                _transformed = _diff_transform(self._Z, _lags)\n\n                # Determine index values for initial values needed to reverse\n                # the differencing for the specified lag\n                if pad_z_inv:\n                    cutoff = Z_inv.index[0]\n                else:\n                    cutoff = Z_inv.index[prior_cum_lag + lag]\n                fh = ForecastingHorizon(np.arange(-1, -(lag + 1), -1))\n                index = fh.to_absolute(cutoff).to_pandas()\n\n                if is_df:\n                    prior_n_timepoint_values = _transformed.loc[index, :]\n                else:\n                    prior_n_timepoint_values = _transformed.loc[index]\n                if pad_z_inv:\n                    Z_inv = pd.concat([prior_n_timepoint_values, Z_inv])\n                else:\n                    Z_inv.update(prior_n_timepoint_values)\n\n                Z_inv = _inverse_diff(Z_inv, lag)\n\n        if pad_z_inv:\n            Z_inv = Z_inv.loc[Z.index, :] if is_df else Z_inv.loc[Z.index]\n\n        return Z_inv\n\n    def fit(self, Z, X=None):\n        \"\"\"Fit the transformation on input series `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to apply the specified transformation on.\n\n        Returns\n        -------\n        self\n        \"\"\"\n        Z = check_series(Z)\n\n        self._fit(Z, X=X)\n\n        self._is_fitted = True\n        return self\n\n    def transform(self, Z, X=None):\n        \"\"\"Return transformed version of input series `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to apply the specified transformation on.\n\n        Returns\n        -------\n        Zt : pd.Series or pd.DataFrame\n            Transformed version of input series `Z`.\n        \"\"\"\n        self.check_is_fitted()\n        Z = check_series(Z)\n\n        Zt = self._transform(Z, X=X)\n\n        return Zt\n\n    def inverse_transform(self, Z, X=None):\n        \"\"\"Reverse transformation on input series `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to reverse the transformation on.\n\n        Returns\n        -------\n        Z_inv : pd.Series or pd.DataFrame\n            The reconstructed timeseries after the transformation has been reversed.\n        \"\"\"\n        self.check_is_fitted()\n        Z = check_series(Z)\n\n        Z_inv = self._inverse_transform(Z, X=X)\n\n        return Z_inv\n", "path": "sktime/transformations/series/difference.py"}], "after_files": [{"content": "#!/usr/bin/env python3 -u\n# -*- coding: utf-8 -*-\n# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)\n\"\"\"Class to iteratively apply differences to a time series.\"\"\"\n__author__ = [\"Ryan Kuhns\"]\n__all__ = [\"Differencer\"]\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.utils import check_array\n\nfrom sktime.forecasting.base import ForecastingHorizon\nfrom sktime.transformations.base import _SeriesToSeriesTransformer\nfrom sktime.utils.validation import is_int\nfrom sktime.utils.validation.series import check_series\n\n\ndef _check_lags(lags):\n    msg = \" \".join(\n        [\n            \"`lags` should be provided as a positive integer scaler, or\",\n            \"a list, tuple or np.ndarray of positive integers,\"\n            f\"but found {type(lags)}.\",\n        ]\n    )\n    non_positive_msg = \"`lags` should be positive integers.\"\n    if isinstance(lags, int):\n        if lags <= 0:\n            raise ValueError(non_positive_msg)\n        lags = check_array([lags], ensure_2d=False)\n    elif isinstance(lags, (list, tuple, np.ndarray)):\n        if not all([is_int(lag) for lag in lags]):\n            raise TypeError(msg)\n        lags = check_array(lags, ensure_2d=False)\n        if (lags <= 0).any():\n            raise ValueError(non_positive_msg)\n    else:\n        raise TypeError(msg)\n\n    return lags\n\n\ndef _diff_transform(Z, lags):\n    Zt = Z.copy()\n\n    if len(lags) == 0:\n        return Zt\n\n    else:\n        for lag in lags:\n            Zt = Zt.diff(lag)\n        return Zt\n\n\ndef _inverse_diff(Z, lag):\n    for i in range(lag):\n        Z.iloc[i::lag] = Z.iloc[i::lag].cumsum()\n\n    return Z\n\n\nclass Differencer(_SeriesToSeriesTransformer):\n    \"\"\"Apply iterative differences to a timeseries.\n\n    The transformation works for univariate and multivariate timeseries. However,\n    the multivariate case applies the same differencing to every series.\n\n    Difference transformations are applied at the specified lags in the order\n    provided.\n\n    For example, given a timeseries with monthly periodicity, using lags=[1, 12]\n    corresponds to applying a standard first difference to handle trend, and\n    followed by a seasonal difference (at lag 12) to attempt to account for\n    seasonal dependence.\n\n    To provide a higher-order difference at the same lag list the lag multiple\n    times. For example, lags=[1, 1] takes iterative first differences like may\n    be needed for a series that is integrated of order 2.\n\n    Parameters\n    ----------\n    lags : int or array-like, default = 1\n        The lags used to difference the data.\n        If a single `int` value is\n\n    drop_na : bool, default = True\n        Whether the differencer should drop the initial observations that\n        contain missing values as a result of the differencing operation(s).\n\n    Attributes\n    ----------\n    lags : int or array-like\n        Lags used to perform the differencing of the input series.\n\n    drop_na : bool\n        Stores whether the Differencer drops the initial observations that contain\n        missing values as a result of the differencing operation(s).\n\n    Examples\n    --------\n    >>> from sktime.transformations.series.difference import Differencer\n    >>> from sktime.datasets import load_airline\n    >>> y = load_airline()\n    >>> transformer = Differencer(lags=[1, 12])\n    >>> y_transform = transformer.fit_transform(y)\n    \"\"\"\n\n    _tags = {\n        \"fit-in-transform\": False,\n        \"transform-returns-same-time-index\": False,\n        \"univariate-only\": False,\n    }\n\n    def __init__(self, lags=1, drop_na=True):\n        self.lags = lags\n        self.drop_na = drop_na\n        self._Z = None\n        self._lags = None\n        self._cumulative_lags = None\n        self._prior_cum_lags = None\n        self._prior_lags = None\n        super(Differencer, self).__init__()\n\n    def _check_inverse_transform_index(self, Z):\n        \"\"\"Check fitted series contains indices needed in inverse_transform.\"\"\"\n        first_idx = Z.index.min()\n        orig_first_idx, orig_last_idx = self._Z.index.min(), self._Z.index.max()\n\n        is_contained_by_fitted_z = False\n        is_future = False\n\n        if first_idx < orig_first_idx:\n            msg = [\n                \"Some indices of `Z` are prior to timeseries used in `fit`.\",\n                \"Reconstruction via `inverse_transform` is not possible.\",\n            ]\n            raise ValueError(\" \".join(msg))\n\n        elif Z.index.difference(self._Z.index).shape[0] == 0:\n            is_contained_by_fitted_z = True\n\n        elif first_idx > orig_last_idx:\n            is_future = True\n\n        pad_z_inv = self.drop_na or is_future\n\n        cutoff = Z.index[0] if pad_z_inv else Z.index[self._cumulative_lags[-1]]\n        fh = ForecastingHorizon(np.arange(-1, -(self._cumulative_lags[-1] + 1), -1))\n        index = fh.to_absolute(cutoff).to_pandas()\n        index_diff = index.difference(self._Z.index)\n\n        if index_diff.shape[0] != 0 and not is_contained_by_fitted_z:\n            msg = [\n                f\"Inverse transform requires indices {index}\",\n                \"to have been stored in `fit()`,\",\n                f\"but the indices {index_diff} were not found.\",\n            ]\n            raise ValueError(\" \".join(msg))\n\n        return is_contained_by_fitted_z, pad_z_inv\n\n    def _fit(self, Z, X=None):\n        \"\"\"Logic used by fit method on `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A timeseries to apply the specified transformation on.\n\n        Returns\n        -------\n        self\n        \"\"\"\n        self._lags = _check_lags(self.lags)\n        self._prior_lags = np.roll(self._lags, shift=1)\n        self._prior_lags[0] = 0\n        self._cumulative_lags = self._lags.cumsum()\n        self._prior_cum_lags = np.zeros_like(self._cumulative_lags)\n        self._prior_cum_lags[1:] = self._cumulative_lags[:-1]\n        self._Z = Z.copy()\n        return self\n\n    def _transform(self, Z, X=None):\n        \"\"\"Logic used by `transform` to apply transformation to `Z`.\n\n        Differences are applied at lags specified in `lags`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            The timeseries to apply the specified transformation on.\n\n        Returns\n        -------\n        Zt : pd.Series or pd.DataFrame\n            The transformed timeseries.\n        \"\"\"\n        Zt = _diff_transform(Z, self._lags)\n        if self.drop_na:\n            Zt = Zt.iloc[self._cumulative_lags[-1] :]\n        return Zt\n\n    def _inverse_transform(self, Z, X=None):\n        \"\"\"Logic used by `inverse_transform` to reverse transformation on  `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to reverse the transformation on.\n\n        Returns\n        -------\n        Z_inv : pd.Series or pd.DataFrame\n            The reconstructed timeseries after the transformation has been reversed.\n        \"\"\"\n        is_df = isinstance(Z, pd.DataFrame)\n        is_contained_by_fit_z, pad_z_inv = self._check_inverse_transform_index(Z)\n\n        # If `Z` is entirely contained in fitted `_Z` we can just return\n        # the values from the timeseires stored in `fit` as a shortcut\n        if is_contained_by_fit_z:\n            Z_inv = self._Z.loc[Z.index, :] if is_df else self._Z.loc[Z.index]\n\n        else:\n            Z_inv = Z.copy()\n            for i, lag_info in enumerate(\n                zip(self._lags[::-1], self._prior_cum_lags[::-1])\n            ):\n                lag, prior_cum_lag = lag_info\n                _lags = self._lags[::-1][i + 1 :]\n                _transformed = _diff_transform(self._Z, _lags)\n\n                # Determine index values for initial values needed to reverse\n                # the differencing for the specified lag\n                if pad_z_inv:\n                    cutoff = Z_inv.index[0]\n                else:\n                    cutoff = Z_inv.index[prior_cum_lag + lag]\n                fh = ForecastingHorizon(np.arange(-1, -(lag + 1), -1))\n                index = fh.to_absolute(cutoff).to_pandas()\n\n                if is_df:\n                    prior_n_timepoint_values = _transformed.loc[index, :]\n                else:\n                    prior_n_timepoint_values = _transformed.loc[index]\n                if pad_z_inv:\n                    Z_inv = pd.concat([prior_n_timepoint_values, Z_inv])\n                else:\n                    Z_inv.update(prior_n_timepoint_values)\n\n                Z_inv = _inverse_diff(Z_inv, lag)\n\n        if pad_z_inv:\n            Z_inv = Z_inv.loc[Z.index, :] if is_df else Z_inv.loc[Z.index]\n\n        return Z_inv\n\n    def fit(self, Z, X=None):\n        \"\"\"Fit the transformation on input series `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to apply the specified transformation on.\n\n        Returns\n        -------\n        self\n        \"\"\"\n        Z = check_series(Z)\n\n        self._fit(Z, X=X)\n\n        self._is_fitted = True\n        return self\n\n    def transform(self, Z, X=None):\n        \"\"\"Return transformed version of input series `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to apply the specified transformation on.\n\n        Returns\n        -------\n        Zt : pd.Series or pd.DataFrame\n            Transformed version of input series `Z`.\n        \"\"\"\n        self.check_is_fitted()\n        Z = check_series(Z)\n\n        Zt = self._transform(Z, X=X)\n\n        return Zt\n\n    def inverse_transform(self, Z, X=None):\n        \"\"\"Reverse transformation on input series `Z`.\n\n        Parameters\n        ----------\n        Z : pd.Series or pd.DataFrame\n            A time series to reverse the transformation on.\n\n        Returns\n        -------\n        Z_inv : pd.Series or pd.DataFrame\n            The reconstructed timeseries after the transformation has been reversed.\n        \"\"\"\n        self.check_is_fitted()\n        Z = check_series(Z)\n\n        Z_inv = self._inverse_transform(Z, X=X)\n\n        return Z_inv\n", "path": "sktime/transformations/series/difference.py"}]}