// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2018 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. // using System; using System.Collections.Generic; namespace IStation.Numerics.LinearRegression { public static class SimpleRegression { /// /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, /// returning its best fitting parameters as (a, b) tuple, /// where a is the intercept and b the slope. /// /// Predictor (independent) /// Response (dependent) public static Tuple Fit(double[] x, double[] y) { if (x.Length != y.Length) { throw new ArgumentException($"All sample vectors must have the same length. However, vectors with disagreeing length {x.Length} and {y.Length} have been provided. A sample with index i is given by the value at index i of each provided vector."); } if (x.Length <= 1) { throw new ArgumentException($"A regression of the requested order requires at least {2} samples. Only {x.Length} samples have been provided."); } // First Pass: Mean (Less robust but faster than ArrayStatistics.Mean) double mx = 0.0; double my = 0.0; for (int i = 0; i < x.Length; i++) { mx += x[i]; my += y[i]; } mx /= x.Length; my /= y.Length; // Second Pass: Covariance/Variance double covariance = 0.0; double variance = 0.0; for (int i = 0; i < x.Length; i++) { double diff = x[i] - mx; covariance += diff*(y[i] - my); variance += diff*diff; } var b = covariance/variance; return new Tuple(my - b*mx, b); } /// /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, /// returning its best fitting parameters as (a, b) tuple, /// where a is the intercept and b the slope. /// /// Predictor-Response samples as tuples public static Tuple Fit(IEnumerable> samples) { var xy = samples.UnpackSinglePass(); return Fit(xy.Item1, xy.Item2); } /// /// Least-Squares fitting the points (x,y) to a line y : x -> b*x, /// returning its best fitting parameter b, /// where the intercept is zero and b the slope. /// /// Predictor (independent) /// Response (dependent) public static double FitThroughOrigin(double[] x, double[] y) { if (x.Length != y.Length) { throw new ArgumentException($"All sample vectors must have the same length. However, vectors with disagreeing length {x.Length} and {y.Length} have been provided. A sample with index i is given by the value at index i of each provided vector."); } if (x.Length <= 1) { throw new ArgumentException($"A regression of the requested order requires at least {2} samples. Only {x.Length} samples have been provided."); } double mxy = 0.0; double mxx = 0.0; for (int i = 0; i < x.Length; i++) { mxx += x[i]*x[i]; mxy += x[i]*y[i]; } return mxy / mxx; } /// /// Least-Squares fitting the points (x,y) to a line y : x -> b*x, /// returning its best fitting parameter b, /// where the intercept is zero and b the slope. /// /// Predictor-Response samples as tuples public static double FitThroughOrigin(IEnumerable> samples) { double mxy = 0.0; double mxx = 0.0; foreach (var sample in samples) { mxx += sample.Item1 * sample.Item1; mxy += sample.Item1 * sample.Item2; } return mxy / mxx; } } }