Repository: statsmodels/statsmodels

Issue:
ETSModel get_prediction bug if start date is greater than dataset
The get_prediction method encounters an error if the start date is after the end of the dataset.

import numpy as np
import pandas as pd
from statsmodels.tsa.api import ETSModel
values = np.arange(100)* (1+np.random.randn(100)/10)
dates = [np.datetime64('today') + np.timedelta64(-101+i) for i in range(100)]
x_train = pd.Series(values, index=dates)
x_train = x_train.asfreq(freq='D', method='bfill')
fit1 = ETSModel(x_train, seasonal_periods=7, trend='add').fit()
works = fit1.get_prediction(start=np.datetime64('today')- np.timedelta64(5), end=np.datetime64('today')+ np.timedelta64(5))
err = fit1.get_prediction(start=np.datetime64('today'), end=np.datetime64('today')+ np.timedelta64(5))


  File "\Anaconda3\lib\site-packages\statsmodels\tsa\exponential_smoothing\ets.py", line 2234, in __init__
    start : (end + 1)

ValueError: could not broadcast input array from shape (0) into shape (6)
ETSModel get_prediction bug if start date is greater than dataset
The get_prediction method encounters an error if the start date is after the end of the dataset.

import numpy as np
import pandas as pd
from statsmodels.tsa.api import ETSModel
values = np.arange(100)* (1+np.random.randn(100)/10)
dates = [np.datetime64('today') + np.timedelta64(-101+i) for i in range(100)]
x_train = pd.Series(values, index=dates)
x_train = x_train.asfreq(freq='D', method='bfill')
fit1 = ETSModel(x_train, seasonal_periods=7, trend='add').fit()
works = fit1.get_prediction(start=np.datetime64('today')- np.timedelta64(5), end=np.datetime64('today')+ np.timedelta64(5))
err = fit1.get_prediction(start=np.datetime64('today'), end=np.datetime64('today')+ np.timedelta64(5))


  File "\Anaconda3\lib\site-packages\statsmodels\tsa\exponential_smoothing\ets.py", line 2234, in __init__
    start : (end + 1)

ValueError: could not broadcast input array from shape (0) into shape (6)


Discussion:

Resolving patch:
diff --git a/statsmodels/tsa/exponential_smoothing/ets.py b/statsmodels/tsa/exponential_smoothing/ets.py
--- a/statsmodels/tsa/exponential_smoothing/ets.py
+++ b/statsmodels/tsa/exponential_smoothing/ets.py
@@ -2230,9 +2230,10 @@ def __init__(
         )
         self.row_labels = self.predicted_mean.index
         self.endog = np.empty(nsmooth + ndynamic) * np.nan
-        self.endog[0 : (end - start + 1)] = results.data.endog[
-            start : (end + 1)
-        ]
+        if nsmooth > 0:
+            self.endog[0: (end - start + 1)] = results.data.endog[
+                start: (end + 1)
+            ]
         self.model = Bunch(
             data=results.model.data.__class__(
                 endog=self.endog, predict_dates=self.row_labels
@@ -2277,7 +2278,15 @@ def __init__(
         else:  # method == 'exact'
             steps = np.ones(ndynamic + nsmooth)
             if ndynamic > 0:
-                steps[(start_dynamic - start) :] = range(1, ndynamic + 1)
+                steps[
+                    (start_dynamic - min(start_dynamic, start)):
+                    ] = range(1, ndynamic + 1)
+            # when we are doing out of sample only prediction,
+            # start > end + 1, and
+            # we only want to output beginning at start
+            if start > end + 1:
+                ndiscard = start - (end + 1)
+                steps = steps[ndiscard:]
             self.forecast_variance = (
                 results.mse * results._relative_forecast_variance(steps)
             )


Test patch:
diff --git a/statsmodels/tsa/tests/test_exponential_smoothing.py b/statsmodels/tsa/tests/test_exponential_smoothing.py
--- a/statsmodels/tsa/tests/test_exponential_smoothing.py
+++ b/statsmodels/tsa/tests/test_exponential_smoothing.py
@@ -691,12 +691,36 @@ def test_prediction_results(austourists_model_fit):
     assert len(summary["mean"].values) == 81
     assert np.all(~np.isnan(summary["mean"]))
 
-    # only out of sample prediction
+    # long out of sample, starting in-sample
     pred = austourists_model_fit.get_prediction(start=67, end=80)
     summary = pred.summary_frame()
     assert len(summary["mean"].values) == 14
     assert np.all(~np.isnan(summary["mean"]))
 
+    # long out of sample, starting at end of sample
+    pred = austourists_model_fit.get_prediction(start=68, end=80)
+    summary = pred.summary_frame()
+    assert len(summary["mean"].values) == 13
+    assert np.all(~np.isnan(summary["mean"]))
+
+    # long out of sample, starting just out of sample
+    pred = austourists_model_fit.get_prediction(start=69, end=80)
+    summary = pred.summary_frame()
+    assert len(summary["mean"].values) == 12
+    assert np.all(~np.isnan(summary["mean"]))
+
+    # long out of sample, starting long out of sample
+    pred = austourists_model_fit.get_prediction(start=79, end=80)
+    summary = pred.summary_frame()
+    assert len(summary["mean"].values) == 2
+    assert np.all(~np.isnan(summary["mean"]))
+
+    # long out of sample, `start`== `end`
+    pred = austourists_model_fit.get_prediction(start=80, end=80)
+    summary = pred.summary_frame()
+    assert len(summary["mean"].values) == 1
+    assert np.all(~np.isnan(summary["mean"]))
+
 
 @pytest.fixture
 def statespace_comparison(austourists):