From a7acebfe3ab2cbdb3f1cc4efc1c8cfbaf91240ff Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Wed, 13 Aug 2014 01:26:27 +0200 Subject: [PATCH] Statistics: RootMeanSquare (RMS) --- src/Numerics/Distance.cs | 11 ++++--- src/Numerics/Statistics/ArrayStatistics.cs | 24 +++++++++++++- src/Numerics/Statistics/Statistics.cs | 26 ++++++++++++++- .../Statistics/StreamingStatistics.cs | 22 ++++++++++++- .../StatisticsTests/StatisticsTests.cs | 33 ++++++++++++++++++- 5 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/Numerics/Distance.cs b/src/Numerics/Distance.cs index 05ddd6ff..2456e988 100644 --- a/src/Numerics/Distance.cs +++ b/src/Numerics/Distance.cs @@ -3,9 +3,9 @@ // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com -// +// // Copyright (c) 2009-2013 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 @@ -14,10 +14,10 @@ // 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 @@ -36,6 +36,9 @@ using MathNet.Numerics.Statistics; namespace MathNet.Numerics { + /// + /// Metrics to measure the distance between two structures. + /// public static class Distance { /// diff --git a/src/Numerics/Statistics/ArrayStatistics.cs b/src/Numerics/Statistics/ArrayStatistics.cs index 5b883953..55bd3e52 100644 --- a/src/Numerics/Statistics/ArrayStatistics.cs +++ b/src/Numerics/Statistics/ArrayStatistics.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -317,6 +317,28 @@ namespace MathNet.Numerics.Statistics return covariance/population1.Length; } + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double RootMeanSquare(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i]*data[i] - mean)/++m; + } + + return Math.Sqrt(mean); + } + /// /// Returns the order statistic (order 1..N) from the unsorted data array. /// WARNING: Works inplace and can thus causes the data array to be reordered. diff --git a/src/Numerics/Statistics/Statistics.cs b/src/Numerics/Statistics/Statistics.cs index 945ad582..3fe30641 100644 --- a/src/Numerics/Statistics/Statistics.cs +++ b/src/Numerics/Statistics/Statistics.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -423,6 +423,30 @@ namespace MathNet.Numerics.Statistics return StreamingStatistics.PopulationCovariance(population1.Where(d => d.HasValue).Select(d => d.Value), population2.Where(d => d.HasValue).Select(d => d.Value)); } + /// + /// Evaluates the root mean square (RMS) also known as quadratic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the RMS of. + public static double RootMeanSquare(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.RootMeanSquare(array) + : StreamingStatistics.RootMeanSquare(data); + } + + /// + /// Evaluates the root mean square (RMS) also known as quadratic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The data to calculate the mean of. + public static double RootMeanSquare(this IEnumerable data) + { + return StreamingStatistics.RootMeanSquare(data.Where(d => d.HasValue).Select(d => d.Value)); + } + /// /// Estimates the sample median from the provided samples (R8). /// diff --git a/src/Numerics/Statistics/StreamingStatistics.cs b/src/Numerics/Statistics/StreamingStatistics.cs index 4c593740..ae9a2886 100644 --- a/src/Numerics/Statistics/StreamingStatistics.cs +++ b/src/Numerics/Statistics/StreamingStatistics.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -328,6 +328,26 @@ namespace MathNet.Numerics.Statistics return comoment/n; } + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double RootMeanSquare(IEnumerable stream) + { + double mean = 0; + ulong m = 0; + bool any = false; + + foreach (var d in stream) + { + mean += (d*d - mean)/++m; + any = true; + } + + return any ? Math.Sqrt(mean) : double.NaN; + } + /// /// Calculates the entropy of a stream of double values. /// Returns NaN if any of the values in the stream are NaN. diff --git a/src/UnitTests/StatisticsTests/StatisticsTests.cs b/src/UnitTests/StatisticsTests/StatisticsTests.cs index 39b5a92d..92748ea2 100644 --- a/src/UnitTests/StatisticsTests/StatisticsTests.cs +++ b/src/UnitTests/StatisticsTests/StatisticsTests.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -78,6 +78,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(() => Statistics.PopulationStandardDeviation(data), Throws.Exception); Assert.That(() => Statistics.Covariance(data, data), Throws.Exception); Assert.That(() => Statistics.PopulationCovariance(data, data), Throws.Exception); + Assert.That(() => Statistics.RootMeanSquare(data), Throws.Exception); Assert.That(() => SortedArrayStatistics.Minimum(data), Throws.Exception.TypeOf()); Assert.That(() => SortedArrayStatistics.Minimum(data), Throws.Exception.TypeOf()); @@ -103,6 +104,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(() => ArrayStatistics.PopulationStandardDeviation(data), Throws.Exception.TypeOf()); Assert.That(() => ArrayStatistics.Covariance(data, data), Throws.Exception.TypeOf()); Assert.That(() => ArrayStatistics.PopulationCovariance(data, data), Throws.Exception.TypeOf()); + Assert.That(() => ArrayStatistics.RootMeanSquare(data), Throws.Exception.TypeOf()); Assert.That(() => ArrayStatistics.MedianInplace(data), Throws.Exception.TypeOf()); Assert.That(() => ArrayStatistics.QuantileInplace(data, 0.3), Throws.Exception.TypeOf()); @@ -115,6 +117,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(() => StreamingStatistics.PopulationStandardDeviation(data), Throws.Exception.TypeOf()); Assert.That(() => StreamingStatistics.Covariance(data, data), Throws.Exception.TypeOf()); Assert.That(() => StreamingStatistics.PopulationCovariance(data, data), Throws.Exception.TypeOf()); + Assert.That(() => StreamingStatistics.RootMeanSquare(data), Throws.Exception.TypeOf()); Assert.That(() => StreamingStatistics.Entropy(data), Throws.Exception.TypeOf()); Assert.That(() => new RunningStatistics(data), Throws.Exception); @@ -138,6 +141,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.DoesNotThrow(() => Statistics.PopulationStandardDeviation(data)); Assert.DoesNotThrow(() => Statistics.Covariance(data, data)); Assert.DoesNotThrow(() => Statistics.PopulationCovariance(data, data)); + Assert.DoesNotThrow(() => Statistics.RootMeanSquare(data)); Assert.DoesNotThrow(() => SortedArrayStatistics.Minimum(data)); Assert.DoesNotThrow(() => SortedArrayStatistics.Maximum(data)); @@ -162,6 +166,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.DoesNotThrow(() => ArrayStatistics.PopulationStandardDeviation(data)); Assert.DoesNotThrow(() => ArrayStatistics.Covariance(data, data)); Assert.DoesNotThrow(() => ArrayStatistics.PopulationCovariance(data, data)); + Assert.DoesNotThrow(() => ArrayStatistics.RootMeanSquare(data)); Assert.DoesNotThrow(() => ArrayStatistics.MedianInplace(data)); Assert.DoesNotThrow(() => ArrayStatistics.QuantileInplace(data, 0.3)); @@ -174,6 +179,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.DoesNotThrow(() => StreamingStatistics.PopulationStandardDeviation(data)); Assert.DoesNotThrow(() => StreamingStatistics.Covariance(data, data)); Assert.DoesNotThrow(() => StreamingStatistics.PopulationCovariance(data, data)); + Assert.DoesNotThrow(() => StreamingStatistics.RootMeanSquare(data)); Assert.DoesNotThrow(() => StreamingStatistics.Entropy(data)); Assert.That(() => new RunningStatistics(data), Throws.Nothing); @@ -780,14 +786,17 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests AssertHelpers.AlmostEqualRelative(1e+9, Statistics.Mean(gaussian.Samples().Take(10000)), 10); AssertHelpers.AlmostEqualRelative(4d, Statistics.Variance(gaussian.Samples().Take(10000)), 0); AssertHelpers.AlmostEqualRelative(2d, Statistics.StandardDeviation(gaussian.Samples().Take(10000)), 1); + AssertHelpers.AlmostEqualRelative(1e+9, Statistics.RootMeanSquare(gaussian.Samples().Take(10000)), 10); AssertHelpers.AlmostEqualRelative(1e+9, ArrayStatistics.Mean(gaussian.Samples().Take(10000).ToArray()), 10); AssertHelpers.AlmostEqualRelative(4d, ArrayStatistics.Variance(gaussian.Samples().Take(10000).ToArray()), 0); AssertHelpers.AlmostEqualRelative(2d, ArrayStatistics.StandardDeviation(gaussian.Samples().Take(10000).ToArray()), 1); + AssertHelpers.AlmostEqualRelative(1e+9, ArrayStatistics.RootMeanSquare(gaussian.Samples().Take(10000).ToArray()), 10); AssertHelpers.AlmostEqualRelative(1e+9, StreamingStatistics.Mean(gaussian.Samples().Take(10000)), 10); AssertHelpers.AlmostEqualRelative(4d, StreamingStatistics.Variance(gaussian.Samples().Take(10000)), 0); AssertHelpers.AlmostEqualRelative(2d, StreamingStatistics.StandardDeviation(gaussian.Samples().Take(10000)), 1); + AssertHelpers.AlmostEqualRelative(1e+9, StreamingStatistics.RootMeanSquare(gaussian.Samples().Take(10000)), 10); AssertHelpers.AlmostEqualRelative(1e+9, new RunningStatistics(gaussian.Samples().Take(10000)).Mean, 10); AssertHelpers.AlmostEqualRelative(4d, new RunningStatistics(gaussian.Samples().Take(10000)).Variance, 0); @@ -852,6 +861,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(ArrayStatistics.PopulationStandardDeviation(data.Data), Is.EqualTo(StreamingStatistics.PopulationStandardDeviation(data.Data)).Within(1e-15), "PopulationStandardDeviation"); Assert.That(ArrayStatistics.Covariance(data.Data, data.Data), Is.EqualTo(StreamingStatistics.Covariance(data.Data, data.Data)).Within(1e-10), "Covariance"); Assert.That(ArrayStatistics.PopulationCovariance(data.Data, data.Data), Is.EqualTo(StreamingStatistics.PopulationCovariance(data.Data, data.Data)).Within(1e-10), "PopulationCovariance"); + Assert.That(ArrayStatistics.RootMeanSquare(data.Data), Is.EqualTo(StreamingStatistics.RootMeanSquare(data.Data)).Within(1e-15), "RootMeanSquare"); } [TestCase("lottery")] @@ -918,6 +928,17 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(new RunningStatistics(new[] { 2d }).Mean, Is.Not.NaN); } + [Test] + public void RootMeanSquareOfEmptyMustBeNaN() + { + Assert.That(Statistics.RootMeanSquare(new double[0]), Is.NaN); + Assert.That(Statistics.RootMeanSquare(new[] { 2d }), Is.Not.NaN); + Assert.That(ArrayStatistics.RootMeanSquare(new double[0]), Is.NaN); + Assert.That(ArrayStatistics.RootMeanSquare(new[] { 2d }), Is.Not.NaN); + Assert.That(StreamingStatistics.RootMeanSquare(new double[0]), Is.NaN); + Assert.That(StreamingStatistics.RootMeanSquare(new[] { 2d }), Is.Not.NaN); + } + [Test] public void SampleVarianceOfEmptyAndSingleMustBeNaN() { @@ -1005,6 +1026,9 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(new DescriptiveStatistics(shorter).Mean, Is.EqualTo(0.375).Within(1e-14), "DescriptiveStatistics.Mean: shorter"); Assert.That(new DescriptiveStatistics(longer).Mean, Is.EqualTo(00.375).Within(1e-14), "DescriptiveStatistics.Mean: longer"); + Assert.That(Statistics.RootMeanSquare(shorter), Is.EqualTo(Math.Sqrt(0.21875)).Within(1e-14), "Statistics.RootMeanSquare: shorter"); + Assert.That(Statistics.RootMeanSquare(longer), Is.EqualTo(Math.Sqrt(0.21875)).Within(1e-14), "Statistics.RootMeanSquare: longer"); + Assert.That(Statistics.Skewness(shorter), Is.EqualTo(0.0).Within(1e-12), "Statistics.Skewness: shorter"); Assert.That(Statistics.Skewness(longer), Is.EqualTo(0.0).Within(1e-12), "Statistics.Skewness: longer"); Assert.That(new DescriptiveStatistics(shorter).Skewness, Is.EqualTo(0.0).Within(1e-12), "DescriptiveStatistics.Skewness: shorter"); @@ -1016,6 +1040,13 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests Assert.That(new DescriptiveStatistics(longer).Kurtosis, Is.EqualTo(-1.36).Within(1e-4), "DescriptiveStatistics.Kurtosis: longer"); } + [Test] + public void RootMeanSquareOfSinusoidal() + { + var data = Generate.Sinusoidal(128, 64, 16, 2.0); + Assert.That(Statistics.RootMeanSquare(data), Is.EqualTo(2.0/Constants.Sqrt2).Within(1e-12)); + } + [Test] public void EntropyIsMinimum() {