Repository: statsmodels/statsmodels

Issue:
HoltWintersResults simulation throws ValueError
### **Describe:**

I use the following example codes given on the page (https://www.statsmodels.org/stable/examples/notebooks/generated/exponential_smoothing.html):

    fit = ExponentialSmoothing(aust, seasonal_periods=4, trend='add', seasonal='mul', initialization_method="estimated").fit()
    simulations = fit.simulate(16, anchor='2009-01-01', repetitions=100, error='mul', random_errors='bootstrap')

I just changed `nsimulations` parameter from `16` to `1` and ran the codes again, then `ValueError` was thrown:

    ValueError                                Traceback (most recent call last)
    e:\python36\lib\site-packages\pandas\core\internals\managers.py in create_block_manager_from_blocks(blocks, axes)
       1652 
    -> 1653         mgr = BlockManager(blocks, axes)
       1654         mgr._consolidate_inplace()

    e:\python36\lib\site-packages\pandas\core\internals\managers.py in __init__(self, blocks, axes, do_integrity_check)
        113         if do_integrity_check:
    --> 114             self._verify_integrity()
        115 

    e:\python36\lib\site-packages\pandas\core\internals\managers.py in _verify_integrity(self)
        310             if block._verify_integrity and block.shape[1:] != mgr_shape[1:]:
    --> 311                 construction_error(tot_items, block.shape[1:], self.axes)
        312         if len(self.items) != tot_items:

    e:\python36\lib\site-packages\pandas\core\internals\managers.py in construction_error(tot_items, block_shape, axes, e)
       1690     raise ValueError("Shape of passed values is {0}, indices imply {1}".format(
    -> 1691         passed, implied))
       1692 

    ValueError: Shape of passed values is (100, 1), indices imply (1, 1)

    During handling of the above exception, another exception occurred:

    ValueError                                Traceback (most recent call last)
    <ipython-input-13-1c647f688c20> in <module>
          1 fit = ExponentialSmoothing(aust, seasonal_periods=4, trend='add', seasonal='mul', initialization_method="estimated").fit()
    ----> 2 simulations = fit.simulate(1, anchor='2009-01-01', repetitions=100, error='mul', random_errors='bootstrap')

    e:\python36\lib\site-packages\statsmodels\tsa\holtwinters\results.py in simulate(self, nsimulations, anchor, repetitions, error, random_errors, random_state)
        756             sim = pd.Series(sim, index=index, name=self.model.endog_names)
        757         else:
    --> 758             sim = pd.DataFrame(sim, index=index)
        759 
        760         return sim

    e:\python36\lib\site-packages\pandas\core\frame.py in __init__(self, data, index, columns, dtype, copy)
        422             else:
        423                 mgr = init_ndarray(data, index, columns, dtype=dtype,
    --> 424                                    copy=copy)
        425 
        426         # For data is list-like, or Iterable (will consume into list)

    e:\python36\lib\site-packages\pandas\core\internals\construction.py in init_ndarray(values, index, columns, dtype, copy)
        165         values = maybe_infer_to_datetimelike(values)
        166 
    --> 167     return create_block_manager_from_blocks([values], [columns, index])
        168 
        169 

    e:\python36\lib\site-packages\pandas\core\internals\managers.py in create_block_manager_from_blocks(blocks, axes)
       1658         blocks = [getattr(b, 'values', b) for b in blocks]
       1659         tot_items = sum(b.shape[0] for b in blocks)
    -> 1660         construction_error(tot_items, blocks[0].shape[1:], axes, e)
       1661 
       1662 

    e:\python36\lib\site-packages\pandas\core\internals\managers.py in construction_error(tot_items, block_shape, axes, e)
       1689         raise ValueError("Empty data passed with indices specified.")
       1690     raise ValueError("Shape of passed values is {0}, indices imply {1}".format(
    -> 1691         passed, implied))
       1692 
       1693 

    ValueError: Shape of passed values is (100, 1), indices imply (1, 1)

Why can `nsimulations` not be `1` ?

### **Versions**
pandas                0.24.2
numpy                 1.16.4
statsmodels           0.12.0


Discussion:

Resolving patch:
diff --git a/statsmodels/tsa/holtwinters/results.py b/statsmodels/tsa/holtwinters/results.py
--- a/statsmodels/tsa/holtwinters/results.py
+++ b/statsmodels/tsa/holtwinters/results.py
@@ -743,10 +743,11 @@ def simulate(
         if use_boxcox:
             y = inv_boxcox(y, lamda)
 
-        sim = np.squeeze(y)
+        sim = np.atleast_1d(np.squeeze(y))
+        if y.shape[0] == 1 and y.size > 1:
+            sim = sim[None, :]
         # Wrap data / squeeze where appropriate
-        use_pandas = isinstance(self.model.data, PandasData)
-        if not use_pandas:
+        if not isinstance(self.model.data, PandasData):
             return sim
 
         _, _, _, index = self.model._get_prediction_index(


Test patch:
diff --git a/statsmodels/tsa/holtwinters/tests/test_holtwinters.py b/statsmodels/tsa/holtwinters/tests/test_holtwinters.py
--- a/statsmodels/tsa/holtwinters/tests/test_holtwinters.py
+++ b/statsmodels/tsa/holtwinters/tests/test_holtwinters.py
@@ -358,7 +358,10 @@ def test_ndarray(self):
     @pytest.mark.xfail(reason="Optimizer does not converge", strict=False)
     def test_forecast(self):
         fit1 = ExponentialSmoothing(
-            self.aust, seasonal_periods=4, trend="add", seasonal="add",
+            self.aust,
+            seasonal_periods=4,
+            trend="add",
+            seasonal="add",
         ).fit(method="bh", use_brute=True)
         assert_almost_equal(
             fit1.forecast(steps=4), [60.9542, 36.8505, 46.1628, 50.1272], 3
@@ -884,8 +887,10 @@ def test_float_boxcox(trend, seasonal):
     assert_allclose(res.params["use_boxcox"], 0.5)
     with pytest.warns(FutureWarning):
         res = ExponentialSmoothing(
-            housing_data, trend=trend, seasonal=seasonal
-        ).fit(use_boxcox = 0.5)
+            housing_data,
+            trend=trend,
+            seasonal=seasonal,
+        ).fit(use_boxcox=0.5)
     assert_allclose(res.params["use_boxcox"], 0.5)
 
 
@@ -1624,7 +1629,10 @@ def test_error_initialization(ses):
         ExponentialSmoothing(ses, initial_seasonal=[1.0, 0.2, 0.05, 4])
     with pytest.raises(ValueError):
         ExponentialSmoothing(
-            ses, trend="add", initialization_method="known", initial_level=1.0,
+            ses,
+            trend="add",
+            initialization_method="known",
+            initial_level=1.0,
         )
     with pytest.raises(ValueError):
         ExponentialSmoothing(
@@ -1963,3 +1971,28 @@ def test_boxcox_components(ses):
     assert not hasattr(res, "_untransformed_level")
     assert not hasattr(res, "_untransformed_trend")
     assert not hasattr(res, "_untransformed_seasonal")
+
+
+@pytest.mark.parametrize("repetitions", [1, 10])
+@pytest.mark.parametrize("random_errors", [None, "bootstrap"])
+def test_forecast_1_simulation(austourists, random_errors, repetitions):
+    # GH 7053
+    fit = ExponentialSmoothing(
+        austourists,
+        seasonal_periods=4,
+        trend="add",
+        seasonal="add",
+        damped_trend=True,
+        initialization_method="estimated",
+    ).fit()
+
+    sim = fit.simulate(
+        1, anchor=0, random_errors=random_errors, repetitions=repetitions
+    )
+    expected_shape = (1,) if repetitions == 1 else (1, repetitions)
+    assert sim.shape == expected_shape
+    sim = fit.simulate(
+        10, anchor=0, random_errors=random_errors, repetitions=repetitions
+    )
+    expected_shape = (10,) if repetitions == 1 else (10, repetitions)
+    assert sim.shape == expected_shape