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
55 lines (47 loc) • 2.46 kB
JavaScript
import CDF from '../cdf/index.js';
import { TestBase } from '../test-base/index.js'
export class OneWayAnova extends TestBase { /** One-way ANOVA for prepared samples object: { name1: number[], name2: number[], ... } */
constructor(samples, welch = false) {
super(samples, 'One-way ANOVA', ['F', 'dfBetween', 'dfWithin', 'p', 'k', 'msw'])
this.#calc(welch)
}
#calc(welch) {
this.dfBetween = this.k - 1;
if (welch) { // TODO not tests
const means = [], ns = [], vars = [], weights = [];
this.samples.forEach(({ mean, n, varianceSample }) => {
means.push(mean);
ns.push(n);
vars.push(varianceSample);
weights.push(n / varianceSample); // Веса Вэлча: w_i = n_i / s_i^2
});
// Сумма весов и взвешенная общая средняя
const W = weights.reduce((a, b) => a + b, 0);
const gmW = means.reduce((s, mi, i) => s + weights[i] * mi, 0) / W;
// Межгрупповая сумма квадратов по Вэлчу
const ssbW = means.reduce((s, mi, i) => s + weights[i] * (mi - gmW) ** 2, 0);
const msbW = ssbW / (this.k - 1);
// Коррекция знаменателя и степеней свободы (Welch–Satterthwaite)
const a_i = weights.map(wi => 1 - wi / W);
const sum_ai = ns.reduce((s, ni, i) => s + (a_i[i] * a_i[i]) / (ni - 1), 0);
const c = 1 + (2 * (this.k - 2) / (this.k * this.k - 1)) * sum_ai;
const df2 = (this.k * this.k - 1) / (3 * sum_ai);
this.ssb = ssbW;
this.msb = msbW;
this.dfWithin = df2;
this.ssw = c * df2; // => msw = c, F = msbW / c — как у Welch
} else {
const N = this.samples.reduce((t, { n }) => t + n, 0);
this.dfWithin = N - this.k;
this.grandMean = this.samples.reduce((t, { sum }) => t + sum, 0) / N
this.ssb = this.samples.reduce((t, { mean, n }) => t + n * Math.pow(mean - this.grandMean, 2), 0);
this.ssw = this.samples.reduce((t, { mean, values }) => t + values.reduce((acc, v) => acc + (v - mean) ** 2, 0), 0);
this.msb = this.ssb / this.dfBetween
}
this.msw = this.ssw / this.dfWithin
this.F = this.msb / this.msw
}
get p() {
if (this._p === undefined) this._p = 1 - CDF.f(this.F, this.dfBetween, this.dfWithin); return this._p;
}
}