Last active
June 8, 2019 02:49
-
-
Save nsdevaraj/c36f6df84491e3b2f774ab6d898b26d0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var exports = module.exports = {}; | |
/*Simple exponential smoothing */ | |
exports.SimpleExponentialSmoothing = function(data, alpha) | |
{ | |
if(data == null) | |
{ | |
throw "data parameter is null"; | |
} | |
else if(data.length < 2) | |
{ | |
throw "data doesn't contain enough data to make a prediction"; | |
} | |
if(alpha > 1 || alpha < 0) | |
{ | |
throw "alpha parameter must be between 0 and 1"; | |
} | |
this.data = data; | |
this.alpha = alpha; | |
this.forecast = null; | |
}; | |
exports.SimpleExponentialSmoothing.prototype.predict = function() | |
{ | |
var forecast = Array(); | |
forecast[0] = null; | |
forecast[1] = 0.5*(this.data[0] + this.data[1]); | |
for(var i = 2; i <= this.data.length; ++i) | |
{ | |
forecast[i] = this.alpha*(this.data[i-1] - forecast[i-1]) + forecast[i-1]; | |
} | |
this.forecast = forecast; | |
return forecast; | |
}; | |
exports.SimpleExponentialSmoothing.prototype.getForecast = function() | |
{ | |
if(this.forecast == null) | |
{ | |
this.predict(); | |
} | |
return this.forecast; | |
} | |
exports.SimpleExponentialSmoothing.prototype.computeMeanSquaredError = function() | |
{ | |
var SSE = 0.0; | |
var n = 0; | |
for(var i = 0; i < this.data.length; ++i) | |
{ | |
if(this.data[i] != null && this.forecast[i] != null) | |
{ | |
SSE += Math.pow(this.data[i] - this.forecast[i], 2); | |
n++; | |
} | |
} | |
return 1/(n-1)*SSE; | |
}; | |
exports.SimpleExponentialSmoothing.prototype.optimizeParameter = function(iter) | |
{ | |
var incr = 1/iter; | |
var bestAlpha = 0.0; | |
var bestError = -1; | |
this.alpha = bestAlpha; | |
while(this.alpha < 1) | |
{ | |
var forecast = this.predict(); | |
var error = this.computeMeanSquaredError(); | |
if(error < bestError || bestError == -1) | |
{ | |
bestAlpha = this.alpha; | |
bestError = error; | |
} | |
this.alpha += incr; | |
} | |
this.alpha = bestAlpha; | |
return this.alpha; | |
} | |
/*Double exponential smoothing */ | |
exports.DoubleExponentialSmoothing = function(data, alpha) | |
{ | |
if(data == null) | |
{ | |
throw "data parameter is null"; | |
} | |
else if(data.length < 2) | |
{ | |
throw "data doesn't contain enough data to make a prediction"; | |
} | |
if(alpha > 1 || alpha < 0) | |
{ | |
throw "alpha parameter must be between 0 and 1"; | |
} | |
this.data = data; | |
this.alpha = alpha; | |
this.forecast = null; | |
}; | |
exports.DoubleExponentialSmoothing.prototype.predict =function (horizon) | |
{ | |
var smoothings = Object(); | |
smoothings.first = Array(); | |
smoothings.second = Array(); | |
smoothings.first[0] = this.data[0]; | |
smoothings.first[1] = this.data[1]; | |
for(var i = 2; i < this.data.length; ++i) | |
{ | |
smoothings.first[i] = this.alpha*this.data[i] + (1-this.alpha)*smoothings.first[i-1]; | |
} | |
smoothings.second[0] = smoothings.first[0]; | |
for(var i = 1; i < this.data.length; ++i) | |
{ | |
smoothings.second[i] = this.alpha*smoothings.first[i] + (1-this.alpha)*smoothings.second[i-1]; | |
} | |
smoothings.a = Array(); | |
smoothings.b = Array(); | |
for(var i = 1; i < this.data.length; ++i) | |
{ | |
smoothings.a[i] = (this.alpha/(1-this.alpha))*(smoothings.first[i] - smoothings.second[i]); | |
smoothings.b[i] = 2*smoothings.first[i] - smoothings.second[i]; | |
} | |
var forecast = Array(); | |
forecast[0] = null; | |
forecast[1] = null; | |
for(var i = 2; i <= this.data.length; ++i) | |
{ | |
forecast[i] = smoothings.a[i-1] + smoothings.b[i-1]; | |
} | |
for(var i = this.data.length +1; i < this.data.length + horizon; ++i) | |
{ | |
forecast[i] = forecast[i-1] + smoothings.a[this.data.length-1]; | |
} | |
this.forecast = forecast; | |
return forecast; | |
} | |
exports.DoubleExponentialSmoothing.prototype.getForecast = function() | |
{ | |
if(this.forecast == null) | |
{ | |
return null; | |
} | |
return this.forecast; | |
} | |
exports.DoubleExponentialSmoothing.prototype.computeMeanSquaredError = function() | |
{ | |
var SSE = 0.0; | |
var n = 0; | |
for(var i = 0; i < this.data.length; ++i) | |
{ | |
if(this.data[i] != null && this.forecast[i] != null) | |
{ | |
SSE += Math.pow(this.data[i] - this.forecast[i], 2); | |
n++; | |
} | |
} | |
return 1/(n-1)*SSE; | |
}; | |
exports.DoubleExponentialSmoothing.prototype.optimizeParameter = function(iter) | |
{ | |
var incr = 1/iter; | |
var bestAlpha = 0.0; | |
var bestError = -1; | |
this.alpha = bestAlpha; | |
while(this.alpha < 1) | |
{ | |
var forecast = this.predict(); | |
var error = this.computeMeanSquaredError(); | |
if(error < bestError || bestError == -1) | |
{ | |
bestAlpha = this.alpha; | |
bestError = error; | |
} | |
this.alpha += incr; | |
} | |
this.alpha = bestAlpha; | |
return this.alpha; | |
} | |
/*Holt smoothing */ | |
exports.HoltSmoothing = function(data, alpha, gamma) | |
{ | |
if(data == null) | |
{ | |
throw "data parameter is null"; | |
} | |
else if(data.length < 2) | |
{ | |
throw "data doesn't contain enough data to make a prediction"; | |
} | |
if(alpha > 1 || alpha < 0) | |
{ | |
throw "alpha parameter must be between 0 and 1"; | |
} | |
if(gamma > 1 || gamma < 0) | |
{ | |
throw "gamma parameter must be between 0 and 1"; | |
} | |
this.data = data; | |
this.alpha = alpha; | |
this.gamma = gamma; | |
this.forecast = null; | |
}; | |
exports.HoltSmoothing.prototype.predict =function (horizon) | |
{ | |
A = Array(); | |
B = Array(); | |
A[0] = 0; | |
B[0] = this.data[0]; | |
for(var i = 1; i < this.data.length; ++i) | |
{ | |
B[i] = this.alpha*this.data[i] + (1-this.alpha)*(B[i-1] + A[i-1]); | |
A[i] = this.gamma*(B[i]-B[i-1])+ (1-this.gamma)*A[i-1]; | |
} | |
var forecast = Array(); | |
forecast[0] = null; | |
for(var i = 1; i <= this.data.length; ++i) | |
{ | |
forecast[i] = A[i-1] + B[i-1]; | |
} | |
for(var i = this.data.length +1; i < this.data.length + horizon; ++i) | |
{ | |
forecast[i] = forecast[i-1] + A[this.data.length - 1]; | |
} | |
this.forecast = forecast; | |
return forecast; | |
} | |
exports.HoltSmoothing.prototype.getForecast = function() | |
{ | |
if(this.forecast == null) | |
{ | |
return null; | |
} | |
return this.forecast; | |
} | |
exports.HoltSmoothing.prototype.computeMeanSquaredError = function() | |
{ | |
var SSE = 0.0; | |
var n = 0; | |
for(var i = 0; i < this.data.length; ++i) | |
{ | |
if(this.data[i] != null && this.forecast[i] != null) | |
{ | |
SSE += Math.pow(this.data[i] - this.forecast[i], 2); | |
n++; | |
} | |
} | |
return 1/(n-1)*SSE; | |
}; | |
exports.HoltSmoothing.prototype.optimizeParameters = function(iter) | |
{ | |
var incr = 1/iter; | |
var bestAlpha = 0.0; | |
var bestError = -1; | |
this.alpha = bestAlpha; | |
var bestGamma = 0.0; | |
this.gamma = bestGamma; | |
while(this.alpha < 1) | |
{ | |
while(this.gamma < 1) | |
{ | |
var forecast = this.predict(); | |
var error = this.computeMeanSquaredError(); | |
if(error < bestError || bestError == -1) | |
{ | |
bestAlpha = this.alpha; | |
bestGamma = this.gamma; | |
bestError = error; | |
} | |
this.gamma += incr; | |
} | |
this.gamma = 0; | |
this.alpha += incr; | |
} | |
this.alpha = bestAlpha; | |
this.gamma = bestGamma; | |
return {"alpha":this.alpha, "gamma":this.gamma}; | |
} | |
/*Holt Winters smoothing */ | |
exports.HoltWintersSmoothing = function(data, alpha, gamma, delta, seasonLength, mult) | |
{ | |
if(data == null) | |
{ | |
throw "data parameter is null"; | |
} | |
else if(data.length < 2) | |
{ | |
throw "data doesn't contain enough data to make a prediction"; | |
} | |
if(alpha > 1 || alpha < 0) | |
{ | |
throw "alpha parameter must be between 0 and 1"; | |
} | |
if(gamma > 1 || gamma < 0) | |
{ | |
throw "gamma parameter must be between 0 and 1"; | |
} | |
if(delta > 1 || delta < 0) | |
{ | |
throw "delta parameter must be between 0 and 1"; | |
} | |
if(seasonLength < 0) | |
{ | |
throw "seasonLength parameter must be a positive integer"; | |
} | |
if(mult != true && mult != false) | |
{ | |
throw "mult parameter must be a boolean"; | |
} | |
this.data = data; | |
this.alpha = alpha; | |
this.gamma = gamma; | |
this.delta = delta; | |
this.seasonLength = seasonLength; | |
this.mult = mult; | |
this.forecast = null; | |
}; | |
exports.HoltWintersSmoothing.prototype.predict =function () | |
{ | |
if(this.mult) | |
{ | |
return this.predictMult(); | |
} | |
else | |
{ | |
return this.predictAdd(); | |
} | |
} | |
exports.HoltWintersSmoothing.prototype.predictAdd = function() | |
{ | |
A = Array(); | |
B = Array(); | |
S = Array(); | |
A[this.seasonLength-1] = 0; | |
var averageFirstSeason = 0; | |
for(var i = 0; i < this.seasonLength; ++i) | |
{ | |
averageFirstSeason += this.data[i]; | |
} | |
B[this.seasonLength-1] = averageFirstSeason/this.seasonLength; | |
for(var i = 0; i < this.seasonLength; ++i) | |
{ | |
S[i] = this.data[i] - averageFirstSeason/this.seasonLength; | |
} | |
for(var i = this.seasonLength; i < this.data.length; ++i) | |
{ | |
B[i] = this.alpha*(this.data[i]- S[i - this.seasonLength])+(1-this.alpha)*(B[i-1]+A[i-1]); | |
A[i] = this.gamma*(B[i]-B[i-1])+(1-this.gamma)*A[i-1]; | |
S[i] = this.delta*(this.data[i]-B[i])+(1-this.delta)*S[i-this.seasonLength]; | |
} | |
var forecast = Array(); | |
for(var i = 0; i < this.seasonLength; ++i) | |
{ | |
forecast[i]= null; | |
} | |
for(var i = this.seasonLength; i < this.data.length; ++i) | |
{ | |
forecast[i] = A[i-1] + B[i-1] + S[i - this.seasonLength]; | |
} | |
for(var i = this.data.length; i < this.data.length + this.seasonLength; ++i) | |
{ | |
forecast[i] = B[this.data.length-1] + (i - this.data.length + 1)*A[this.data.length-1] + S[i - this.seasonLength]; | |
} | |
this.forecast = forecast; | |
return forecast; | |
} | |
exports.HoltWintersSmoothing.prototype.predictMult = function() | |
{ | |
A = Array(); | |
B = Array(); | |
S = Array(); | |
A[this.seasonLength-1] = 0; | |
var averageFirstSeason = 0; | |
for(var i = 0; i < this.seasonLength; ++i) | |
{ | |
averageFirstSeason += this.data[i]; | |
} | |
B[this.seasonLength-1] = averageFirstSeason/this.seasonLength; | |
for(var i = 0; i < this.seasonLength; ++i) | |
{ | |
S[i] = (this.data[i])/(averageFirstSeason/this.seasonLength); | |
} | |
for(var i = this.seasonLength; i < this.data.length; ++i) | |
{ | |
B[i] = this.alpha*(this.data[i]/S[i - this.seasonLength])+(1-this.alpha)*(B[i-1]+A[i-1]); | |
A[i] = this.gamma*(B[i]-B[i-1])+(1-this.gamma)*A[i-1]; | |
S[i] = this.delta*(this.data[i]/B[i])+(1-this.delta)*S[i-this.seasonLength]; | |
} | |
var forecast = Array(); | |
for(var i = 0; i < this.seasonLength; ++i) | |
{ | |
forecast[i]= null; | |
} | |
for(var i = this.seasonLength; i < this.data.length; ++i) | |
{ | |
forecast[i] = (A[i-1] + B[i-1])*S[i - this.seasonLength]; | |
} | |
for(var i = this.data.length; i < this.data.length + this.seasonLength; ++i) | |
{ | |
forecast[i] = (B[this.data.length-1] + (i - this.data.length + 1)*A[this.data.length-1])*S[i -this.seasonLength]; | |
} | |
this.forecast = forecast; | |
return forecast; | |
} | |
exports.HoltWintersSmoothing.prototype.getForecast = function() | |
{ | |
if(this.forecast == null) | |
{ | |
this.predict(); | |
} | |
return this.forecast; | |
} | |
exports.HoltWintersSmoothing.prototype.computeMeanSquaredError = function() | |
{ | |
var SSE = 0.0; | |
var n = 0; | |
for(var i = 0; i < this.data.length; ++i) | |
{ | |
if(this.data[i] != null && this.forecast[i] != null) | |
{ | |
SSE += Math.pow(this.data[i] - this.forecast[i], 2); | |
n++; | |
} | |
} | |
return 1/(n-1)*SSE; | |
}; | |
exports.HoltWintersSmoothing.prototype.optimizeParameters = function(iter) | |
{ | |
var incr = 1/iter; | |
var bestAlpha = 0.0; | |
var bestError = -1; | |
this.alpha = bestAlpha; | |
var bestGamma = 0.0; | |
this.gamma = bestGamma; | |
var bestDelta = 0.0; | |
this.delta = bestDelta; | |
while(this.alpha < 1) | |
{ | |
while(this.gamma < 1) | |
{ | |
while(this.delta < 1) | |
{ | |
var forecast = this.predict(); | |
var error = this.computeMeanSquaredError(); | |
if(error < bestError || bestError == -1) | |
{ | |
bestAlpha = this.alpha; | |
bestGamma = this.gamma; | |
bestDelta = this.delta; | |
bestError = error; | |
} | |
this.delta += incr; | |
} | |
this.delta = 0; | |
this.gamma += incr; | |
} | |
this.gamma = 0; | |
this.alpha += incr; | |
} | |
this.alpha = bestAlpha; | |
this.gamma = bestGamma; | |
this.delta = bestDelta; | |
return {"alpha":this.alpha, "gamma":this.gamma, "delta":this.delta}; | |
} | |
/*Moving average */ | |
exports.MovingAverage = function(data) | |
{ | |
if(data == null) | |
{ | |
throw "data parameter is null"; | |
} | |
else if(data.length < 3) | |
{ | |
throw "data doesn't contain enough data to make a prediction"; | |
} | |
this.data = data; | |
}; | |
exports.MovingAverage.prototype.smooth = function(order) | |
{ | |
if(order < 1) | |
{ | |
throw "order parameter must be a positive integer"; | |
} | |
var result = Array(); | |
for(var i = order; i < this.data.length - order ; ++i) | |
{ | |
result[i] = 0; | |
for(var j = i - order; j <= i + order; j++) | |
{ | |
result[i] += this.data[j]; | |
} | |
result[i] /= 2*order +1; | |
} | |
return result; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function assertNotEmpty(list){ | |
if(list.length === 0) | |
throw new Error("List length is zero"); | |
} | |
function simple_exponential_smoothing(series, alpha){ | |
assertNotEmpty(series); | |
var prev = 0.0; | |
var ses_series = []; | |
for(var i = 0; i < series.length; i++) { | |
if (i === 0){ | |
ses_series.push(series[i]); | |
} else { | |
ses_series.push( alpha*series[i] + (1.0-alpha)*prev ); | |
} | |
prev = ses_series[i]; | |
} | |
return ses_series; | |
} | |
/** | |
* Calculates the simple moving average with (2*range + 1) points | |
* @param {Array.<Number>} series | |
* @param {Number} order | |
* @return {Array.<Number>} moving average series | |
*/ | |
function simple_moving_average(series, range){ | |
assertNotEmpty(series); | |
var slen = series.length; | |
var window = 2*range + 1; | |
var sma_series = []; | |
for(var i = 0; i < (slen - window) + 1; i++){ | |
var average = 0; | |
for(var j = i; j < i + window; j++){ | |
average = average + series[j]; | |
} | |
sma_series.push(average / window); | |
} | |
return sma_series; | |
} | |
/** | |
* Low pass filter: dampen high frequencies | |
* @param {Array.<Number>} series | |
* @return {Array.<Number>} low pass series | |
*/ | |
function low_pass(series){ | |
return simple_moving_average(series, 3); | |
} | |
/** | |
* High pass filter: dampen low frequencies | |
* @param {Array.<Number>} series | |
* @return {Array.<Number>} high pass series | |
*/ | |
function high_pass(series){ | |
var low = low_pass(series); | |
var high = []; | |
for(var i = 0; i < low.length; i++){ | |
high.push(series[i] - low[i]); | |
} | |
return high; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment