UNPKG

@entestat/formula

Version:

fast excel formula parser

1,149 lines (943 loc) 39.4 kB
const FormulaError = require('../error'); const {FormulaHelpers, Types} = require('../helpers'); const H = FormulaHelpers; const jStat = require("jstat"); const MathFunctions = require('./math'); const SQRT2PI = 2.5066282746310002; const DistributionFunctions = { 'BETA.DIST': (x, alpha, beta, cumulative, a, b) => { x = H.accept(x, Types.NUMBER); alpha = H.accept(alpha, Types.NUMBER); beta = H.accept(beta, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); a = H.accept(a, Types.NUMBER, 0); b = H.accept(b, Types.NUMBER, 1); if (alpha <= 0 || beta <= 0 || x < a || x > b || a === b) throw FormulaError.NUM; x = (x - a) / (b - a); return cumulative ? jStat.beta.cdf(x, alpha, beta) : jStat.beta.pdf(x, alpha, beta) / (b - a); }, 'BETA.INV': (probability, alpha, beta, a, b) => { probability = H.accept(probability, Types.NUMBER); alpha = H.accept(alpha, Types.NUMBER); beta = H.accept(beta, Types.NUMBER); a = H.accept(a, Types.NUMBER, 0); b = H.accept(b, Types.NUMBER, 1); if (alpha <= 0 || beta <= 0 || probability <= 0 || probability > 1) throw FormulaError.NUM; return jStat.beta.inv(probability, alpha, beta) * (b - a) + a; }, 'BINOM.DIST': (numberS, trials, probabilityS, cumulative) => { numberS = H.accept(numberS, Types.NUMBER); trials = H.accept(trials, Types.NUMBER); probabilityS = H.accept(probabilityS, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); if (trials < 0 || probabilityS < 0 || probabilityS > 1 || numberS < 0 || numberS > trials) throw FormulaError.NUM; return cumulative ? jStat.binomial.cdf(numberS, trials, probabilityS) : jStat.binomial.pdf(numberS, trials, probabilityS); }, 'BINOM.DIST.RANGE': (trials, probabilityS, numberS, numberS2) => { trials = H.accept(trials, Types.NUMBER); probabilityS = H.accept(probabilityS, Types.NUMBER); numberS = H.accept(numberS, Types.NUMBER); numberS2 = H.accept(numberS2, Types.NUMBER, numberS); if (trials < 0 || probabilityS < 0 || probabilityS > 1 || numberS < 0 || numberS > trials || numberS2 < numberS || numberS2 > trials) throw FormulaError.NUM; let result = 0; for (let i = numberS; i <= numberS2; i++) { result += MathFunctions.COMBIN(trials, i) * Math.pow(probabilityS, i) * Math.pow(1 - probabilityS, trials - i); } return result; }, 'BINOM.INV': (trials, probabilityS, alpha) => { trials = H.accept(trials, Types.NUMBER); probabilityS = H.accept(probabilityS, Types.NUMBER); alpha = H.accept(alpha, Types.NUMBER); if (trials < 0 || probabilityS < 0 || probabilityS > 1 || alpha < 0 || alpha > 1) throw FormulaError.NUM; let x = 0; while (x <= trials) { if (jStat.binomial.cdf(x, trials, probabilityS) >= alpha) { return x; } x++; } }, 'CHISQ.DIST': (x, degFreedom, cumulative) => { x = H.accept(x, Types.NUMBER); degFreedom = H.accept(degFreedom, Types.NUMBER); cumulative = H.accept(cumulative, Types.NUMBER); degFreedom = Math.trunc(degFreedom); if (x < 0 || degFreedom < 1 || degFreedom > 10 ** 10) throw FormulaError.NUM; return cumulative ? jStat.chisquare.cdf(x, degFreedom) : jStat.chisquare.pdf(x, degFreedom); }, 'CHISQ.DIST.RT': (x, degFreedom) => { x = H.accept(x, Types.NUMBER); degFreedom = H.accept(degFreedom, Types.NUMBER); degFreedom = Math.trunc(degFreedom); if (x < 0 || degFreedom < 1 || degFreedom > 10 ** 10) throw FormulaError.NUM; return 1 - jStat.chisquare.cdf(x, degFreedom); }, 'CHISQ.INV': (probability, degFreedom) => { probability = H.accept(probability, Types.NUMBER); degFreedom = H.accept(degFreedom, Types.NUMBER); degFreedom = Math.trunc(degFreedom); if (probability < 0 || probability > 1 || degFreedom < 1 || degFreedom > 10 ** 10) throw FormulaError.NUM; return jStat.chisquare.inv(probability, degFreedom); }, 'CHISQ.INV.RT': (probability, degFreedom) => { probability = H.accept(probability, Types.NUMBER); degFreedom = H.accept(degFreedom, Types.NUMBER); degFreedom = Math.trunc(degFreedom); if (probability < 0 || probability > 1 || degFreedom < 1 || degFreedom > 10 ** 10) throw FormulaError.NUM; return jStat.chisquare.inv(1 - probability, degFreedom); }, 'CHISQ.TEST': (actualRange, expectedRange) => { const actual = H.accept(actualRange, Types.ARRAY, undefined, false, false); const expected = H.accept(expectedRange, Types.ARRAY, undefined, false, false); if (actual.length !== expected.length || actual[0].length !== expected[0].length || actual.length === 1 && actual[0].length === 1) throw FormulaError.NA; const row = actual.length; const col = actual[0].length; let dof = (row - 1) * (col - 1); if (row === 1) dof = col - 1; else dof = row - 1; let xsqr = 0; for (let i = 0; i < row; i++) { for (let j = 0; j < col; j++) { if (typeof actual[i][j] !== "number" || typeof expected[i][j] !== "number") continue; if (expected[i][j] === 0) throw FormulaError.DIV0; xsqr += Math.pow((actual[i][j] - expected[i][j]), 2) / expected[i][j]; } } // Get independent by X square and its degree of freedom let p = Math.exp(-0.5 * xsqr); if ((dof % 2) === 1) { p = p * Math.sqrt(2 * xsqr / Math.PI); } let k = dof; while (k >= 2) { p = p * xsqr / k; k = k - 2; } let t = p, a = dof; while (t > 0.000000000000001 * p) { a = a + 2; t = t * xsqr / a; p = p + t; } return 1 - p; }, 'CONFIDENCE.NORM': (alpha, std, size) => { alpha = H.accept(alpha, Types.NUMBER); std = H.accept(std, Types.NUMBER); size = H.accept(size, Types.NUMBER); size = Math.trunc(size); if (alpha <= 0 || alpha >= 1 || std <= 0 || size < 1) throw FormulaError.NUM; return jStat.normalci(1, alpha, std, size)[1] - 1; }, 'CONFIDENCE.T': (alpha, std, size) => { alpha = H.accept(alpha, Types.NUMBER); std = H.accept(std, Types.NUMBER); size = H.accept(size, Types.NUMBER); size = Math.trunc(size); if (alpha <= 0 || alpha >= 1 || std <= 0 || size < 1) throw FormulaError.NUM; if (size === 1) throw FormulaError.DIV0; return jStat.tci(1, alpha, std, size)[1] - 1; }, CORREL: (array1, array2) => { array1 = H.accept(array1, Types.ARRAY, undefined, true, true); array2 = H.accept(array2, Types.ARRAY, undefined, true, true); if (array1.length !== array2.length) throw FormulaError.NA; // filter out values that are not number const filterArr1 = [], filterArr2 = []; for (let i = 0; i < array1.length; i++) { if (typeof array1[i] !== "number" || typeof array2[i] !== "number") continue; filterArr1.push(array1[i]); filterArr2.push(array2[i]); } if (filterArr1.length <= 1) throw FormulaError.DIV0; return jStat.corrcoeff(filterArr1, filterArr2); }, 'COVARIANCE.P': (array1, array2) => { array1 = H.accept(array1, Types.ARRAY, undefined, true, true); array2 = H.accept(array2, Types.ARRAY, undefined, true, true); if (array1.length !== array2.length) throw FormulaError.NA; // filter out values that are not number const filterArr1 = [], filterArr2 = []; for (let i = 0; i < array1.length; i++) { if (typeof array1[i] !== "number" || typeof array2[i] !== "number") continue; filterArr1.push(array1[i]); filterArr2.push(array2[i]); } const mean1 = jStat.mean(filterArr1), mean2 = jStat.mean(filterArr2); let result = 0; for (let i = 0; i < filterArr1.length; i++) { result += (filterArr1[i] - mean1) * (filterArr2[i] - mean2); } return result / filterArr1.length; }, 'COVARIANCE.S': (array1, array2) => { array1 = H.accept(array1, Types.ARRAY, undefined, true, true); array2 = H.accept(array2, Types.ARRAY, undefined, true, true); if (array1.length !== array2.length) throw FormulaError.NA; // filter out values that are not number const filterArr1 = [], filterArr2 = []; for (let i = 0; i < array1.length; i++) { if (typeof array1[i] !== "number" || typeof array2[i] !== "number") continue; filterArr1.push(array1[i]); filterArr2.push(array2[i]); } if (filterArr1.length <= 1) throw FormulaError.DIV0; return jStat.covariance(filterArr1, filterArr2); }, DEVSQ: (...numbers) => { let sum = 0, x = []; // parse number only if the input is literal H.flattenParams(numbers, Types.NUMBER, true, (item, info) => { if (typeof item === "number") { sum += item; x.push(item); } }); const mean = sum / x.length; sum = 0; for (let i = 0; i < x.length; i++) { sum += (x[i] - mean) ** 2; } return sum; }, 'EXPON.DIST': (x, lambda, cumulative) => { x = H.accept(x, Types.NUMBER); lambda = H.accept(lambda, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); if (x < 0 || lambda <= 0) throw FormulaError.NUM; return cumulative ? jStat.exponential.cdf(x, lambda) : jStat.exponential.pdf(x, lambda); }, 'F.DIST': (x, d1, d2, cumulative) => { x = H.accept(x, Types.NUMBER); d1 = H.accept(d1, Types.NUMBER); d2 = H.accept(d2, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If x is negative, F.DIST returns the #NUM! error value. // If deg_freedom1 < 1, F.DIST returns the #NUM! error value. // If deg_freedom2 < 1, F.DIST returns the #NUM! error value. if (x < 0 || d1 < 1 || d2 < 1) { throw FormulaError.NUM; } // If deg_freedom1 or deg_freedom2 is not an integer, it is truncated. d1 = Math.trunc(d1); d2 = Math.trunc(d2); return cumulative ? jStat.centralF.cdf(x, d1, d2) : jStat.centralF.pdf(x, d1, d2); }, 'F.DIST.RT': (x, d1, d2) => { // David x = H.accept(x, Types.NUMBER); d1 = H.accept(d1, Types.NUMBER); d2 = H.accept(d2, Types.NUMBER); // If deg_freedom1 < 1 F.DIST.RT returns the #NUM! error value. // If deg_freedom2 < 1 F.DIST.RT returns the #NUM! error value. // If x is negative, F.DIST.RT returns the #NUM! error value. if (x < 0 || d1 < 1 || d2 < 1) { throw FormulaError.NUM; } // If deg_freedom1 or deg_freedom2 is not an integer, it is truncated. d1 = Math.trunc(d1); d2 = Math.trunc(d2); return 1 - jStat.centralF.cdf(x, d1, d2); }, 'F.INV': (probability, d1, d2) => { // David probability = H.accept(probability, Types.NUMBER); d1 = H.accept(d1, Types.NUMBER); d2 = H.accept(d2, Types.NUMBER); // If probability < 0 or probability > 1, F.INV returns the #NUM! error value. if (probability < 0.0 || probability > 1.0) { throw FormulaError.NUM; } // If deg_freedom1 < 1, or deg_freedom2 < 1, F.INV returns the #NUM! error value. if (d1 < 1.0 || d2 < 1.0) { throw FormulaError.NUM; } // If deg_freedom1 or deg_freedom2 is not an integer, it is truncated. d1 = Math.trunc(d1); d2 = Math.trunc(d2); return jStat.centralF.inv(probability, d1, d2); }, 'F.INV.RT': (probability, d1, d2) => { // David probability = H.accept(probability, Types.NUMBER); d1 = H.accept(d1, Types.NUMBER); d2 = H.accept(d2, Types.NUMBER); // If Probability is < 0 or probability is > 1, F.INV.RT returns the #NUM! error value. if (probability < 0.0 || probability > 1.0) { throw FormulaError.NUM; } // If Deg_freedom1 is < 1, or Deg_freedom2 is < 1, F.INV.RT returns the #NUM! error value. if (d1 < 1.0 || d1 >= Math.pow(10, 10)) { throw FormulaError.NUM; } // If Deg_freedom2 is < 1 or Deg_freedom2 is ≥ 10^10, F.INV.RT returns the #NUM! error value. if (d2 < 1.0 || d2 >= Math.pow(10, 10)) { throw FormulaError.NUM; } // If Deg_freedom1 or Deg_freedom2 is not an integer, it is truncated. d1 = Math.trunc(d1); d2 = Math.trunc(d2); return jStat.centralF.inv(1.0 - probability, d1, d2); }, /** * https://en.wikipedia.org/wiki/F-test_of_equality_of_variances */ 'F.TEST': (array1, array2) => { array1 = H.accept(array1, Types.ARRAY, undefined, true, true); array2 = H.accept(array2, Types.ARRAY, undefined, true, true); // filter out values that are not number const x1 = [], x2 = []; let x1Mean = 0, x2Mean = 0; for (let i = 0; i < Math.max(array1.length, array2.length); i++) { if (typeof array1[i] === "number") { x1.push(array1[i]); x1Mean += array1[i]; } if (typeof array2[i] === "number") { x2.push(array2[i]); x2Mean += array2[i]; } } if (x1.length <= 1 || x2.length <= 1) throw FormulaError.DIV0; x1Mean /= x1.length; x2Mean /= x2.length; let s1 = 0, s2 = 0; // sample variance S^2 for (let i = 0; i < x1.length; i++) { s1 += (x1Mean - x1[i]) ** 2 } s1 /= x1.length - 1; for (let i = 0; i < x2.length; i++) { s2 += (x2Mean - x2[i]) ** 2 } s2 /= x2.length - 1; // P(F<=f) one-tail * 2 return jStat.centralF.cdf(s1 / s2, x1.length - 1, x2.length - 1) * 2; }, FISHER: (x) => { // David x = H.accept(x, Types.NUMBER); // If x ≤ -1 or if x ≥ 1, FISHER returns the #NUM! error value. if (x <= -1 || x >= 1) { throw FormulaError.NUM; } return Math.log((1 + x) / (1 - x)) / 2; }, FISHERINV: (x) => { // David x = H.accept(x, Types.NUMBER); let e2y = Math.exp(2 * x); return (e2y - 1) / (e2y + 1); }, // FIXME FORECAST: (x, knownYs, knownXs) => { x = H.accept(x, Types.NUMBER); knownYs = H.accept(knownYs, Types.ARRAY, undefined, true, true); knownXs = H.accept(knownXs, Types.ARRAY, undefined, true, true); if (knownXs.length !== knownYs.length) throw FormulaError.NA; // filter out values that are not number const filteredY = [], filteredX = []; let xAllEqual = true; for (let i = 0; i < knownYs.length; i++) { if (typeof knownYs[i] !== "number" || typeof knownXs[i] !== "number") continue; filteredY.push(knownYs[i]); filteredX.push(knownXs[i]); if (knownXs[i] !== knownXs[0]) xAllEqual = false; } if (xAllEqual) throw FormulaError.DIV0; const yMean = jStat.mean(filteredY); const xMean = jStat.mean(filteredX); let numerator = 0, denominator = 0; for (let i = 0; i < filteredY.length; i++) { numerator += (filteredX[i] - xMean) * (filteredY[i] - yMean); denominator += (filteredX[i] - xMean) ** 2; } const b = numerator / denominator; const a = yMean - b * xMean; return a + b * x; }, 'FORECAST.ETS': () => { // skip, not yet possible to implement, may need tensorflow.js ? }, 'FORECAST.ETS.CONFINT': () => { // skip }, 'FORECAST.ETS.SEASONALITY': () => { // skip }, 'FORECAST.ETS.STAT': () => { // skip }, 'FORECAST.LINEAR': (...params) => { return DistributionFunctions.FORECAST(...params); }, FREQUENCY: (dataArray, binsArray) => { dataArray = H.accept(dataArray, Types.ARRAY, undefined, true, true); binsArray = H.accept(binsArray, Types.ARRAY, undefined, true, true); const binsArrayFiltered = []; for (let i = 0; i < binsArray.length; i++) { if (typeof binsArray[i] !== "number") continue; binsArrayFiltered.push(binsArray[i]); } binsArrayFiltered.sort(); binsArrayFiltered.push(Infinity); const result = []; for (let j = 0; j < binsArrayFiltered.length; j++) { result[j] = []; result[j][0] = 0; for (let i = 0; i < dataArray.length; i++) { if (typeof dataArray[i] !== "number") { continue; } const curr = dataArray[i]; if (curr <= binsArrayFiltered[j]) { result[j][0]++; dataArray[i] = null; } } } // return a 2d array return result; }, GAMMA: (x) => { // David // If Number contains characters that are not valid, GAMMA returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); // If Number is a negative integer or 0, GAMMA returns the #NUM! error value. if (x === 0 || (x < 0 && x === Math.trunc(x))) { throw FormulaError.NUM; } return jStat.gammafn(x); }, 'GAMMA.DIST': (x, alpha, beta, cumulative) => { // David // If x, alpha, or beta is nonnumeric, GAMMA.DIST returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); alpha = H.accept(alpha, Types.NUMBER); beta = H.accept(beta, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If x < 0, GAMMA.DIST returns the #NUM! error value. // If alpha ≤ 0 or if beta ≤ 0, GAMMA.DIST returns the #NUM! error value. if (x < 0 || alpha <= 0 || beta <= 0) { throw FormulaError.NUM; } return cumulative ? jStat.gamma.cdf(x, alpha, beta, true) : jStat.gamma.pdf(x, alpha, beta, false); }, 'GAMMA.INV': (probability, alpha, beta) => { // David // If any argument is text, GAMMA.INV returns the #VALUE! error value. probability = H.accept(probability, Types.NUMBER); alpha = H.accept(alpha, Types.NUMBER); beta = H.accept(beta, Types.NUMBER); // If probability < 0 or probability > 1, GAMMA.INV returns the #NUM! error value. // If alpha ≤ 0 or if beta ≤ 0, GAMMA.INV returns the #NUM! error value. if (probability < 0 || probability > 1 || alpha <= 0 || beta <= 0) { throw FormulaError.NUM; } return jStat.gamma.inv(probability, alpha, beta); }, GAMMALN: (x) => { // David x = H.accept(x, Types.NUMBER); // If x is nonnumeric, GAMMALN returns the #VALUE! error value. // If x ≤ 0, GAMMALN returns the #NUM! error value. if (x <= 0) { throw FormulaError.NUM; } return jStat.gammaln(x); }, 'GAMMALN.PRECISE': (x) => { // David // return distribution.GAMMALN(x); x = H.accept(x, Types.NUMBER); // If x is nonnumeric, GAMMALN returns the #VALUE! error value. // If x ≤ 0, GAMMALN returns the #NUM! error value. if (x <= 0) { throw FormulaError.NUM; } return jStat.gammaln(x); }, GAUSS: (z) => { // David // If z is not a valid number, GAUSS returns the #NUM! error value. // If z is not a valid data type, GAUSS returns the #VALUE! error value. z = H.accept(z, Types.NUMBER); return jStat.normal.cdf(z, 0, 1) - 0.5; }, GEOMEAN: (...numbers) => { // David const filterArr = []; // parse number only if the input is literal H.flattenParams(numbers, Types.NUMBER, true, (item, info) => { if (typeof item === "number") { filterArr.push(item); } }); return jStat.geomean(filterArr); }, GROWTH: (knownY, knownX, newX, useConst) => { // Credits: Ilmari Karonen (http://stackoverflow.com/questions/14161990/how-to-implement-growth-function-in-javascript) knownY = H.accept(knownY, Types.ARRAY, undefined, true, true); for (let i = 0; i < knownY.length; i++) { if (typeof knownY[i] !== "number") throw FormulaError.VALUE; } knownX = H.accept(knownX, Types.ARRAY, null, true, true); const isKnownXOmitted = knownX == null; if (knownX == null) { knownX = []; for (let i = 1; i <= knownY.length; i++) { knownX.push(i); } } else { if (knownX.length !== knownY.length) throw FormulaError.REF; for (let i = 0; i < knownX.length; i++) { if (typeof knownX[i] !== "number") throw FormulaError.VALUE; } } newX = H.accept(newX, Types.ARRAY, null, false, true); if (newX == null && isKnownXOmitted) { newX = []; for (let i = 1; i <= knownY.length; i++) { newX.push(i); } newX = [newX]; } else if (newX == null) { newX = Array.isArray(knownX[0]) ? knownX : [knownX]; } useConst = H.accept(useConst, Types.BOOLEAN, true); // Calculate sums over the data: const n = knownY.length; let avg_x = 0, avg_y = 0, avg_xy = 0, avg_xx = 0; for (let i = 0; i < n; i++) { const x = knownX[i]; const y = Math.log(knownY[i]); avg_x += x; avg_y += y; avg_xy += x * y; avg_xx += x * x; } avg_x /= n; avg_y /= n; avg_xy /= n; avg_xx /= n; // Compute linear regression coefficients: let beta; let alpha; if (useConst) { beta = (avg_xy - avg_x * avg_y) / (avg_xx - avg_x * avg_x); alpha = avg_y - beta * avg_x; } else { beta = avg_xy / avg_xx; alpha = 0; } // Compute and return result array: const new_y = []; for (let i = 0; i < newX.length; i++) { new_y[i] = []; for (let j = 0; j < newX[0].length; j++) { if (typeof newX[i][j] !== "number") throw FormulaError.VALUE; new_y[i][j] = Math.exp(alpha + beta * newX[i][j]); } } return new_y; }, HARMEAN: (...numbers) => { let cnt = 0, denominator = 0; // parse number only if the input is literal H.flattenParams(numbers, Types.NUMBER, true, (item, info) => { if (typeof item === "number") { denominator += 1 / item; cnt++; } }); return cnt / denominator; }, 'HYPGEOM.DIST': (sample_s, number_sample, population_s, number_pop, cumulative) => { // num_successes, num_draws, successes_in_pop, pop_size // If any argument is nonnumeric, HYPGEOM.DIST returns the #VALUE! error value. sample_s = H.accept(sample_s, Types.NUMBER); number_sample = H.accept(number_sample, Types.NUMBER); population_s = H.accept(population_s, Types.NUMBER); number_pop = H.accept(number_pop, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // All arguments are truncated to integers. sample_s = Math.trunc(sample_s); number_sample = Math.trunc(number_sample); population_s = Math.trunc(population_s); number_pop = Math.trunc(number_pop); // // If number_pop ≤ 0, HYPGEOM.DIST returns the #NUM! error value. if (number_pop <= 0 || sample_s < 0 || number_sample <= 0 || population_s <= 0) { throw FormulaError.NUM; } // // If number_sample ≤ 0 or number_sample > number_population, HYPGEOM.DIST returns the #NUM! error value. if (number_sample > number_pop) { throw FormulaError.NUM; } // // If population_s ≤ 0 or population_s > number_population, HYPGEOM.DIST returns the #NUM! error value. if (population_s > number_pop) { throw FormulaError.NUM; } // If sample_s < 0 or sample_s is greater than the lesser of number_sample or population_s, HYPGEOM.DIST returns the #NUM! error value. // Google and Mircrosoft has different version on this funtion if (number_sample < sample_s || population_s < sample_s) { throw FormulaError.NUM; } // If sample_s is less than the larger of 0 or (number_sample - number_population + population_s), HYPGEOM.DIST returns the #NUM! error value. if (sample_s < (number_sample - number_pop + population_s)) { throw FormulaError.NUM; } function pdf(x, n, M, N) { return MathFunctions.COMBIN(M, x) * MathFunctions.COMBIN(N - M, n - x) / MathFunctions.COMBIN(N, n); } function cdf(x, n, M, N) { let result = 0; for (let i = 0; i <= x; i++) { result += pdf(i, n, M, N); } return result; } return cumulative ? cdf(sample_s, number_sample, population_s, number_pop) : pdf(sample_s, number_sample, population_s, number_pop); }, INTERCEPT: (knownYs, knownXs) => { // similar to FORECAST knownYs = H.accept(knownYs, Types.ARRAY, undefined, true, true); knownXs = H.accept(knownXs, Types.ARRAY, undefined, true, true); if (knownXs.length !== knownYs.length) throw FormulaError.NA; // filter out values that are not number const filteredY = [], filteredX = []; for (let i = 0; i < knownYs.length; i++) { if (typeof knownYs[i] !== "number" || typeof knownXs[i] !== "number") continue; filteredY.push(knownYs[i]); filteredX.push(knownXs[i]); } if (filteredY.length <= 1) throw FormulaError.DIV0; const yMean = jStat.mean(filteredY); const xMean = jStat.mean(filteredX); let numerator = 0, denominator = 0; for (let i = 0; i < filteredY.length; i++) { numerator += (filteredX[i] - xMean) * (filteredY[i] - yMean); denominator += (filteredX[i] - xMean) ** 2; } const b = numerator / denominator; return yMean - b * xMean; }, KURT: (...numbers) => { let mean = 0, range = []; // parse number only if the input is literal H.flattenParams(numbers, Types.NUMBER, true, (item, info) => { if (typeof item === "number") { mean += item; range.push(item); } }); const n = range.length; mean /= n; let sigma = 0; for (let i = 0; i < n; i++) { sigma += Math.pow(range[i] - mean, 4); } sigma = sigma / Math.pow(jStat.stdev(range, true), 4); return ((n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3))) * sigma - 3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)); }, LINEST: () => { }, LOGEST: () => { }, 'LOGNORM.DIST': (x, mean, standard_dev, cumulative) => { // if any argument is nonnumeric, LOGNORM.DIST returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); mean = H.accept(mean, Types.NUMBER); standard_dev = H.accept(standard_dev, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If x ≤ 0 or if standard_dev ≤ 0, LOGNORM.DIST returns the #NUM! error value. if (x <= 0 || standard_dev <= 0) { throw FormulaError.NUM; } return cumulative ? jStat.lognormal.cdf(x, mean, standard_dev) : jStat.lognormal.pdf(x, mean, standard_dev); }, 'LOGNORM.INV': (probability, mean, standard_dev) => { // If any argument is nonnumeric, LOGNORM.INV returns the #VALUE! error value. probability = H.accept(probability, Types.NUMBER); mean = H.accept(mean, Types.NUMBER); standard_dev = H.accept(standard_dev, Types.NUMBER); // If probability <= 0 or probability >= 1, LOGNORM.INV returns the #NUM! error value. if (probability <= 0 || probability >= 1) { throw FormulaError.NUM; } // If standard_dev <= 0, LOGNORM.INV returns the #NUM! error value. if (standard_dev <= 0) { throw FormulaError.NUM; } return jStat.lognormal.inv(probability, mean, standard_dev); }, 'MODE.MULT': () => { }, 'MODE.SNGL': () => { }, 'NEGBINOM.DIST': (number_f, number_s, probability_s, cumulative) => { // If any argument is nonnumeric, NEGBINOM.DIST returns the #VALUE! error value. number_f = H.accept(number_f, Types.NUMBER); number_s = H.accept(number_s, Types.NUMBER); probability_s = H.accept(probability_s, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // Number_f and number_s are truncated to integers. number_f = Math.trunc(number_f); number_s = Math.trunc(number_s); // If probability_s < 0 or if probability > 1, NEGBINOM.DIST returns the #NUM! error value. if (probability_s < 0 || probability_s > 1) { throw FormulaError.NUM; } // If number_f < 0 or number_s < 1, NEGBINOM.DIST returns the #NUM! error value. if (number_f < 0 || number_s < 1) { throw FormulaError.NUM; } return cumulative ? jStat.negbin.cdf(number_f, number_s, probability_s) : jStat.negbin.pdf(number_f, number_s, probability_s); }, 'NORM.DIST': (x, mean, standard_dev, cumulative) => { // If mean or standard_dev is nonnumeric, NORM.DIST returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); mean = H.accept(mean, Types.NUMBER); standard_dev = H.accept(standard_dev, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If standard_dev ≤ 0, NORM.DIST returns the #NUM! error value. if (standard_dev <= 0) { throw FormulaError.NUM; } // If mean = 0, standard_dev = 1, and cumulative = TRUE, NORM.DIST returns the standard normal distribution, NORM.S.DIST. return cumulative ? jStat.normal.cdf(x, mean, standard_dev) : jStat.normal.pdf(x, mean, standard_dev); }, 'NORM.INV': (probability, mean, standard_dev) => { // If any argument is nonnumeric, NORM.INV returns the #VALUE! error value. probability = H.accept(probability, Types.NUMBER); mean = H.accept(mean, Types.NUMBER); standard_dev = H.accept(standard_dev, Types.NUMBER); // If probability <= 0 or if probability >= 1, NORM.INV returns the #NUM! error value. if (probability <= 0 || probability >= 1) { throw FormulaError.NUM; } // If standard_dev ≤ 0, NORM.INV returns the #NUM! error value. if (standard_dev <= 0) { throw FormulaError.NUM; } // If mean = 0 and standard_dev = 1, NORM.INV uses the standard normal distribution (see NORMS.INV). // if(mean === 0 && standard_dev === 1){ // } return jStat.normal.inv(probability, mean, standard_dev); }, 'NORM.S.DIST': (z, cumulative) => { // If z is nonnumeric, NORM.S.DIST returns the #VALUE! error value. z = H.accept(z, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); return (cumulative) ? jStat.normal.cdf(z, 0, 1) : jStat.normal.pdf(z, 0, 1); }, 'NORM.S.INV': (probability) => { // If probability is nonnumeric, NORMS.INV returns the #VALUE! error value. probability = H.accept(probability, Types.NUMBER); // If probability <= 0 or if probability >= 1, NORMS.INV returns the #NUM! error value. if (probability <= 0 || probability >= 1) { throw FormulaError.NUM; } return jStat.normal.inv(probability, 0, 1); }, PEARSON: () => { }, 'PERCENTILE.EXC': () => { }, 'PERCENTILE.INC': () => { }, 'PERCENTRANK.EXC': () => { }, 'PERCENTRANK.INC': () => { }, PERMUTATIONA: () => { }, PHI: (x) => { // If x is a numeric value that is not valid, PHI returns the #NUM! error value. x = H.accept(x, Types.NUMBER); return Math.exp(-0.5 * x * x) / SQRT2PI; }, 'POISSON.DIST': (x, mean, cumulative) => { // If x or mean is nonnumeric, POISSON.DIST returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); mean = H.accept(mean, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If x < 0, POISSON.DIST returns the #NUM! error value. // If mean < 0, POISSON.DIST returns the #NUM! error value. if (x < 0 || mean < 0) { throw FormulaError.NUM; } // If x is not an integer, it is truncated. x = Math.trunc(x); return cumulative ? jStat.poisson.cdf(x, mean) : jStat.poisson.pdf(x, mean); }, 'PROB': () => { }, 'QUARTILE.EXC': () => { }, 'QUARTILE.INC': () => { }, 'RANK.AVG': () => { }, 'RANK.EQ': () => { }, RSQ: () => { }, SKEW: () => { }, 'SKEW.P': () => { }, SLOPE: () => { }, STANDARDIZE: (x, mean, standard_dev) => { x = H.accept(x, Types.NUMBER); mean = H.accept(mean, Types.NUMBER); standard_dev = H.accept(standard_dev, Types.NUMBER); // If standard_dev ≤ 0, STANDARDIZE returns the #NUM! error value. if (standard_dev <= 0) { throw FormulaError.NUM; } return (x - mean) / standard_dev; }, 'STDEV.P': () => { }, 'STDEV.S': () => { }, STDEVA: () => { }, STDEVPA: () => { }, STEYX: () => { }, 'T.DIST': (x, deg_freedom, cumulative) => { // If any argument is nonnumeric, T.DIST returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); deg_freedom = H.accept(deg_freedom, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If deg_freedom < 1, T.DIST returns an error value. Deg_freedom needs to be at least 1. if (deg_freedom < 1) { throw FormulaError.NUM; } return cumulative ? jStat.studentt.cdf(x, deg_freedom) : jStat.studentt.pdf(x, deg_freedom); }, 'T.DIST.2T': (x, deg_freedom) => { // If any argument is nonnumeric, T.DIST.2T returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); deg_freedom = H.accept(deg_freedom, Types.NUMBER); // If deg_freedom < 1, T.DIST.2T returns the #NUM! error value. // If x < 0, then T.DIST.2T returns the #NUM! error value. if (deg_freedom < 1 || x < 0) { throw FormulaError.NUM; } return (1 - jStat.studentt.cdf(x, deg_freedom)) * 2; }, 'T.DIST.RT': (x, deg_freedom) => { // If any argument is nonnumeric, T.DIST.RT returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); deg_freedom = H.accept(deg_freedom, Types.NUMBER); // If deg_freedom < 1, T.DIST.RT returns the #NUM! error value. if (deg_freedom < 1) { throw FormulaError.NUM; } return 1 - jStat.studentt.cdf(x, deg_freedom); }, 'T.INV': (probability, deg_freedom) => { // If either argument is nonnumeric, T.INV returns the #VALUE! error value. probability = H.accept(probability, Types.NUMBER); deg_freedom = H.accept(deg_freedom, Types.NUMBER); // If probability <= 0 or if probability > 1, T.INV returns the #NUM! error value. // If deg_freedom < 1, T.INV returns the #NUM! error value. if (probability <= 0 || probability > 1 || deg_freedom < 1) { throw FormulaError.NUM; } // If deg_freedom is not an integer, it is truncated. deg_freedom = deg_freedom % 1 === 0 ? deg_freedom : Math.trunc(deg_freedom); return jStat.studentt.inv(probability, deg_freedom); }, 'T.INV.2T': (probability, deg_freedom) => { // If either argument is nonnumeric, T.INV.2T returns the #VALUE! error value. probability = H.accept(probability, Types.NUMBER); deg_freedom = H.accept(deg_freedom, Types.NUMBER); // If probability <= 0 or if probability > 1, T.INV.2T returns the #NUM! error value. // If deg_freedom < 1, T.INV.2T returns the #NUM! error value. if (probability <= 0 || probability > 1 || deg_freedom < 1) { throw FormulaError.NUM; } // If deg_freedom is not an integer, it is truncated. deg_freedom = deg_freedom % 1 === 0 ? deg_freedom : Math.trunc(deg_freedom); return Math.abs(jStat.studentt.inv(probability / 2, deg_freedom)); }, 'T.TEST': () => { }, TREND: () => { }, TRIMMEAN: () => { }, 'VAR.P': () => { }, 'VAR.S': () => { }, 'VARA': () => { }, 'VARPA': () => { }, 'WEIBULL.DIST': (x, alpha, beta, cumulative) => { // If x, alpha, or beta is nonnumeric, WEIBULL.DIST returns the #VALUE! error value. x = H.accept(x, Types.NUMBER); alpha = H.accept(alpha, Types.NUMBER); beta = H.accept(beta, Types.NUMBER); cumulative = H.accept(cumulative, Types.BOOLEAN); // If x < 0, WEIBULL.DIST returns the #NUM! error value. // If alpha ≤ 0 or if beta ≤ 0, WEIBULL.DIST returns the #NUM! error value. if (x < 0 || alpha <= 0 || beta <= 0) { throw FormulaError.NUM; } return cumulative ? 1 - Math.exp(-Math.pow(x / beta, alpha)) : Math.pow(x, alpha - 1) * Math.exp(-Math.pow(x / beta, alpha)) * alpha / Math.pow(beta, alpha); }, 'Z.TEST': () => { } }; module.exports = { DistributionFunctions, };