ruby 52 lines · 8 steps

A weighted moving average forecaster in Ruby

A class that predicts the next value in a series by blending recent points with normalized weights.

Explained by highlit
1class WeightedMovingAverageForecaster
2 DEFAULT_WINDOW = 5
3 
4 def initialize(window: DEFAULT_WINDOW, weights: nil)
5 @window = window
6 @weights = normalize(weights || default_weights(window))
7 
8 unless @weights.size == @window
9 raise ArgumentError, "expected #{@window} weights, got #{@weights.size}"
10 end
11 end
12 
13 def forecast(series)
14 return nil if series.size < @window
15 
16 recent = series.last(@window)
17 recent.zip(@weights).sum { |value, weight| value * weight }
18 end
19 
20 def forecast_next(series, steps:)
21 working = series.dup
22 
23 Array.new(steps) do
24 predicted = forecast(working)
25 break [] if predicted.nil?
26 
27 working << predicted
28 predicted
29 end
30 end
31 
32 def rolling(series)
33 return [] if series.size < @window
34 
35 (@window..series.size).map do |upper|
36 forecast(series[0...upper])
37 end
38 end
39 
40 private
41 
42 def default_weights(window)
43 (1..window).to_a
44 end
45 
46 def normalize(weights)
47 total = weights.sum.to_f
48 raise ArgumentError, "weights must sum to a positive value" unless total.positive?
49 
50 weights.map { |w| w / total }
51 end
52end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Normalizing weights so they sum to one lets the forecast stay on the same scale as the input values.
  2. 2Feeding a prediction back into the series turns a one-step forecast into a multi-step one.
  3. 3Validating configuration in the constructor catches mismatched inputs before any forecasting runs.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A weighted moving average forecaster in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code