Friday, December 2, 2016

RunningAverage,how to calculate average without storing each individual value in C#


If I want to get an average of a series of values, I usually just put them in a List<float> and the run the Average() function on it.
But from time to time, there is a need to calculate averages continuously over time where the list alternative just won't cut it as the calculation will cost more and more over time.
The alternative is to write your own class that does just that, calculate the average continuously but doesn't store each individual value. I.e calculate average without storing values.
Different solutions are discussed at:
http://math.stackexchange.com/questions/106700/incremental-averageing
There seems to be many names for this type of averaging. Incremental average, travelling average or running average just to name a few. I stuck with Running Average.

Some source-code (c#)
public class RunningAverage
{
    public float Average => _sum / _count;
    private float _sum;
    private int _count;
    public void Add(float v)
    {
        _sum += v;
        _count++;
    }
}

[TestMethod]
public void RunningAverageTest_NoValue()
{
    var target = new RunningAverage();
    Assert.AreEqual(float.NaN, target.Average);
}

[TestMethod]
public void RunningAverageTest_OneValue()
{
    var target = new RunningAverage();
    target.Add(10);
    Assert.AreEqual(10, target.Average);
}

[TestMethod]
public void RunningAverageTest_100Values()
{
    var target = new RunningAverage();
    var expected = new List<float>();
    for (int i = 0; i < 100; i++)
    {
        target.Add(i);
        expected.Add(i);
    }
    Assert.AreEqual(expected.Average(), target.Average);
}

2017-01-03, update
Changes were made to improve the solution, based on comment from Dan in the comments section. i.e. changed average calculation to the Average getter. Updated unit-test RunningAverageTest_NoValue to assume float.NaN instead of 0.


All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Hope this helps someone out there :)


2 comments:

  1. Not very effective(1) or precise(2):
    1) you calculate the sum and average every time you add a number: it would be faster to just increment the count and add to the sum (which should be an instance var instead of a local var)
    2) you will get rounding errors on numbers with just a few counts: when you keep doing it again and again the lack of precision will be cumulative. It's much more precise to have a method (or property> that yields the average when you need it.

    Instead:
    Declare "private float _sum;" instead of the _average var.
    Make Average a get property.
    Add a RunningAverage() method that initializes the _count and the _sum.
    Add a Average() method which returns _sum/_count instead of _average.
    Change the Add method to just add v to _sum and increment _count.

    Since you will probably add numbers much more often than you will get the average the Add() method should be as fast as possible. The Average method will be at least as fast as the calculating part of the original Add() method, but the calculation will only be performed when you need it.

    Greetings
    /Dan

    ReplyDelete
    Replies
    1. Hi there, thank you for pointing this out. Post updated to reflect this better solution :)

      Delete