/* Chalkboard - Statistics Namespace Version 3.0.4 Euler Released July 20th, 2026 */ /* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /// namespace Chalkboard { /** * The statistics namespace. * @namespace */ export namespace stat { /** @ignore */ const $quickselect = (arr: number[], k: number): number => { const select = (left: number, right: number, k: number): number => { if (left === right) return arr[left]; let pivotIndex = Math.floor(Math.random() * (right - left + 1)) + left; const pivotValue = arr[pivotIndex]; arr[pivotIndex] = arr[right]; arr[right] = pivotValue; let storeIndex = left; for (let i = left; i < right; i++) { if (arr[i] < pivotValue) { const temp = arr[storeIndex]; arr[storeIndex] = arr[i]; arr[i] = temp; storeIndex++; } } arr[right] = arr[storeIndex]; arr[storeIndex] = pivotValue; if (k === storeIndex) return arr[k]; else if (k < storeIndex) return select(left, storeIndex - 1, k); else return select(storeIndex + 1, right, k); }; return select(0, arr.length - 1, k); }; /** * Calculates the absolute value of all the elements of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.absolute([-3, 4, -5]); // Returns [3, 4, 5] */ export const absolute = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.absolute: Parameter "arr" must be an array of finite numbers.`); const result: number[] = []; for (let i = 0; i < arr.length; i++) { result.push(Math.abs(arr[i])); } return result; }; /** * Calculates the addition of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number[]} * @example * const result = Chalkboard.stat.add([1, 1, 2, 3], [1, 2, 3, 5]); // Returns [2, 3, 5, 8] */ export const add = (arr1: number[], arr2: number[]): number[] => { if (arr1.length !== arr2.length) throw new Error(`Chalkboard.stat.add: Parameters "arr1" and "arr2" must have the same length.`); const result: number[] = []; for (let i = 0; i < arr1.length; i++) { result.push(arr1[i] + arr2[i]); } return result; }; /** * Returns an array with linearly-spaced elements. * @param {number} inf - The lower bound * @param {number} sup - The upper bound * @param {number} [length=sup-inf+1] - The length of the array * @returns {number[]} * @example * const result = Chalkboard.stat.array(-2, 2); // Returns [-2, -1, 0, 1, 2] */ export const array = (inf: number, sup: number, length: number = sup - inf + 1): number[] => { if (!Number.isFinite(inf)) throw new Error(`Chalkboard.stat.array: Parameter "inf" must be a finite number.`); if (!Number.isFinite(sup)) throw new Error(`Chalkboard.stat.array: Parameter "sup" must be a finite number.`); if (!Number.isInteger(length) || length < 2) throw new Error(`Chalkboard.stat.array: Parameter "length" must be an integer greater than or equal to 2.`); const result = []; const step = (sup - inf) / (length - 1); for (let i = 0; i < length; i++) { result.push(inf + step * i); } return result; }; /** * Calculates the autocorrelation of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.autocorrelation([1, 0, -1, 0]); // Returns the sequence's autocorrelation */ export const autocorrelation = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.autocorrelation: Parameter "arr" must be an array of finite numbers.`); return Chalkboard.stat.correlation(arr, arr); }; /** * Calculates the posterior probability using Bayes' theorem. * @param {number} pA - The prior probability of A (i.e. P(A)) * @param {number} pGivenA - The probability of B given A (i.e. P(B|A)) * @param {number} pGivenNotA - The probability of B given not A (i.e. P(B|!A)) * @returns {number} * @example * const result = Chalkboard.stat.Bayes(0.01, 0.99, 0.05); // Returns the posterior probability */ export const Bayes = (pA: number, pGivenA: number, pGivenNotA: number): number => { if (pA < 0 || pA > 1 || pGivenA < 0 || pGivenA > 1 || pGivenNotA < 0 || pGivenNotA > 1) throw new Error(`Chalkboard.stat.Bayes: All probabilities must be between 0 and 1.`); return (pGivenA * pA) / (pGivenA * pA + pGivenNotA * (1 - pA)); }; /** * Calculates the change of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number[]} * @example * const result = Chalkboard.stat.change([2, 4, 8], [3, 8, 4]); // Returns [1, 4, -4] */ export const change = (arr1: number[], arr2: number[]): number[] => { if (arr1.length !== arr2.length) throw new Error(`Chalkboard.stat.change: Parameters "arr1" and "arr2" must have the same length.`); const result = []; for (let i = 0; i < arr1.length; i++) { result.push(Chalkboard.numb.change(arr1[i], arr2[i])); } return result; }; /** * Calculates the chi-squared test of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number[]} * @example * const result = Chalkboard.stat.chiSquared([20, 30, 50], [25, 25, 50]); // Returns the componentwise chi-squared contributions */ export const chiSquared = (arr1: number[], arr2: number[]): number[] => { if (arr1.length !== arr2.length) throw new Error(`Chalkboard.stat.chiSquared: Parameters "arr1" and "arr2" must have the same length.`); const result = []; for (let i = 0; i < arr1.length; i++) { result.push(((arr1[i] - arr2[i]) * (arr1[i] - arr2[i])) / arr2[i]); } return result; }; /** * Calculates the 95% confidence interval of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.confidenceInterval([2, 4, 4, 4, 5, 5, 7, 9]); // Returns a 95% confidence interval */ export const confidenceInterval = (arr: number[], confidence: number = 0.95): [number, number] => { if (confidence <= 0 || confidence >= 1) throw new Error(`Chalkboard.stat.confidenceInterval: Parameter "confidence" must be between 0 and 1 (exclusive).`); const z = Chalkboard.stat.inormal(1 - (1 - confidence) / 2); const mean = Chalkboard.stat.mean(arr); const standardError = Chalkboard.stat.error(arr); return [mean - z * standardError, mean + z * standardError]; }; /** * Returns an array constrained within a range. * @param {number[]} arr - The array * @param {number[]} range - The range * @returns {number[]} * @example * const result = Chalkboard.stat.constrain([-2, 0, 3], [0, 1]); // Returns [0, 0, 1] */ export const constrain = (arr: number[], range: [number, number] = [0, 1]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.constrain: Parameter "arr" must be an array of finite numbers.`); const result = []; for (let i = 0; i < arr.length; i++) { result.push(Chalkboard.numb.constrain(arr[i], range)); } return result; }; /** * Calculates the convolution of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number[]} * @example * const result = Chalkboard.stat.convolution([1, 2, 1], [1, -1]); // Returns the discrete convolution */ export const convolution = (arr1: number[], arr2: number[]): number[] => { if (!Array.isArray(arr1)) throw new Error(`Chalkboard.stat.convolution: Parameter "arr1" must be an array of finite numbers.`); if (!Array.isArray(arr2)) throw new Error(`Chalkboard.stat.convolution: Parameter "arr2" must be an array of finite numbers.`); const result = []; for (let i = 0; i < arr1.length + arr2.length - 1; i++) { let sum = 0; for (let j = Math.max(0, i - arr2.length + 1); j < Math.min(arr1.length, i + 1); j++) { sum += arr1[j] * arr2[i - j]; } result.push(sum); } return result; }; /** * Calculates the cross-correlation of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number[]} * @example * const result = Chalkboard.stat.correlation([1, 2, 3], [3, 2, 1]); // Returns the cross-correlation */ export const correlation = (arr1: number[], arr2: number[]): number[] => { if (!Array.isArray(arr1)) throw new Error(`Chalkboard.stat.correlation: Parameter "arr1" must be an array of finite numbers.`); if (!Array.isArray(arr2)) throw new Error(`Chalkboard.stat.correlation: Parameter "arr2" must be an array of finite numbers.`); const result = []; for (let i = 0; i < arr1.length + arr2.length - 1; i++) { let sum = 0; for (let j = Math.max(0, i - arr2.length + 1); j < Math.min(arr1.length, i + 1); j++) { sum += arr1[j] * arr2[arr2.length - 1 - i + j]; } result.push(sum); } return result; }; /** * Calculates the Pearson correlation coefficient of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number} * @example * const result = Chalkboard.stat.correlationCoefficient([1, 2, 3], [2, 4, 6]); // Returns 1 */ export const correlationCoefficient = (arr1: number[], arr2: number[]): number => { if (!Array.isArray(arr1)) throw new Error(`Chalkboard.stat.correlationCoefficient: Parameter "arr1" must be an array of finite numbers.`); if (!Array.isArray(arr2)) throw new Error(`Chalkboard.stat.correlationCoefficient: Parameter "arr2" must be an array of finite numbers.`); return Chalkboard.stat.covariance(arr1, arr2) / (Chalkboard.stat.deviation(arr1) * Chalkboard.stat.deviation(arr2)); }; /** * Calculates the covariance of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number} * @example * const result = Chalkboard.stat.covariance([1, 2, 3], [2, 4, 6]); // Returns the covariance */ export const covariance = (arr1: number[], arr2: number[]): number => { if (arr1.length !== arr2.length) throw new Error(`Chalkboard.stat.covariance: Parameters "arr1" and "arr2" must have the same length.`); const mean1 = Chalkboard.stat.mean(arr1); const mean2 = Chalkboard.stat.mean(arr2); let sum = 0; for (let i = 0; i < arr1.length; i++) { sum += (arr1[i] - mean1) * (arr2[i] - mean2); } return sum / arr1.length; }; /** * Calculates the cumulative maximum of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.cummax([3, 1, 4, 2, 5]); // Returns [3, 3, 4, 4, 5] */ export const cummax = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.cummax: Parameter "arr" must be an array of finite numbers.`); const result = []; let max = -Infinity; for (const value of arr) { max = Math.max(max, value); result.push(max); } return result; }; /** * Calculates the cumulative minimum of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.cummin([3, 1, 4, 0, 5]); // Returns [3, 1, 1, 0, 0] */ export const cummin = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.cummin: Parameter "arr" must be an array of finite numbers.`); const result = []; let min = Infinity; for (const value of arr) { min = Math.min(min, value); result.push(min); } return result; }; /** * Calculates the cumulative product of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.cummul([1, 2, 3, 4]); // Returns [1, 2, 6, 24] */ export const cummul = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.cummul: Parameter "arr" must be an array of finite numbers.`); const result = []; let mul = 1; for (let i = 0; i < arr.length; i++) { mul *= arr[i]; result.push(mul); } return result; }; /** * Calculates the cumulative sum of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.cumsum([1, 2, 3, 4]); // Returns [1, 3, 6, 10] */ export const cumsum = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.cumsum: Parameter "arr" must be an array of finite numbers.`); const result = []; let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i]; result.push(sum); } return result; }; /** * Calculates the standard deviation of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.deviation([2, 4, 4, 4, 5, 5, 7, 9]); // Returns 2 */ export const deviation = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.deviation: Parameter "arr" must be an array of finite numbers.`); let result = 0; for (let i = 0; i < arr.length; i++) { result += (arr[i] - Chalkboard.stat.mean(arr)) * (arr[i] - Chalkboard.stat.mean(arr)); } return Chalkboard.real.sqrt(result / arr.length); }; /** * Calculates the dot product of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number} * @example * const result = Chalkboard.stat.dot([1, 2, 3], [4, 5, 6]); // Returns 32 */ export const dot = (arr1: number[], arr2: number[]): number => { if (arr1.length !== arr2.length) throw new Error(`Chalkboard.stat.dot: Parameters "arr1" and "arr2" must have the same length.`); let result = 0; for (let i = 0; i < arr1.length; i++) { result += arr1[i] * arr2[i]; } return result; }; /** * Calculates the standard error of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.error([2, 4, 4, 4, 5, 5, 7, 9]); // Returns the standard error */ export const error = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.error: Parameter "arr" must be an array of finite numbers.`); return Chalkboard.stat.deviation(arr) / Chalkboard.real.sqrt(arr.length); }; /** * Checks if the elements of an array are equal to a number or the elements of another array, and then returns an array with the elements that pass the check. * @param {number[]} arr - The array * @param {number | {number}[]} arrORnum - The array or number * @returns {number[]} * @example * const result = Chalkboard.stat.eq([1, 2, 2, 3], 2); // Returns the values equal to 2 */ export const eq = (arr: number[], arrORnum: number | number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.eq: Parameter "arr" must be an array of finite numbers.`); if ((typeof arrORnum !== "number" || Number.isNaN(arrORnum)) && !Array.isArray(arrORnum)) throw new Error(`Chalkboard.stat.eq: Parameter "arrORnum" must be a number or an array.`); if (Array.isArray(arrORnum) && arr.length !== arrORnum.length) throw new Error(`Chalkboard.stat.eq: Parameters "arr" and "arrORnum" must have the same length when "arrORnum" is an array.`); const result = []; if (Array.isArray(arrORnum)) { if (arr.length === arrORnum.length) { for (let i = 0; i < arr.length; i++) { if (arr[i] === arrORnum[i]) { result.push(arr[i]); } } } } else { for (let i = 0; i < arr.length; i++) { if (arr[i] === arrORnum) { result.push(arr[i]); } } } return result; }; /** * Calculates the expected value of an array. * @param {number[]} arr - The array * @param {number[]} [probabilities] - The probabilities of the corresponding elements of the array (optional, defaults to equiprobable) * @returns {number} * @example * const result = Chalkboard.stat.expected([0, 1, 2], [0.25, 0.5, 0.25]); // Returns 1 */ export const expected = (arr: number[], probabilities?: number[]): number => { if (!Array.isArray(arr) || arr.length === 0) throw new Error(`Chalkboard.stat.expected: Parameter "arr" must be a non-empty array.`); if (probabilities !== undefined && !Array.isArray(probabilities)) throw new Error(`Chalkboard.stat.expected: Parameter "probabilities" must be an array.`); if (!probabilities) { probabilities = Array(arr.length).fill(1 / arr.length); } if (arr.length !== probabilities.length) throw new Error(`Chalkboard.stat.expected: Parameters "arr" and "probabilities" must have the same length.`); let result = 0; for (let i = 0; i < arr.length; i++) { result += arr[i] * probabilities[i]; } return result; }; /** * Defines a Gaussian function. * @param {number} height - The height of the distribution * @param {number} mean - The mean of the distribution * @param {number} deviation - The standard deviation of the distribution * @returns {ChalkboardFunction} * @example * const bell = Chalkboard.stat.Gaussian(1, 0, 1); * const peak = Chalkboard.real.val(bell, 0); // Returns 1 */ export const Gaussian = (height: number, mean: number, deviation: number): ChalkboardFunction => { if (!Number.isFinite(height)) throw new Error(`Chalkboard.stat.Gaussian: Parameter "height" must be a finite number.`); if (!Number.isFinite(mean)) throw new Error(`Chalkboard.stat.Gaussian: Parameter "mean" must be a finite number.`); if (!Number.isFinite(deviation) || deviation <= 0) throw new Error(`Chalkboard.stat.Gaussian: Parameter "deviation" must be a positive finite number.`); return Chalkboard.real.define((x) => height * Math.exp(-((x - mean) * (x - mean)) / (2 * deviation * deviation))); }; /** * Checks if the elements of an array are greater than (or equal to) a number or the elements of another array, and then returns an array with the elements that pass the check. * @param {number[]} arr - The array * @param {number | number[]} arrORnum - The array or number * @param {boolean} [includeEnd=false] - Whether the check is "less than" (false) or "less than or equal to" (true) * @returns {number[]} * @example * const result = Chalkboard.stat.gt([1, 2, 3, 4], 2); // Returns [3, 4] */ export const gt = (arr: number[], arrORnum: number | number[], includeEnd: boolean = false): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.gt: Parameter "arr" must be an array of finite numbers.`); if ((typeof arrORnum !== "number" || Number.isNaN(arrORnum)) && !Array.isArray(arrORnum)) throw new Error(`Chalkboard.stat.gt: Parameter "arrORnum" must be a number or an array.`); if (Array.isArray(arrORnum) && arr.length !== arrORnum.length) throw new Error(`Chalkboard.stat.gt: Parameters "arr" and "arrORnum" must have the same length when "arrORnum" is an array.`); if (includeEnd !== undefined && (typeof includeEnd !== "boolean")) throw new Error(`Chalkboard.stat.gt: Parameter "includeEnd" must be a boolean.`); const result = []; if (Array.isArray(arrORnum)) { if (arr.length === arrORnum.length) { for (let i = 0; i < arr.length; i++) { if (includeEnd) { if (arr[i] >= arrORnum[i]) { result.push(arr[i]); } } else { if (arr[i] > arrORnum[i]) { result.push(arr[i]); } } } } } else { for (let i = 0; i < arr.length; i++) { if (includeEnd) { if (arr[i] >= arrORnum) { result.push(arr[i]); } } else { if (arr[i] > arrORnum) { result.push(arr[i]); } } } } return result; }; /** * Checks if the elements of an array are less than (or equal to) and greater than (or equal to) a number or the elements of another array, and then returns an array with the elements that pass the check. * @param {number[]} arr - The array * @param {number | number[]} inf - The array or number to check "less than (or equal to)" with * @param {number | number[]} sup - The array or number to check "greater than (or equal to)" with * @param {boolean} [includeInf=false] - Whether the check is "less than" (false) or "less than or equal to" (true) * @param {boolean} [includeSup=false] - Whether the check is "greater than" (false) or "greater than or equal to" (true) * @returns {number[]} * @example * const result = Chalkboard.stat.ineq([1, 2, 3, 4, 5], 2, 4, true, true); // Returns [2, 3, 4] */ export const ineq = (arr: number[], inf: number | number[], sup: number | number[], includeInf: boolean = false, includeSup: boolean = false): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.ineq: Parameter "arr" must be an array of finite numbers.`); if ((typeof inf !== "number" || Number.isNaN(inf)) && !Array.isArray(inf)) throw new Error(`Chalkboard.stat.ineq: Parameter "inf" must be a number or an array.`); if ((typeof sup !== "number" || Number.isNaN(sup)) && !Array.isArray(sup)) throw new Error(`Chalkboard.stat.ineq: Parameter "sup" must be a number or an array.`); if (Array.isArray(inf) !== Array.isArray(sup)) throw new Error(`Chalkboard.stat.ineq: Parameters "inf" and "sup" must either both be numbers or both be arrays.`); if (Array.isArray(inf) && Array.isArray(sup) && (arr.length !== inf.length || arr.length !== sup.length)) throw new Error(`Chalkboard.stat.ineq: Parameters "arr", "inf", and "sup" must have the same length when "inf" and "sup" are arrays.`); if (includeInf !== undefined && (typeof includeInf !== "boolean")) throw new Error(`Chalkboard.stat.ineq: Parameter "includeInf" must be a boolean.`); if (includeSup !== undefined && (typeof includeSup !== "boolean")) throw new Error(`Chalkboard.stat.ineq: Parameter "includeSup" must be a boolean.`); const result = []; if (Array.isArray(inf) && Array.isArray(sup)) { if (arr.length === inf.length && arr.length === sup.length) { for (let i = 0; i < arr.length; i++) { if (includeInf) { if (includeSup) { if (arr[i] >= inf[i] && arr[i] <= sup[i]) { result.push(arr[i]); } } else { if (arr[i] >= inf[i] && arr[i] < sup[i]) { result.push(arr[i]); } } } else { if (includeSup) { if (arr[i] > inf[i] && arr[i] <= sup[i]) { result.push(arr[i]); } } else { if (arr[i] > inf[i] && arr[i] < sup[i]) { result.push(arr[i]); } } } } } } else { const infNum = inf as number; const supNum = sup as number; for (let i = 0; i < arr.length; i++) { if (includeInf) { if (includeSup) { if (arr[i] >= infNum && arr[i] <= supNum) { result.push(arr[i]); } } else { if (arr[i] >= infNum && arr[i] < supNum) { result.push(arr[i]); } } } else { if (includeSup) { if (arr[i] > infNum && arr[i] <= supNum) { result.push(arr[i]); } } else { if (arr[i] > infNum && arr[i] < supNum) { result.push(arr[i]); } } } } } return result; }; /** * Calculates an approximation of the inverse of the cumulative distribution function (CDF) of the standard normal distribution using the Beasley-Springer-Moro algorithm. * @param {number} p - The probability (must be between 0 and 1) * @returns {number} * @example * const result = Chalkboard.stat.inormal(0.975); // Returns approximately 1.96 */ export const inormal = (p: number): number => { if (!Number.isFinite(p) || p <= 0 || p >= 1) throw new Error(`Chalkboard.stat.inormal: Parameter "p" must be a finite number strictly between 0 and 1.`); const a = [2.50662823884, -18.61500062529, 41.39119773534, -25.44106049637]; const b = [-8.4735109309, 23.08336743743, -21.06224101826, 3.13082909833]; const c = [0.3374754822726147, 0.9761690190917186, 0.1607979714918209, 0.0276438810333863, 0.0038405729373609, 0.0003951896511919, 0.0000321767881768, 0.0000002888167364, 0.0000003960315187]; let x = p - 0.5; if (Math.abs(x) < 0.42) { const r = x * x; return x * (((a[3] * r + a[2]) * r + a[1]) * r + a[0]) / ((((b[3] * r + b[2]) * r + b[1]) * r + b[0]) * r + 1); } else { const r = p < 0.5 ? p : 1 - p; const s = Math.log(-Math.log(r)); let t = c[0]; for (let i = 1; i < c.length; i++) { t += c[i] * Math.pow(s, i); } return p < 0.5 ? -t : t; } }; /** * Interpolates missing values (null or undefined) in an array using linear or quadratic interpolation. * @param {(number | null | undefined)[]} arr - The array with missing values * @param {"linear" | "quadratic"} [type="linear"] - The interpolation method, either "linear" or "quadratic" * @returns {number[]} * @example * const result = Chalkboard.stat.interpolate([1, null, 9], "linear"); // Returns [1, 5, 9] */ export const interpolate = (arr: (number | null | undefined)[], type: "linear" | "quadratic" = "linear"): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.interpolate: Parameter "arr" must be an array.`); if (type !== undefined && (typeof type !== "string")) throw new Error(`Chalkboard.stat.interpolate: Parameter "type" must be a string.`); const result = arr.slice(); for (let i = 0; i < result.length; i++) { if (result[i] == null) { let prevIndex = i - 1; let nextIndex = i + 1; while (prevIndex >= 0 && result[prevIndex] == null) prevIndex--; while (nextIndex < result.length && result[nextIndex] == null) nextIndex++; const prevValue = prevIndex >= 0 ? result[prevIndex] as number : 0; const nextValue = nextIndex < result.length ? result[nextIndex] as number : 0; if (type === "linear") { const t = (i - prevIndex) / (nextIndex - prevIndex); result[i] = Chalkboard.real.lerp([prevValue, nextValue], t); } else if (type === "quadratic" && prevIndex > 0 && nextIndex < result.length) { const prevPrevIndex = prevIndex - 1; const prevPrevValue = prevPrevIndex >= 0 ? result[prevPrevIndex] as number : prevValue; const t = (i - prevIndex) / (nextIndex - prevIndex); result[i] = Chalkboard.real.qerp( [prevPrevIndex, prevPrevValue], [prevIndex, prevValue], [nextIndex, nextValue], prevIndex + t * (nextIndex - prevIndex) ); } else { const t = (i - prevIndex) / (nextIndex - prevIndex); result[i] = Chalkboard.real.lerp([prevValue, nextValue], t); } } } return result as number[]; }; /** * Calculates the interquartile range of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.interquartileRange([1, 2, 3, 4, 5, 6, 7]); // Returns the interquartile range */ export const interquartileRange = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.interquartileRange: Parameter "arr" must be an array of finite numbers.`); return Chalkboard.stat.quartile(arr, "Q3") - Chalkboard.stat.quartile(arr, "Q1"); }; /** * Calculates the kurtosis of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.kurtosis([1, 2, 3, 4, 5]); // Returns the sample's kurtosis */ export const kurtosis = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.kurtosis: Parameter "arr" must be an array of finite numbers.`); let result = 0; const mean = Chalkboard.stat.mean(arr); const deviation = Chalkboard.stat.deviation(arr); for (let i = 0; i < arr.length; i++) { result += (arr[i] - mean) * (arr[i] - mean) * (arr[i] - mean) * (arr[i] - mean); } return result / (deviation * deviation * deviation * deviation) - 3; }; /** * Checks if the elements of an array are less than (or equal to) a number or the elements of another array, and then returns an array with the elements that pass the check. * @param {number[]} arr - The array * @param {number | number[]} arrORnum - The array or number * @param {boolean} [includeEnd=false] - Whether the check is "less than" (false) or "less than or equal to" (true) * @returns {number[]} * @example * const result = Chalkboard.stat.lt([1, 2, 3, 4], 3); // Returns [1, 2] */ export const lt = (arr: number[], arrORnum: number | number[], includeEnd: boolean = false): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.lt: Parameter "arr" must be an array of finite numbers.`); if ((typeof arrORnum !== "number" || Number.isNaN(arrORnum)) && !Array.isArray(arrORnum)) throw new Error(`Chalkboard.stat.lt: Parameter "arrORnum" must be a number or an array.`); if (Array.isArray(arrORnum) && arr.length !== arrORnum.length) throw new Error(`Chalkboard.stat.lt: Parameters "arr" and "arrORnum" must have the same length when "arrORnum" is an array.`); if (includeEnd !== undefined && (typeof includeEnd !== "boolean")) throw new Error(`Chalkboard.stat.lt: Parameter "includeEnd" must be a boolean.`); const result = []; if (Array.isArray(arrORnum)) { if (arr.length === arrORnum.length) { for (let i = 0; i < arr.length; i++) { if (includeEnd) { if (arr[i] <= arrORnum[i]) { result.push(arr[i]); } } else { if (arr[i] < arrORnum[i]) { result.push(arr[i]); } } } } } else { for (let i = 0; i < arr.length; i++) { if (includeEnd) { if (arr[i] <= arrORnum) { result.push(arr[i]); } } else { if (arr[i] < arrORnum) { result.push(arr[i]); } } } } return result; }; /** * Calculates the mean absolute deviation of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.mad([1, 1, 2, 2, 4]); // Returns the mean absolute deviation */ export const mad = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.mad: Parameter "arr" must be an array of finite numbers.`); let result = 0; for (let i = 0; i < arr.length; i++) { result += Math.abs(arr[i] - Chalkboard.stat.mean(arr)); } return result / arr.length; }; /** * Returns the maximum value of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.max([-1, 5, 3]); // Returns 5 */ export const max = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.max: Parameter "arr" must be an array of finite numbers.`); let max = arr[0]; for (let i = 0; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; }; /** * Calculates the mean of an array. * @param {number[]} arr - The array * @param {"arithmetic" | "geometric" | "harmonic"} [type="arithmetic"] - The type of mean, which can be "arithmetic", "geometric", or "harmonic" * @returns {number} * @example * const result = Chalkboard.stat.mean([1, 2, 4, 8], "geometric"); // Returns the geometric mean */ export const mean = (arr: number[], type: "arithmetic" | "geometric" | "harmonic" = "arithmetic"): number => { let result = 0; if (type === "arithmetic") { for (let i = 0; i < arr.length; i++) { result += arr[i]; } return result / arr.length; } else if (type === "geometric") { result = 1; for (let i = 0; i < arr.length; i++) { result *= arr[i]; } return Chalkboard.real.root(Math.abs(result), arr.length); } else if (type === "harmonic") { for (let i = 0; i < arr.length; i++) { result += 1 / arr[i]; } return arr.length / result; } else { throw new Error(`Chalkboard.stat.mean: Parameter "type" must be arithmetic, geometric, or harmonic.`); } }; /** * Calculates the moving mean of an array. * @param {number[]} arr - The array * @param {number} windowSize - The size of the moving window * @returns {number[]} * @example * const result = Chalkboard.stat.meanMoving([1, 2, 3, 4, 5], 3); // Returns [2, 3, 4] */ export const meanMoving = (arr: number[], windowSize: number): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.meanMoving: Parameter "arr" must be an array.`); if (!Number.isInteger(windowSize) || windowSize < 1 || windowSize > arr.length) throw new Error(`Chalkboard.stat.meanMoving: Parameter "windowSize" must be a positive integer less than or equal to the array length.`); const result = []; for (let i = 0; i <= arr.length - windowSize; i++) { const windowArr = arr.slice(i, i + windowSize); result.push(Chalkboard.stat.sum(windowArr) / windowSize); } return result; }; /** * Calculates the weighted mean of an array. * @param {number[]} arr - The array * @param {number[]} weights - The weights * @returns {number} * @example * const result = Chalkboard.stat.meanWeighted([70, 80, 90], [0.2, 0.3, 0.5]); // Returns 83 */ export const meanWeighted = (arr: number[], weights: number[]): number => { if (arr.length !== weights.length) throw new Error(`Chalkboard.stat.meanWeighted: Parameters "values" and "weights" must have the same length.`); let sum = 0, weightSum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i] * weights[i]; weightSum += weights[i]; } return sum / weightSum; }; /** * Returns the median value of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.median([1, 3, 3, 6, 7, 8, 9]); // Returns 6 */ export const median = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.median: Parameter "arr" must be an array of finite numbers.`); if (arr.length === 0) return NaN; const copy = arr.slice(); const mid = Math.floor(copy.length / 2); if (copy.length % 2 === 1) { return $quickselect(copy, mid); } else { return ($quickselect(copy, mid - 1) + $quickselect(copy, mid)) / 2.0; } }; /** * Returns the minimum value of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.min([-1, 5, 3]); // Returns -1 */ export const min = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.min: Parameter "arr" must be an array of finite numbers.`); let min = arr[0]; for (let i = 0; i < arr.length; i++) { if (arr[i] < min) { min = arr[i]; } } return min; }; /** * Returns the mode (the most recurring value) of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.mode([1, 2, 2, 3, 3, 3]); // Returns 3 */ export const mode = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.mode: Parameter "arr" must be an array of finite numbers.`); if (arr.length === 0) return NaN; const frequency = new Map(); let maxFreq = 0; let result = arr[0]; for (let i = 0; i < arr.length; i++) { const val = arr[i]; const count = (frequency.get(val) || 0) + 1; frequency.set(val, count); if (count > maxFreq) { maxFreq = count; result = val; } } return result; }; /** * Calculates the product of all elements in an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.mul([1, 2, 3, 4]); // Returns 24 */ export const mul = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.mul: Parameter "arr" must be an array of finite numbers.`); let result = 1; for (let i = 0; i < arr.length; i++) { result *= arr[i]; } return result; }; /** * Calculates the negation of all the elements of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.negate([1, -2, 3]); // Returns [-1, 2, -3] */ export const negate = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.negate: Parameter "arr" must be an array of finite numbers.`); const result: number[] = []; for (let i = 0; i < arr.length; i++) { result.push(-arr[i]); } return result; }; /** * Calculates the norm of an array. * @param {number[]} arr - The array * @param {"L0" | "L1" | "L2" | "LInfinity"} [type="L2"] - The type of norm, which can be "L0", "L1", "L2", or "LInfinity" * @returns {number} * @example * const result = Chalkboard.stat.norm([3, 4]); // Returns 5 */ export const norm = (arr: number[], type: "L0" | "L1" | "L2" | "LInfinity" = "L2"): number => { let result = 0; if (type === "L0") { for (let i = 0; i < arr.length; i++) { if (arr[i] !== 0) { result += 1; } } return result; } else if (type === "L1") { for (let i = 0; i < arr.length; i++) { result += Math.abs(arr[i]); } return result; } else if (type === "L2") { for (let i = 0; i < arr.length; i++) { result += arr[i] * arr[i]; } return Chalkboard.real.sqrt(result); } else if (type === "LInfinity") { return Math.abs(Chalkboard.stat.max(arr)); } else { throw new Error(`Chalkboard.stat.norm: Parameter "type" must be L0, L1, L2, or LInfinity.`); } }; /** * Calculates the value of the standard normal distribution at a given point. * @param {number} x - The point * @returns {number} * @example * const result = Chalkboard.stat.normal(0); // Returns approximately 0.3989 */ export const normal = (x: number): number => { if (typeof x !== "number" || Number.isNaN(x)) throw new Error(`Chalkboard.stat.normal: Parameter "x" must be a number that is not NaN.`); if (!Number.isFinite(x)) return 0; const standardNormal = Chalkboard.real.define((x) => 1 / Math.sqrt(2 * Math.PI) * Math.exp(-0.5 * x * x)); const f = standardNormal.rule as (x: number) => number; return f(x); }; /** * Calculates the normalization of an array. * @param {number[]} arr - The array * @param {"L0" | "L1" | "L2" | "LInfinity"} [type="L2"] - The type of norm to normalize with, which can be "L0", "L1", "L2", or "LInfinity" * @returns {number[]} * @example * const result = Chalkboard.stat.normalize([3, 4]); // Returns [0.6, 0.8] */ export const normalize = (arr: number[], type: "L0" | "L1" | "L2" | "LInfinity" = "L2"): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.normalize: Parameter "arr" must be an array of finite numbers.`); if (type !== undefined && (typeof type !== "string")) throw new Error(`Chalkboard.stat.normalize: Parameter "type" must be a string.`); const result = []; const norm = Chalkboard.stat.norm(arr, type); for (let i = 0; i < arr.length; i++) { result.push(arr[i] / norm); } return result; }; /** * Calculates the norm squared of an array. * @param {number[]} arr - The array * @param {"L0" | "L1" | "L2" | "LInfinity"} [type="L2"] - The type of norm squared, which can be "L0", "L1", "L2", or "LInfinity" * @returns {number} * @example * const result = Chalkboard.stat.normsq([3, 4]); // Returns 25 */ export const normsq = (arr: number[], type: "L0" | "L1" | "L2" | "LInfinity" = "L2"): number => { let result = 0; if (type === "L0") { for (let i = 0; i < arr.length; i++) { if (arr[i] !== 0) { result += 1; } } return result * result; } else if (type === "L1") { for (let i = 0; i < arr.length; i++) { result += Math.abs(arr[i]); } return result * result; } else if (type === "L2") { for (let i = 0; i < arr.length; i++) { result += arr[i] * arr[i]; } return result; } else if (type === "LInfinity") { return Math.abs(Chalkboard.stat.max(arr)) * Math.abs(Chalkboard.stat.max(arr)); } else { throw new Error(`Chalkboard.stat.normsq: Parameter "type" must be L0, L1, L2, or LInfinity.`); } }; /** * Pads an array with a specified number to a given length. * @param {number[]} arr - The array * @param {number} length - The desired length of the array * @param {number} [num=0] - The number to pad with (default is 0) * @returns {number[]} * @example * const result = Chalkboard.stat.pad([1, 2, 3], 5, 0); // Returns [1, 2, 3, 0, 0] */ export const pad = (arr: number[], length: number, num: number = 0): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.pad: Parameter "arr" must be an array of finite numbers.`); if (!Number.isInteger(length) || length < 0) throw new Error(`Chalkboard.stat.pad: Parameter "length" must be a non-negative integer.`); if (num !== undefined && (!Number.isFinite(num))) throw new Error(`Chalkboard.stat.pad: Parameter "num" must be a finite number.`); const result = arr.slice(); while (result.length < length) { result.push(num); } return result; }; /** * Calculates the percentile of a number in an array. * @param {number[]} arr - The array * @param {number} num - The number * @returns {number} * @example * const result = Chalkboard.stat.percentile([1, 2, 3, 4, 5], 50); // Returns 3 */ export const percentile = (arr: number[], num: number): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.percentile: Parameter "arr" must be an array of finite numbers.`); if (!Number.isFinite(num)) throw new Error(`Chalkboard.stat.percentile: Parameter "num" must be a finite number.`); let result = 0; for (let i = 0; i < arr.length; i++) { if (num >= arr[i]) { result++; } } return (result / arr.length) * 100; }; /** * Prints an array in the console. * @param {number[]} arr - The array * @returns {void} * @example * Chalkboard.stat.print([1, 1, 2, 3, 5, 8]); // Prints the Fibonacci values */ export const print = (arr: number[]): void => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.print: Parameter "arr" must be an array of finite numbers.`); console.log(Chalkboard.stat.toString(arr)); }; /** * Calculates a quartile of an array. * @param {number[]} arr - The array * @param {"Q1" | "Q2" | "Q3"} type - The type of quartile, which can be "Q1", "Q2", or "Q3" * @returns {number} * @example * const result = Chalkboard.stat.quartile([1, 2, 3, 4, 5, 6, 7], "Q2"); // Returns the median */ export const quartile = (arr: number[], type: "Q1" | "Q2" | "Q3"): number => { if (arr.length === 0) return NaN; const copy = arr.slice(); if (type === "Q2") return Chalkboard.stat.median(copy); const mid = Math.floor(copy.length / 2); if (type === "Q1") { const q1Mid = Math.floor(mid / 2); if (mid % 2 === 1) return $quickselect(copy, q1Mid); else return ($quickselect(copy, q1Mid - 1) + $quickselect(copy, q1Mid)) / 2.0; } else if (type === "Q3") { const offset = copy.length % 2 === 0 ? mid : mid + 1; const q3Mid = offset + Math.floor(mid / 2); if (mid % 2 === 1) return $quickselect(copy, q3Mid); else return ($quickselect(copy, q3Mid - 1) + $quickselect(copy, q3Mid)) / 2.0; } else { throw new Error(`Chalkboard.stat.quartile: Parameter "type" must be Q1, Q2, or Q3.`); } }; /** * Returns an array with random elements. * @param {number} length - The length of the array * @param {number} [inf=0] - The lower bound (optional, defaults to 0) * @param {number} [sup=1] - The upper bound (optional, defaults to 1) * @returns {number[]} * @example * const result = Chalkboard.stat.random(100, -1, 1); // Returns 100 random values from [-1, 1] */ export const random = (length: number, inf: number = 0, sup: number = 1): number[] => { if (!Number.isInteger(length) || length < 0) throw new Error(`Chalkboard.stat.random: Parameter "length" must be a non-negative integer.`); if (inf !== undefined && (!Number.isFinite(inf))) throw new Error(`Chalkboard.stat.random: Parameter "inf" must be a finite number.`); if (sup !== undefined && (!Number.isFinite(sup))) throw new Error(`Chalkboard.stat.random: Parameter "sup" must be a finite number.`); const result = []; for (let i = 0; i < length; i++) { result.push(Chalkboard.numb.random(inf, sup)); } return result; }; /** * Returns the range (the maximum value minus the minimum value) of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.range([-3, 2, 7]); // Returns 10 */ export const range = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.range: Parameter "arr" must be an array of finite numbers.`); return Chalkboard.stat.max(arr) - Chalkboard.stat.min(arr); }; /** * Calculates a regression model for an array of data. * @param {number[][]} data - The data, an array of arrays that represent an ordered pair of 2D coordinates * @param {"linear" | "polynomial" | "power" | "exponential" | "logarithmic"} [type="linear"] - The type of regression model, which can be "linear", "polynomial", "power", "exponential", or "logarithmic" * @param {number} [degree=2] - The degree of the leading coefficient of the polynomial regression model * @returns {ChalkboardFunction} * @example * const line = Chalkboard.stat.regression([[0, 1], [1, 3], [2, 5]], "linear"); * const y = Chalkboard.real.val(line, 3); // Returns 7 */ export const regression = (data: number[][], type: "linear" | "polynomial" | "power" | "exponential" | "logarithmic" = "linear", degree: number = 2): ChalkboardFunction => { if (!Array.isArray(data) || data.length === 0 || !Array.isArray(data[0]) || data[0].length < 2) throw new Error(`Chalkboard.stat.regression: Parameter "data" must be a non-empty array of coordinate pairs.`); if (!Number.isInteger(degree) || degree < 0) throw new Error(`Chalkboard.stat.regression: Parameter "degree" must be a non-negative integer.`); if (type === "linear") { let x = 0, y = 0; let xx = 0, xy = 0; for (let i = 0; i < data.length; i++) { x += data[i][0]; y += data[i][1]; xx += data[i][0] * data[i][0]; xy += data[i][0] * data[i][1]; } const a = (data.length * xy - x * y) / (data.length * xx - x * x); const b = y / data.length - (a * x) / data.length; return Chalkboard.real.define((x) => a * x + b); } else if (type === "polynomial") { const A = Chalkboard.matr.init(); for (let i = 0; i < data.length; i++) { A.push([]); for (let j = 0; j <= degree; j++) { A[i].push(Chalkboard.real.pow(data[i][0], j) as number); } } const AT = Chalkboard.matr.transpose(A); const B = Chalkboard.matr.init(); for (let i = 0; i < data.length; i++) { B.push([data[i][1]]); } const ATA = Chalkboard.matr.mul(AT, A); const ATAI = Chalkboard.matr.invert(ATA); const x = Chalkboard.matr.mul(Chalkboard.matr.mul(ATAI, AT), B); const coeff: number[] = []; for (let i = 0; i < x.length; i++) { coeff.push(x[i][0]); } return Chalkboard.real.define((x) => { let result = coeff[0]; result += coeff[1] * x; for (let i = 2; i <= degree; i++) { result += coeff[i] * Math.pow(x, i); } return result; }); } else if (type === "power") { const arr = [0, 0, 0, 0]; for (let i = 0; i < data.length; i++) { arr[0] += Math.log(data[i][0]); arr[1] += Math.log(data[i][1]) * Math.log(data[i][0]); arr[2] += Math.log(data[i][1]); arr[3] += Math.log(data[i][0]) * Math.log(data[i][0]); } const a = Chalkboard.E((arr[2] - ((data.length * arr[1] - arr[2] * arr[0]) / (data.length * arr[3] - arr[0] * arr[0])) * arr[0]) / data.length); const b = (data.length * arr[1] - arr[2] * arr[0]) / (data.length * arr[3] - arr[0] * arr[0]); return Chalkboard.real.define((x) => a * Math.pow(x, b)); } else if (type === "exponential") { const arr = [0, 0, 0, 0, 0, 0]; for (let i = 0; i < data.length; i++) { arr[0] += data[i][0]; arr[1] += data[i][1]; arr[2] += data[i][0] * data[i][0] * data[i][1]; arr[3] += data[i][1] * Math.log(data[i][1]); arr[4] += data[i][0] * (data[i][1] * Math.log(data[i][1])); arr[5] += data[i][0] * data[i][1]; } const a = Chalkboard.E((arr[2] * arr[3] - arr[5] * arr[4]) / (arr[1] * arr[2] - arr[5] * arr[5])); const b = (arr[1] * arr[4] - arr[5] * arr[3]) / (arr[1] * arr[2] - arr[5] * arr[5]); return Chalkboard.real.define((x) => a * Math.exp(b * x)); } else if (type === "logarithmic") { const arr = [0, 0, 0, 0]; for (let i = 0; i < data.length; i++) { arr[0] += Math.log(data[i][0]); arr[1] += data[i][1] * Math.log(data[i][0]); arr[2] += data[i][1]; arr[3] += Math.log(data[i][0]) * Math.log(data[i][0]); } const a = (arr[2] - ((data.length * arr[1] - arr[2] * arr[0]) / (data.length * arr[3] - arr[0] * arr[0])) * arr[0]) / data.length; const b = (data.length * arr[1] - arr[2] * arr[0]) / (data.length * arr[3] - arr[0] * arr[0]); return Chalkboard.real.define((x) => a + b * Math.log(x)); } else { throw new Error(`Chalkboard.stat.regression: Parameter "type" must be linear, polynomial, power, exponential, or logarithmic.`); } }; /** * Performs resampling on an array depending on the specified resampling method. * @param {number[]} arr - The array * @param {number} [samples] - The number of samples (optional, default is 100 for "bootstrap" and the length of the array for "jackknife") * @param {"bootstrap" | "jackknife"} [type="bootstrap"] - The type of resampling method, which can be "bootstrap" or "jackknife" * @returns {number[][]} * @example * const result = Chalkboard.stat.resampling([1, 2, 3, 4], 4, "jackknife"); // Returns the jackknife samples */ export const resampling = (arr: number[], samples?: number, type: "bootstrap" | "jackknife" = "bootstrap"): number[][] => { if (samples !== undefined && (!Number.isInteger(samples) || samples < 1)) throw new Error(`Chalkboard.stat.resampling: Parameter "samples" must be a positive integer.`); if (type === "bootstrap") { const numSamples = samples ?? 100; const result = []; for (let i = 0; i < numSamples; i++) { const sample = []; for (let j = 0; j < arr.length; j++) { sample.push(arr[Math.floor(Math.random() * arr.length)]); } result.push(sample); } return result; } else if (type === "jackknife") { const numSamples = samples ?? arr.length; const allJackknifeSamples = []; for (let i = 0; i < arr.length; i++) { allJackknifeSamples.push(arr.slice(0, i).concat(arr.slice(i + 1))); } if (numSamples < allJackknifeSamples.length) { const selectedSamples = []; const usedIndices = new Set(); while (selectedSamples.length < numSamples) { const randomIndex = Math.floor(Math.random() * allJackknifeSamples.length); if (!usedIndices.has(randomIndex)) { usedIndices.add(randomIndex); selectedSamples.push(allJackknifeSamples[randomIndex]); } } return selectedSamples; } return allJackknifeSamples; } else { throw new Error(`Chalkboard.stat.resampling: Parameter "type" must be bootstrap or jackknife.`); } }; /** * Reverses the elements of an array. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.reverse([1, 1, 2, 3, 5]); // Returns [5, 3, 2, 1, 1] */ export const reverse = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.reverse: Parameter "arr" must be an array of finite numbers.`); const result: number[] = []; for (let i = arr.length - 1; i >= 0; i--) { result.push(arr[i]); } return result; }; /** * Calculates the scalar multiplication of an array. * @param {number[]} arr - The array * @param {number} num - The scalar * @returns {number[]} * @example * const result = Chalkboard.stat.scl([1, 2, 3], 3); // Returns [3, 6, 9] */ export const scl = (arr: number[], num: number): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.scl: Parameter "arr" must be an array of finite numbers.`); if (!Number.isFinite(num)) throw new Error(`Chalkboard.stat.scl: Parameter "num" must be a finite number.`); const result: number[] = []; for (let i = 0; i < arr.length; i++) { result.push(arr[i] * num); } return result; }; /** * Returns an array with its elements randomly shuffled. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.shuffle([1, 2, 3, 4]); // Returns a random permutation */ export const shuffle = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.shuffle: Parameter "arr" must be an array of finite numbers.`); let index, temp, rindex; for (index = arr.length - 1; index > 0; index--) { rindex = Math.floor(Chalkboard.numb.random(0, index + 1)); temp = arr[index]; arr[index] = arr[rindex]; arr[rindex] = temp; } return arr; }; /** * Calculates the skewness of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.skewness([1, 2, 3, 4, 10]); // Returns the sample's skewness */ export const skewness = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.skewness: Parameter "arr" must be an array of finite numbers.`); let result = 0; const mean = Chalkboard.stat.mean(arr); const deviation = Chalkboard.stat.deviation(arr); for (let i = 0; i < arr.length; i++) { result += (arr[i] - mean) * (arr[i] - mean) * (arr[i] - mean); } return result / ((arr.length - 1) * (deviation * deviation * deviation)); }; /** * Calculates the subtraction of two arrays. * @param {number[]} arr1 - The first array * @param {number[]} arr2 - The second array * @returns {number[]} * @example * const result = Chalkboard.stat.sub([5, 8, 13], [2, 3, 5]); // Returns [3, 5, 8] */ export const sub = (arr1: number[], arr2: number[]): number[] => { if (arr1.length !== arr2.length) throw new Error(`Chalkboard.stat.sub: Parameters "arr1" and "arr2" must have the same length.`); const result: number[] = []; for (let i = 0; i < arr1.length; i++) { result.push(arr1[i] - arr2[i]); } return result; }; /** * Returns an array of all the subsets of an array. * @param {number[]} arr - The array * @returns {number[][]} * @example * const result = Chalkboard.stat.subsets([1, 2, 3]); // Returns the eight subsets */ export const subsets = (arr: number[]): number[][] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.subsets: Parameter "arr" must be an array of finite numbers.`); let result: number[][] = [[]]; arr.sort(); for (let i = 0; i < arr.length; i++) { if (i === 0 || arr[i] !== arr[i - 1]) { const curr = arr[i]; const subsetsWithCurr = []; for (let j = 0; j < result.length; j++) { const subset = result[j].slice(); subset.push(curr); subsetsWithCurr.push(subset); } result = result.concat(subsetsWithCurr); } } return result; }; /** * Calculates the sum of all elements in an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.sum([1, 2, 3, 4]); // Returns 10 */ export const sum = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.sum: Parameter "arr" must be an array of finite numbers.`); let result = 0; for (let i = 0; i < arr.length; i++) { result += arr[i]; } return result; }; /** * Converts an array to a matrix. * @param {number[]} arr - The array * @param {number} rows - The number of rows of the matrix * @param {number} [cols=rows] - The number of columns of the matrix (optional, defaults to the number of rows to make a square matrix) * @returns {ChalkboardMatrix} * @example * const result = Chalkboard.stat.toMatrix([1, 2, 3, 4], 2); // Returns [[1, 2], [3, 4]] */ export const toMatrix = (arr: number[], rows: number, cols: number = rows): ChalkboardMatrix => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.toMatrix: Parameter "arr" must be an array of finite numbers.`); if (!Number.isInteger(rows) || rows < 0) throw new Error(`Chalkboard.stat.toMatrix: Parameter "rows" must be a non-negative integer.`); if (!Number.isInteger(cols) || cols < 0) throw new Error(`Chalkboard.stat.toMatrix: Parameter "cols" must be a non-negative integer.`); const result = Chalkboard.matr.init(); let index = 0; for (let i = 0; i < rows; i++) { result[i] = []; for (let j = 0; j < cols; j++) { if (index < arr.length) { result[i].push(arr[index]); } else { result[i].push(0); } index++; } } return result; }; /** * Converts an array to an object. * @param {number[]} arr - The array * @returns {object} * @example * const result = Chalkboard.stat.toObject([2, 3, 5]); // Returns an indexed object */ export const toObject = (arr: number[]): object => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.toObject: Parameter "arr" must be an array of finite numbers.`); const result: { [key: string]: number } = {}; for (let i = 0; i < arr.length; i++) { result["_" + i.toString()] = arr[i]; } return result; }; /** * Converts an array to a set. * @param {number[]} arr - The array * @returns {ChalkboardSet} * @example * const result = Chalkboard.stat.toSet([1, 1, 2, 3, 5]); // Returns the set {1, 2, 3, 5} */ export const toSet = (arr: number[]): ChalkboardSet => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.toSet: Parameter "arr" must be an array of finite numbers.`); return Chalkboard.abal.set(arr); }; /** * Converts an array to a string. * @param {number[]} arr - The array * @returns {string} * @example * const result = Chalkboard.stat.toString([1, 2, 3]); // Returns "[1, 2, 3]" */ export const toString = (arr: number[]): string => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.toString: Parameter "arr" must be an array.`); return "[" + arr.join(", ") + "]"; }; /** * Converts an array to a tensor. * @param {number[]} arr - The array * @param {number[]} size - The size of the tensor * @returns {ChalkboardTensor} * @example * const result = Chalkboard.stat.toTensor([1, 2, 3, 4], 2, 2); // Returns [[1, 2], [3, 4]] */ export const toTensor = (arr: number[], ...size: number[]): ChalkboardTensor => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.toTensor: Parameter "arr" must be an array of finite numbers.`); if (Array.isArray(size[0])) { size = size[0]; } if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.stat.toTensor: Parameter "size" must begin and end with non-negative integers.`); return Chalkboard.tens.resize(arr, ...size); }; /** * Converts an array to a vector. * @param {number[]} arr - The array * @param {number} dimension - The dimension of the vector, which can be 2, 3, or 4 * @param {number} [index=0] - The index of the array to start the vector * @returns {ChalkboardVector} * @example * const result = Chalkboard.stat.toVector([3, 4, 5], 3); // Returns the vector (3, 4, 5) */ export const toVector = (arr: number[], dimension: 2 | 3 | 4, index: number = 0): ChalkboardVector => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.toVector: Parameter "arr" must be an array.`); if (dimension !== 2 && dimension !== 3 && dimension !== 4) throw new Error(`Chalkboard.stat.toVector: Parameter "dimension" must be 2, 3, or 4.`); if (!Number.isInteger(index) || index < 0 || index + dimension > arr.length) throw new Error(`Chalkboard.stat.toVector: Parameter "index" and parameter "dimension" must fit within the array bounds.`); if (dimension === 2) { return Chalkboard.vect.init(arr[index], arr[index + 1]); } else if (dimension === 3) { return Chalkboard.vect.init(arr[index], arr[index + 1], arr[index + 2]); } else if (dimension === 4) { return Chalkboard.vect.init(arr[index], arr[index + 1], arr[index + 2], arr[index + 3]); } else { throw new Error(`Chalkboard.stat.toVector: Parameter "dimension" must be 2, 3, or 4.`); } }; /** * Returns the unique elements of an array. * @template T * @param {T[]} arr - The array * @returns {T[]} * @example * const result = Chalkboard.stat.unique([1, 1, 2, 3, 3, 5]); // Returns [1, 2, 3, 5] */ export const unique = (arr: T[]): T[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.unique: Parameter "arr" must be an array.`); if (arr.length === 0) return []; const firstType = typeof arr[0]; if (firstType === "number" || firstType === "string" || firstType === "boolean") return Array.from(new Set(arr)); const stableStringify = (obj: any): string => { const replacer = (key: string, value: any) => { if (value !== null && typeof value === "object" && !Array.isArray(value)) { const sortedObj: Record = {}; Object.keys(value).sort().forEach(k => { sortedObj[k] = value[k]; }); return sortedObj; } return value; }; return JSON.stringify(obj, replacer); }; const seen = new Map(); for (const item of arr) { const typePrefix = item === null ? "null" : typeof item; const valuePart = (typePrefix === "undefined" || Number.isNaN(item as any)) ? "" : stableStringify(item); const key = `${typePrefix}:${valuePart}`; if (!seen.has(key)) seen.set(key, item); } return Array.from(seen.values()); }; /** * Calculates the variance of an array. * @param {number[]} arr - The array * @returns {number} * @example * const result = Chalkboard.stat.variance([2, 4, 4, 4, 5, 5, 7, 9]); // Returns 4 */ export const variance = (arr: number[]): number => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.variance: Parameter "arr" must be an array of finite numbers.`); let result = 0; for (let i = 0; i < arr.length; i++) { result += (arr[i] - Chalkboard.stat.mean(arr)) * (arr[i] - Chalkboard.stat.mean(arr)); } return result / arr.length; }; /** * Calculates the standardization of the elements of an array according to their mean and standard deviation. * @param {number[]} arr - The array * @returns {number[]} * @example * const result = Chalkboard.stat.zscored([2, 4, 4, 4, 5, 5, 7, 9]); // Returns the z-scores */ export const zscored = (arr: number[]): number[] => { if (!Array.isArray(arr)) throw new Error(`Chalkboard.stat.zscored: Parameter "arr" must be an array of finite numbers.`); let result: number[] = []; const mean = Chalkboard.stat.mean(arr); const deviation = Chalkboard.stat.deviation(arr); for (let i = 0; i < arr.length; i++) { result.push((arr[i] - mean) / deviation); } return result; }; } }