From 81677082a9859b921b02f055a22629a8d91c77bc Mon Sep 17 00:00:00 2001 From: kevinroundy Date: Wed, 23 Apr 2025 13:36:11 -0700 Subject: [PATCH] fix: bug causes incorrect lookups into seasonality vector The seasonality vector is being indexed in an incorrect fashion in the Predict function, whether or not the training data ended at the full completion of a full season's period, we predict as if we were at the beginning of the season. Consider the following case where the code behaves correctly: trainLen = 70 period = 7 (i.e., len of training window) When we predict on the first day after the train window, day 71, we index the seasonals array as follows: seasonals[(m-1)%period] ... where m=1, thus we index seasonals[0], or the first day of the week, and all is as it should be. But if the training data length is not evenly divisible by the seasonality's period, we still start our predictions at seasonals[0]. For instance if we have 71 days of training, using the same data, and we predict on day 72, we will index the training data with: seasonals[(m-1)%period] ... where m=1, thus we index seasonals[0], or the first day of the week, but this is *not correct*. We should be looking at the second day of the week. In fact, no matter how long the training period was, Predict will always start the training period at the first element of the season, even if the training data cuts off in the middle of a season. The solution is to add the length of the training period to the modulo calculation. --- forecast/algs/hw/predict.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/forecast/algs/hw/predict.go b/forecast/algs/hw/predict.go index 6108faa..cc1b192 100644 --- a/forecast/algs/hw/predict.go +++ b/forecast/algs/hw/predict.go @@ -29,6 +29,7 @@ func (hw *HoltWinters) Predict(ctx context.Context, n uint) (*dataframe.SeriesFl seasonals []float64 = hw.tstate.seasonalComps trnd float64 = hw.tstate.trendLevel period int = int(hw.cfg.Period) + trainLen int = int(hw.tstate.T) ) for i := uint(0); i < n; i++ { @@ -40,9 +41,9 @@ func (hw *HoltWinters) Predict(ctx context.Context, n uint) (*dataframe.SeriesFl var fval float64 if hw.cfg.SeasonalMethod == Multiplicative { - fval = (st + float64(m)*trnd) * seasonals[(m-1)%period] + fval = (st + float64(m)*trnd) * seasonals[(trainLen+m-1)%period] } else { - fval = (st + float64(m)*trnd) + seasonals[(m-1)%period] + fval = (st + float64(m)*trnd) + seasonals[(trainLen+m-1)%period] } nsf.Append(fval, dataframe.DontLock)