Source: statsLib.js

var zTable = require('./zTable');

/** Class aggregating statistical methods. */
class statsLib{

    /**
     * Evaluates sum of an array.
     * @param {number[]} inArr - The array to sum
     * @return {number} Numerical value representing the sum of all array elements
     */
    static sum(inArr){
        let total = 0;
        for (let i = 0; i < inArr.length; i++){
            total += inArr[i];
        }
        return total;
    }

    /**
     * Evaluates geometric sum of an array.
     * @param {number[]} inArr - The array to sum
     * @return {number} Numerical value representing the geometric sum of all array elements
     */
    static geometricSum(inArr){
        if (inArr.length === 0){
            return 0;
        }
        let total = inArr[0];
        for (let i = 1; i < inArr.length; i++){
            total *= inArr[i];
        }
        return total;
    }

    /**
     * Evaluates statistical mean of an array.
     * @param {number[]} inArr - The array to find mean for.
     * @return {number} Numerical value representing the mean of all array elements
     */
    static mean(inArr){
        return statsLib.sum(inArr) / inArr.length;
    }

    /**
     * Evaluates statistical geometric mean of an array.
     * @param {number[]} inArr - The array to find geometric mean for.
     * @return {number} Numerical value representing the geometric mean of all array elements
     */
    static geometricMean(inArr){
        let totalGeometric = statsLib.geometricSum(inArr);
        return Math.pow(totalGeometric, 1/inArr.length);
    }

    /**
     * Evaluates statistical median of an array.
     * @param {number[]} inArr - The array to find median for.
     * @return {number} Numerical value representing the median of all array elements
     */
    static median(inArr){
        let newArr = [...inArr];
        let arrSizeEven = newArr.length % 2 === 0;
        let midIdx = Math.floor(newArr.length / 2);

        newArr.sort(function(a, b) {return a - b;});
        return arrSizeEven ? (newArr[midIdx] + newArr[midIdx - 1]) / 2 : newArr[midIdx];
    }

    /**
     * Evaluates statistical mode of an array.
     * @param {number[]} inArr - The array to find mode for.
     * @return {number} Numerical value representing the mode of all array elements
     */
    static mode(inArr){
        let retMode = [];
        let modeDict = {};
        let maxCount = 0;
        for (let i = 0; i < inArr.length; i++){
            (inArr[i] in modeDict) ? modeDict[inArr[i]]++ : modeDict[inArr[i]] = 1;
            if (modeDict[inArr[i]] > maxCount){
                retMode = [inArr[i]];
                maxCount = modeDict[inArr[i]];
            } else if (modeDict[inArr[i]] === maxCount){
                retMode.push(inArr[i]);
            }
        }
        return retMode;
    }

    /**
     * Evaluates statistical standard deviation of an array.
     * @param {number[]} inArr - The array to find standard deviation for
     * @param {{population: boolean}} opt - Option denoting if a population or a sample is provided
     * @return {number} Numerical value representing the standard deviation of all array elements
     */
    static stdev(inArr, opt = {
        "population": true,
    }){
        let size = inArr.length;
        if (!opt["population"]){
            size--;
        }
        let mean = statsLib.mean(inArr);
        let total = 0;
        for (let i = 0; i < inArr.length; i++){
            total += Math.pow((inArr[i] - mean), 2);
        }
        return Math.sqrt(total / size);
    }

    /**
     * Finds an array element representing the given percentile.
     * @param {number} percentile - Percentile to find in the given array (0 <= percentile <= 100)
     * @param {number[]} inArr - Array to find the percentile element in
     * @return {!number} A numerical value that is a part of the input array representing the given percentile
     */
    static absolutePercentile(percentile, inArr){
        if (!inArr.length){
            return undefined;
        }
        let newArr = [...inArr];
        newArr.sort(function(a, b) {return a - b;});
        let idxRank = Math.round((percentile / 100) * newArr.length) - 1;
        return newArr[idxRank];
    }

    /**
     * Evaluates the Z score of a given value and a sample or population dataset array.
     * @param {number} val - Value to find Z score for
     * @param {number[]} inArr - The array representing the population or sample
     * @param {{population: boolean}} opt - Option denoting if a population or a sample is provided
     * @return {number} Numerical value representing the Z score the given value in the context of the dataset
     */
    static zScore(val, inArr, opt = {
        "population": true,
    }){
        let mean = statsLib.mean(inArr);
        let stdev = statsLib.stdev(inArr, opt);
        return (val - mean) / stdev;
    }

    /**
     * Evaluates the percentile of a given value and a sample or population dataset array.
     * @param {number} val - Value to find percentile for
     * @param {number[]} inArr - The array representing the population or sample
     * @param {{population: boolean}} opt - Option denoting if a population or a sample is provided
     * @return {number} Numerical value representing the percentile the given value in the context of the dataset
     */
    static percentile(val, inArr, opt = {
        "population": true,
    }){
        let z = statsLib.zScore(val, inArr, opt);
        return statsLib.zScorePercentile(z);
    }

    /**
     * Evaluates the percentile of a given value and a sample or population dataset array.
     * @param {number} val - Value to find percentile for
     * @param {number} mean - The value representing mean of the dataset
     * @param {number} stdev - The value representing standard deviation of the dataset
     * @return {number} Numerical value representing the percentile the given value in the context of the dataset
     */
    static percentileFromMeanAndStdev(val, mean, stdev){
        let diff = val - mean;
        let zScore = (diff/stdev);
        return statsLib.zScorePercentile(zScore);
    }

    /**
     * Evaluates the percentile of a given Z score, maximum is [-3.99, 3.99]. If the value is too large
       or too small, it goes to the nearest valid Z score.
     * @param {number} score - Z score to find percentile for
     * @return {number} Numerical value representing the percentile the given Z score
     */
    static zScorePercentile(score){
        if (!(typeof score == 'number')){
            return undefined;
        }
        score = (score > 3.99) ? 3.99: score;
        score = (score < -3.99) ? -3.99: score;
        let combinedIdx = score.toFixed(2);
        let idx1 =  combinedIdx.substr(0, combinedIdx.length - 1);
        let idx2 = combinedIdx.substr(combinedIdx.length - 1, 1);
        return zTable.zScoreTab[idx1][idx2];
    }

    /**
     * Evaluates statistical correlation of two arrays.
     * @param {number[]} inArr1 - The first array to find correlation for
     * @param {number[]} inArr2 - The second array to find correlation for
     * @return {number} Numerical value representing the correlation of the 2 arrays
     */
    static correlation(inArr1, inArr2){
        if (inArr1.length !== inArr2.length){
            return 0;
        }
    
        const arrSum = (x) => {
            let sum = 0;
            for (let i = 0; i < x.length; i++){
                sum += x[i];
            }
            return sum;
        }
        
        let n = inArr1.length;
        let inArr1Sum = arrSum(inArr1);
        let inArr2Sum = arrSum(inArr2);
        
        
        let inArr1Sq = arrSum(inArr1.map(function(num) {
            return Math.pow(num, 2)
        }));
    
        let inArr2Sq = arrSum(inArr2.map(function(num) {
            return Math.pow(num, 2)
        }));
    
        let xy = 0;
        for (let i = 0; i < n; i++){
            xy += inArr1[i] * inArr2[i];  
        }
        
        let numo = (n*xy) - (inArr1Sum*inArr2Sum);
        let deno0 = (n*inArr1Sq) - Math.pow(inArr1Sum,2);
        let deno1 = (n*inArr2Sq) - Math.pow(inArr2Sum,2);
        let deno = Math.sqrt(deno0*deno1)
        let r = numo/deno;
        return r;
    }
}

module.exports = {
    statsLib: statsLib,
};