How to create an average line for an irregular tim

2020-02-06 04:59发布

问题:

I'm building irregular time graphs with HighCharts that at the moment look like so:

And I'm wondering if it's possible to create an 'average' line for the three (or possibly more in future) lines.

It would start following the blue line, then go closer to the green line mid-January, etc.

At the moment the code I'm working with looks like:

$('#chart').highcharts({
  chart: { type: 'spline' },
  title: { text: '' },
  xAxis: { type: 'datetime' },
  yAxis: {
    title: { text: '' }
  }
  series: [{
    name: 'Line 1',
    data: [
      [Date.UTC(2014,0,16), 173.33],
      [Date.UTC(2014,0,23), 163.33],
      [Date.UTC(2014,0,30), 137.67],
      [Date.UTC(2014,1,6), 176.33],
      [Date.UTC(2014,1,13), 178.67],
      [Date.UTC(2014,1,27), 167.33],
    ],
    color: 'purple'
  },
  {
    name: 'Line 2',
    data: [
      [Date.UTC(2014,0,11), 156.33],
      [Date.UTC(2014,1,15), 167.67],
    ],
    color: 'green'
  },
  {
    name: 'Line 3',
    data: [
      [Date.UTC(2014,0,1), 135],
      [Date.UTC(2014,0,5), 146.33],
      [Date.UTC(2014,0,27), 146.75],
    ],
    color: 'blue'
  }]
});

回答1:

What you are describing is called a trend or regression line. Highcharts doesn't have a built in ability to add these lines, but the math isn't too difficult (and besides, it's more fun to do it yourself). I've coded up the simplest example I can using least squared linear regression.

/////////////////////
//utility functions//
////////////////////
// linear regression
// given array of x values and array of y values
// returns rV object with slope/intercept
lineFit = function(xs, ys, rV){
    rV.slope = 0.0;
    rV.intercept = 0.0;
    rV.rSquared = 1.0; // assume perfection

    if (xs.length < 2)
    {
        return false;
    }

    if (xs.Count != ys.Count)
    {
        return false;
    }

    var N = xs.length;
    var sumX = sumFunc(xs,null);
    var sumY = sumFunc(ys,null);
    var funcSq = function(i){return (i*i);}
    var funcSq2 = function(i,j){return (i*j);}
    var sumXx = sumFunc(xs, funcSq);
    var sumYy = sumFunc(ys, funcSq);
    var sumXy = sumFunc(zip(xs,ys),funcSq2); 

    rV.slope = ((N * sumXy) - (sumX * sumY)) / (N * sumXx - (sumX*sumX));
    rV.intercept = (sumY - rV.slope * sumX) / N;
    rV.rSquared = Math.abs((rV.slope * (sumXy - (sumX * sumY) / N)) / (sumYy - ((sumY * sumY) / N)));
    return true;
}   

// sums arrays with optional function transformation
sumFunc = function(arr, func){
    var total = 0;
    $.each(arr, function(i,k){
        if ($.isArray(k)){
            if (func == null){
                k = k[0] + k[1];
            }else{                
                k = func(k[0],k[1]);
            }
        } else {
            if (func != null){
                k = func(k);
            }
        }
        total += k;
    });
    return total;
}

// python style zip function
// to pair to array together
zip = function(arr1,arr2) {
    var rV = [];
    for(var i=0; i<arr1.length; i++){
       rV.push([arr1[i],arr2[i]]);
    }
    return rV;
}

The lineFit function will return the rV object (by reference) with attributes of slope and intercept. After that you can add a line to Highcharts with good old fashioned y = slope * x + intercept and minX is the starting value for the regression line and maxX is the ending value.

 {
    name: 'Regression Line',
    data: [[minX, reg.slope * minX + reg.intercept], 
           [maxX, reg.slope * maxX + reg.intercept]],
    color: 'red',
    marker:{enabled:false},
    lineWidth: 5
  }

Working fiddle here.



回答2:

Based on ideas provided by the answer from Mark, I wrote some code to generate a custom fourth line, using the data from all three lines, and calculating the required value for each point.

My new code is as follows:

  line1 = [
    [Date.UTC(2014,0,16), 173.33],
    [Date.UTC(2014,0,23), 163.33],
    [Date.UTC(2014,0,30), 137.67],
    [Date.UTC(2014,1,6), 176.33],
    [Date.UTC(2014,1,13), 178.67],
    [Date.UTC(2014,1,27), 167.33],
  ];

  line2 = [
    [Date.UTC(2014,0,11), 156.33],
    [Date.UTC(2014,1,15), 167.67],
  ];

  line3 = [
    [Date.UTC(2014,0,1), 135],
    [Date.UTC(2014,0,5), 146.33],
    [Date.UTC(2014,0,27), 146.75],
    [Date.UTC(2014,2,2), 168.75]
  ];

  function average(array, index) {
    sum = array[0][1];
    for(var i = 1; i <= index; i++) {
      sum += array[i][1];
    }

    value = sum / (index + 1);
    return parseFloat(value.toFixed(2));
  }

  // Make a fourth line with all of the data points for the other 
  // three lines, sorted by date    
  all_lines = line1.concat(line2).concat(line3);
  all_lines.sort(function(a, b) { return a[0] - b[0]});

  // Calculate the value for each data point in the fourth line - 
  // the average of all the values before it
  average_line = [];
  for(var i = 0; i < all_lines.length; i++) {
    average_line.push([all_lines[i][0], average(all_lines, i)])
  }

  $('#chart').highcharts({
    chart: { type: 'spline' },
    title: {
        text: '',
    },
    xAxis: {
        type: 'datetime'
    },
    yAxis: {
        title: {
            text: ''
        }
    },
    legend: {
        layout: 'vertical',
        align: 'right',
        verticalAlign: 'middle',
        borderWidth: 0
    },
    series: [{
        name: 'Line 1',
        data: line1,
        color: 'purple'
    },
    {
        name: 'Line 2',
        data: line2,
        color: 'green'
    },
    {
        name: 'Line 3',
        data: line3,
        color: 'blue'
    },
    {
        name: 'Average',
        data: average_line,
        color: 'red'
    }]
  });

The graph as it looks now (with one extra data point on the blue line) is: