Skip to main content

Section 12.6 Moving Average

Suppose it’s June 2019, and you are asked to make a prediction for June 2020. Your first guess might be that this year’s value will be repeated next year.
Now suppose it’s May 2020, and you are asked to revise your prediction for June 2020. You could use the results from the last three months, and the autocorrelation model from the previous section, to predict the year-over-year difference.
Finally, suppose you check the predictions for the last few months, and see that they have been consistently too low. That suggests that the prediction for next month might also be too low, so you could revise it upward. The underlying assumption is that recent prediction errors predict future prediction errors.
To see whether they do, we can make a DataFrame with the residuals from the autoregression model in the first column, and lagged versions of the residuals in the other columns. For this example, I’ll use lags of 1 and 6 months.
Listing 12.6.1. Python Code
df_ma = pd.DataFrame({"resid": resid_ar})

for lag in [1, 6]:
    df_ma[f"lag{lag}"] = resid_ar.shift(lag)

df_ma = df_ma.dropna()
We can use ols to make an autoregression model for the residuals. This part of the model is called a "moving average" because it reduces variability in the predictions in a way that’s analogous to the effect of a moving average. I don’t find that term particularly helpful, but it is conventional.
Anyway, here’s a summary of the autoregression model for the residuals.
Listing 12.6.2. Python Code
formula = make_formula(df_ma)
results_ma = smf.ols(formula=formula, data=df_ma).fit()
display_summary(results_ma)
Table 12.6.3.
coef std err t P>|t| [0.025 0.975]
Intercept -14.0016 114.697 -0.122 0.903 -239.863 211.860
lag1 0.0014 0.062 0.023 0.982 -0.120 0.123
lag6 -0.1592 0.063 -2.547 0.011 -0.282 -0.036
Table 12.6.4.
R-squared: 0.0247
The \(R^2\) is quite small, so it looks like this part of the model won’t help very much. But the p-value for the 6-month lag is small, which suggests that it contributes more information than we’d expect by chance.
Now we can use the model to generate retrodictions for the residuals.
Listing 12.6.5. Python Code
pred_ma = results_ma.predict(df_ma)
Then, to generate retrodictions for the year-over-year differences, we add the adjustment from the second model to the retrodictions from the first.
Listing 12.6.6. Python Code
pred_diff = pred_ar + pred_ma
The \(R^2\) value for the sum of the two models is about 0.332, which is just a little better than the result without the moving average adjustment (0.319).
Listing 12.6.7. Python Code
$ resid_ma = (diff - pred_diff).dropna()
R2 = 1 - resid_ma.var() / diff.var()
R2
np.float64(0.3315101001391231)
Next we’ll use these year-over-year differences to generate retrodictions for the original values.