123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- using System;
- using System.Linq;
- namespace ScottPlot.Statistics
- {
- public class LinearRegressionLine
- {
- public readonly double slope;
- public readonly double offset;
- public readonly double rSquared;
- private readonly int pointCount;
- private readonly double[] xs;
- private readonly double[] ys;
- public LinearRegressionLine(double[] xs, double[] ys)
- {
- if ((xs.Length != ys.Length) || (xs.Length < 2))
- {
- throw new ArgumentException("xs and ys must be the same length and have at least 2 points");
- }
- pointCount = ys.Length;
- this.xs = xs;
- this.ys = ys;
- (slope, offset, rSquared) = GetCoefficients(xs, ys);
- }
- public LinearRegressionLine(double[] ys, double firstX, double xSpacing)
- {
- // this constructor doesn't require an X array to be passed in at all
- pointCount = ys.Length;
- double[] xs = new double[pointCount];
- for (int i = 0; i < pointCount; i++)
- {
- xs[i] = firstX + xSpacing * i;
- }
- this.xs = xs;
- this.ys = ys;
- (slope, offset, rSquared) = GetCoefficients(xs, ys);
- }
- public override string ToString()
- {
- return $"Linear fit for {pointCount} points: Y = {slope}x + {offset} (R² = {rSquared})";
- }
- private static (double, double, double) GetCoefficients(double[] xs, double[] ys)
- {
- double sumXYResidual = 0;
- double sumXSquareResidual = 0;
- double meanX = xs.Average();
- double meanY = ys.Average();
- for (int i = 0; i < xs.Length; i++)
- {
- sumXYResidual += (xs[i] - meanX) * (ys[i] - meanY);
- sumXSquareResidual += (xs[i] - meanX) * (xs[i] - meanX);
- }
- // Note: least-squares regression line always passes through (x̅,y̅)
- double slope = sumXYResidual / sumXSquareResidual;
- double offset = meanY - (slope * meanX);
- // calcualte R squared (https://en.wikipedia.org/wiki/Coefficient_of_determination)
- double ssTot = 0;
- double ssRes = 0;
- for (int i = 0; i < ys.Length; i++)
- {
- double thisY = ys[i];
- double distanceFromMeanSquared = Math.Pow(thisY - meanY, 2);
- ssTot += distanceFromMeanSquared;
- double modelY = slope * xs[i] + offset;
- double distanceFromModelSquared = Math.Pow(thisY - modelY, 2);
- ssRes += distanceFromModelSquared;
- }
- double rSquared = 1 - ssRes / ssTot;
- return (slope, offset, rSquared);
- }
- public double GetValueAt(double x)
- {
- return offset + slope * x;
- }
- public double[] GetValues()
- {
- double[] values = new double[pointCount];
- for (int i = 0; i < pointCount; i++)
- {
- values[i] = GetValueAt(xs[i]);
- }
- return values;
- }
- public double[] GetResiduals()
- {
- // the residual is the difference between the actual and predicted value
- double[] residuals = new double[ys.Length];
- for (int i = 0; i < ys.Length; i++)
- {
- residuals[i] = ys[i] - GetValueAt(xs[i]);
- }
- return residuals;
- }
- }
- }
|