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
text/typescript
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;