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

74 lines (68 loc) 3.51 kB
export function normalizeRules(rules) { return rules.map(r => { if (r.length === 2) return { kind: 'eq', left: r[0], right: r[1] }; // если второй элемент число/строка — это правило "равно" else if (r.length === 3) return { kind: 'range', left: r[0], min:r[1], max:r[2] }; else throw new Error('Wrong rule format: ' + JSON.stringify(r)); }); } export function inRange(x, min, max) { if (min != null && x < min) return false; if (max == null) return x >= min; // [min, +∞) return x >= min && x < max; // [min, max) } /** * Частоты значений. Если переданы labels, группирует по меткам: * labels может быть: * - [['male', 1], ['female', 2]] * - [['child', 1, 12], ['adult', 18, 60], ...] * * Правило для диапазонов: [min, max) (правая граница исключена), max === null => [min, +∞) * Значения, не попавшие ни в одну метку, попадают в ключ '_other'. */ // Поддерживаются формы: // - [label, value] — метка по точному значению // - [label, min, max] — метка по диапазону [min, max) // - [newValue, oldValue] — recode по точному значению // - [newValue, min, max] — recode по диапазону [min, max) export function frequencies({ values, labels }) { const freq = new Map(); if (!labels || labels.length === 0) { for (const v of values) freq.set(v, (freq.get(v) || 0) + 1); return Object.fromEntries(freq); } const rules = normalizeRules(labels); for (const v of values) { let key = null; for (const rule of rules) { if (rule.kind === 'eq' && v === rule.right) { key = rule.left; break; } if (rule.kind === 'range' && typeof v === 'number' && inRange(v, rule.min, rule.max)) { key = rule.left; break; } } if (key == null) key = '_other'; freq.set(key, (freq.get(key) || 0) + 1); } if (freq.has('_other') && freq.get('_other') === 0) freq.delete('_other'); // Если ни одного значения не ушло в _other — можно его удалить return Object.fromEntries(freq); } /** * Перекодировка массива values по правилам: * ranges: * - [[newValue, oldValue], ...] — заменяет точные соответствия * - [[newValue, min, max], ...] — заменяет по диапазонам [min, max) * При множественных совпадениях применяется ПЕРВОЕ подходящее правило. * Значения без совпадений остаются как есть. */ export function recode({ values }, ranges) { const rules = normalizeRules(ranges); return values.map(v => { for (const rule of rules) { if (rule.kind === 'eq') { if (v === rule.right) return rule.left; } else if (rule.kind === 'range' && typeof v === 'number') { if (inRange(v, rule.min, rule.max)) return rule.left; } } return v; // если правило не найдено — оставляем исходное значение }); }