locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
65 lines (64 loc) • 2.98 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.correlation = correlation;
const _statistics_ts_1 = require("../_helpers/_statistics.js");
function correlation(x, y, method = 'linear') {
// discuss at: https://locutus.io/python/statistics/correlation/
// parity verified: Python 3.12
// original by: Kevin van Zonneveld (https://kvz.io)
// note 1: Returns Pearson correlation by default and supports Python's ranked mode for Spearman correlation.
// example 1: correlation([1, 2, 3], [1, 5, 7])
// returns 1: 0.9819805060619659
// example 2: correlation([1, 2, 3], [7, 5, 3])
// returns 2: -1
// example 3: correlation([1, 2, 3], [3, 2, 1], 'ranked')
// returns 3: -1
const left = (0, _statistics_ts_1.assertStatisticsArray)(x, 'correlation').map((value) => (0, _statistics_ts_1.toStatisticNumber)(value, 'correlation'));
const right = (0, _statistics_ts_1.assertStatisticsArray)(y, 'correlation').map((value) => (0, _statistics_ts_1.toStatisticNumber)(value, 'correlation'));
const n = left.length;
if (right.length !== n) {
throw new Error('correlation requires that both inputs have same number of data points');
}
if (n < 2) {
throw new Error('correlation requires at least two data points');
}
if (method !== 'linear' && method !== 'ranked') {
throw new TypeError(`Unknown method: '${String(method)}'`);
}
const xValues = method === 'ranked' ? rankValues(left) : centerValues(left);
const yValues = method === 'ranked' ? rankValues(right) : centerValues(right);
const sxy = (0, _statistics_ts_1.sumProducts)(xValues, yValues);
const sxx = (0, _statistics_ts_1.sumProducts)(xValues, xValues);
const syy = (0, _statistics_ts_1.sumProducts)(yValues, yValues);
const denominator = Math.sqrt(sxx * syy);
if (denominator === 0) {
throw new Error('at least one of the inputs is constant');
}
return sxy / denominator;
}
function centerValues(values) {
const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
return values.map((value) => value - mean);
}
function rankValues(values) {
const n = values.length;
const start = (n - 1) / -2;
const indexed = values.map((value, index) => ({ value, index })).sort((a, b) => a.value - b.value);
const ranks = new Array(n);
let position = 0;
while (position < indexed.length) {
let end = position + 1;
while (end < indexed.length && indexed[end]?.value === indexed[position]?.value) {
end += 1;
}
const averageRank = start + (position + end - 1) / 2;
for (let cursor = position; cursor < end; cursor += 1) {
const entry = indexed[cursor];
if (entry) {
ranks[entry.index] = averageRank;
}
}
position = end;
}
return ranks;
}