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
JavaScript
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; // если правило не найдено — оставляем исходное значение
});
}