Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: ETS multiplicative error models log-likelihood calculation #9137

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
15 changes: 8 additions & 7 deletions statsmodels/tsa/exponential_smoothing/ets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,13 +1160,14 @@ def _loglike_internal(
res = self._residuals(yhat, data=data)
logL = -self.nobs / 2 * (np.log(2 * np.pi * np.mean(res ** 2)) + 1)
if self.error == "mul":
# GH-7331: in some cases, yhat can become negative, so that a
# multiplicative model is no longer well-defined. To avoid these
# parameterizations, we clip negative values to very small positive
# values so that the log-transformation yields very large negative
# values.
yhat[yhat <= 0] = 1 / (1e-8 * (1 + np.abs(yhat[yhat <= 0])))
logL -= np.sum(np.log(yhat))
# In some cases, yhat can become negative or zero, so that a
# multiplicative model is no longer well-defined. Zero values
# are replaced with 10^-32 (a very small number). For more
# information on the derivation of the log-likelihood for the
# multiplicative error models see:
# https://openforecast.org/adam/ADAMETSEstimationLikelihood.html
yhat[yhat == 0] = 1e-32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really should leave the <=. == 0 is likely too strict, and if it really is ==0, then <=0 will catch all cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fun corner cases. FWIW, I don't think you need the clipping at all anymore. It's almost certainly not being triggered here, and if you do switch it to clip the negatives, then it becomes unstable again.

logL -= np.sum(np.log(np.abs(yhat)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think reflecting negatives is a good idea. Better to replace with small numbers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that the absolute value is not there to avoid negative numbers, but instead arises mathematically:

If $\epsilon_t \sim \mathcal{N}(0, \sigma^2)$ then $y_t = \mu_{y,t}(1+\epsilon_t) \sim \mathcal{N}(\mu_{y,t}, \mu_{y,t}^2 \sigma^2)$. We then end up with the following log-likelihood:

$\ell(\boldsymbol{\theta}, {\sigma}^2 | \mathbf{y}) = -\frac{1}{2} \sum_{t=1}^T \log(2 \pi \mu_{y,t}^2 \sigma^2) -\frac{1}{2} \sum_{t=1}^T \frac{\epsilon_t^2}{\sigma^2}$ . As you see, we have $\mu_{y,t}^2$ in the formula above, which after simplifications can be written as: $-\frac{T}{2} \log(2 \pi \sigma^2) -\frac{1}{2} \sum_{t=1}^T \frac{\epsilon_t^2}{\sigma^2} - \sum_{t=1}^T \log |\mu_{y,t}|$.

So, it is necessary to have it there for the correct calculation of the log-likelihood. The derivations are based on the Section 11.1 here: https://openforecast.org/adam/ADAMETSEstimationLikelihood.html

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing the PR @bashtage and thanks for the additional explanation on the likelihood derivation @config-i1.

Based on @config-i1's explanation and the other implementations, I think it makes sense to keep the np.abs(yhat) do you agree @bashtage?

return logL

@contextlib.contextmanager
Expand Down