#region License Information /* HeuristicLab * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using HeuristicLab.Common; namespace HeuristicLab.Problems.DataAnalysis { public class OnlinePearsonsRCalculator : DeepCloneable, IOnlineCalculator { private double sumX; private double sumY; private double sumWe; private double sumXX; private double sumYY; private double sumXY; private OnlineCalculatorError errorState; public double R { get { if (!(sumXX > 0.0 && sumYY > 0.0)) { return (sumXX == sumYY) ? 1.0 : 0.0; } return sumXY / Math.Sqrt(sumXX * sumYY); } } public double MeanX { get { return sumX / sumWe; } } public double MeanY { get { return sumY / sumWe; } } public double NaiveCovariance { get { return sumXY / sumWe; } } public double SampleCovariance { get { if (sumWe > 1.0) { errorState = OnlineCalculatorError.None; return sumXY / (sumWe - 1); } errorState = OnlineCalculatorError.InsufficientElementsAdded; return double.NaN; } } public double NaiveVarianceX { get { return sumXX / sumWe; } } public double SampleVarianceX { get { if (sumWe > 1.0) { errorState = OnlineCalculatorError.None; return sumXX / (sumWe - 1); } errorState = OnlineCalculatorError.InsufficientElementsAdded; return double.NaN; } } public double NaiveStdevX { get { return Math.Sqrt(NaiveVarianceY); } } public double SampleStdevX { get { return Math.Sqrt(SampleVarianceX); } } public double NaiveVarianceY { get { return sumYY / sumWe; } } public double SampleVarianceY { get { if (sumWe > 1.0) { errorState = OnlineCalculatorError.None; return sumYY / (sumWe - 1); } errorState = OnlineCalculatorError.InsufficientElementsAdded; return double.NaN; } } public double NaiveStdevY { get { return Math.Sqrt(NaiveVarianceY); } } public double SampleStdevY { get { return Math.Sqrt(SampleVarianceX); } } public OnlinePearsonsRCalculator() { } protected OnlinePearsonsRCalculator(OnlinePearsonsRCalculator original, Cloner cloner) : base(original, cloner) { sumX = original.sumX; sumY = original.sumY; sumXX = original.sumXX; sumYY = original.sumYY; sumXY = original.sumXY; sumWe = original.sumWe; errorState = original.ErrorState; } public override IDeepCloneable Clone(Cloner cloner) { return new OnlinePearsonsRCalculator(this, cloner); } #region IOnlineCalculator Members public OnlineCalculatorError ErrorState { get { return errorState; } } public double Value { get { return R; } } public void Reset() { sumXX = sumYY = sumXY = sumX = sumY = sumWe = 0.0; errorState = OnlineCalculatorError.None; } public void Add(double x, double y) { if (sumWe <= 0.0) { sumX = x; sumY = y; sumWe = 1; return; } // Delta to previous mean double deltaX = x * sumWe - sumX, deltaY = y * sumWe - sumY; double oldWe = sumWe; // Incremental update sumWe += 1; double f = 1.0 / (sumWe * oldWe); // Update sumXX += f * deltaX * deltaX; sumYY += f * deltaY * deltaY; // should equal weight * deltaY * neltaX! sumXY += f * deltaX * deltaY; // Update means sumX += x; sumY += y; } #endregion public static double Calculate(IEnumerable first, IEnumerable second, out OnlineCalculatorError errorState) { var x = first.GetEnumerator(); x.MoveNext(); var y = second.GetEnumerator(); y.MoveNext(); double sumXX = 0.0, sumYY = 0.0, sumXY = 0.0; double sumX = x.Current, sumY = y.Current; int i = 1; // Inlined computation of Pearson correlation, to avoid allocating objects // This is a numerically stabilized version, avoiding sum-of-squares. while (x.MoveNext() & y.MoveNext()) { double xv = x.Current, yv = y.Current; // Delta to previous mean double deltaX = xv * i - sumX, deltaY = yv * i - sumY; // Increment count first double oldi = i; // Convert to double! ++i; double f = 1.0 / (i * oldi); // Update sumXX += f * deltaX * deltaX; sumYY += f * deltaY * deltaY; // should equal deltaY * deltaX! sumXY += f * deltaX * deltaY; // Update sums sumX += xv; sumY += yv; } errorState = OnlineCalculatorError.None; // One or both series were constant: return !(sumXX > 0.0 && sumYY > 0.0) ? sumXX == sumYY ? 1.0 : 0.0 : // sumXY / Math.Sqrt(sumXX * sumYY); } } }