Repository: statsmodels/statsmodels

Issue:
ARIMA ValueError with constant exog variable
I'm getting a ValueError in ARIMA which seems to happen when an exog variable has a constant value. This happens in real world data, a small snippet of which I reproduce here:
```
>>> import pandas as pd
>>> from statsmodels.tsa.arima_model import ARIMA
>>> X = [0.5,0.5,0.5,0.5,0.5]
>>> Y = [-0.011866,0.003380,0.015357,0.004451,-0.020889]
>>> T = ['2000-01-01', '2000-01-02', '2000-01-03', '2000-01-04', '2000-01-05']
>>> TDT = pd.to_datetime(T)
>>> df = pd.DataFrame({'x': X, 'y': Y})
>>> df.index = TDT
>>> df
              x         y
2000-01-01  0.5 -0.011866
2000-01-02  0.5  0.003380
2000-01-03  0.5  0.015357
2000-01-04  0.5  0.004451
2000-01-05  0.5 -0.020889
>>> m = ARIMA(endog=df.y, order=(1,1,0), exog=df.x)
>>> r = m.fit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.5/dist-packages/statsmodels/tsa/arima_model.py", line 1104, in fit
    callback, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/statsmodels/tsa/arima_model.py", line 919, in fit
    start_params = self._fit_start_params((k_ar, k_ma, k), method)
  File "/usr/local/lib/python3.5/dist-packages/statsmodels/tsa/arima_model.py", line 563, in _fit_start_params
    bounds=bounds, iprint=-1)
  File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/lbfgsb.py", line 193, in fmin_l_bfgs_b
    **opts)
  File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/lbfgsb.py", line 328, in _minimize_lbfgsb
    f, g = func_and_grad(x)
  File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/lbfgsb.py", line 273, in func_and_grad
    f = fun(x, *args)
  File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/optimize.py", line 292, in function_wrapper
    return function(*(wrapper_args + args))
  File "/usr/local/lib/python3.5/dist-packages/statsmodels/tsa/arima_model.py", line 554, in <lambda>
    func = lambda params: -self.loglike_css(params)
  File "/usr/local/lib/python3.5/dist-packages/statsmodels/tsa/arima_model.py", line 788, in loglike_css
    y -= dot(self.exog, newparams[:k])
ValueError: shapes (4,1) and (2,) not aligned: 1 (dim 1) != 2 (dim 0)
```
It also seems ARIMA does not work for more than 2 exog variables, but I guess that is a separate bug report...

Thanks very much.


Discussion:
Parts of this have changed since 0.6.1. It should work for any number of exog, but there is some specific code that checks that there is no constant column in the exog when constants are added through the trend keyword to avoid singular exog.

I don't have access to a computer right know, and can check the details in the new year. I'm pretty sure arima does not support singular exog like this.
(issue didn't get labels and got lost)

The user needs to avoid the duplicate constant by using e.g. trend='nc'
But at least we need a better error message. (and fail right away?)

check for 0.10

Resolving patch:
diff --git a/statsmodels/tsa/arima_model.py b/statsmodels/tsa/arima_model.py
--- a/statsmodels/tsa/arima_model.py
+++ b/statsmodels/tsa/arima_model.py
@@ -394,7 +394,7 @@ def _make_arma_exog(endog, exog, trend):
     if exog is None and trend == 'c':   # constant only
         exog = np.ones((len(endog), 1))
     elif exog is not None and trend == 'c':  # constant plus exogenous
-        exog = add_trend(exog, trend='c', prepend=True)
+        exog = add_trend(exog, trend='c', prepend=True, has_constant='raise')
     elif exog is not None and trend == 'nc':
         # make sure it's not holding constant from last run
         if exog.var() == 0:


Test patch:
diff --git a/statsmodels/tsa/tests/test_arima.py b/statsmodels/tsa/tests/test_arima.py
--- a/statsmodels/tsa/tests/test_arima.py
+++ b/statsmodels/tsa/tests/test_arima.py
@@ -14,7 +14,8 @@
 from statsmodels.datasets.macrodata import load_pandas as load_macrodata_pandas
 import statsmodels.sandbox.tsa.fftarma as fa
 from statsmodels.tools.testing import assert_equal
-from statsmodels.tools.sm_exceptions import ValueWarning
+from statsmodels.tools.sm_exceptions import (
+    ValueWarning, HessianInversionWarning)
 from statsmodels.tsa.arma_mle import Arma
 from statsmodels.tsa.arima_model import AR, ARMA, ARIMA
 from statsmodels.regression.linear_model import OLS
@@ -2338,31 +2339,33 @@ def test_arima_exog_predict():
         res_002.forecast(steps=h, exog=np.empty(20))
 
 
-def test_arima_fit_mutliple_calls():
+def test_arima_fit_multiple_calls():
     y = [-1214.360173, -1848.209905, -2100.918158, -3647.483678, -4711.186773]
     mod = ARIMA(y, (1, 0, 2))
     # Make multiple calls to fit
-    with warnings.catch_warnings(record=True) as w:
+    with pytest.warns(HessianInversionWarning, match="no bse or cov"):
         mod.fit(disp=0, start_params=[np.mean(y), .1, .1, .1])
     assert_equal(mod.exog_names,  ['const', 'ar.L1.y', 'ma.L1.y', 'ma.L2.y'])
-    with warnings.catch_warnings(record=True) as w:
-        res= mod.fit(disp=0, start_params=[np.mean(y), .1, .1, .1])
+
+    mod.exog = None  # FIXME: this shouldn't be necessary
+    with pytest.warns(HessianInversionWarning, match="no bse or cov"):
+        res = mod.fit(disp=0, start_params=[np.mean(y), .1, .1, .1])
     assert_equal(mod.exog_names,  ['const', 'ar.L1.y', 'ma.L1.y', 'ma.L2.y'])
 
-    #ensure summary() works
+    # ensure summary() works  # TODO: separate and pytest.mark.smoke
     res.summary()
 
-    #test multiple calls when there is only a constant term
+    # test multiple calls when there is only a constant term
     mod = ARIMA(y, (0, 0, 0))
     # Make multiple calls to fit
-    with warnings.catch_warnings(record=True) as w:
-        mod.fit(disp=0, start_params=[np.mean(y)])
+    mod.fit(disp=0, start_params=[np.mean(y)])
     assert_equal(mod.exog_names,  ['const'])
-    with warnings.catch_warnings(record=True) as w:
-        res = mod.fit(disp=0, start_params=[np.mean(y)])
+
+    mod.exog = None  # FIXME: this shouldn't be necessary
+    res = mod.fit(disp=0, start_params=[np.mean(y)])
     assert_equal(mod.exog_names,  ['const'])
 
-    # ensure summary() works
+    # ensure summary() works  # TODO: separate and pytest.mark.smoke
     res.summary()
 
 
@@ -2378,9 +2381,13 @@ def test_long_ar_start_params():
 
     model = ARMA(y, order=(2, 2))
 
-    res = model.fit(method='css',start_ar_lags=10, disp=0)
-    res = model.fit(method='css-mle',start_ar_lags=10, disp=0)
-    res = model.fit(method='mle',start_ar_lags=10, disp=0)
+    model.fit(method='css',start_ar_lags=10, disp=0)
+
+    model.exog = None  # FIXME: should not be necessary
+    model.fit(method='css-mle',start_ar_lags=10, disp=0)
+
+    model.exog = None  # FIXME: should not be necessary
+    model.fit(method='mle',start_ar_lags=10, disp=0)
     assert_raises(ValueError, model.fit, start_ar_lags=nobs+5, disp=0)
 
 
@@ -2476,3 +2483,20 @@ def test_arima_summary_no_lags(reset_randomstate):
     assert 'const ' in summ
     assert 'x1 ' in summ
     assert 'x2 ' in summ
+
+
+def test_constant_column_trend():
+    # GH#3343 analogous to GH#5258 for AR, when the user passes a constant exog
+    #  and also passes trend="c", raise instead _make_arma_exog used to
+    #  silently drop a column and return an inconsistenct k_trend value.
+
+    exog = np.array([0.5, 0.5, 0.5, 0.5, 0.5])
+    endog =  np.array([-0.011866, 0.003380, 0.015357, 0.004451, -0.020889])
+
+    model = ARIMA(endog=endog, order=(1, 1, 0), exog=exog)
+
+    # Fitting with a constant and constant exog raises because of colinearity
+    with pytest.raises(ValueError, match="x contains a constant"):
+        model.fit(trend="c")
+
+    # FIXME: calling model.fit(trend="nc") raises for orthogonal reasons