Skip to content

Instantly share code, notes, and snippets.

@FergusInLondon
Last active October 3, 2018 14:28
Show Gist options
  • Save FergusInLondon/df118082c6fe7ced96685280647db031 to your computer and use it in GitHub Desktop.
Save FergusInLondon/df118082c6fe7ced96685280647db031 to your computer and use it in GitHub Desktop.
A little JS object for calculating rolling averages, complete with callbacks upon update.
/* Fergus In London - https://fergus.london - <[email protected]>
*
* Simple little function for calculating rolling averages, both simple and
* weighted. Example can be found on the GitHub Gist, or on my website.
*
* Do as you please, no rights reserved and no warranties provided. :)
*/
/**
* Constructs a new RollingAverage object.
*
* @see roller.js
* @param Number Defaults to 10
* @return Object
*/
var RollingAverage = function(limit) {
// Instance Variables
var sorted = [],
points = [],
callbacks = [function(r) {
sorted = points.slice(0); // this is a clone. yeah. fuck javascript.
sorted.sort();
}];
// Set Defaults
limit = limit || 10;
/**
* Iterate through all callbacks and call them; works to the order of which
* the callbacks are set.
*/
function runCallbacks() {
for (var i = 0; i < callbacks.length; i++)
callbacks[i]();
};
// Public interface
var public = {
/**
* Retrieves the latest datapoint.
*/
latest: function() {
var len = points.length;
return (len > 0) ? points[len - 1] : null;
},
/**
* Sets a new callback, and binds the current context to it, allowing
* the callback to call the averaging methods.
*/
onUpdate: function(cb) {
callbacks.push(cb.bind(public));
},
/**
* Pushes a new datapoint, whilst ensuring that the length of the stored
* datapoints adheres to the user supplied value.
*/
push: function(val) {
points.push(val);
if (points.length > limit) points.shift();
runCallbacks();
},
/**
* Averaging Function: Mean.
*/
mean: function() {
return (function(sum, n) {
for (var i = 0; i < n; i++)
sum += points[i];
return sum;
})(0, points.length) / points.length;
},
/**
* Averaging Function: Median.
*/
median: function() {
if ((sorted.length % 2) > 0)
return sorted[Math.ceil(sorted.length / 2)];
return (sorted[sorted.length / 2] + sorted[(sorted.length / 2) + 1]) / 2;
},
/**
* Averaging Function: Mode.
*/
mode: function() {
return (function(currentGreatest, map) {
for (var i = 0; i < sorted.length; i++) {
map[sorted[i]] = map[sorted[i]] ? map[sorted[i]]++ : 1;
if (!map[currentGreatest] || map[sorted[i]] > map[currentGreatest])
currentGreatest = sorted[i];
}
return currentGreatest;
})(0, {});
},
/**
* Averaging Function: Range.
*/
range: function() {
return sorted[sorted.length - 1] - sorted[0];
},
/**
* Computes a weighted/exponential average, arguably the most useful
* type for real-time/continuous data.
*/
weightedAverage: function() {
return (function(sum, n, weightBase){
for (var i=0; i<n; i++)
sum += points[i] * ((i+1) / weightBase);
return sum;
})(0, points.length, ((points.length * (points.length+1)) / 2));
}
};
return public;
};
@FergusInLondon
Copy link
Author

FergusInLondon commented Apr 27, 2017

Usage

Demonstration

var rolling = RollingAverage(7);
rolling.onUpdate(function(){ console.log("New Value: " + this.latest());                         });
rolling.onUpdate(function(){ console.log("Rolling Mean: " + this.mean());                        });
rolling.onUpdate(function(){ console.log("Rolling Median: " + this.median());                    });
rolling.onUpdate(function(){ console.log("Rolling Mode: " + this.mode());                        });
rolling.onUpdate(function(){ console.log("Rolling Range: " + this.range());                      });
rolling.onUpdate(function(){ console.log("Rolling Weighted Average: " + this.weightedAverage()); });

for(var i=0;i<50;i++)
  rolling.push(i);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment