admin管理员组

文章数量:1415475

I'm new to D3 and trying to do a moving average of previous and next values on my data in order to smooth it out.

Currently, I have it working using the 2 previous values + the current value. It does work but 1) how would I also use the next values, and 2) what if I wanted to use the 15 previous and 15 next values? (it would be crazy to have 30 individual vars for storing all of them)

I'm used to traditional javascript but lost as to how to traverse the data this way in D3. Hope someone can enlighten me, thanks.

See the whole code on bl.ocks:

Or just the data parsing code here:

d3.json("by_date_mod.json", function(error, data) {

// Setup each row of data by formatting the Date for X, and by converting to a number for Y.
data = data.rows;
data.forEach(function(d) {
  d.key = parseDate(String(d.key));
  d.value = +d.value;
});

x.domain(d3.extent(data, function(d) { return d.key; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);

// Setup the moving average calculation.
// Currently is a hacky way of doing it by manually storing and using the previous 3 values for averaging.
// Looking for another way to address previous values so we can make the averaging window much larger (like 15 previous values).
var prevPrevVal = 0;
var prevVal = 0;
var curVal = 0
var movingAverageLine = d3.svg.line()
.x(function(d,i) { return x(d.key); })
.y(function(d,i) {
    if (i == 0) {
        prevPrevVal  = y(d.value);
        prevVal = y(d.value);
        curVal =  y(d.value);
    } else if (i == 1) {
        prevPrevVal = prevVal;
        prevVal = curVal;
        curVal = (prevVal + y(d.value)) / 2.0;
    } else {
        prevPrevVal = prevVal;
        prevVal = curVal;
        curVal = (prevPrevVal + prevVal + y(d.value)) / 3.0;
    }
    return curVal;
})
.interpolate("basis");

// Draw the moving average version of the data, as a line.
graph1.append("path")
  .attr("class", "average")
  .attr("d", movingAverageLine(data));

// Draw the raw data as an area.
graph1.append("path")
    .datum(data)
    .attr("class", "area")
    .attr("d", area);

// Draw the X-axis of the graph.
graph1.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

// Draw the Y-axis of the graph.
graph1.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Value");
});

I'm new to D3 and trying to do a moving average of previous and next values on my data in order to smooth it out.

Currently, I have it working using the 2 previous values + the current value. It does work but 1) how would I also use the next values, and 2) what if I wanted to use the 15 previous and 15 next values? (it would be crazy to have 30 individual vars for storing all of them)

I'm used to traditional javascript but lost as to how to traverse the data this way in D3. Hope someone can enlighten me, thanks.

See the whole code on bl.ocks: http://bl.ocks/jcnesci/7439277

Or just the data parsing code here:

d3.json("by_date_mod.json", function(error, data) {

// Setup each row of data by formatting the Date for X, and by converting to a number for Y.
data = data.rows;
data.forEach(function(d) {
  d.key = parseDate(String(d.key));
  d.value = +d.value;
});

x.domain(d3.extent(data, function(d) { return d.key; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);

// Setup the moving average calculation.
// Currently is a hacky way of doing it by manually storing and using the previous 3 values for averaging.
// Looking for another way to address previous values so we can make the averaging window much larger (like 15 previous values).
var prevPrevVal = 0;
var prevVal = 0;
var curVal = 0
var movingAverageLine = d3.svg.line()
.x(function(d,i) { return x(d.key); })
.y(function(d,i) {
    if (i == 0) {
        prevPrevVal  = y(d.value);
        prevVal = y(d.value);
        curVal =  y(d.value);
    } else if (i == 1) {
        prevPrevVal = prevVal;
        prevVal = curVal;
        curVal = (prevVal + y(d.value)) / 2.0;
    } else {
        prevPrevVal = prevVal;
        prevVal = curVal;
        curVal = (prevPrevVal + prevVal + y(d.value)) / 3.0;
    }
    return curVal;
})
.interpolate("basis");

// Draw the moving average version of the data, as a line.
graph1.append("path")
  .attr("class", "average")
  .attr("d", movingAverageLine(data));

// Draw the raw data as an area.
graph1.append("path")
    .datum(data)
    .attr("class", "area")
    .attr("d", area);

// Draw the X-axis of the graph.
graph1.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

// Draw the Y-axis of the graph.
graph1.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Value");
});
Share Improve this question asked Nov 12, 2013 at 21:41 jcnescijcnesci 912 silver badges10 bronze badges 2
  • I would calculate the values for the moving average separately and then pass that data to D3. – Lars Kotthoff Commented Nov 12, 2013 at 21:47
  • @LarsKotthoff If it es to that, sure. I'd just like to rule-out whether D3 has traversal methods to do this first. Thanks, – jcnesci Commented Nov 12, 2013 at 22:21
Add a ment  | 

2 Answers 2

Reset to default 5

You need a function to calculate the moving average:

var movingWindowAvg = function (arr, step) {  // Window size = 2 * step + 1
    return arr.map(function (_, idx) { 
        var wnd = arr.slice(idx - step, idx + step + 1); 
        var result = d3.sum(wnd) / wnd.length;

        // Check for isNaN, the javascript way
        result = (result == result) ? result : _;

        return result; 
    });
};

var avgData = movingWindowAvg(avg, 7); // 15 step moving window.

Note that this functions fudges the values a bit at the borders of the original array, when a plete window cannot be extracted.

Update: If the result is NaN, convert the result to the present number in the beginning. Checking result == result is the remended way of testing for NaNs in Javascript.

If you really don't need a variable size window, this cumulative average might be a faster option without the slicing overhead:

    function cumAvg(objects, accessor) {
      return objects.reduce(
          function(avgs, currObj, i) { 
            if (i == 1) {
              return [ accessor(currObj) ];
            } else {
              var lastAvg = avgs[i - 2]; // reduce idxs are 1-based, arrays are 0
              avgs.push( lastAvg + ( (accessor(currObj) - lastAvg) / i) );
              return avgs;
            }
          }
    }

本文标签: javascriptd3js moving average with previous and next data valuesStack Overflow