Repository: statsmodels/statsmodels

Issue:
Use sharex=True in plotting seasonal decomposition
This is a tiny change in the `plot` method of `DecomposeResult` (in statsmodels.tsa.seasonal). I started to use it just a few minutes ago on a quite long time series (8760 samples), so that in a interactive Matlplotlib plot, it is useful to zoom horizontally. Unfortunately, the horizontal zooms are not in sync.

Adding `sharex=True` in the call to `plt.subplots` allows synchronizing the zoom between all seasonal components.

An alternative would be to have this synchronization optional (one extra parameter to `DecomposeResult.plot`), but I don't see a  use case which needs *dis*-synchronization, what do you think?


Discussion:
Is there an API call to disable it from an existing plot? If so, could you add a note that sharex is true by default. Axis synchronization can be disabled using ax.set_????
> Is there an API call to disable it from an existing plot?

Good question, I hadn't thought of this. It took me a little digging to follow the layers of successive closed Matplotlib Issues and PR:
- https://github.com/matplotlib/matplotlib/issues/318
- https://github.com/matplotlib/matplotlib/pull/1312 (2012-2015), not merged
- https://github.com/matplotlib/matplotlib/pull/9923 (2017-2020), not merged, with last comment "#15287 does some of this."
- https://github.com/matplotlib/matplotlib/pull/15287 (2019-2020), merge, but with the first comment stating that "This intentionally does not allow unsharing or changing the shared axes, as there are bigger questions on the API there (#9923)"

So the short answer seems to be "no, it's not possible to unshare axes", but perhaps I missed something cause I read the threads quite fast.

Coming back to the display of decomposed time series, I don't think it's a problem though, for two reasons:

1. having synced horizontal axes is a sensible default (or said differently, for me it's a bug that they are currently not synced)
2. `DecomposeResult.plot` is only a quick convenience display for fast [EDA](https://en.wikipedia.org/wiki/Exploratory_data_analysis). There are other plot settings that a user may wish different (like the appearance of the residue), but then it doesn't cost that much to remake the plot from scratch using the decomposed channels (`DecomposeResult.trend`...)
Do  you want to add a keyword argument so that this is optional?  I'm find with True being the default.

Sorry for the late reply.

> Do you want to add a keyword argument so that this is optional? I'm find with True being the default.

This is possible, but is it worth complexifying the API by an extra keyword? Of course it's only a small extra complexity, but on the other hand, I don't see a use case for having *unsynced* axes. 

More generally, there are many other small plot parameters that could be brought to the `DecomposeResult.plot()` method signature, but none can be changed at present, so why just one "sharex=True"?


Resolving patch:
diff --git a/statsmodels/tsa/seasonal.py b/statsmodels/tsa/seasonal.py
--- a/statsmodels/tsa/seasonal.py
+++ b/statsmodels/tsa/seasonal.py
@@ -342,7 +342,7 @@ def plot(
         else:
             xlim = (0, self._observed.shape[0] - 1)
 
-        fig, axs = plt.subplots(len(series), 1)
+        fig, axs = plt.subplots(len(series), 1, sharex=True)
         for i, (ax, (series, def_name)) in enumerate(zip(axs, series)):
             if def_name != "residual":
                 ax.plot(series)


Test patch:
diff --git a/statsmodels/tsa/forecasting/tests/test_stl.py b/statsmodels/tsa/forecasting/tests/test_stl.py
--- a/statsmodels/tsa/forecasting/tests/test_stl.py
+++ b/statsmodels/tsa/forecasting/tests/test_stl.py
@@ -40,6 +40,16 @@ def test_smoke(data):
     assert hasattr(res.model_result, "forecast")
 
 
+@pytest.mark.matplotlib
+def test_sharex(data):
+    stlf = STLForecast(data, ARIMA, model_kwargs={"order": (2, 0, 0)})
+    res = stlf.fit(fit_kwargs={})
+    plt = res.result.plot()
+    grouper_view = plt.axes[0].get_shared_x_axes()
+    sibs = grouper_view.get_siblings(plt.axes[1])
+    assert len(sibs) == 4
+
+
 MODELS = [
     (ARIMA, {"order": (2, 0, 0), "trend": "c"}),
     (ExponentialSmoothing, {"trend": True}),