Skip to content

Instantly share code, notes, and snippets.

@zackham
Created December 20, 2011 00:30
Show Gist options
  • Save zackham/1499597 to your computer and use it in GitHub Desktop.
Save zackham/1499597 to your computer and use it in GitHub Desktop.
// example use: calculateMovingAverages('watts', [30, 60])
calculateMovingAverages: function(metric, windowDurations) {
var points = this._points;
// need at least a couple points to not throw an error..
if(points.length <= 2) return;
var baseMetric = 'watts';
for(var durI=0; durI < windowDurations.length; durI++) {
n = windowDurations[durI];
var avgMetric = baseMetric + '_ma_' + n;
var halfN = n/2;
var firstT = points[0].time;
var lastT = points[points.length-1].time;
var i = 0;
// find first point that is >= n/2 seconds in,
while(points[++i] && points[i].time - firstT < halfN) ;
// there isnt even n seconds worth of data! get out of here
if(!points[i] || lastT - points[i].time < halfN) return;
// if we already did it gtfo
if(points[i][avgMetric]) return;
// alright, i is the first point where we are at least n/2 seconds
// in, and we know we have at least n/2 seconds after i, so lets
// do it
//
// we need to do the full calculation for the first point
var res = this.timeWeightedAverage(baseMetric, points[i].time - halfN, points[i].time + halfN);
points[i][avgMetric] = res.avg;
var backI = res.outerStartI, frontI = res.innerEndI;
for(++i; lastT - points[i].time >= halfN; i++) {
var time = points[i].time;
var prevTime = points[i-1].time;
// we move forward a point, which is deltaT seconds. we'll
// discard/add this amount of time from the back/front
var deltaT = time - prevTime;
// figure out what we're going to add from the front
var toAdd = this.timeWeightedAverage(baseMetric, prevTime + halfN, time + halfN, frontI);
frontI = toAdd.innerEndI;
// figure out what we're going to discard from the back
var toDiscard = this.timeWeightedAverage(baseMetric, prevTime - halfN, time - halfN, backI);
backI = toDiscard.outerStartI;
// we're adding to / removing from this value
var lastAvg = points[i-1][avgMetric];
// we normalize by multiplying each value by the duration they
// represent, then divide by our total duration n
var newAvg = (n*lastAvg + deltaT*(toAdd.avg - toDiscard.avg)) / n;
points[i][avgMetric] = newAvg;
}
}
},
timeWeightedAverage: function(metric, startT, endT, startI) {
var points = this._points;
if(!startT) startT = 0;
if(!endT) endT = points[points.length-2].time;
if(!startI) startI = 1;
// find startI, the idx of the first point after startT
while(points[startI].time < startT) startI++;
// calculate time weighted avg from startI through endI, the idx
// of the last point before endT
var i=startI;
var avgTimesDuration=0;
while(points[++i] && points[i].time <= endT) {
var dt = points[i].time - points[i-1].time;
var avg = (points[i][metric] + points[i-1][metric]) / 2;
avgTimesDuration += dt * avg;
}
endI = i - 1;
// both start and end times fall between two consecutive points,
// we overshoot and need to back up endI (which should always be
// <= endT!
if(points[endI].time > endT) endI--;
var tmpavg = avgTimesDuration / (points[endI].time - points[startI].time);
// calculate the extra interpolated bit at the beginning
var firstPt = points[startI];
var prevPt = points[startI-1];
var leftDeltaT = firstPt.time - startT;
var leftEstValue = firstPt[metric] - (firstPt[metric] - prevPt[metric]) * leftDeltaT / (firstPt.time - prevPt.time);
// and the extra interpolated bit at the end
var lastPt = points[endI];
var nextPt = points[endI+1];
var rightDeltaT = endT - lastPt.time;
var rightEstValue = lastPt[metric] + (nextPt[metric] - lastPt[metric]) * rightDeltaT / (nextPt.time - lastPt.time)
var finalAvg;
if(endI < startI) {
// both times fall between two points, so just average the
// estimated values
finalAvg = (leftEstValue + rightEstValue)/2;
} else {
var finalProduct = leftDeltaT * (leftEstValue+firstPt[metric])/2 + avgTimesDuration + rightDeltaT * (rightEstValue+lastPt[metric])/2;
finalAvg = finalProduct / (endT - startT);
}
return {
outerStartI: startI-1,
innerEndI: endI,
avg: finalAvg
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment