/*
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;
};
}
}