Source: data/Statistics.js

/**
 * The Statistics class provides simple statistical analysis for numeric data series.
 * It filters out non-numeric (NaN), empty, and +/- Infinity values.
 * 
 * @author rkoppe <roland.koppe@awi.de>
 */
export class Statistics {
    min = null;
    q25 = null;
    mean = null;
    median = null;
    q75 = null;
    max = null;
    stddev = null;
    var = null;
    n = null;
    sum = null;

    /**
     * Constructs a Statistics object and calculates statistical properties for the given data array.
     * 
     * @param {number[]} data - The data array to calculate statistics for.
     */
    constructor(data) {
        if (!(data && data.length && (data.length > 0))) return this;

        // copy array, don't sort in place, NaN to the end
        data = data.slice().sort(function (a, b) {
            if ((a == '') || isNaN(a) || !isFinite(a)) return 1;
            if ((b == '') || isNaN(b) || !isFinite(b)) return -1;
            return a - b;
        });

        let min = Number.MAX_VALUE;
        let max = Number.MIN_VALUE;
        let n = 0;
        let sum = 0;

        // simple statistics
        for (let i = 0; i < data.length; i++) {
            let value = data[i];
            if ((value == '') || isNaN(value) || !isFinite(value)) break;
            if (min > value) min = value;
            if (max < value) max = value;
            sum += value;
            n++;
        }

        if (n == 0) return;

        // slice data to not contain NaN and Infinity
        data = data.slice(0, n);

        this.min = min;
        this.max = max;
        this.n = n;
        this.sum = sum;
        this.mean = this.sum / this.n;

        // variance and standard deviation
        sum = 0;
        for (let i = 0; i < data.length; i++) {
            let value = data[i];
            sum += Math.pow(value - this.mean, 2);
        }
        this.var = sum / (this.n - 1);
        this.stddev = Math.sqrt(this.var);

        // continous percentiles
        this.q25 = this._percentile(0.25, data);
        this.median = this._percentile(0.50, data);
        this.q75 = this._percentile(0.75, data);
    }

    /**
     * Calculates and returns the continuous percentile defined by
     * given percent and sorted! data array.
     * 
     * @param {*} percent 
     * @param {*} data 
     * @returns number
     */
    _percentile(percent, data) {
        if (data.length == 1) return data[0];

        let pos = percent * (data.length - 1);
        if (Number.isInteger(pos)) {
            return data[pos];
        } else {
            pos = Math.floor(pos);
            return (data[pos] + data[pos + 1]) / 2;
        }

    }
}