From adf8345089d47df531c4220435d06efe0b69c422 Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Sat, 3 Feb 2018 14:54:26 +0100 Subject: [PATCH] Regression: Fit.LineThroughOrigin --- src/FSharp/Fit.fs | 8 +++ src/Numerics/Fit.cs | 22 +++++++- .../LinearRegression/SimpleRegression.cs | 53 ++++++++++++++++++- src/UnitTests/FitTests.cs | 26 ++++++++- 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/FSharp/Fit.fs b/src/FSharp/Fit.fs index 9aec4a93..40450cce 100644 --- a/src/FSharp/Fit.fs +++ b/src/FSharp/Fit.fs @@ -47,6 +47,14 @@ module Fit = /// returning a function y' for the best fitting line. let lineFunc x y = Fit.LineFunc(x,y) |> tofs + /// Least-Squares fitting the points (x,y) to a line y : x -> b*x, + /// returning its best fitting parameter b. + let lineThroughOrigin x y = Fit.LineThroughOrigin(x,y) + + /// Least-Squares fitting the points (x,y) to a line y : x -> b*x, + /// returning a function y' for the best fitting line. + let lineThroughOriginFunc x y = Fit.LineThroughOriginFunc(x,y) |> tofs + /// Least-Squares fitting the points ((x0,x1,...,xk),y) to a linear surface y : X -> p0*x0 + p1*x1 + ... + pk*xk, /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. let multiDim intercept x y = Fit.MultiDim(x,y,intercept) diff --git a/src/Numerics/Fit.cs b/src/Numerics/Fit.cs index a07b668c..28805816 100644 --- a/src/Numerics/Fit.cs +++ b/src/Numerics/Fit.cs @@ -3,7 +3,7 @@ // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // -// Copyright (c) 2009-2015 Math.NET +// 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 @@ -61,6 +61,26 @@ namespace MathNet.Numerics return z => intercept + slope*z; } + /// + /// Least-Squares fitting the points (x,y) to a line through origin y : x -> b*x, + /// returning its best fitting parameter b, + /// where the intercept is zero and b the slope. + /// + public static double LineThroughOrigin(double[] x, double[] y) + { + return SimpleRegression.FitThroughOrigin(x, y); + } + + /// + /// Least-Squares fitting the points (x,y) to a line through origin y : x -> b*x, + /// returning a function y' for the best fitting line. + /// + public static Func LineThroughOriginFunc(double[] x, double[] y) + { + double slope = SimpleRegression.FitThroughOrigin(x, y); + return z => slope * z; + } + /// /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to a linear surface y : X -> p0*x0 + p1*x1 + ... + pk*xk, /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. diff --git a/src/Numerics/LinearRegression/SimpleRegression.cs b/src/Numerics/LinearRegression/SimpleRegression.cs index f3c6d162..16c72e48 100644 --- a/src/Numerics/LinearRegression/SimpleRegression.cs +++ b/src/Numerics/LinearRegression/SimpleRegression.cs @@ -3,7 +3,7 @@ // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // -// Copyright (c) 2009-2013 Math.NET +// 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 @@ -91,5 +91,56 @@ namespace MathNet.Numerics.LinearRegression 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(string.Format(Resources.SampleVectorsSameLength, x.Length, y.Length)); + } + + if (x.Length <= 1) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, 2, x.Length)); + } + + 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; + } } } diff --git a/src/UnitTests/FitTests.cs b/src/UnitTests/FitTests.cs index e365a813..0a1b7808 100644 --- a/src/UnitTests/FitTests.cs +++ b/src/UnitTests/FitTests.cs @@ -3,7 +3,7 @@ // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // -// Copyright (c) 2009-2016 Math.NET +// 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 @@ -29,6 +29,8 @@ using System; using System.Linq; + +using MathNet.Numerics.LinearRegression; using MathNet.Numerics.Statistics; using NUnit.Framework; @@ -74,6 +76,28 @@ namespace MathNet.Numerics.UnitTests } } + [Test] + public void FitsToBestLineThroughOrigin() + { + // Mathematica: Fit[{{1,4.986},{2,2.347},{3,2.061},{4,-2.995},{5,-2.352},{6,-5.782}}, {x}, x] + // -> -0.467791 x + + var x = Enumerable.Range(1, 6).Select(Convert.ToDouble).ToArray(); + var y = new[] { 4.986, 2.347, 2.061, -2.995, -2.352, -5.782 }; + + var resp = Fit.LineThroughOrigin(x, y); + Assert.AreEqual(-0.467791, resp, 1e-4); + + var resf = Fit.LineThroughOriginFunc(x, y); + foreach (var z in Enumerable.Range(-3, 10)) + { + Assert.AreEqual(-0.467791 * z, resf(z), 1e-4); + } + + var respSeq = SimpleRegression.FitThroughOrigin(Generate.Map2(x, y, Tuple.Create)); + Assert.AreEqual(-0.467791, respSeq, 1e-4); + } + [Test] public void FitsToMeanOnOrder0Polynomial() {