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

79 lines (69 loc) 3.23 kB
import CDF from '../cdf/index.js'; import { TestBase } from '../test-base/index.js'; function tieGroupSizes(arr) { /** Размеры групп связок (только size>1) */ const map = new Map(); for (const v of arr) map.set(v, (map.get(v) || 0) + 1); const sizes = []; for (const s of map.values()) if (s > 1) sizes.push(s); return sizes; } function kendallCounts(X, Y) { /** Подсчёт C, D и связок Tx, Ty */ const n = X.length; let C = 0, D = 0; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const dx = Math.sign(X[j] - X[i]); const dy = Math.sign(Y[j] - Y[i]); if (dx === 0 || dy === 0) continue; // пары с ничьей по X или Y не учитываем в C,D if (dx === dy) C++; else D++; } } const TxSizes = tieGroupSizes(X), TySizes = tieGroupSizes(Y); const n0 = n * (n - 1) / 2; const Tx = TxSizes.reduce((s, g) => s + g * (g - 1) / 2, 0); const Ty = TySizes.reduce((s, g) => s + g * (g - 1) / 2, 0); return { n, n0, C, D, Tx, Ty, TxSizes, TySizes }; } function tieSums(sizes) { /** Полезные суммы по tie-группам для Var(S) */ let s1 = 0, s2 = 0, s3 = 0; for (const t of sizes) { s1 += t * (t - 1); s2 += t * (t - 1) * (2 * t + 5); s3 += t * (t - 1) * (t - 2); } return { s1, s2, s3 }; } export class Kendall extends TestBase { constructor(samples, { twoSided = true } = {}) { super(samples, 'Kendall’s tau-b', ['tau','z','t','df','p'], { sameSize:true,min: 2 }); // Передадим уже подготовленные массивы в TestBase (минимум 2 наблюдения) this.twoSided = twoSided this.samples.forEach(v => { if (v.n !== this.n) v.values.slice(0, this.n) }) if (this.n < 2) throw new Error('Spearman: need at least 2 observations'); this.#calc() } #calc() { const { n, n0, C, D, Tx, Ty, TxSizes, TySizes } = kendallCounts(this.samples[0].values, this.samples[1].values); const denomTau = Math.sqrt(Math.max((n0 - Tx) * (n0 - Ty), 1e-16)); const S = C - D; const tau = denomTau === 0 ? 0 : S / denomTau; const { s1: sx1, s2: sx2, s3: sx3 } = tieSums(TxSizes); const { s1: sy1, s2: sy2, s3: sy3 } = tieSums(TySizes); let varS = (n * (n - 1) * (2 * n + 5) - sx2 - sy2) / 18; if (n > 1) varS += (sx1 * sy1) / (2 * n * (n - 1)); if (n > 2) varS += (sx3 * sy3) / (9 * n * (n - 1) * (n - 2)); const cc = S === 0 ? 0 : (S > 0 ? 1 : -1); const z = varS > 0 ? (S - cc) / Math.sqrt(varS) : 0; this.tau = Math.max(-1, Math.min(1, tau)); this.z = z; this.n = n; this.S = S; this.Tx = Tx; this.Ty = Ty; this.n0 = n0; // Доп. поля на всякий случай } get p() { let p = 2 * (1 - CDF.phi(Math.abs(this.z))); // let p = 2 * (1 - CDF.t(Math.abs(this.z), 1e6)); if (!this.twoSided) p = p / 2; if (p < 0) p = 0; if (p > 1) p = 1; return p } get t() { return this.z; } // чтобы у всех корреляций был 't'/'stat' get df() { return Infinity; } // или null — на твой вкус }