Repository: statsmodels/statsmodels

Issue:
BUG: Seasonal order parameters in SARIMAX are used even if the season is length 0.
Currently, there are two problems:

1. No exception or warning is issued if P, D, or Q are set when s=0.
2. In some cases (see below), the seasonal parameters (which shouldn't be in use since s=0) end up in the state space system matrices and affect the results.

Example of the problems:

```python
endog = np.random.normal(size=100)
mod = sm.tsa.SARIMAX(endog, order=(0, 0, 0), seasonal_order=(1, 0, 0, 0))
res = mod.smooth([0.2, 0.5])
print(mod['transition'])
```

gives

```
[[0.2]]
```

when it should raise an exception (or the transition matrix should at least be equal to zero).

See https://stackoverflow.com/questions/57158563/why-p-d-q-terms-are-taken-into-account-into-a-sarimax-model-with-0-periodicity


Discussion:

Resolving patch:
diff --git a/statsmodels/tsa/statespace/sarimax.py b/statsmodels/tsa/statespace/sarimax.py
--- a/statsmodels/tsa/statespace/sarimax.py
+++ b/statsmodels/tsa/statespace/sarimax.py
@@ -324,6 +324,18 @@ def __init__(self, endog, exog=None, order=(1, 0, 0),
                  hamilton_representation=False, concentrate_scale=False,
                  trend_offset=1, use_exact_diffuse=False, **kwargs):
 
+        # Save given orders
+        self.order = order
+        self.seasonal_order = seasonal_order
+
+        # Validate orders
+        if len(self.order) != 3:
+            raise ValueError('`order` argument must be an iterable with three'
+                             ' elements.')
+        if len(self.seasonal_order) != 4:
+            raise ValueError('`seasonal_order` argument must be an iterable'
+                             ' with four elements.')
+
         # Model parameters
         self.seasonal_periods = seasonal_order[3]
         self.measurement_error = measurement_error
@@ -336,10 +348,6 @@ def __init__(self, endog, exog=None, order=(1, 0, 0),
         self.concentrate_scale = concentrate_scale
         self.use_exact_diffuse = use_exact_diffuse
 
-        # Save given orders
-        self.order = order
-        self.seasonal_order = seasonal_order
-
         # Enforce non-MLE coefficients if time varying coefficients is
         # specified
         if self.time_varying_regression and self.mle_regression:
@@ -420,6 +428,15 @@ def __init__(self, endog, exog=None, order=(1, 0, 0),
             int(np.sum(self.polynomial_seasonal_ma) - 1)
         )
 
+        # Make sure we don't have a seasonal specification without a valid
+        # seasonal period.
+        if self.seasonal_order[3] == 1:
+            raise ValueError('Seasonal period must be greater than 1.')
+        if self.seasonal_order[3] == 0 and (self.k_seasonal_ar > 0 or
+                                            self.k_seasonal_ma > 0):
+            raise ValueError('Seasonal AR or MA components cannot be set when'
+                             ' the given seasonal period is equal to 0.')
+
         # Make internal copies of the differencing orders because if we use
         # simple differencing, then we will need to internally use zeros after
         # the simple differencing has been performed


Test patch:
diff --git a/statsmodels/tsa/statespace/tests/test_sarimax.py b/statsmodels/tsa/statespace/tests/test_sarimax.py
--- a/statsmodels/tsa/statespace/tests/test_sarimax.py
+++ b/statsmodels/tsa/statespace/tests/test_sarimax.py
@@ -2177,11 +2177,6 @@ def test_arima000():
     res = mod.smooth(mod.start_params)
     assert_allclose(res.smoothed_state[1:, 1:], endog.diff()[1:].T)
 
-    # SARIMA(0, 1, 0)x(0, 1, 0, 1)
-    mod = sarimax.SARIMAX(endog, order=(0, 1, 0), measurement_error=True,
-                          seasonal_order=(0, 1, 0, 1))
-    res = mod.smooth(mod.start_params)
-
     # Exogenous variables
     error = np.random.normal(size=nobs)
     endog = np.ones(nobs) * 10 + error
@@ -2676,3 +2671,27 @@ def test_simple_differencing_strindex():
 
     assert_(mod._index.equals(pd.RangeIndex(start=0, stop=len(values) - 1)))
     assert_(mod.data.row_labels.equals(index[1:]))
+
+
+def test_invalid_order():
+    endog = np.zeros(10)
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, order=(1,))
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, order=(1, 2, 3, 4))
+
+
+def test_invalid_seasonal_order():
+    endog = np.zeros(10)
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, seasonal_order=(1,))
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, seasonal_order=(1, 2, 3, 4, 5))
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, seasonal_order=(1, 0, 0, 0))
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, seasonal_order=(0, 0, 1, 0))
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, seasonal_order=(1, 0, 1, 0))
+    with pytest.raises(ValueError):
+        sarimax.SARIMAX(endog, seasonal_order=(0, 0, 0, 1))