statistical-js
Version:
Simple to use, completely useful
500 lines (419 loc) • 15.9 kB
JavaScript
'use strict';
import Validator from '../utils/validator';
import epsilon from '../constante/epsilon';
import chiSquaredTable from '../constante/chiSquaredTable';
class StatisticalMethod {
/**
* Create a new object that provide all statistical methods.
*/
constructor() {
this._validator = new Validator();
this._chiSquaredProbTable = chiSquaredTable;
this._epsilon = epsilon;
}
/**
* Return table of chi squared prob.
*
* @returns {*}
*/
get chiSquaredProbTable() {
return this._chiSquaredProbTable;
}
/**
* Return the smallest value of the sample.
*
* @param {Array} sample
* @returns {number}
*/
min(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return sample.sort((a, b) => a - b)[0];
}
/**
* Return the biggest value of the sample.
*
* @param {Array} sample
* @returns {number}
*/
max(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return sample.sort((a, b) => a + b)[0];
}
/**
* The [Sum](https://en.wikipedia.org/wiki/Sum).
*
* @param sample
* @returns {*}
*/
sum(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return sample.reduce((accumulator, current) => accumulator + current, 0);
}
/**
* The [Median](https://en.wikipedia.org/wiki/Median).
*
* @param {Array} sample
* @returns {number}
*/
median(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
const middle = Math.floor(sample.length / 2);
const isEven = sample.length % 2 === 0;
sample = sample.sort((a, b) => a - b);
return isEven ? (sample[middle - 1] + sample[middle]) / 2 : sample[middle];
}
/**
* The [Mode](https://en.wikipedia.org/wiki/Mode).
*
* @param {Array} sample
* @returns {*}
*/
mode(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
const counter = {};
let mode = [];
let max = 0;
sample.map((value, index) => {
if (!(sample[index] in counter)) counter[sample[index]] = 0;
counter[sample[index]]++;
if (counter[sample[index]] === max) mode.push(sample[index]);
if (counter[sample[index]] > max) {
max = counter[sample[index]];
mode = [sample[index]];
}
});
return mode.length > 1 ? mode : mode[0];
}
/**
* The [Mean](https://en.wikipedia.org/wiki/Mean).
*
* @param {Array} sample
* @returns {number}
*/
mean(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return this.sum(sample) / sample.length;
}
/**
* The [Variance](https://en.wikipedia.org/wiki/Variance).
*
* @param {Array} sample
* @returns {number}
*/
variance(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
const avg = this.mean(sample);
const n = sample.length;
return this.sum(sample.map(value => Math.pow(value - avg, 2))) / n;
}
/**
* The [Standard Deviation](https://en.wikipedia.org/wiki/Standard_deviation).
*
* @param {Array} sample
* @returns {number}
*/
stdDeviation(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return Math.sqrt(this.variance(sample));
}
/**
* The [Quantile](http://en.wikipedia.org/wiki/Quantile).
*
* @param {Array} sample
* @param {number} index
* @returns {Array}
*/
quantile(sample, index) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
this._validator.validate('index', index, ['isNumber', [0, 1]]);
const sortedSample = sample.sort((a, b) => a - b);
return sortedSample[Math.ceil((sample.length * index) - 1)];
}
/**
* The [Percentile](https://en.wikipedia.org/wiki/Percentile).
*
* @param {Array} sample
* @param {number} index
* @returns {Array}
*/
percentile(sample, index) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
this._validator.validate('index', index, ['isNumber', [0, 100]]);
const sortedSample = sample.sort((a, b) => a - b);
return sortedSample[Math.ceil((index / 100) * sample.length)];
}
/**
* Return the entire result of descriptives statistics above.
*
* @param {Array} sample
* @returns {{min: number, max: number, sum: *, median: number, mode: *, mean: number, variance: number, stdDeviation: number, quantile: Array}}
*/
summary(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return {
min: this.min(sample),
max: this.max(sample),
sum: this.sum(sample),
median: this.median(sample),
mode: this.mode(sample),
mean: this.mean(sample),
variance: this.variance(sample),
stdDeviation: this.stdDeviation(sample),
quantile: {
q1: this.quantile(sample, 0.25),
q3: this.quantile(sample, 0.75)
}
};
}
/**
* The [Factorial](https://en.wikipedia.org/wiki/Factorial).
*
* @param {number} n
* @returns {number}
*/
factorial(n) {
this._validator.validate('n', n, ['isNumber', 'positive']);
let factorialResult = 1;
for (let i = 2; i <= n; i++) {
factorialResult *= i;
}
return factorialResult;
}
/**
* The [Geometric Mean](https://en.wikipedia.org/wiki/Geometric_mean).
*
* @param {Array} sample
* @returns {number}
*/
geometricMean(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return Math.pow(sample.reduce((accumulator, current) => accumulator * current, 1), 1 / sample.length);
}
/**
* The [Harmonic Mean](https://en.wikipedia.org/wiki/Harmonic_mean).
*
* @param {Array} sample
* @returns {number}
*/
harmonicMean(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return sample.length / sample.reduce((accumulator, current) => accumulator + (1 / current), 0);
}
/**
* The [Interquartile range](http://en.wikipedia.org/wiki/Interquartile_range)
*
* @param sample
* @returns {number}
*/
interQuartileRange(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return this.quantile(sample, 0.75) - this.quantile(sample, 0.25);
}
/**
* The [Variance](https://en.wikipedia.org/wiki/Harmonic_mean).
* The [Biais](https://fr.wikipedia.org/wiki/Estimateur_(statistique)#Biais).
*
* Non biased variance
*
* @param {Array} sample
* @returns {number}
*/
sampleVariance(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
const avg = this.mean(sample);
const n = sample.length - 1;
return this.sum(sample.map(value => Math.pow(value - avg, 2))) / n;
}
/**
* The [Standard Deviation](https://en.wikipedia.org/wiki/Standard_deviation).
* The [Biais](https://fr.wikipedia.org/wiki/Estimateur_(statistique)#Biais).
*
* Non biased std deviation
*
* @param {Array} sample
* @returns {number}
*/
sampleStdDeviation(sample) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
return Math.sqrt(this.sampleVariance(sample));
}
/**
* The [Sample covariance](https://en.wikipedia.org/wiki/Sample_mean_and_sampleCovariance) of two datasets:
*
* @param {Array} sample1
* @param {Array} sample2
* @returns {number}
*/
covariance(sample1, sample2) {
this._validator.validate('sample1', sample1, ['isArray', 'length > 0']);
this._validator.validate('sample2', sample2, ['isArray', 'length > 0']);
this._validator.validate('sample1 and sample2', [sample1, sample2], ['length =']);
const meanX = this.mean(sample1);
const meanY = this.mean(sample2);
const numerator = sample1.reduce((accumulator, current, i) => {
accumulator += (current - meanX) * (sample2[i] - meanY);
return accumulator;
}, 0);
const besselsCorrection = sample1.length - 1;
return numerator / besselsCorrection;
}
/**
* The [Binomial Distribution](http://en.wikipedia.org/wiki/Binomial_distribution).
*
* @param {number} trials
* @param {number} probability
* @returns {{}}
*/
binomial(trials, probability) {
this._validator.validate('trials', trials, ['isNumber']);
this._validator.validate('probability', probability, ['isNumber', [0, 1]]);
let x = 0;
let cumulativeProbability = 0;
const cells = {};
do {
cells[x] = this.factorial(trials) / (this.factorial(x) * this.factorial(trials - x)) * (Math.pow(probability, x) * Math.pow(1 - probability, trials - x));
cumulativeProbability += cells[x];
x++;
} while (cumulativeProbability < 1 - this._epsilon);
return cells;
}
/**
* The [Bernoulli distribution](http://en.wikipedia.org/wiki/Bernoulli_distribution).
*
* @param {number} p
* @returns {Object}
*/
bernoulli(p) {
this._validator.validate('p', p, ['isNumber', [0, 1]]);
return this.binomial(1, p);
}
/**
* The [Poisson Distribution](http://en.wikipedia.org/wiki/Poisson_distribution).
*
* @param {number} lambda
* @returns {{}}
*/
poisson(lambda) {
this._validator.validate('lambda', lambda, ['strictlyPositive']);
let x = 0;
let cumulativeProbability = 0;
let cells = {};
do {
cells[x] = (Math.pow(Math.E, -lambda) * Math.pow(lambda, x)) / this.factorial(x);
cumulativeProbability += cells[x];
x++;
} while (cumulativeProbability < 1 - this._epsilon);
return cells;
}
/**
* The [χ2 (Chi-Squared) Goodness-of-Fit Test](http://en.wikipedia.org/wiki/Goodness_of_fit#Pearson.27s_chi-squared_test).
* return if data follow a specified distribution
*
* @param {Array} sample
* @param {Function} distributionType
* @param {number} significance
* @returns {boolean}
*
* @exemple
* chiSquaredGoodnessOfFit(sample, 'poisson', 0.05)); //= false
*/
chiSquaredGoodnessOfFit(sample, distributionType, significance) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
this._validator.validate('distributionType', distributionType, ['isFunction']);
this._validator.validate('significance', significance, ['isNumber', 'positive']);
/* Generate an array with number of ocurences for each data in sample. */
let observedFrequencies = [];
observedFrequencies = sample.reduce((accumulator, val) => {
if (accumulator[val] === undefined) accumulator[val] = 0;
accumulator[val] += 1;
return accumulator;
}, []).filter(v => v !== undefined);
/* number of hypothesized distribution parameters estimated, expected to be supplied in the distribution test. */
/* Lose one degree of freedom for estimating `lambda` from the sample data. */
const sampleMean = this.mean(sample);
/* The hypothesized distribution. Generate the hypothesized distribution. */
const hypothesizedDistribution = distributionType(sampleMean);
/* Create an array holding a histogram of expected data given the */
/* sample size and hypothesized distribution. */
let expectedFrequencies = [];
expectedFrequencies = Object.entries(hypothesizedDistribution).reduce((accumulator, current, i) => {
if (observedFrequencies[i]) accumulator[i] = current[1] * sample.length;
return accumulator;
}, []);
/* Concat frequencies < 3 with the previous one */
expectedFrequencies = Object.entries(expectedFrequencies).reduceRight((previous, current) => {
if (previous[1] < 3) current[1] += previous[1];
return current;
});
/* Compute chiSquared value */
let chiSquared = 0;
chiSquared = Object.entries(observedFrequencies).reduce((accumulator, current, i) => {
accumulator += Math.pow((current[1] - expectedFrequencies[i]), 2) / expectedFrequencies[i];
return accumulator;
}, chiSquared);
const c = 1;
const degreesOfFreedom = Object.keys(observedFrequencies).length - c - 1;
return this._chiSquaredProbTable[degreesOfFreedom][significance] < chiSquared;
}
/**
* The [a one-sample t-test](https://en.wikipedia.org/wiki/Student%27s_t-test#One-sample_t-test).
*
* @param {Array} sample
* @param {number} mu
* @returns {number}
*/
tTestOneSample(sample, mu) {
this._validator.validate('sample', sample, ['isArray', 'length > 0']);
this._validator.validate('mu', mu, ['isNumber']);
const mean = this.mean(sample);
const sd = this.stdDeviation(sample);
const sqrtSampleSize = Math.sqrt(sample.length);
/* t-value */
return (mean - mu) / (sd / sqrtSampleSize);
}
/**
* The [two sample t-test](http://en.wikipedia.org/wiki/Student's_t-test).
*
* @param {Array} sample1
* @param {Array} sample2
* @returns {number}
*/
tTestTwoSample(sample1, sample2) {
this._validator.validate('sample1', sample1, ['isArray', 'length > 0']);
this._validator.validate('sample2', sample2, ['isArray', 'length > 0']);
const n = sample1.length;
const m = sample2.length;
const meanX = this.mean(sample1);
const meanY = this.mean(sample2);
const sampleVarianceX = this.sampleVariance(sample1);
const sampleVarianceY = this.sampleVariance(sample2);
const weightedVariance = (((n - 1) * sampleVarianceX) + ((m - 1) * sampleVarianceY)) / (n + m - 2);
/* t-value */
return (meanX - meanY) / Math.sqrt(weightedVariance * (1 / (n + 1) / m));
}
/**
* [Simple linear regression](http://en.wikipedia.org/wiki/Simple_linear_regression)
*
* @param {Array<Array<number>>} data
* @returns {*}
*/
linearRegression(data) {
this._validator.validate('data', data, ['isArray']);
const dataLength = data.length;
/* 1 element, the result will be a slope to 0 and an intersect ot the second coordinate elements */
if (dataLength === 1) return { slope, intersect: data[0][1] };
/* Compute all sum, and finally the slope and intersect */
let sumX = 0, sumY = 0, sumXX = 0, sumXY = 0;
data.forEach(element => {
sumX += element[0];
sumY += element[1];
sumXX += Math.pow(element[0], 2);
sumXY += element[0] * element[1];
});
const slope = ((dataLength * sumXY) - (sumX * sumY)) / ((dataLength * sumXX) - (sumX * sumX));
const intersect = (sumY / dataLength) - ((slope * sumX) / dataLength);
// Return both values as an object.
return { slope, intersect };
}
}
export default StatisticalMethod;