Created
December 20, 2011 00:30
-
-
Save zackham/1499597 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
// 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