Skip to content

Instantly share code, notes, and snippets.

@mattdot
Created May 11, 2018 23:46
Show Gist options
  • Save mattdot/d459b1cb15480fefd953841a1ac70be8 to your computer and use it in GitHub Desktop.
Save mattdot/d459b1cb15480fefd953841a1ac70be8 to your computer and use it in GitHub Desktop.
Online/Streaming implementation of Std Deviation and Variance in C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Statistics
{
public static class OnlineStatistics
{
/// <summary>
/// Returns the power sum average based on the blog post from Subliminal Messages
/// Use the power sum average to help derive the running variance.
/// </summary>
/// <see cref="http://subluminal.wordpress.com/2008/07/31/running-standard-deviations/"/>
/// <param name="newValue">the new value being added to the power sum</param>
/// <param name="expiringValue">the old value being removed from the power sum, or null</param>
/// <param name="n">the window size (or number of samples if less than window size)</param>
/// <param name="previousPSA">the previous power sum average</param>
/// <returns>the power sum average</returns>
public static float PowerSumAverage(float newValue, float? expiringValue, int n, float previousPSA)
{
if (expiringValue.HasValue)
{
return previousPSA + (((newValue * newValue) - (expiringValue.Value * expiringValue.Value)) / (float)n);
}
else
{
return previousPSA + (newValue * newValue - previousPSA) / (float)n;
}
}
/// <summary>
///
/// </summary>
/// <param name="newValue"></param>
/// <param name="expiringValue"></param>
/// <param name="n"></param>
/// <param name="previousMean"></param>
/// <returns></returns>
public static float SimpleMovingAverage(float newValue, float? expiringValue, int n, float previousMean)
{
return (expiringValue.HasValue) ? (previousMean + ((newValue - expiringValue.Value) / (float)n)) : (previousMean + ((newValue - previousMean) / (float)n));
}
/// <summary>
///
/// </summary>
/// <param name="newValue"></param>
/// <param name="expiringValue"></param>
/// <param name="n"></param>
/// <param name="previousVariance"></param>
/// <param name="asma"></param>
/// <param name="psa"></param>
/// <returns></returns>
public static float RunningVariance(float newValue, float? expiringValue, int n, float previousVariance, ref float asma, ref float psa)
{
asma = SimpleMovingAverage(newValue, expiringValue, n, asma);
psa = PowerSumAverage(newValue, expiringValue, n, psa);
return (psa * n - n * asma * asma) / n;
}
/// <summary>
///
/// </summary>
/// <param name="newValue"></param>
/// <param name="expiringValue"></param>
/// <param name="n"></param>
/// <param name="previousStdDev"></param>
/// <param name="asma"></param>
/// <param name="psa"></param>
/// <returns></returns>
public static float RunningStddev(float newValue, float? expiringValue, int n, float previousStdDev, ref float asma, ref float psa)
{
return (float)Math.Sqrt(RunningVariance(newValue, expiringValue, n, previousStdDev * previousStdDev, ref asma, ref psa));
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace StatisticsTests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var expectedResults = new[]
{
new { asma = 3f, psa = 9f, var = 0f },
new { asma = 4f, psa = 17.0000f, var = 1.0000f },
new { asma = 5.3333f, psa = 32.6667f, var = 4.2222f },
new { asma = 7.6667f, psa = 63.0000f, var = 4.2222f },
new { asma = 7.3333f, psa = 60.0000f, var = 6.2222f },
new { asma = 7.3333f, psa = 60.0000f, var = 6.2222f },
new { asma = 8.0000f, psa = 74.6667f, var = 10.6667f },
new { asma = 11.6667f, psa = 144.3333f, var = 8.2222f },
new { asma = 12.6667f, psa = 163.3333f, var = 2.8889f },
new { asma = 11.6667f, psa = 142.3333f, var = 6.2222f }
};
float[] vals = new float[] { 3, 5, 8, 10, 4, 8, 12, 15, 11, 9 };
float variance = 0f;
float psa = 0f;
float asma = 0f;
int window = 3;
for (int i = 0; i < vals.Length; i++)
{
var newVal = vals[i];
float? oldVal = (i >= window) ? (float?)vals[i - window] : null;
var n = Math.Min(i + 1, window);
variance = OnlineStatistics.RunningVariance(newVal, oldVal, n, variance, ref asma, ref psa);
Assert.AreEqual(expectedResults[i].asma, asma, 0.1);
Assert.AreEqual(expectedResults[i].psa, psa, 0.1);
Assert.AreEqual(expectedResults[i].var, variance, 0.1);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment