Repository: sktime/sktime

Issue:
[BUG] KANForecaster fails with newest version (2.6.0)
**Describe the bug**
In the CI (https://github.com/sktime/sktime/actions/runs/10824795191/job/30045061812) the fit of the KANForecaster fails due to `TypeError: MultKAN.fit() got an unexpected keyword argument 'device'`, since the device is not a key word of fit anymore. Now it is a parameter of `__init__`


Discussion:

Patch:
diff --git a/pyproject.toml b/pyproject.toml
index e2ff6c0a1..302699197 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -287,7 +287,7 @@ dl = [
   'tensorflow<2.17,>=2; python_version < "3.12"',
   'torch; python_version < "3.12"',
   'transformers[torch]<4.41.0; python_version < "3.12"',
-  'pykan>=0.2,<0.2.7; python_version > "3.9.7"',
+  'pykan>=0.2.1,<0.2.7; python_version > "3.9.7"',
   'pytorch-forecasting>=1.0.0,<1.2.0',
   'lightning>=2.0; python_version < "3.12"',
   'gluonts>=0.14.3; python_version < "3.12"',
diff --git a/sktime/forecasting/pykan_forecaster.py b/sktime/forecasting/pykan_forecaster.py
index 525130eef..6abfe4e84 100644
--- a/sktime/forecasting/pykan_forecaster.py
+++ b/sktime/forecasting/pykan_forecaster.py
@@ -181,7 +181,7 @@ class PyKANForecaster(BaseForecaster):
                     device=self.device,
                     **self._model_params,
                 ).initialize_from_another_model(model, ds_new["train_input"])
-            results = model.fit(ds_new, device=self.device, **self._fit_params)
+            results = model.fit(ds_new, **self._fit_params)
             if len(self.test_losses) == 0 or results["test_loss"][-1] < min(
                 self.test_losses
             ):
diff --git a/sktime/transformations/series/difference.py b/sktime/transformations/series/difference.py
index e40cda889..a2dc74254 100644
--- a/sktime/transformations/series/difference.py
+++ b/sktime/transformations/series/difference.py
@@ -19,7 +19,7 @@ from sktime.utils.validation import is_int
 def _check_lags(lags):
     msg = " ".join(
         [
-            "`lags` should be provided as a positive integer scaler, or",
+            "`lags` should be provided as a positive integer scaler, or ",
             "a list, tuple or np.ndarray of positive integers,"
             f"but found {type(lags)}.",
         ]
@@ -338,13 +338,12 @@ class Differencer(BaseTransformer):
         X_orig_index = X.index
 
         X = update_data(X=self._X, X_new=X)
+        X = X.sort_index()
 
         X = self._check_freq(X)
 
         Xt = _diff_transform(X, self._lags)
 
-        Xt = Xt.loc[X_orig_index]
-
         na_handling = self.na_handling
         if na_handling == "drop_na":
             Xt = Xt.iloc[self._lagsum :]
@@ -358,6 +357,12 @@ class Differencer(BaseTransformer):
                 f"{na_handling}"
             )
 
+        if na_handling != "drop_na":
+            Xt = Xt.loc[X_orig_index]
+        else:
+            new_index = Xt.index.intersection(X_orig_index)
+            Xt = Xt.loc[new_index]
+
         return Xt
 
     def _inverse_transform(self, X, y=None):


Test patch:
diff --git a/sktime/transformations/series/tests/test_differencer.py b/sktime/transformations/series/tests/test_differencer.py
index 644dd57a0..c0769cdb4 100644
--- a/sktime/transformations/series/tests/test_differencer.py
+++ b/sktime/transformations/series/tests/test_differencer.py
@@ -44,6 +44,35 @@ def test_differencer_produces_expected_results(na_handling):
     _assert_array_almost_equal(y_transformed, y_expected)
 
 
+@pytest.mark.skipif(
+    not run_test_for_class(Differencer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+@pytest.mark.parametrize("na_handling", Differencer.VALID_NA_HANDLING_STR)
+@pytest.mark.parametrize("lags", lags_to_test)
+def test_differencer_transform_memory(na_handling, lags):
+    """Test that Differencer remembers data seen in fit.
+
+    We will create three transformed data, with the same lags, and different
+    na_handling. All should be the same, because:
+
+    * the Differencer should remember data seen in fit
+    * the values affected by na_handling are cut off at the start of the series
+    """
+    y_airline = load_airline()
+    y_airline_start = y_airline[:-24]
+    y_airline_end = y_airline[-24:]
+
+    transformer = Differencer(na_handling=na_handling, lags=lags)
+    transformer.fit(y_airline_start)
+    yt_separate = transformer.transform(y_airline_end)
+    yt_together_trafo = transformer.transform(y_airline)[-24:]
+    yt_together_fit = transformer.fit_transform(y_airline)[-24:]
+
+    _assert_array_almost_equal(yt_separate, yt_together_trafo)
+    _assert_array_almost_equal(yt_separate, yt_together_fit)
+
+
 @pytest.mark.skipif(
     not run_test_for_class(Differencer),
     reason="run test only if softdeps are present and incrementally (if requested)",
@@ -240,3 +269,48 @@ def test_differencer_inverse_does_not_memorize():
 
     # model output should not be similar to train input
     assert not np.allclose(y_train[1:].to_numpy(), model_ins.to_numpy())
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(Differencer),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+def test_dropna_pipeline():
+    """Test that Differencer works in a pipeline with a forecaster.
+
+    Failure case of #7076.
+    """
+    from sklearn.linear_model import ElasticNetCV
+
+    from sktime.datasets import load_longley
+    from sktime.forecasting.compose import (
+        ForecastingPipeline,
+        TransformedTargetForecaster,
+        YfromX,
+    )
+
+    # Load the data
+    y, X = load_longley()
+
+    # Create separate transformers for y and X
+    transformer = Differencer(na_handling="drop_na")
+
+    # Define the TransformedTargetForecaster for y
+    pipe_y = TransformedTargetForecaster(
+        steps=[
+            ("transform_y", transformer),
+            ("forecaster", YfromX(estimator=ElasticNetCV(max_iter=50000))),
+        ]
+    )
+
+    # Create the ForecastingPipeline for y and X
+    pipe_yX = ForecastingPipeline(
+        steps=[("transform_X", transformer), ("pipe_y", pipe_y)]
+    )
+
+    # Fit the pipeline to your data
+    pipe_yX.fit(y=y.iloc[:-1], X=X.iloc[:-1, :])
+
+    # Predict
+    y_pred = pipe_yX.predict(fh=[1], X=X.iloc[-1:, :])
+    assert len(y_pred) == 1