Repository: sktime/sktime

Issue:
[BUG] Prophet with logistic growth does not work
**Describe the bug**
It is not compatible with the case that the growth parameter of prophet is equal to ‘logistic‘

**To Reproduce**
```python

from sktime.datasets import load_airline
y = load_airline()
y = y.to_timestamp(freq="M")
y_train, y_test = temporal_train_test_split(y, test_size=36)

y_train_df = pd.DataFrame(y_train,index = y_train.index,columns=['y'])
y_train_df["cap"] = max(y_train_df["y"])
y_train_df['floor'] = 0

forecaster = Prophet(
    growth = 'logistic'
)

forecaster.fit(y_train_df)
```

**Expected behavior**
<!--
-->
The growth parameter compatible with prophet is equal to ‘logistic‘
**Additional context**
<!--
Add any other context about the problem here.
-->

**Versions**
<details>

<!--
Please run the following code snippet and paste the output here:
 -->
from sktime import show_versions; show_versions()

System:
    python: 3.8.3 (default, Jul  2 2020, 11:26:31)  [Clang 10.0.0 ]
executable: /Users/baixiaotiao/opt/anaconda3/bin/python
   machine: macOS-10.16-x86_64-i386-64bit

Python dependencies:
          pip: 20.1.1
   setuptools: 49.2.0.post20200714
      sklearn: 0.24.2
       sktime: 0.6.1
  statsmodels: 0.12.2
        numpy: 1.20.3
        scipy: 1.5.0
       Cython: 0.29.21
       pandas: 1.2.5
   matplotlib: 3.2.2
       joblib: 0.16.0
        numba: 0.50.1
     pmdarima: 1.8.2
      tsfresh: None


</details>

<!-- Thanks for contributing! -->



Discussion:
Yes, I can confirm this, this is because setting the growth parameter to "logistic" requires passing a `pd.DataFrame` with additional columns "cap" and "floor". 

This is related to #980. 
Well, but certainly not caused by #980 (because it's not merged yet), but potentially solved by #980, right?
> Yes, I can confirm this, this is because setting the growth parameter to "logistic" requires passing a `pd.DataFrame` with additional columns "cap" and "floor".

What do you mean by 'I can confirm this'. Are you saying there should be a bug? Or are you saying that passing the correct dataframe would fix the bug?

I ask because in the example code, a dataframe with columns cap and floor has been passed.


@Lovkush-A I just meant that I can reproduce the error and that I agree with @muzibai that we're currently not covering that use case. 
Thanks for explaining Markus. I think this bug would be a useful way to compare the different input/output frameworks that are in discussion!

@fkiraly How would your framework deal with a class in which the inner-type depends on user-input?
>@fkiraly How would your framework deal with a class in which the inner-type depends on user-input?

Assuming that "user input" means behaviour controlled by hyper-parameters set in construction, the paradigm would be reading that hyper-parameter in `_fit` and dealing with any additional conversion or input logic there. `fit` would be set up to pass through the supported inner types and otherwise convert to them.

Generally, implementers can do whatever they want in `_fit` in addition to the "generic" conversions which only take over if nothing smarter is specified.

Resolving patch:
diff --git a/sktime/forecasting/base/adapters/_fbprophet.py b/sktime/forecasting/base/adapters/_fbprophet.py
--- a/sktime/forecasting/base/adapters/_fbprophet.py
+++ b/sktime/forecasting/base/adapters/_fbprophet.py
@@ -66,6 +66,18 @@ def _fit(self, y, X=None, fh=None):
             for col in X.columns:
                 self._forecaster.add_regressor(col)
 
+        # Add floor and bottom when growth is logistic
+        if self.growth == "logistic":
+
+            if self.growth_cap is None:
+                raise ValueError(
+                    "Since `growth` param is set to 'logistic', expecting `growth_cap`"
+                    " to be non `None`: a float."
+                )
+
+            df["cap"] = self.growth_cap
+            df["floor"] = self.growth_floor
+
         if self.verbose:
             self._forecaster.fit(df=df)
         else:
@@ -108,6 +120,10 @@ def _predict(self, fh=None, X=None):
             X = X.copy()
             df, X = _merge_X(df, X)
 
+        if self.growth == "logistic":
+            df["cap"] = self.growth_cap
+            df["floor"] = self.growth_floor
+
         out = self._forecaster.predict(df)
 
         out.set_index("ds", inplace=True)
@@ -233,7 +249,7 @@ def _merge_X(df, X):
     ----------
     fh : sktime.ForecastingHorizon
     X : pd.DataFrame
-        Exog data
+        Exogeneous data
     df : pd.DataFrame
         Contains a DatetimeIndex column "ds"
 
diff --git a/sktime/forecasting/fbprophet.py b/sktime/forecasting/fbprophet.py
--- a/sktime/forecasting/fbprophet.py
+++ b/sktime/forecasting/fbprophet.py
@@ -37,7 +37,15 @@ class Prophet(_ProphetAdapter):
             country_name: Name of the country, like 'UnitedStates' or 'US'
     growth: str, default="linear"
         String 'linear' or 'logistic' to specify a linear or logistic
-        trend.
+        trend. If 'logistic' specified float for 'growth_cap' must be provided.
+    growth_floor: float, default=0
+        Growth saturation minimum value.
+        Used only if  `growth="logistic"`, has no effect otherwise
+        (if `growth` is not `"logistic"`).
+    growth_cap: float, default=None
+        Growth saturation maximum aka carrying capacity.
+        Mandatory (float) iff `growth="logistic"`, has no effect and is optional,
+        otherwise (if `growth` is not `"logistic"`).
     changepoints: list or None, default=None
         List of dates at which to include potential changepoints. If
         not specified, potential changepoints are selected automatically.
@@ -126,6 +134,8 @@ def __init__(
         add_country_holidays=None,
         # Args of fbprophet
         growth="linear",
+        growth_floor=0.0,
+        growth_cap=None,
         changepoints=None,
         n_changepoints=25,
         changepoint_range=0.8,
@@ -150,6 +160,8 @@ def __init__(
         self.add_country_holidays = add_country_holidays
 
         self.growth = growth
+        self.growth_floor = growth_floor
+        self.growth_cap = growth_cap
         self.changepoints = changepoints
         self.n_changepoints = n_changepoints
         self.changepoint_range = changepoint_range


Test patch: