From 3af161b16870412650e2234de9ca885663616578 Mon Sep 17 00:00:00 2001 From: Iain McDonald Date: Wed, 22 May 2013 23:31:26 +0100 Subject: [PATCH] Add Spearman Rank Correlation Coefficient More more info see https://en.wikipedia.org/wiki/Spearman_rank_correlation --- src/Examples/Statistics.cs | 6 ++- src/Numerics/Statistics/Correlation.cs | 44 ++++++++++++++++++- .../StatisticsTests/CorrelationTests.cs | 26 ++++++++++- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/Examples/Statistics.cs b/src/Examples/Statistics.cs index 9e24c5c0..77fdcd15 100644 --- a/src/Examples/Statistics.cs +++ b/src/Examples/Statistics.cs @@ -77,7 +77,7 @@ namespace Examples Console.WriteLine(@"{0} - Standard deviation", chiSquare.StdDev.ToString(" #0.00000;-#0.00000")); Console.WriteLine(@"{0} - Skewness", chiSquare.Skewness.ToString(" #0.00000;-#0.00000")); Console.WriteLine(); - + // 2. Generate 1000 samples of the ChiSquare(5) distribution Console.WriteLine(@"2. Generate 1000 samples of the ChiSquare(5) distribution"); var data = new double[1000]; @@ -120,12 +120,14 @@ namespace Examples // 5. Correlation coefficient between 1000 samples of ChiSquare(5) and ChiSquare(2.5) Console.WriteLine(@"5. Correlation coefficient between 1000 samples of ChiSquare(5) and ChiSquare(2.5) is {0}", Correlation.Pearson(data, dataB).ToString("N04")); + Console.WriteLine(@"6. Ranked correlation coefficient between 1000 samples of ChiSquare(5) and ChiSquare(2.5) is {0}", Correlation.Spearman(data, dataB).ToString("N04")); Console.WriteLine(); // 6. Correlation coefficient between 1000 samples of f(x) = x * 2 and f(x) = x * x data = SignalGenerator.EquidistantInterval(x => x * 2, 0, 100, 1000); dataB = SignalGenerator.EquidistantInterval(x => x * x, 0, 100, 1000); - Console.WriteLine(@"6. Correlation coefficient between 1000 samples of f(x) = x * 2 and f(x) = x * x is {0}", Correlation.Pearson(data, dataB).ToString("N04")); + Console.WriteLine(@"7. Correlation coefficient between 1000 samples of f(x) = x * 2 and f(x) = x * x is {0}", Correlation.Pearson(data, dataB).ToString("N04")); + Console.WriteLine(@"8. Ranked correlation coefficient between 1000 samples of f(x) = x * 2 and f(x) = x * x is {0}", Correlation.Spearman(data, dataB).ToString("N04")); Console.WriteLine(); } } diff --git a/src/Numerics/Statistics/Correlation.cs b/src/Numerics/Statistics/Correlation.cs index 67abb60a..25ba78ae 100644 --- a/src/Numerics/Statistics/Correlation.cs +++ b/src/Numerics/Statistics/Correlation.cs @@ -32,7 +32,7 @@ namespace MathNet.Numerics.Statistics { using System; using System.Collections.Generic; - using Properties; + using System.Linq; /// /// A class with correlation measures between two datasets. @@ -88,5 +88,47 @@ namespace MathNet.Numerics.Statistics return r / Math.Sqrt(varA * varB); } + + /// + /// Computes the Spearman Ranked Correlation Coefficient. + /// + /// Sample data series A. + /// Sample data series B. + /// The Spearman Ranked Correlation Coefficient. + public static double Spearman(IEnumerable dataA, IEnumerable dataB) + { + return Pearson(RankedSeries(dataA.ToList()), RankedSeries(dataB.ToList())); + } + + private static IEnumerable RankedSeries(ICollection series) + { + if (series == null || series.Count == 0) + return Enumerable.Empty(); + + var rankedSamples = series.Select((sample, index) => new { Sample = sample, RankIndex = index }).OrderBy(s => s.Sample).ToList(); + + var rankedArray = new double[series.Count]; + + var previousSample = rankedSamples.Select((sampleIndex, index) => new { SampleIndex = sampleIndex, LoopIndex = index }).First(); + foreach (var rankedSampleIndex in rankedSamples.Select((sampleIndex, index) => new { SampleIndex = sampleIndex, LoopIndex = index })) + { + var currentSample = rankedSampleIndex; + + if (Math.Abs(currentSample.SampleIndex.Sample - previousSample.SampleIndex.Sample) <= 0) + continue; + + var rankedValue = (currentSample.LoopIndex + previousSample.LoopIndex - 1) / 2d + 1; + foreach (var index in Enumerable.Range(previousSample.LoopIndex, currentSample.LoopIndex - previousSample.LoopIndex)) + rankedArray[rankedSamples[index].RankIndex] = rankedValue; + + previousSample = currentSample; + } + + var finalValue = (rankedSamples.Count + previousSample.LoopIndex - 1) / 2d + 1; + foreach (var index in Enumerable.Range(previousSample.LoopIndex, rankedSamples.Count - previousSample.LoopIndex)) + rankedArray[rankedSamples[index].RankIndex] = finalValue; + + return rankedArray; + } } } diff --git a/src/UnitTests/StatisticsTests/CorrelationTests.cs b/src/UnitTests/StatisticsTests/CorrelationTests.cs index b03ad1f3..504fed58 100644 --- a/src/UnitTests/StatisticsTests/CorrelationTests.cs +++ b/src/UnitTests/StatisticsTests/CorrelationTests.cs @@ -71,7 +71,7 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests var dataB = _data["lew"].Data.Take(200); var corr = Correlation.Pearson(dataA, dataB); - AssertHelpers.AlmostEqual(corr, -0.029470861580726, 13); + AssertHelpers.AlmostEqual(-0.029470861580726, corr, 13); } /// @@ -84,6 +84,30 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests var dataB = _data["lew"].Data; Assert.Throws(() => Correlation.Pearson(dataA, dataB)); } + + /// + /// Spearman correlation test. + /// + [Test] + public void SpearmanCorrelationTest() + { + var dataA = _data["lottery"].Data.Take(200); + var dataB = _data["lew"].Data.Take(200); + + var corr = Correlation.Spearman(dataA, dataB); + AssertHelpers.AlmostEqual(-0.0382856977898528, corr, 13); + } + + /// + /// Spearman correlation test fail. + /// + [Test] + public void SpearmanCorrelationTestFail() + { + var dataA = _data["lottery"].Data; + var dataB = _data["lew"].Data; + Assert.Throws(() => Correlation.Spearman(dataA, dataB)); + } } #endif }