Skip to content

Instantly share code, notes, and snippets.

@StaverDmitry
Created June 7, 2017 01:37
Show Gist options
  • Save StaverDmitry/1172f072d072a9e0849bdc9084fb4e42 to your computer and use it in GitHub Desktop.
Save StaverDmitry/1172f072d072a9e0849bdc9084fb4e42 to your computer and use it in GitHub Desktop.
module ForecastGenerator
class Seasoned
attr_reader :data, :data_trend, :forecast
def initialize(data)
@data = data
@coeffs = get_coefficients
@trend_params = trend_params
@data_trend = trend(@data.count)
end
def forecast(length_months = defaults[:length_months])
forecast = {}
forecast_trend = forecast_trend(length_months)
dates = (DateTime.now + 1.month..(DateTime.now + length_months.months))
.map(&:beginning_of_month).uniq
dates.each_with_index do |date, i|
forecast[date.strftime("%Y/%m/%d")] = forecast_trend[i] * @coeffs[date.month]
end
forecast
end
def forecast_trend_with_dates(length_months = defaults[:length_months])
dates = (DateTime.now + 1.month..(DateTime.now + length_months.months))
.map(&:beginning_of_month)
.map{ |d| d.strftime("%Y/%m/%d") }
.uniq
result = {}
trend(@data.count + 1, @data.count + length_months).each { |i,v| result[dates[i]] = v}
result
end
def forecast_trend(length_months = defaults[:length_months])
trend(@data.count + 1, @data.count + length_months)
end
private
def trend(since = 1, till)
trend = {}
trend_values(since, till).each_with_index do |v,i|
trend[i] = v
end
trend
end
def trend_values(since = 1, till)
(since..till).map { |i| i * @trend_params[:a] + @trend_params[:b] }
end
def trend_params
not_seasoned = remove_seasonality
linear_regression(not_seasoned)
end
# Two-step smoothing only
def perform_smoothing(options={steps:[12,2]})
data = @data.sort_by { |date, value| date.to_date }
options[:steps].each_with_index do |amount, step_number|
smoothed = []
offset = (amount / 2)
first = offset
last = data.count - offset
first.upto(last) do |i|
date = data.to_a[i - step_number].first
range = data.drop(i - offset).first(amount)
average = range.map(&:last).sum / amount
smoothed.push([date, average])
end
data = smoothed
end
data.to_h
end
def get_coefficients
smoothed = perform_smoothing
grouped_by_month = smoothed.map do |date, value|
{date: date,
coeff: @data[date].to_f / value}
end.group_by { |c| c[:date].to_date.month }
average_by_month = grouped_by_month.map do |grouped|
{month: grouped[0],
coeff: grouped[1].map { |g| g[:coeff] }.sum / grouped.count }
end
average = average_by_month.inject(0) {|sum,a| sum + a[:coeff]}
normalized = {}
average_by_month.each do |avg|
normalized[avg[:month]] = avg[:coeff] / average * 12
end
normalized
end
def remove_seasonality
@data.map do |date, value|
coeff = @coeffs[date.to_date.month]
{date: date,
value: value / coeff }
end
end
def linear_regression(values)
values = values.map { |v| [values.index(v), v[:value]] }.to_h
regression = LinearRegression.new(values)
{a: regression.slope, b: regression.y_intercept}
end
def self.defaults
{ length_months: 24 ,
initial_data_length: 36}
end
def defaults
self.class.defaults
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment