UNPKG

linear-least-squares

Version:

Finds the best curve to fit a set of points through minimizing the sum of the squares of the offset of each point from the curve.

125 lines (94 loc) 3.79 kB
class LinearLeastSquares { _points: Array<[number, number]>; _size: number; _sum: [number, number]; _sum_of_squares: [number, number]; _mean: [number, number]; _total_sum_of_squares: number; _sum_of_squares_of_residuals: number; _root_mean_squares_error: number; _r_squared: number; /** * @param points Array<[number, number]>. */ constructor(points: Array<[number, number]>) { this._points = points; this._size = this._points.length; this._sum = [0, 0]; this._sum_of_squares = [0, 0]; this._mean = [0, 0]; } process_points(): void { this._sum = [0, 0]; this._sum_of_squares = [0, 0]; this._mean = [0, 0]; for (let point of this._points) { this._sum[0] = this._sum[0] + point[0]; this._sum[1] = this._sum[1] + point[1]; this._sum_of_squares[0] = this._sum_of_squares[0] + Math.pow(point[0], 2); this._sum_of_squares[1] = this._sum_of_squares[1] + (point[0] * point[1]); } this._mean[0] = this._sum[0] / this._size; this._mean[1] = this._sum[1] / this._size; } slope(): number { let rise: number = (this._size * this._sum_of_squares[1]) - (this._sum[0] * this._sum[1]); let run: number = (this._size * this._sum_of_squares[0]) - Math.pow(this._sum[0], 2); return rise / run; } y_intercept(): number { return (this._mean[1]) - (this.slope() * this._mean[0]); } /** * @param m number that represents the slop. * @param x the x-value you want to predict the y-value for. * @param b number that represnets the y-intercept. */ predicted_point(m: number, x: number, b: number): [number, number] { return [x, (x * m) + b]; } /** * @param m number that represents the slop. * @param b number that represnets the y-intercept. */ evaluate_fit(m: number, b: number): void { this._total_sum_of_squares = 0; this._sum_of_squares_of_residuals = 0; this._root_mean_squares_error = 0; for (let point of this._points) { let predicted_y: number = this.predicted_point(m, point[0], b)[1]; let y_offset: number = point[1] - this._mean[1]; let residual: number = point[1] - predicted_y; this._sum_of_squares_of_residuals = this._sum_of_squares_of_residuals + Math.pow(residual, 2); this._total_sum_of_squares = this._total_sum_of_squares + Math.pow(y_offset, 2); } this._root_mean_squares_error = Math.sqrt(this._sum_of_squares_of_residuals / this._size); this._r_squared = 1 - (this._sum_of_squares_of_residuals / this._total_sum_of_squares); } compute_fit(): object { this.process_points(); let m: number = this.slope(); let b: number = this.y_intercept(); this.evaluate_fit(m, b); let fit: object = { "m": m, "b": b, "rmse": this._root_mean_squares_error, "r_squared": this._r_squared }; return fit; } /** * @param m number that represents the slop. * @param b number that represnets the y-intercept. */ predicted_points(m: number, b: number): Array<[number, number]> { let points: Array<[number, number]> = []; let max: number = Math.max(...this._points.map(point => point[0])); // for (let x of x_values.filter( // (val, i, arr) => arr.findIndex(x => x === val) === i // )) { // points.push([ x, (x * m) + b]); // } for (let x of Array.from(Array(max).keys())) { points.push([ x, (x * m) + b]); } return points; } } module.exports = LinearLeastSquares;