Repository: statsmodels/statsmodels

Issue:
BUG: non-monotonic date indexes for TSA models should be ignored
Currently, you can have an index like:

```python
tmp = pd.period_range(start=2000, end=2002, freq='A')
index = tmp.tolist() + tmp.tolist()
index
```

```
[Period('2000', 'A-DEC'),
 Period('2001', 'A-DEC'),
 Period('2002', 'A-DEC'),
 Period('2000', 'A-DEC'),
 Period('2001', 'A-DEC'),
 Period('2002', 'A-DEC')]
```

and the TSA models will try to use it, because they only check for date-type index that has an associated frequency. But for period indexes, we need to also check that they're monotonic (`is_monotonic`) and that they're full (`is_full`).

We should ignore these indexes for e.g. forecasting purposes, as we do with other types of date indexes that don't have associated frequencies.


Discussion:

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
@@ -201,11 +201,13 @@ def _init_dates(self, dates=None, freq=None):
         # Get attributes of the index
         has_index = index is not None
         date_index = isinstance(index, (DatetimeIndex, PeriodIndex))
+        period_index = isinstance(index, PeriodIndex)
         int_index = isinstance(index, Int64Index)
         range_index = isinstance(index, RangeIndex)
         has_freq = index.freq is not None if date_index else None
         increment = Index(range(self.endog.shape[0]))
         is_increment = index.equals(increment) if int_index else None
+        is_monotonic = index.is_monotonic if date_index else None
 
         # Issue warnings for unsupported indexes
         if has_index and not (date_index or range_index or is_increment):
@@ -215,12 +217,17 @@ def _init_dates(self, dates=None, freq=None):
             warnings.warn('A date index has been provided, but it has no'
                           ' associated frequency information and so will be'
                           ' ignored when e.g. forecasting.', ValueWarning)
+        if date_index and not is_monotonic:
+            warnings.warn('A date index has been provided, but it is not'
+                          ' monotonic and so will be ignored when e.g.'
+                          ' forecasting.', ValueWarning)
 
         # Construct the internal index
         index_generated = False
+        valid_index = ((date_index and has_freq and is_monotonic) or
+                       (int_index and is_increment) or range_index)
 
-        if ((date_index and has_freq) or (int_index and is_increment) or
-                range_index):
+        if valid_index:
             _index = index
         else:
             _index = increment


Test patch:
diff --git a/statsmodels/tsa/base/tests/test_tsa_indexes.py b/statsmodels/tsa/base/tests/test_tsa_indexes.py
--- a/statsmodels/tsa/base/tests/test_tsa_indexes.py
+++ b/statsmodels/tsa/base/tests/test_tsa_indexes.py
@@ -18,6 +18,7 @@
 from numpy.testing import assert_equal, assert_raises
 
 from statsmodels.tsa.base import tsa_model
+from statsmodels.tools.sm_exceptions import ValueWarning
 
 nobs = 5
 base_dta = np.arange(nobs)
@@ -1109,3 +1110,30 @@ def test_custom_index():
     # Test invalid custom index
     assert_raises(ValueError, mod._get_prediction_index, start_key, end_key,
                   index=['f', 'g', 'h'])
+
+
+def test_nonmonotonic_periodindex():
+    # Create a nonmonotonic period index
+    tmp = pd.period_range(start=2000, end=2002, freq='A')
+    index = tmp.tolist() + tmp.tolist()
+    endog = pd.Series(np.zeros(len(index)), index=index)
+
+    message = ('A date index has been provided, but it is not'
+               ' monotonic and so will be ignored when e.g.'
+               ' forecasting.')
+    with pytest.warns(ValueWarning, match=message):
+        tsa_model.TimeSeriesModel(endog)
+
+
+@pytest.mark.xfail(reason='Pandas PeriodIndex.is_full does not yet work for'
+                          ' all frequencies (e.g. frequencies with a'
+                          ' multiplier, like "2Q").')
+def test_nonfull_periodindex():
+    index = pd.PeriodIndex(['2000-01', '2000-03'], freq='M')
+    endog = pd.Series(np.zeros(len(index)), index=index)
+
+    message = ('A Period index has been provided, but it is not'
+               ' full and so will be ignored when e.g.'
+               ' forecasting.')
+    with pytest.warns(ValueWarning, match=message):
+        tsa_model.TimeSeriesModel(endog)