UNPKG

als-statistics

Version:

A powerful and lightweight JavaScript library for descriptive statistics, regression, clustering, outlier detection, and noise analysis using a flexible table/column architecture.

106 lines (90 loc) 4.17 kB
const { tCDF } = require('../cdf'); const MatrixUtils = require('./matrix-utils'); class LinearRegression { constructor(table, yName, xNames = []) { this.table = table; this.yName = yName; this.xNames = xNames this._originalX = xNames.length > 0 ? xNames : Object.keys(table.columns).filter(k => k !== yName); this._mediator = null; this._moderator = null; this._interactionName = null; } mediator(name) { this._mediator = name; return this } moderator(name) { this._moderator = name; return this } calculate() { const fullCols = { ...this.table.columns }; this.xNames = [...this._originalX]; if (this._mediator && !this.xNames.includes(this._mediator)) this.xNames.push(this._mediator); // Медиатор if (this._moderator) { const x = this.table.columns[this._originalX[0]].values; const z = this.table.columns[this._moderator].values; this._interactionName = `${this._originalX[0]}*${this._moderator}`; fullCols[this._interactionName] = { values: x.map((xi, i) => xi * z[i]) }; if (!this.xNames.includes(this._moderator)) this.xNames.push(this._moderator); this.xNames.push(this._interactionName); } this.y = this.table.columns[this.yName].values; this.X = fullCols[this.xNames[0]].values.map((_, i) => [1, ...this.xNames.map(name => fullCols[name].values[i])]); this.n = this.X.length; this.k = this.X[0].length; this.coefficients = this.computeCoefficients(); if (this.coefficients.some(c => !isFinite(c))) throw new Error("Regression failed: singular matrix or constant predictors"); this.yHat = this.predict(this.X); this.residuals = this.y.map((yi, i) => yi - this.yHat[i]); this.r2 = this.computeR2(); this.standardErrors = this.computeStandardErrors(); this.pValues = this.computePValues(); return this; } computeCoefficients() { const XT = MatrixUtils.transpose(this.X); const XTX = MatrixUtils.multiply(XT, this.X); const XTy = MatrixUtils.multiplyVec(XT, this.y); return MatrixUtils.solve ? MatrixUtils.solve(XTX, XTy) : MatrixUtils.multiplyVec(MatrixUtils.inverse(XTX), XTy); } predict(X) { return X.map(row => row.reduce((acc, val, j) => acc + val * this.coefficients[j], 0)) } computeR2() { const yMean = this.y.reduce((a, b) => a + b, 0) / this.n; const ssTot = this.y.reduce((acc, yi) => acc + (yi - yMean) ** 2, 0); const ssRes = this.residuals.reduce((acc, e) => acc + e ** 2, 0); return 1 - ssRes / ssTot; } computeStandardErrors() { const XT = MatrixUtils.transpose(this.X); const XTX = MatrixUtils.multiply(XT, this.X); const invXTX = MatrixUtils.inverse(XTX); const mse = this.residuals.reduce((acc, e) => acc + e ** 2, 0) / (this.n - this.k); return invXTX.map((row, i) => Math.sqrt(row[i] * mse)); } computePValues() { return this.standardErrors.map((se, i) => { const t = this.coefficients[i] / se; return 2 * (1 - tCDF(Math.abs(t), this.n - this.k)); }); } get result() { if(!this.xNames) this.calculate() return { Variable: ['Intercept', ...this.xNames], Coefficient: this.coefficients, StdError: this.standardErrors, pValue: this.pValues }; } get htmlTable() { const { Variable, Coefficient, StdError, pValue } = this.result return /*html*/`<table> <thead><tr style="text-align:left;">${['Variable', 'Coefficient', 'StdError', 'pValue'].map(v => /*html*/`<th>${v}</th>`).join('')}</tr></thead> <tbody> ${Variable.map((vName, i) => /*html*/`<tr> <th style="text-align:left;">${vName}</th> <td>${Coefficient[i].toFixed(3)}</td> <td>${StdError[i].toFixed(3)}</td> <td>${pValue[i].toFixed(5)}</td> </tr>`).join('')} </tbody> </table>` } } module.exports = LinearRegression