Repository: statsmodels/statsmodels

Issue:
BUG: Forecasting SARIMAX with data indexing by Int64Index where simple_differencing=True
**Description**
When forecasting some Sarimax models, that built with Pandas DataFrame indexed by pd.Int64Index and the model build with setting simple_differencing to True the forecast function raise an error.

**Code sample**

```python
import statsmodels.api as sm
import pandas as pd

data = pd.Series([1,2,1,2,1,2,12,1,3,6,4,1,6,5,1,5,3,1,6,5,4,1,6,4,2,3,1,1,2,1,2,3,1,3,2,1,2,3,1,3,2,1,6,5,3,2,1,2])
model = sm.tsa.SARIMAX(data,order=(0,1,1),simple_differencing = True).fit(disp=False)
 
model.forecast()
```
success and give

> 48    0.627449
>dtype: float64.

 But the code 

```python
data = pd.Series([1,2,1,2,1,2,12,1,3,6,4,1,6,5,1,5,3,1,6,5,4,1,6,4,2,3,1,1,2,1,2,3,1,3,2,1,2,3,1,3,2,1,6,5,3,2,1,2])
data.index = pd.Int64Index(data.index)
model = sm.tsa.SARIMAX(data,order=(0,1,1),simple_differencing = True).fit(disp=False)

model.forecast()

```
raise an error

> Traceback (most recent call last):
> 
>   File "<ipython-input-45-87d05bd4ab9b>", line 1, in <module>
>     model.forecast()
> 
>   File "C:\ProgramData\Anaconda3\lib\site-packages\statsmodels\base\wrapper.py", line 95, in wrapper
>     obj = data.wrap_output(func(results, *args, **kwargs), how)
> 
>   File "C:\ProgramData\Anaconda3\lib\site-packages\statsmodels\tsa\statespace\mlemodel.py", line 2484, in forecast
>     return self.predict(start=self.nobs, end=end, **kwargs)
> 
>   File "C:\ProgramData\Anaconda3\lib\site-packages\statsmodels\tsa\statespace\mlemodel.py", line 2457, in predict
>     prediction_results = self.get_prediction(start, end, dynamic, **kwargs)
> 
>   File "C:\ProgramData\Anaconda3\lib\site-packages\statsmodels\tsa\statespace\sarimax.py", line 1894, in get_prediction
>     self.model._get_prediction_index(start, end, index, silent=True))
> 
>   File "C:\ProgramData\Anaconda3\lib\site-packages\statsmodels\tsa\base\tsa_model.py", line 524, in _get_prediction_index
>     raise KeyError('The `start` argument could not be matched to a'
> 
> KeyError: 'The `start` argument could not be matched to a location related to the index of the data.'




Discussion:
Thanks very much for this report!

Resolving patch:
diff --git a/statsmodels/tsa/base/tsa_model.py b/statsmodels/tsa/base/tsa_model.py
--- a/statsmodels/tsa/base/tsa_model.py
+++ b/statsmodels/tsa/base/tsa_model.py
@@ -228,6 +228,7 @@ def _init_dates(self, dates=None, freq=None):
         self._index = _index
         self._index_generated = index_generated
         self._index_none = index is None
+        self._index_int64 = int_index and not range_index and not date_index
         self._index_dates = date_index and not index_generated
         self._index_freq = self._index.freq if self._index_dates else None
         self._index_inferred_freq = inferred_freq
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
@@ -7,6 +7,7 @@
 from warnings import warn
 
 import numpy as np
+import pandas as pd
 
 from statsmodels.compat.pandas import Appender
 
@@ -81,9 +82,10 @@ class SARIMAX(MLEModel):
         Whether or not to use partially conditional maximum likelihood
         estimation. If True, differencing is performed prior to estimation,
         which discards the first :math:`s D + d` initial rows but results in a
-        smaller state-space formulation. If False, the full SARIMAX model is
-        put in state-space form so that all datapoints can be used in
-        estimation. Default is False.
+        smaller state-space formulation. See the Notes section for important
+        details about interpreting results when this option is used. If False,
+        the full SARIMAX model is put in state-space form so that all
+        datapoints can be used in estimation. Default is False.
     enforce_stationarity : bool, optional
         Whether or not to transform the AR parameters to enforce stationarity
         in the autoregressive component of the model. Default is True.
@@ -278,6 +280,17 @@ class SARIMAX(MLEModel):
     compute the variance of the measurement error in this case one would
     multiply `snr.measurement_error` parameter by the scale.
 
+    If `simple_differencing = True` is used, then the `endog` and `exog` data
+    are differenced prior to putting the model in state-space form. This has
+    the same effect as if the user differenced the data prior to constructing
+    the model, which has implications for using the results:
+
+    - Forecasts and predictions will be about the *differenced* data, not about
+      the original data. (while if `simple_differencing = False` is used, then
+      forecasts and predictions will be about the original data).
+    - If the original data has an Int64Index, a new RangeIndex will be created
+      for the differenced data that starts from one, and forecasts and
+      predictions will use this new index.
 
     Detailed information about state space models can be found in [1]_. Some
     specific references are:
@@ -569,7 +582,9 @@ def prepare_data(self):
                 self.data._cache['row_labels'] = (
                     self.data.row_labels[orig_length - new_length:])
             if self._index is not None:
-                if self._index_generated:
+                if self._index_int64:
+                    self._index = pd.RangeIndex(start=1, stop=new_length + 1)
+                elif self._index_generated:
                     self._index = self._index[:-(orig_length - new_length)]
                 else:
                     self._index = self._index[orig_length - new_length:]
@@ -1945,7 +1960,12 @@ def get_prediction(self, start=None, end=None, dynamic=False, index=None,
             Array of out of sample forecasts.
         """
         if start is None:
-            start = self.model._index[0]
+            if (self.model.simple_differencing and
+                    not self.model._index_generated and
+                    not self.model._index_dates):
+                start = 0
+            else:
+                start = self.model._index[0]
 
         # Handle start, end, dynamic
         _start, _end, _out_of_sample, prediction_index = (


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
@@ -19,7 +19,7 @@
 from .results import results_sarimax
 from statsmodels.tools import add_constant
 from numpy.testing import (
-    assert_equal, assert_almost_equal, assert_raises, assert_allclose
+    assert_, assert_equal, assert_almost_equal, assert_raises, assert_allclose
 )
 
 
@@ -2640,3 +2640,39 @@ def test_start_params_small_nobs():
     with pytest.warns(UserWarning, match=match):
         start_params = mod.start_params
         assert_allclose(start_params, [0, np.var(endog[:4])])
+
+
+def test_simple_differencing_int64index():
+    values = np.log(realgdp_results['value']).values
+    endog = pd.Series(values, index=pd.Int64Index(range(len(values))))
+    mod = sarimax.SARIMAX(endog, order=(1, 1, 0), simple_differencing=True)
+
+    assert_(mod._index.equals(endog.index[1:]))
+
+
+def test_simple_differencing_rangeindex():
+    values = np.log(realgdp_results['value']).values
+    endog = pd.Series(values, index=pd.RangeIndex(start=0, stop=len(values)))
+    mod = sarimax.SARIMAX(endog, order=(1, 1, 0), simple_differencing=True)
+
+    assert_(mod._index.equals(endog.index[1:]))
+
+
+def test_simple_differencing_dateindex():
+    values = np.log(realgdp_results['value']).values
+    endog = pd.Series(values, index=pd.period_range(
+        start='2000', periods=len(values), freq='M'))
+    mod = sarimax.SARIMAX(endog, order=(1, 1, 0), simple_differencing=True)
+
+    assert_(mod._index.equals(endog.index[1:]))
+
+
+def test_simple_differencing_strindex():
+    values = np.log(realgdp_results['value']).values
+    index = pd.Int64Index(range(len(values))).map(str)
+    endog = pd.Series(values, index=index)
+    with pytest.warns(UserWarning):
+        mod = sarimax.SARIMAX(endog, order=(1, 1, 0), simple_differencing=True)
+
+    assert_(mod._index.equals(pd.RangeIndex(start=0, stop=len(values) - 1)))
+    assert_(mod.data.row_labels.equals(index[1:]))