Repository: sktime/sktime

Issue:
[ENH] Adding a parameter to forecasters from statsmodels to control verbosity of output
**Is your feature request related to a problem? Please describe.**
Some forecasters (such as SARIMAX) that are direct wrappers of statsmodels forecasters, print multiple messages when fitting the model (see image for an example). There is currently no way of controling the level of the printed output.

![image](https://github.com/sktime/sktime/assets/32366550/d1d152b3-c3c6-40ba-969c-fac678c20ebc)


**Describe the solution you'd like**
It would be nice to have a parameter `verbose` to control the level of verbosity. The stasmodels implementation has a parameter called `disp` (see https://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.sarimax.SARIMAX.fit.html#statsmodels.tsa.statespace.sarimax.SARIMAX.fit) that can do this, but there is currently no way of accessing this parameter from the sktime implementation.




Discussion:
Hm, we need to be careful about whether this parameter was added in a recent `statsforecast` version (the parameter interfaces are not very stable across versios of `statsforecast`), apart from that it should be possible to add it in `_GeneralisedStatsforecastAdapter`.

FYI @yarnabrina.

@tvdboom, would you be open to try contribute this (possibly with pointers where to add and how to handle unstable interfaces)?
The `disp` parameter seems to be available at least since version 0.8. I'll make a PR, bit first we need to agree on the API. My idea was to add a boolean parameter to the constructor of the sktime models called `verbose`, that is passed internnally to `disp` in the `fit` method of the statsmodel model. Does this sound reasonable?
I've no problem with proposed change format. And if you find some other arguments which you find useful but missing for some other forecasters, those can be added as well.

My personal preference will be to avoid renaming and keep argument name (and default) as is (its easier to check in future for both developers and users in my opinion), unless you think there are some other issues.
Agreed with @yarnabrina.

Regarding the naming, I have no strong opinion, though I would slightly go with @tvdboom's suggestion, as there are certain parameters that occur in many estimators, and a case can be made to ensure their names are consistent across.

Examples:
* `random_state`
* `sp`, "seasonal periodicity"
* parallelization backend strings
* and `verbose`
@fkiraly you mentioned `_GeneralisedStatsForecastAdapter` but I see that SARIMAX (which is the model for my use case) actually inherits from `_StatsModelsAdapter`. What's the difference? Should both be adjusted?
Apologies, I see what is going on here - I got confused in the first place, sorry.

You were referring to a model from `statsmodels`, not `statsforecast` - this is also why the adapters are different, these are two packages. The confusion is due to similar names, and both packages offering ARIMA type models natively.

I suppose one should revisit `_StatsModelsAdapter` then - I think `_GeneralisedStatsForecastAdapter` is currently more robust and more cleanly designed, but of course they adapt two different packages, so the adaptation logic has to remain distinct.

FYI @yarnabrina, apologies for creating unnecessary confusion, this is about `statsmodels`, not `statsforecast`.

Resolving patch:
diff --git a/sktime/forecasting/sarimax.py b/sktime/forecasting/sarimax.py
--- a/sktime/forecasting/sarimax.py
+++ b/sktime/forecasting/sarimax.py
@@ -85,7 +85,9 @@ class SARIMAX(_StatsModelsAdapter):
         Whether or not to use exact diffuse initialization for non-stationary
         states. Default is False (in which case approximate diffuse
         initialization is used).
-    random_state : int, RandomState instance or None, optional ,
+    disp : bool, optional, default=False
+        Set to True to print convergence messages.
+    random_state : int, RandomState instance or None, optional, default=None
         default=None – If int, random_state is the seed used by the random
         number generator; If RandomState instance, random_state is the random
         number generator; If None, the random number generator is the
@@ -140,6 +142,7 @@ def __init__(
         freq=None,
         missing="none",
         validate_specification=True,
+        disp=False,
         random_state=None,
     ):
         self.order = order
@@ -160,6 +163,9 @@ def __init__(
         self.missing = missing
         self.validate_specification = validate_specification
 
+        # Fit params
+        self.disp = disp
+
         super().__init__(random_state=random_state)
 
     def _fit_forecaster(self, y, X=None):
@@ -186,7 +192,7 @@ def _fit_forecaster(self, y, X=None):
             missing=self.missing,
             validate_specification=self.validate_specification,
         )
-        self._fitted_forecaster = self._forecaster.fit()
+        self._fitted_forecaster = self._forecaster.fit(disp=self.disp)
 
     def summary(self):
         """Get a summary of the fitted forecaster.


Test patch: