Created
May 11, 2018 23:46
-
-
Save mattdot/d459b1cb15480fefd953841a1ac70be8 to your computer and use it in GitHub Desktop.
Online/Streaming implementation of Std Deviation and Variance in C#
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
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)); | |
} | |
} | |
} |
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
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