Repository: sktime/sktime (Python)

Issue:
[BUG] Forecasting Pipeline containing an imputer transformer fails when the underlying forecaster can not handle missing data
**Describe the bug**

A forecasting pipeline that **contains an imputer** and a forecaster that **does not accept missing values** fails to work in the latest release of sktime (0.13.4). This used to work fine in 0.13.2 release.

**To Reproduce**

```python
# Works in 0.13.2, but fails in 0.13.4

import numpy as np
from sktime.datasets import load_longley
from sktime.forecasting.trend import TrendForecaster
from sktime.transformations.compose import TransformerPipeline
from sktime.forecasting.compose import ForecastingPipeline, TransformedTargetForecaster
from sktime.transformations.series.impute import Imputer

y, X = load_longley()
y[10] = np.nan  # Add missing values


transformer_y = TransformerPipeline(steps = [("imputer", Imputer())])

forecaster = TransformedTargetForecaster(
    steps = [
        ("transformer_y", transformer_y),
        ("model", TrendForecaster())
    ]
)

pipe = ForecastingPipeline(steps = [("forecaster", forecaster)])

pipe.fit(y)
```

```python-traceback
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
[<ipython-input-18-6f4acc86cd81>](https://localhost:8080/#) in <module>
----> 1 pipe.fit(y)

2 frames
[/usr/local/lib/python3.7/dist-packages/sktime/forecasting/base/_base.py](https://localhost:8080/#) in fit(self, y, X, fh)
    286 
    287         # check and convert X/y
--> 288         X_inner, y_inner = self._check_X_y(X=X, y=y)
    289 
    290         # set internal X/y to the new X/y

[/usr/local/lib/python3.7/dist-packages/sktime/forecasting/base/_base.py](https://localhost:8080/#) in _check_X_y(self, X, y)
   1298                 )
   1299 
-> 1300             _check_missing(y_metadata, "y")
   1301 
   1302         else:

[/usr/local/lib/python3.7/dist-packages/sktime/forecasting/base/_base.py](https://localhost:8080/#) in _check_missing(metadata, obj_name)
   1244                     )
   1245                 if metadata["has_nans"]:
-> 1246                     raise ValueError(msg)
   1247 
   1248         # retrieve supported mtypes

ValueError: ForecastingPipeline cannot handle missing data (nans), but y passed contained missing data.
```

**Expected behavior**
Would have expected this to work because there is an imputer in the pipeline.

**Additional context**
This used to work fine up until the 0.13.2 release. This started breaking after the recent 0.13.4 release (maybe even 0.13.3, but I have not explicitly tested that). **The initial impact was observed in pycaret when the unit tests started failing on GitHub.**

Working Notebook in 0.13.2: https://gist.github.com/ngupta23/fe385efcad52b1d6c4c11f955d13da8e
Notebook with error in 0.13.4: https://gist.github.com/ngupta23/c35b23ae7b786faff2f020a36efcbb38

**Versions**
<details>

<!--
Please run the following code snippet and paste the output here:
 
from sktime import show_versions; show_versions()
-->

</details>

<!-- Thanks for contributing! -->



Discussion:

Patch:
diff --git a/sktime/forecasting/compose/_pipeline.py b/sktime/forecasting/compose/_pipeline.py
index 302286ad959..4714f5606ee 100644
--- a/sktime/forecasting/compose/_pipeline.py
+++ b/sktime/forecasting/compose/_pipeline.py
@@ -299,7 +299,7 @@ class ForecastingPipeline(_Pipeline):
         "X_inner_mtype": SUPPORTED_MTYPES,
         "ignores-exogeneous-X": False,
         "requires-fh-in-fit": False,
-        "handles-missing-data": False,
+        "handles-missing-data": True,
         "capability:pred_int": True,
         "X-y-must-have-same-index": False,
     }
@@ -311,7 +311,6 @@ def __init__(self, steps):
         tags_to_clone = [
             "ignores-exogeneous-X",  # does estimator ignore the exogeneous X?
             "capability:pred_int",  # can the estimator produce prediction intervals?
-            "handles-missing-data",  # can estimator handle missing data?
             "requires-fh-in-fit",  # is forecasting horizon already required in fit?
             "enforce_index_type",  # index type that needs to be enforced in X/y
         ]
@@ -713,7 +712,7 @@ class TransformedTargetForecaster(_Pipeline):
         "X_inner_mtype": SUPPORTED_MTYPES,
         "ignores-exogeneous-X": False,
         "requires-fh-in-fit": False,
-        "handles-missing-data": False,
+        "handles-missing-data": True,
         "capability:pred_int": True,
         "X-y-must-have-same-index": False,
     }
@@ -727,7 +726,6 @@ def __init__(self, steps):
         tags_to_clone = [
             "ignores-exogeneous-X",  # does estimator ignore the exogeneous X?
             "capability:pred_int",  # can the estimator produce prediction intervals?
-            "handles-missing-data",  # can estimator handle missing data?
             "requires-fh-in-fit",  # is forecasting horizon already required in fit?
             "enforce_index_type",  # index type that needs to be enforced in X/y
         ]


Test patch:
diff --git a/sktime/forecasting/compose/tests/test_pipeline.py b/sktime/forecasting/compose/tests/test_pipeline.py
index 2a7286a78d5..ec8d53628c3 100644
--- a/sktime/forecasting/compose/tests/test_pipeline.py
+++ b/sktime/forecasting/compose/tests/test_pipeline.py
@@ -38,6 +38,7 @@
 from sktime.transformations.series.outlier_detection import HampelFilter
 from sktime.utils._testing.estimator_checks import _assert_array_almost_equal
 from sktime.utils._testing.series import _make_series
+from sktime.utils.estimators import MockForecaster
 
 
 def test_pipeline():
@@ -342,3 +343,30 @@ def compute_expected_y_pred(y_train, X_train, X_test, fh):
     expected = compute_expected_y_pred(y_train, X_train, X_test, fh)
     _assert_array_almost_equal(actual, expected, decimal=2)
     _assert_array_almost_equal(actual_alt, expected, decimal=2)
+
+
+def test_tag_handles_missing_data():
+    """Test missing data with Imputer in pipelines.
+
+    Make sure that no exception is raised when NaN and Imputer is given.
+    This test is based on bug issue #3547.
+    """
+    forecaster = MockForecaster()
+    # make sure that test forecaster cant handle missing data
+    forecaster.set_tags(**{"handles-missing-data": False})
+
+    y = _make_series()
+    y[10] = np.nan
+
+    # test only TransformedTargetForecaster
+    y_pipe = TransformedTargetForecaster(
+        steps=[("transformer_y", Imputer()), ("model", forecaster)]
+    )
+    y_pipe.fit(y)
+
+    # test TransformedTargetForecaster and ForecastingPipeline nested
+    y_pipe = TransformedTargetForecaster(
+        steps=[("transformer_y", Imputer()), ("model", forecaster)]
+    )
+    X_pipe = ForecastingPipeline(steps=[("forecaster", y_pipe)])
+    X_pipe.fit(y)