Repository: statsmodels/statsmodels

Issue:
BUG: ExponentialSmoothing error for estimated initialization_method with fewer than 10 observations
Using Holt-Winters models for less than 10 observations works when `initialization_method=None` but fails with `initialization_method='estimated'` even though "estimated" is the default.

```python
import pandas as pd
from statsmodels.tsa.api import ExponentialSmoothing
data = [456.2713,  440.5881, 425.3325,  485.1494,  506.0482,  526.792 ,  514.2689,  494.211 ]
index= pd.date_range(start='2000', end='2008', freq='A')
oildata = pd.Series(data, index)
ExponentialSmoothing(oildata, initialization_method='estimated').fit()
```

This results in the error: `ValueError: Cannot use heuristic method with less than 10 observations.`

#### Expected Output

I expect to get the same result as if I had used `ExponentialSmoothing(oildata).fit()` which is a warning,

`FutureWarning: After 0.13 initialization must be handled at model creation`

and a result `<statsmodels.tsa.holtwinters.results.HoltWintersResultsWrapper at 0x7f2611dde4f0>`

#### Preliminary research

The problem appears to be in [ExponentialSmoothing._initialize()](https://github.com/statsmodels/statsmodels/blob/59962a5407bf9c5e5be8d481b39a727fe3a6a122/statsmodels/tsa/holtwinters/model.py#L419-L431) where, if the initialization method is `None`, it issues a warning and then returns. However, if the initialization method is "estimated", it gets picked up at the [bottom of the method](https://github.com/statsmodels/statsmodels/blob/59962a5407bf9c5e5be8d481b39a727fe3a6a122/statsmodels/tsa/holtwinters/model.py#L446-L447) and calls `_intialize_heuristic()`, which then calls `_initialization_heuristic()` which [doesn't permit fewer than 10](https://github.com/statsmodels/statsmodels/blob/59962a5407bf9c5e5be8d481b39a727fe3a6a122/statsmodels/tsa/exponential_smoothing/initialization.py#L43-L45) observations.

#### Output of ``import statsmodels.api as sm; sm.show_versions()``

<details>


INSTALLED VERSIONS
------------------
Python: 3.8.5.final.0
OS: Linux 5.8.0-41-generic #46~20.04.1-Ubuntu SMP Mon Jan 18 17:52:23 UTC 2021 x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8

statsmodels
===========

Installed: 0.12.2 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/statsmodels)

Required Dependencies
=====================

cython: Not installed
numpy: 1.19.5 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/numpy)
scipy: 1.6.0 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/scipy)
pandas: 1.2.0 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/pandas)
    dateutil: 2.7.3 (/usr/lib/python3/dist-packages/dateutil)
patsy: 0.5.1 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/patsy)

Optional Dependencies
=====================

matplotlib: 3.3.3 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/matplotlib)
    backend: module://ipykernel.pylab.backend_inline 
cvxopt: Not installed
joblib: 1.0.0 (/opt/oneten/muaddib/virtenv/lib/python3.8/site-packages/joblib)

Developer Tools
================

IPython: 7.19.0 (/usr/local/lib/python3.8/dist-packages/IPython)
    jinja2: 2.10.1 (/usr/lib/python3/dist-packages/jinja2)
sphinx: Not installed
    pygments: 2.7.4 (/usr/local/lib/python3.8/dist-packages/pygments)
pytest: Not installed
virtualenv: Not installed

</details>



Discussion:
Thanks very much for this report!

I think this can be pretty easily fixed by using `_initialization_heuristic` if we have at least 10 observations and use `_initialization_simple` if we have fewer than that.

I could not see a good workaround for this at the moment, other than the fact that if you are focused on linear (not muliplicative) models, then you can use `sm.tsa.statespace.ExponentialSmoothing`:

```python
import pandas as pd
from statsmodels.tsa.statespace.exponential_smoothing import ExponentialSmoothing
data = [456.2713,  440.5881, 425.3325,  485.1494,  506.0482,  526.792 ,  514.2689,  494.211 ]
index= pd.date_range(start='2000', end='2008', freq='A')
oildata = pd.Series(data, index)
ExponentialSmoothing(oildata, initialization_method='estimated').fit()
```

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
@@ -165,6 +165,7 @@
 from statsmodels.tsa.exponential_smoothing import base
 import statsmodels.tsa.exponential_smoothing._ets_smooth as smooth
 from statsmodels.tsa.exponential_smoothing.initialization import (
+    _initialization_simple,
     _initialization_heuristic,
 )
 from statsmodels.tsa.tsatools import freq_to_period
@@ -561,10 +562,7 @@ def set_initialization_method(
                     " for models with a seasonal component when"
                     ' initialization method is set to "known".'
                 )
-        elif (
-            self.initialization_method == "heuristic"
-            or self.initialization_method == "estimated"
-        ):
+        elif self.initialization_method == "heuristic":
             (
                 initial_level,
                 initial_trend,
@@ -575,6 +573,29 @@ def set_initialization_method(
                 seasonal=self.seasonal,
                 seasonal_periods=self.seasonal_periods,
             )
+        elif self.initialization_method == "estimated":
+            if self.nobs < 10 + 2 * (self.seasonal_periods // 2):
+                (
+                    initial_level,
+                    initial_trend,
+                    initial_seasonal,
+                ) = _initialization_simple(
+                    self.endog,
+                    trend=self.trend,
+                    seasonal=self.seasonal,
+                    seasonal_periods=self.seasonal_periods,
+                )
+            else:
+                (
+                    initial_level,
+                    initial_trend,
+                    initial_seasonal,
+                ) = _initialization_heuristic(
+                    self.endog,
+                    trend=self.trend,
+                    seasonal=self.seasonal,
+                    seasonal_periods=self.seasonal_periods,
+                )
         if not self.has_trend:
             initial_trend = 0
         if not self.has_seasonal:
diff --git a/statsmodels/tsa/exponential_smoothing/initialization.py b/statsmodels/tsa/exponential_smoothing/initialization.py
--- a/statsmodels/tsa/exponential_smoothing/initialization.py
+++ b/statsmodels/tsa/exponential_smoothing/initialization.py
@@ -9,11 +9,12 @@
 def _initialization_simple(endog, trend=False, seasonal=False,
                            seasonal_periods=None):
     # See Section 7.6 of Hyndman and Athanasopoulos
+    nobs = len(endog)
     initial_trend = None
     initial_seasonal = None
 
     # Non-seasonal
-    if seasonal is None:
+    if seasonal is None or not seasonal:
         initial_level = endog[0]
         if trend == 'add':
             initial_trend = endog[1] - endog[0]
@@ -21,8 +22,14 @@ def _initialization_simple(endog, trend=False, seasonal=False,
             initial_trend = endog[1] / endog[0]
     # Seasonal
     else:
+        if nobs < 2 * seasonal_periods:
+            raise ValueError('Cannot compute initial seasonals using'
+                             ' heuristic method with less than two full'
+                             ' seasonal cycles in the data.')
+
         initial_level = np.mean(endog[:seasonal_periods])
         m = seasonal_periods
+
         if trend is not None:
             initial_trend = (pd.Series(endog).diff(m)[m:2 * m] / m).mean()
 
diff --git a/statsmodels/tsa/holtwinters/model.py b/statsmodels/tsa/holtwinters/model.py
--- a/statsmodels/tsa/holtwinters/model.py
+++ b/statsmodels/tsa/holtwinters/model.py
@@ -32,7 +32,10 @@
     string_like,
 )
 from statsmodels.tsa.base.tsa_model import TimeSeriesModel
-from statsmodels.tsa.exponential_smoothing.ets import _initialization_heuristic
+from statsmodels.tsa.exponential_smoothing.ets import (
+    _initialization_simple,
+    _initialization_heuristic
+)
 from statsmodels.tsa.holtwinters import (
     _exponential_smoothers as smoothers,
     _smoothers as py_smoothers,
@@ -443,8 +446,23 @@ def _initialize(self):
             raise ValueError(msg.format("seasonal"))
         if self._initialization_method == "legacy-heuristic":
             return self._initialize_legacy()
-        elif self._initialization_method in ("heuristic", "estimated"):
+        elif self._initialization_method == "heuristic":
             return self._initialize_heuristic()
+        elif self._initialization_method == "estimated":
+            if self.nobs < 10 + 2 * (self.seasonal_periods // 2):
+                return self._initialize_simple()
+            else:
+                return self._initialize_heuristic()
+
+    def _initialize_simple(self):
+        trend = self.trend if self.has_trend else False
+        seasonal = self.seasonal if self.has_seasonal else False
+        lvl, trend, seas = _initialization_simple(
+            self._y, trend, seasonal, self.seasonal_periods
+        )
+        self._initial_level = lvl
+        self._initial_trend = trend
+        self._initial_seasonal = seas
 
     def _initialize_heuristic(self):
         trend = self.trend if self.has_trend else False


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
@@ -1999,3 +1999,18 @@ def test_forecast_1_simulation(austourists, random_errors, repetitions):
     )
     expected_shape = (10,) if repetitions == 1 else (10, repetitions)
     assert sim.shape == expected_shape
+
+
+@pytest.mark.parametrize("trend", [None, "add"])
+@pytest.mark.parametrize("seasonal", [None, "add"])
+@pytest.mark.parametrize("nobs", [9, 10])
+def test_estimated_initialization_short_data(ses, trend, seasonal, nobs):
+    # GH 7319
+    res = ExponentialSmoothing(
+        ses[:nobs],
+        trend=trend,
+        seasonal=seasonal,
+        seasonal_periods=4,
+        initialization_method='estimated'
+    ).fit()
+    assert res.mle_retvals.success
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
@@ -1033,3 +1033,18 @@ def test_one_step_ahead(setup_model):
     df1 = pred1.summary_frame(alpha=0.05)
     df2 = pred1.summary_frame(alpha=0.05)
     assert_allclose(df1.iloc[0, 0], df2.iloc[0, 0])
+
+
+@pytest.mark.parametrize("trend", [None, "add"])
+@pytest.mark.parametrize("seasonal", [None, "add"])
+@pytest.mark.parametrize("nobs", [9, 10])
+def test_estimated_initialization_short_data(oildata, trend, seasonal, nobs):
+    # GH 7319
+    res = ETSModel(
+        oildata[:nobs],
+        trend=trend,
+        seasonal=seasonal,
+        seasonal_periods=4,
+        initialization_method='estimated'
+    ).fit()
+    assert ~np.any(np.isnan(res.params))