Repository: sktime/sktime

Issue:
[ENH] Pass `pmdarima` and `prophet` models' `fit_params` to constructor
Native (not `sktime`) `pmdarima` models `ARIMA` and `AutoARIMA`, and `Prophet` (in`prophet`, not `sktime`) have some parameters which can be passed via `fit_params` (natively in `pmdarima` or `prophet`).
Users should be able to pass those parameters through the constructor of the `sktime` interface classes.
We should:

* check whether we are missing any parameters in the constructors that can be passed as `fit_params`. (if none are missing, we do not need to do the second step for that class) 
    - [ ] `Prophet`
    - [ ] `ARIMA`
    - [ ] `AutoARIMA` 
* if yes, add them to the constructor and ensure they are passed on to the interfaced model. The recipe is as below.
    - [ ] `Prophet`
    - [ ] `ARIMA`
    - [ ] `AutoARIMA` 
 
Good first issue since recipe is available.

---
Recipe for interfacing models with `fit_params` or any other `fit` args, in sktime:

* pass the parameter(s) to the constructor (the `__init__` method), using explicit parameter names and default, not `**kwargs`
* write it to `self` in the constructor
* in `_fit`, read it from `self` and pass it to the wrapped model's `fit` method
* ensure docstring is complete and defaults documented (optimally same as in wrapped estimator)

This allows to expose all parameter choices via the scikit-learn compatible interface (which relies on the constructor to expose all parameter choices).

For an example of this, you can look at `smoothing_level` in `ExponentialSmoothing`.

Discussion:
tpvasconcelos: > * could you add a `get_test_params` parameter set that involves `fit_params`?

@fkiraly Prophet does not document which parameters are accepted here. These could also change depending on the selected Stan backend. In this case, I would say that there are no interesting cases to add to `get_test_params`. WDYT?
fkiraly: > I would say that there are no interesting cases to add to `get_test_params`. WDYT?

Well, that's odd - if we do not know a single example, are we sure it makes sense to add this as a parameter of the estimator?
tpvasconcelos: > Well, that's odd - if we do not know a single example, are we sure it makes sense to add this as a parameter of the estimator?

An example would be `prophet_forecaster.fit(df, seed=1234, algorithm="Newton")`. My point is that I don't think this is a **relevant** example, for the reasons I shared above and in my other comment. 

But maybe you can help me better understand what `get_test_params` is used for.

fkiraly: > But maybe you can help me better understand what `get_test_params` is used for.

this is used purely in testing, to generate a set of instances so that the estimator - or the interface code - receives good coverage, see last points in
https://www.sktime.net/en/latest/developer_guide/add_estimators.html#how-to-use-sktime-extension-templates

So, if we add the forward dict, we should add an arbitrary example where it is not `None`, so we test its forwarding.
tpvasconcelos: Got it! Then we can assume that the default stan backend will be used by prophet (cmdstanpy) and that these parameters will be passed down to [cmdstanpy.CmdStanModel.optimize](https://mc-stan.org/cmdstanpy/api.html#cmdstanpy.CmdStanModel.optimize) and [cmdstanpy.CmdStanModel.sample](https://mc-stan.org/cmdstanpy/api.html#cmdstanpy.CmdStanModel.sample) 
tpvasconcelos: @fkiraly Could you take a look at the current changes and let me know if there's anything that you'd still like me to address? Thanks in advance!
tpvasconcelos: @fkiraly @yarnabrina thanks again for all the valuable input, attention to detail, and promptness in reviewing this! It's highly appreciated ❤️ 

Patch:
diff --git a/sktime/forecasting/base/adapters/_fbprophet.py b/sktime/forecasting/base/adapters/_fbprophet.py
index acfd91f47..79ac65bf0 100644
--- a/sktime/forecasting/base/adapters/_fbprophet.py
+++ b/sktime/forecasting/base/adapters/_fbprophet.py
@@ -116,11 +116,12 @@ class _ProphetAdapter(BaseForecaster):
             df["cap"] = self.growth_cap
             df["floor"] = self.growth_floor
 
+        fit_kwargs = self.fit_kwargs or {}
         if self.verbose:
-            self._forecaster.fit(df=df)
+            self._forecaster.fit(df=df, **fit_kwargs)
         else:
             with _suppress_stdout_stderr():
-                self._forecaster.fit(df=df)
+                self._forecaster.fit(df=df, **fit_kwargs)
 
         return self
 
diff --git a/sktime/forecasting/fbprophet.py b/sktime/forecasting/fbprophet.py
index e814a8b16..23bacac22 100644
--- a/sktime/forecasting/fbprophet.py
+++ b/sktime/forecasting/fbprophet.py
@@ -46,7 +46,7 @@ class Prophet(_ProphetAdapter):
         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
+        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.
@@ -111,6 +111,10 @@ class Prophet(_ProphetAdapter):
     stan_backend: str or None, default=None
         str as defined in StanBackendEnum. If None, will try to
         iterate over all available backends and find the working one.
+    fit_kwargs: dict or None, default=None
+        Dict with args for Prophet.fit().
+        These are additional arguments passed to the optimizing or sampling
+        functions in Stan.
 
     References
     ----------
@@ -158,6 +162,7 @@ class Prophet(_ProphetAdapter):
         uncertainty_samples=1000,
         stan_backend=None,
         verbose=0,
+        fit_kwargs=None,
     ):
         self.freq = freq
         self.add_seasonality = add_seasonality
@@ -182,6 +187,7 @@ class Prophet(_ProphetAdapter):
         self.uncertainty_samples = uncertainty_samples
         self.stan_backend = stan_backend
         self.verbose = verbose
+        self.fit_kwargs = fit_kwargs
 
         super().__init__()
 
@@ -233,5 +239,6 @@ class Prophet(_ProphetAdapter):
             "daily_seasonality": False,
             "uncertainty_samples": 10,
             "verbose": False,
+            "fit_kwargs": {"seed": 12345},
         }
         return params


Test patch:
diff --git a/sktime/forecasting/tests/test_prophet.py b/sktime/forecasting/tests/test_prophet.py
index e1cc10127..82ca8d252 100644
--- a/sktime/forecasting/tests/test_prophet.py
+++ b/sktime/forecasting/tests/test_prophet.py
@@ -3,7 +3,9 @@
 # copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
 """
 
-__author__ = ["fkiraly"]
+__author__ = ["fkiraly", "tpvasconcelos"]
+
+from unittest import mock
 
 import pandas as pd
 import pytest
@@ -77,3 +79,28 @@ def test_prophet_period_fh(convert_to_datetime):
     else:
         assert isinstance(y_pred.index, pd.PeriodIndex)
         assert (y_pred.index == fh_index).all()
+
+
+@pytest.mark.skipif(
+    not run_test_for_class(Prophet),
+    reason="run test only if softdeps are present and incrementally (if requested)",
+)
+@pytest.mark.parametrize(
+    "fit_kwargs", [None, {"foo": "bar"}, {"foo1": "bar1", "foo2": "bar2"}]
+)
+def test_prophet_fit_kwargs_are_passed_down(fit_kwargs: dict):
+    """Test that the `fit_kwargs` hyperparameter is passed down to Prophet.fit()."""
+    from sktime.datasets import load_airline
+    from sktime.forecasting.fbprophet import Prophet
+
+    y = load_airline()
+    with mock.patch("prophet.forecaster.Prophet.fit") as mock_fit:
+        forecaster = Prophet(fit_kwargs=fit_kwargs)
+        forecaster.fit(y)
+        mock_fit.assert_called_once()
+        assert mock_fit.call_args.args == ()
+        call_kwargs = mock_fit.call_args.kwargs
+        # `df` should always be one of the arguments but
+        # we don't care about its actual value here
+        call_kwargs.pop("df")
+        assert call_kwargs == (fit_kwargs or {})