From efbb2705562d7d76df985cbee23940d8720ba3dd Mon Sep 17 00:00:00 2001 From: Marcus Cuda Date: Thu, 4 Nov 2010 22:37:22 +0800 Subject: [PATCH] stats: added Percentile class and missing complex MatlabWriter tests --- src/MathNet.Numerics.5.1.ReSharper | 5 +- src/Numerics/Numerics.csproj | 1 + src/Numerics/Properties/Resources.Designer.cs | 9 + src/Numerics/Properties/Resources.resx | 3 + src/Numerics/Statistics/Percentile.cs | 210 ++++++++++++++++++ src/Silverlight/Silverlight.csproj | 3 + .../Complex/IO/MatlabWriterTests.cs | 116 ++++++++++ .../Complex32/IO/MatlabWriterTests.cs | 116 ++++++++++ .../StatisticsTests/PercentileTests.cs | 90 ++++++++ src/UnitTests/UnitTests.csproj | 1 + 10 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 src/Numerics/Statistics/Percentile.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Complex/IO/MatlabWriterTests.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Complex32/IO/MatlabWriterTests.cs create mode 100644 src/UnitTests/StatisticsTests/PercentileTests.cs diff --git a/src/MathNet.Numerics.5.1.ReSharper b/src/MathNet.Numerics.5.1.ReSharper index 3bcec989..06d7ae33 100644 --- a/src/MathNet.Numerics.5.1.ReSharper +++ b/src/MathNet.Numerics.5.1.ReSharper @@ -29,7 +29,10 @@ kronecker Cholesky Eigen mxn -nxn +nxn +Nist +NIST's +Excel's diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 48affc99..25b87f83 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -389,6 +389,7 @@ + diff --git a/src/Numerics/Properties/Resources.Designer.cs b/src/Numerics/Properties/Resources.Designer.cs index e457ff6b..dc4c3628 100644 --- a/src/Numerics/Properties/Resources.Designer.cs +++ b/src/Numerics/Properties/Resources.Designer.cs @@ -627,6 +627,15 @@ namespace MathNet.Numerics.Properties { } } + /// + /// Looks up a localized string similar to Data must contain at least {0} values.. + /// + internal static string MustContainAtLeast { + get { + return ResourceManager.GetString("MustContainAtLeast", resourceCulture); + } + } + /// /// Looks up a localized string similar to Name cannot contain a space. name: {0}. /// diff --git a/src/Numerics/Properties/Resources.resx b/src/Numerics/Properties/Resources.resx index 8dfc7518..9cee9b16 100644 --- a/src/Numerics/Properties/Resources.resx +++ b/src/Numerics/Properties/Resources.resx @@ -345,4 +345,7 @@ ddd MMM dd HH:mm:ss yyyy + + Data must contain at least {0} values. + \ No newline at end of file diff --git a/src/Numerics/Statistics/Percentile.cs b/src/Numerics/Statistics/Percentile.cs new file mode 100644 index 00000000..00b8e808 --- /dev/null +++ b/src/Numerics/Statistics/Percentile.cs @@ -0,0 +1,210 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// Copyright (c) 2009-2010 Math.NET +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +namespace MathNet.Numerics.Statistics +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Methods to calculate the percentiles. + /// + public enum PercentileMethod + { + /// + /// Using the method recommened my NIST, + /// http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm + /// + Nist = 0, + + /// + /// Using the nearest rank, http://en.wikipedia.org/wiki/Percentile#Nearest_Rank + /// + Nearest, + + /// + /// Using the same method as Excel does, + /// http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm + /// + Excel, + + /// + /// Use linear interpolation between the two nearest ranks, + /// http://en.wikipedia.org/wiki/Percentile#Linear_Interpolation_Between_Closest_Ranks + /// + Interpolation + } + + /// + /// Class to calculate percentiles. + /// + public class Percentile + { + /// + /// Holds the data. + /// + private readonly List _data; + + /// + /// Gets or sets the method used to calculate the percentiles. + /// + /// The calculation method. + /// defaults to . + public PercentileMethod Method + { + get; + set; + } + + /// + /// Initializes a new instance of the class. + /// + /// The data to calculate the percentiles of. + public Percentile(IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + if (data.Count() < 3) + { + throw new ArgumentException(string.Format(Properties.Resources.MustContainAtLeast, 3), "data"); + } + + _data = new List(data); + _data.Sort(); + } + + /// + /// Computes the percentile. + /// + /// The percentile, must be between 0.0 and 1.0 (inclusive). + /// the requested percentile. + public double Compute(double percentile) + { + if (percentile < 0 || percentile > 100) + { + throw new ArgumentException("Percentile value must be between 0 and 100."); + } + + if (percentile == 0.0) + { + return _data[0]; + } + + if (percentile == 1.0) + { + return _data[_data.Count - 1]; + } + + var result = double.NaN; + switch (Method) + { + case PercentileMethod.Nist: + result = Nist(percentile); + break; + case PercentileMethod.Nearest: + result = Nearest(percentile); + break; + case PercentileMethod.Interpolation: + result = Interpolation(percentile); + break; + case PercentileMethod.Excel: + result = Excel(percentile); + break; + } + + return result; + } + + /// + /// Computes the percentiles for the given list. + /// + /// The percentiles, must be between 0.0 and 1.0 (inclusive) + /// the values that correspond to the given percentiles. + public IList Compute(IEnumerable percentiles) + { + if (percentiles == null) + { + throw new ArgumentNullException("percentiles"); + } + + return percentiles.Select(Compute).ToList(); + } + + /// + /// Computes the percentile using the nearest value. + /// + /// The percentile. + /// the percentile using the nearest value. + private double Nearest(double percentile) + { + var n = (int)Math.Round((_data.Count * percentile) + 0.5, 0); + return _data[n - 1]; + } + + /// + /// Computes the percentile using Excel's method. + /// + /// The percentile. + /// the percentile using Excel's method. + private double Excel(double percentile) + { + var tmp = 1 + (percentile * (_data.Count - 1.0)); + var k = (int)tmp; + var d = tmp - k; + + return _data[k - 1] + (d * (_data[k] - _data[k - 1])); + } + + /// + /// Computes the percentile using interpolation. + /// + /// The percentile. + /// the percentile using the interpolation. + private double Interpolation(double percentile) + { + var k = (int)(_data.Count * percentile); + var pk = (k - 0.5) / _data.Count; + return _data[k - 1] + (_data.Count * (percentile - pk) * (_data[k] - _data[k - 1])); + } + + /// + /// Computes the percentile using NIST's method. + /// + /// The percentile. + /// the percentile using NIST's method. + private double Nist(double percentile) + { + var tmp = percentile * (_data.Count + 1.0); + var k = (int)tmp; + var d = tmp - k; + + return _data[k - 1] + (d * (_data[k] - _data[k - 1])); + } + } +} diff --git a/src/Silverlight/Silverlight.csproj b/src/Silverlight/Silverlight.csproj index 1e8cbf48..4dadeb21 100644 --- a/src/Silverlight/Silverlight.csproj +++ b/src/Silverlight/Silverlight.csproj @@ -929,6 +929,9 @@ Statistics\MCMC\UnivariateSliceSampler.cs + + Statistics\Percentile.cs + Statistics\Statistics.cs diff --git a/src/UnitTests/LinearAlgebraTests/Complex/IO/MatlabWriterTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/IO/MatlabWriterTests.cs new file mode 100644 index 00000000..f8d66bb0 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/IO/MatlabWriterTests.cs @@ -0,0 +1,116 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.IO +{ + using System; + using System.IO; + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.IO; + using LinearAlgebra.IO; + using MbUnit.Framework; + + [TestFixture] + public class MatlabMatrixWriterTests + { + [Test] + public void Constructor_ThrowsArgumentException() + { + Assert.Throws(() => new MatlabMatrixWriter(string.Empty)); + Assert.Throws(() => new MatlabMatrixWriter(null)); + } + + [Test] + public void WriteMatrices_ThrowsArgumentException() + { + Matrix matrix = new DenseMatrix(1, 1); + var writer = new MatlabMatrixWriter("somefile3"); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, new[] { string.Empty })); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, new string[] { null })); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix, matrix }, new[] { "matrix" })); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, new[] { "some matrix" })); + writer.Dispose(); + } + + [Test] + public void WriteMatrices_ThrowsArgumentNullException() + { + var writer = new MatlabMatrixWriter("somefile4"); + Assert.Throws(() => writer.WriteMatrices(new Matrix[] { null }, new[] { "matrix" })); + Matrix matrix = new DenseMatrix(1, 1); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, null)); + writer.Dispose(); + } + + [Test] + public void WriteMatricesTest() + { + Matrix mat1 = new DenseMatrix(5, 3); + for (var i = 0; i < mat1.ColumnCount; i++) + { + mat1[i, i] = new Complex(i + .1, i + .1); + } + + Matrix mat2 = new DenseMatrix(4, 5); + for (var i = 0; i < mat2.RowCount; i++) + { + mat2[i, i] = new Complex(i + .1, i + .1); + } + + Matrix mat3 = new SparseMatrix(5, 4); + for (var i = 0; i < mat3.ColumnCount; i++) + { + mat3[i, i] = new Complex(i + .1, i + .1); + } + + Matrix mat4 = new SparseMatrix(3, 5); + for (var i = 0; i < mat4.RowCount; i++) + { + mat4[i, i] = new Complex(i + .1, i + .1); + } + + var write = new[] { mat1, mat2, mat3, mat4 }; + + var names = new[] { "mat1", "dense_matrix_2", "s1", "sparse2" }; + if (File.Exists("test.mat")) + { + File.Delete("test.mat"); + } + + var writer = new MatlabMatrixWriter("test.mat"); + writer.WriteMatrices(write, names); + writer.Dispose(); + + var reader = new MatlabMatrixReader("test.mat"); + var read = reader.ReadMatrices(names); + + Assert.AreEqual(write.Length, read.Count); + + for (var i = 0; i < write.Length; i++ ) + { + var w = write[i]; + var r = read[names[i]]; + + Assert.AreEqual(w.RowCount, r.RowCount); + Assert.AreEqual(w.ColumnCount, r.ColumnCount); + Assert.IsTrue(w.Equals(r)); + } + } + + [Test] + public void WriteMatrix_ThrowsArgumentException() + { + Matrix matrix = new DenseMatrix(1, 1); + var writer = new MatlabMatrixWriter("somefile1"); + Assert.Throws(() => writer.WriteMatrix(matrix, string.Empty)); + Assert.Throws(() => writer.WriteMatrix(matrix, null)); + writer.Dispose(); + } + + [Test] + public void WriteMatrix_ThrowsArgumentNullException() + { + var writer = new MatlabMatrixWriter("somefile2"); + Assert.Throws(() => writer.WriteMatrix(null, "matrix")); + writer.Dispose(); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex32/IO/MatlabWriterTests.cs b/src/UnitTests/LinearAlgebraTests/Complex32/IO/MatlabWriterTests.cs new file mode 100644 index 00000000..e8b2207b --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex32/IO/MatlabWriterTests.cs @@ -0,0 +1,116 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.IO +{ + using System; + using System.IO; + using LinearAlgebra.Complex32; + using LinearAlgebra.Complex32.IO; + using LinearAlgebra.IO; + using MbUnit.Framework; + using Numerics; + + [TestFixture] + public class MatlabMatrixWriterTests + { + [Test] + public void Constructor_ThrowsArgumentException() + { + Assert.Throws(() => new MatlabMatrixWriter(string.Empty)); + Assert.Throws(() => new MatlabMatrixWriter(null)); + } + + [Test] + public void WriteMatrices_ThrowsArgumentException() + { + Matrix matrix = new DenseMatrix(1, 1); + var writer = new MatlabMatrixWriter("somefile3"); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, new[] { string.Empty })); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, new string[] { null })); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix, matrix }, new[] { "matrix" })); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, new[] { "some matrix" })); + writer.Dispose(); + } + + [Test] + public void WriteMatrices_ThrowsArgumentNullException() + { + var writer = new MatlabMatrixWriter("somefile4"); + Assert.Throws(() => writer.WriteMatrices(new Matrix[] { null }, new[] { "matrix" })); + Matrix matrix = new DenseMatrix(1, 1); + Assert.Throws(() => writer.WriteMatrices(new[] { matrix }, null)); + writer.Dispose(); + } + + [Test] + public void WriteMatricesTest() + { + Matrix mat1 = new DenseMatrix(5, 3); + for (var i = 0; i < mat1.ColumnCount; i++) + { + mat1[i, i] = new Complex32(i + .1f, i + .1f); + } + + Matrix mat2 = new DenseMatrix(4, 5); + for (var i = 0; i < mat2.RowCount; i++) + { + mat2[i, i] = new Complex32(i + .1f, i + .1f); + } + + Matrix mat3 = new SparseMatrix(5, 4); + for (var i = 0; i < mat3.ColumnCount; i++) + { + mat3[i, i] = new Complex32(i + .1f, i + .1f); + } + + Matrix mat4 = new SparseMatrix(3, 5); + for (var i = 0; i < mat4.RowCount; i++) + { + mat4[i, i] = new Complex32(i + .1f, i + .1f); + } + + var write = new[] { mat1, mat2, mat3, mat4 }; + + var names = new[] { "mat1", "dense_matrix_2", "s1", "sparse2" }; + if (File.Exists("test.mat")) + { + File.Delete("test.mat"); + } + + var writer = new MatlabMatrixWriter("test.mat"); + writer.WriteMatrices(write, names); + writer.Dispose(); + + var reader = new MatlabMatrixReader("test.mat"); + var read = reader.ReadMatrices(names); + + Assert.AreEqual(write.Length, read.Count); + + for (var i = 0; i < write.Length; i++ ) + { + var w = write[i]; + var r = read[names[i]]; + + Assert.AreEqual(w.RowCount, r.RowCount); + Assert.AreEqual(w.ColumnCount, r.ColumnCount); + Assert.IsTrue(w.Equals(r)); + } + } + + [Test] + public void WriteMatrix_ThrowsArgumentException() + { + Matrix matrix = new DenseMatrix(1, 1); + var writer = new MatlabMatrixWriter("somefile1"); + Assert.Throws(() => writer.WriteMatrix(matrix, string.Empty)); + Assert.Throws(() => writer.WriteMatrix(matrix, null)); + writer.Dispose(); + } + + [Test] + public void WriteMatrix_ThrowsArgumentNullException() + { + var writer = new MatlabMatrixWriter("somefile2"); + Assert.Throws(() => writer.WriteMatrix(null, "matrix")); + writer.Dispose(); + } + } +} diff --git a/src/UnitTests/StatisticsTests/PercentileTests.cs b/src/UnitTests/StatisticsTests/PercentileTests.cs new file mode 100644 index 00000000..e964f09f --- /dev/null +++ b/src/UnitTests/StatisticsTests/PercentileTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Gallio.Framework; +using MbUnit.Framework; +using MbUnit.Framework.ContractVerifiers; + +namespace MathNet.Numerics.UnitTests.StatisticsTests +{ + using Statistics; + + [TestFixture] + public class PercentileTests + { + + private static readonly double[] _data = { + 95.1772, + 95.1567, + 95.1937, + 95.1959, + 95.1442, + 95.061, + 95.1591, + 95.1195, + 95.1065, + 95.0925, + 95.199, + 95.1682 + }; + + + [Test] + public void CanComputePercentileUsingNistMethod() + { + var percentile = new Percentile(_data); + percentile.Method = PercentileMethod.Nist; + Assert.AreEqual(95.19807, percentile.Compute(.9)); + } + + [Test] + public void CanComputePercentileUsingExcelMethod() + { + var percentile = new Percentile(_data); + percentile.Method = PercentileMethod.Excel; + Assert.AreEqual(95.19568, percentile.Compute(.9)); + } + + + [Test] + public void CanComputePercentileUsingNearestMethod() + { + var percentile = new Percentile(_data); + percentile.Method = PercentileMethod.Nearest; + Assert.AreEqual(95.1959, percentile.Compute(.9)); + } + + + [Test] + public void CanComputePercentileUsingInterpolationMethod() + { + var data = new double[] { 1, 2, 3, 4, 5 }; + var percentile = new Percentile(data); + percentile.Method = PercentileMethod.Interpolation; + var values = new[] { .25, .5, .75 }; + var percentiles = percentile.Compute(values); + Assert.AreEqual(1.75, percentiles[0]); + Assert.AreEqual(3.0, percentiles[1]); + Assert.AreEqual(4.25, percentiles[2]); + } + + [Test] + public void SmallDataSetThrowArgumentException() + { + var data = new double[] { }; + Assert.Throws(() => new Percentile(data)); + data = new double[] { 1 }; + Assert.Throws(() => new Percentile(data)); + data = new double[] { 1, 2 }; + Assert.Throws(() => new Percentile(data)); + } + + [Test] + public void InvalidPercentileValuesThrowArgumentExecption() + { + var percentile = new Percentile(_data); + Assert.Throws(() => percentile.Compute(-0.1)); + Assert.Throws(() => percentile.Compute(100.1)); + } + } +} diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 8e512ca5..4fa6dcbf 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -362,6 +362,7 @@ +