UNPKG

als-statistics

Version:

Modular JS statistics toolkit for Node.js and the browser: descriptive stats, correlations (Pearson/Spearman/Kendall), t-tests & ANOVA (Student/Welch), reliability (Cronbach’s alpha), regression (linear/logistic), clustering (DBSCAN/HDBSCAN), and table/co

66 lines (57 loc) 2.44 kB
import CDF from '../cdf/index.js'; import { TestBase } from '../test-base/index.js'; function rankAverage(arr) { /** Compute average ranks with tie handling. Returns { ranks, tieGroups } */ const n = arr.length; const idx = Array.from({ length: n }, (_, i) => i).filter(i => Number.isFinite(arr[i])); const pairs = idx.map(i => [arr[i], i]).sort((a, b) => (a[0] - b[0])); const ranks = new Array(n).fill(NaN); const tieGroups = []; // sizes of tied groups let i = 0; let r = 1; while (i < pairs.length) { let j = i + 1; while (j < pairs.length && pairs[j][0] === pairs[i][0]) j++; const size = j - i; const avgRank = (r + (r + size - 1)) / 2; for (let k = i; k < j; k++) { ranks[pairs[k][1]] = avgRank; } if (size > 1) tieGroups.push(size); r += size; i = j; } return { ranks, tieGroups }; } export class Spearman extends TestBase { /** Spearman rank correlation (двухвыборочный) */ constructor(samples) { super(samples, 'Spearman', ['r', 't', 'df', 'p'], { sameSize: false, minK: 2 }); this.#calc() } #calc() { const s1 = this.samples[0], s2 = this.samples[1]; const n = Math.min(s1.n, s2.n); if (s1.n !== n) s1.values = s1.values.slice(0, n); if (s2.n !== n) s2.values = s2.values.slice(0, n); this.n = n; const x = s1.values, y = s2.values; const rx = rankAverage(x).ranks, ry = rankAverage(y).ranks; const mx = rx.reduce((a, b) => a + b, 0) / n, my = ry.reduce((a, b) => a + b, 0) / n; // 4) Пирсон по рангам let num = 0, sx = 0, sy = 0; for (let i = 0; i < n; i++) { const dx = rx[i] - mx, dy = ry[i] - my; num += dx * dy; sx += dx * dx; sy += dy * dy; } if (sx === 0 || sy === 0) this.#zeroXY(); // Если дисперсия в одном из рангов нулевая (все значения одинаковые) else this.#calcpr(sx,sy,num) } #zeroXY() { this.r = 0; this.df = this.n - 2; this.t = 0; this.p = 1; } #calcpr(sx,sy,num) { this.r = num / Math.sqrt(sx * sy); this.df = this.n - 2; if (Math.abs(this.r) >= 1) { this.t = this.r > 0 ? Infinity : -Infinity; this.p = 0; } else { this.t = this.r * Math.sqrt(this.df / (1 - this.r * this.r)); this.p = 2 * (1 - CDF.t(Math.abs(this.t), this.df)); } } }