Standard deviation implementation on Measure-Object (#6238)

Implement iterative standard deviation algorithm for Measure-Object
This commit is contained in:
Amaan Marfatia
2018-03-15 20:45:59 -07:00
committed by Sergei Vorobev
parent 339e4533e7
commit 80d5df55c2
2 changed files with 100 additions and 5 deletions
@@ -34,7 +34,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
public GenericMeasureInfo()
{
Average = Sum = Maximum = Minimum = null;
Average = Sum = Maximum = Minimum = StandardDeviation = null;
}
/// <summary>
@@ -71,6 +71,11 @@ namespace Microsoft.PowerShell.Commands
///
/// </summary>
public double? Minimum { get; set; }
/// <summary>
/// The Standard Deviation of property values.
/// </summary>
public double? StandardDeviation { get; set; }
}
/// <summary>
@@ -90,7 +95,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
public GenericObjectMeasureInfo()
{
Average = Sum = null;
Average = Sum = StandardDeviation = null;
Maximum = Minimum = null;
}
@@ -128,6 +133,11 @@ namespace Microsoft.PowerShell.Commands
///
/// </summary>
public object Minimum { get; set; }
/// <summary>
/// The Standard Deviation of property values.
/// </summary>
public double? StandardDeviation { get; set; }
}
/// <summary>
@@ -225,6 +235,8 @@ namespace Microsoft.PowerShell.Commands
// Generic/Numeric statistics
internal double sum = 0.0;
internal double sumPrevious = 0.0;
internal double variance = 0.0;
internal object max = null;
internal object min = null;
@@ -263,6 +275,24 @@ namespace Microsoft.PowerShell.Commands
#endregion Common parameters in both sets
/// <summary>
/// Set to true if Standard Deviation is to be returned.
/// </summary>
[Parameter(ParameterSetName = GenericParameterSet)]
public SwitchParameter StandardDeviation
{
get
{
return _measureStandardDeviation;
}
set
{
_measureStandardDeviation = value;
}
}
private bool _measureStandardDeviation;
/// <summary>
/// Set to true is Sum is to be returned
/// </summary>
@@ -525,7 +555,7 @@ namespace Microsoft.PowerShell.Commands
AnalyzeString(strValue, stat);
}
if (_measureAverage || _measureSum)
if (_measureAverage || _measureSum || _measureStandardDeviation)
{
double numValue = 0.0;
if (!LanguagePrimitives.TryConvertTo(objValue, out numValue))
@@ -708,8 +738,19 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
private void AnalyzeNumber(double numValue, Statistics stat)
{
if (_measureSum || _measureAverage)
if (_measureSum || _measureAverage || _measureStandardDeviation)
{
stat.sumPrevious = stat.sum;
stat.sum += numValue;
}
if (_measureStandardDeviation && stat.count > 1)
{
// Based off of iterative method of calculating variance on
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
double avgPrevious = stat.sumPrevious / (stat.count - 1);
stat.variance *= (stat.count - 2.0) / (stat.count - 1);
stat.variance += (numValue - avgPrevious) * (numValue - avgPrevious) / stat.count;
}
}
/// <summary>
@@ -790,6 +831,7 @@ namespace Microsoft.PowerShell.Commands
{
double? sum = null;
double? average = null;
double? StandardDeviation = null;
object max = null;
object min = null;
@@ -797,8 +839,14 @@ namespace Microsoft.PowerShell.Commands
{
if (_measureSum)
sum = stat.sum;
if (_measureAverage && stat.count > 0)
average = stat.sum / stat.count;
if (_measureStandardDeviation)
{
StandardDeviation = Math.Sqrt(stat.variance);
}
}
if (_measureMax)
@@ -835,6 +883,7 @@ namespace Microsoft.PowerShell.Commands
gmi.Count = stat.count;
gmi.Sum = sum;
gmi.Average = average;
gmi.StandardDeviation = StandardDeviation;
if (null != max)
{
gmi.Maximum = (double)max;
@@ -887,7 +936,7 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// Whether or not a numeric conversion error occurred.
/// If true, then average/sum will not be output.
/// If true, then average/sum/standard deviation will not be output.
/// </summary>
private bool _nonNumericError = false;
@@ -3,6 +3,7 @@
Describe "Measure-Object" -Tags "CI" {
BeforeAll {
$testObject = 1,3,4
$testObject2 = 1..100
}
It "Should be able to be called without error" {
@@ -17,6 +18,51 @@ Describe "Measure-Object" -Tags "CI" {
$($testObject | Measure-Object).Count | Should Be $testObject.Length
}
It "Should calculate Standard Deviation" {
$actual = ($testObject | Measure-Object -StandardDeviation)
# We check this way since .StandardDeviation returns a double value
# 1.52752523165195 was calculated outside powershell using formula from
# http://mathworld.wolfram.com/StandardDeviation.html
[Math]::abs($actual.StandardDeviation - 1.52752523165195) | Should -BeLessThan .00000000000001
}
It "Should calculate Standard Deviation" {
$actual = ($testObject2 | Measure-Object -StandardDeviation)
# We check this way since .StandardDeviation returns a double value
# 29.011491975882 was calculated outside powershell using formula from
# http://mathworld.wolfram.com/StandardDeviation.html
[Math]::abs($actual.StandardDeviation - 29.011491975882) | Should -BeLessThan .0000000000001
}
It "Should calculate Standard Deviation with -Sum" {
$actual = ($testObject | Measure-Object -Sum -StandardDeviation)
# We check this way since .StandardDeviation returns a double value
$actual.Sum | Should Be 8
# 1.52752523165195 was calculated outside powershell using formula from
# http://mathworld.wolfram.com/StandardDeviation.html
[Math]::abs($actual.StandardDeviation - 1.52752523165195) | Should -BeLessThan .00000000000001
}
It "Should calculate Standard Deviation with -Average" {
$actual = ($testObject | Measure-Object -Average -StandardDeviation)
# We check this way since .StandardDeviation returns a double value
[Math]::abs($actual.Average - 2.66666666666667) | Should -BeLessThan .00000000000001
# 1.52752523165195 was calculated outside powershell using formula from
# http://mathworld.wolfram.com/StandardDeviation.html
[Math]::abs($actual.StandardDeviation - 1.52752523165195) | Should -BeLessThan .00000000000001
}
It "Should calculate Standard Deviation with -Sum -Average" {
$actual = ($testObject2 | Measure-Object -Sum -Average -StandardDeviation)
# We check this way since .StandardDeviation returns a double value
$actual.Sum | Should Be 5050
$actual.Average | Should Be 50.5
# 29.011491975882 was calculated outside powershell using formula from
# http://mathworld.wolfram.com/StandardDeviation.html
[Math]::abs($actual.StandardDeviation - 29.011491975882) | Should -BeLessThan .0000000000001
}
It "Should be able to count using the Property switch" {
$expected = $(Get-ChildItem $TestDrive).Length
$actual = $(Get-ChildItem $TestDrive | Measure-Object -Property Length).Count