From 20de4b1702e4220849fb6df622fd2c35abdac972 Mon Sep 17 00:00:00 2001 From: IUser Date: Mon, 11 Mar 2013 23:57:23 -0700 Subject: [PATCH] Percentile: Bug fixes and compliance with Excel. High boundary check was against 100, should be 1.0. Close to 0 or 1 percentile values for Nist and Interpolation methods caused array out of bounds exceptions. Seemingly arbitrary small dataset restriction removed. Out of range percentile values and empty dataset returns double.NaN instead of throwing, similar to Excel returning #NUM! in such cases. --- src/Numerics/Statistics/Percentile.cs | 19 ++++++++------- .../StatisticsTests/PercentileTests.cs | 23 +++++++------------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/Numerics/Statistics/Percentile.cs b/src/Numerics/Statistics/Percentile.cs index 00b8e808..019e85e4 100644 --- a/src/Numerics/Statistics/Percentile.cs +++ b/src/Numerics/Statistics/Percentile.cs @@ -90,11 +90,6 @@ namespace MathNet.Numerics.Statistics throw new ArgumentNullException("data"); } - if (data.Count() < 3) - { - throw new ArgumentException(string.Format(Properties.Resources.MustContainAtLeast, 3), "data"); - } - _data = new List(data); _data.Sort(); } @@ -106,12 +101,12 @@ namespace MathNet.Numerics.Statistics /// the requested percentile. public double Compute(double percentile) { - if (percentile < 0 || percentile > 100) + if (percentile < 0 || percentile > 1 || _data.Count == 0) { - throw new ArgumentException("Percentile value must be between 0 and 100."); + return double.NaN; } - if (percentile == 0.0) + if (percentile == 0.0 || _data.Count == 1) { return _data[0]; } @@ -190,6 +185,9 @@ namespace MathNet.Numerics.Statistics { var k = (int)(_data.Count * percentile); var pk = (k - 0.5) / _data.Count; + if(k == 0) + return _data[0]; + return _data[k - 1] + (_data.Count * (percentile - pk) * (_data[k] - _data[k - 1])); } @@ -202,6 +200,11 @@ namespace MathNet.Numerics.Statistics { var tmp = percentile * (_data.Count + 1.0); var k = (int)tmp; + if(k == 0) + return _data[0]; + if(k == _data.Count) + return _data[k - 1]; + var d = tmp - k; return _data[k - 1] + (d * (_data[k] - _data[k - 1])); diff --git a/src/UnitTests/StatisticsTests/PercentileTests.cs b/src/UnitTests/StatisticsTests/PercentileTests.cs index 0cad934f..940ffcf0 100644 --- a/src/UnitTests/StatisticsTests/PercentileTests.cs +++ b/src/UnitTests/StatisticsTests/PercentileTests.cs @@ -99,32 +99,25 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests } /// - /// Small dataset throws ArgumentException. + /// Empty dataset returns NaN. /// [Test] - public void SmallDataSetThrowArgumentException() + public void EmptyDataSetReturnsNaN() { var data = new double[] { }; - var data1 = data; - Assert.Throws(() => new Percentile(data1)); - - data = new double[] { 1 }; - var data2 = data; - Assert.Throws(() => new Percentile(data2)); - - data = new double[] { 1, 2 }; - Assert.Throws(() => new Percentile(data)); + var percentile = new Percentile(data); + Assert.IsTrue(double.IsNaN(percentile.Compute(0))); } /// - /// Invalid percentile values throw ArgumentException. + /// Invalid percentile values return NaN. /// [Test] - public void InvalidPercentileValuesThrowArgumentException() + public void InvalidPercentileValuesReturnNaN() { var percentile = new Percentile(Data); - Assert.Throws(() => percentile.Compute(-0.1)); - Assert.Throws(() => percentile.Compute(100.1)); + Assert.IsTrue(double.IsNaN(percentile.Compute(-0.1))); + Assert.IsTrue(double.IsNaN(percentile.Compute(1.1))); } } }