A JavaScript implementation of descriptive, regression, and inference statistics
—
Root finding, special functions, and numerical utilities for advanced mathematical operations.
import {
bisect,
factorial,
gamma,
gammaln,
sign,
numericSort,
quickselect,
epsilon,
combinations,
combinationsReplacement,
permutationsHeap,
equalIntervalBreaks
} from "simple-statistics";function bisect(
func: (x: any) => number,
start: number,
end: number,
maxIterations: number,
errorTolerance: number
): number;Finds roots of a function using the bisection method - a robust numerical algorithm for finding where f(x) = 0.
Parameters:
func: (x: any) => number - Function to find the root ofstart: number - Left boundary of search intervalend: number - Right boundary of search intervalmaxIterations: number - Maximum number of iterationserrorTolerance: number - Acceptable error toleranceReturns: number - Root value where f(x) ≈ 0
Requirements: Function must be continuous and f(start) and f(end) must have opposite signs.
import { bisect } from "simple-statistics";
// Find where x² - 4 = 0 (should be x = 2)
const quadratic = (x: number) => x * x - 4;
const root = bisect(quadratic, 0, 5, 100, 0.0001);
console.log(`Root of x² - 4 = 0: ${root}`); // ≈ 2.0000
// Find break-even point for business model
const profit = (price: number) => (price - 10) * (100 - price) - 200;
const breakEven = bisect(profit, 5, 50, 100, 0.01);
console.log(`Break-even price: $${breakEven.toFixed(2)}`);function factorial(n: number): number;Calculates the factorial of a non-negative integer (n!).
Parameters:
n: number - Non-negative integerReturns: number - n! = n × (n-1) × ... × 2 × 1
import { factorial } from "simple-statistics";
const fact5 = factorial(5); // 120
const fact0 = factorial(0); // 1 (by definition)
console.log(`5! = ${fact5}`);
// Combinatorics: number of ways to arrange 8 people
const arrangements = factorial(8); // 40,320
console.log(`Ways to arrange 8 people: ${arrangements.toLocaleString()}`);function gamma(n: number): number;Calculates the gamma function, which extends factorials to real numbers.
Parameters:
n: number - Input valueReturns: number - Γ(n) value
Properties:
import { gamma } from "simple-statistics";
const gamma4 = gamma(4); // 6 (same as 3!)
const gammaHalf = gamma(0.5); // √π ≈ 1.772
console.log(`Γ(4) = ${gamma4}`);
console.log(`Γ(0.5) = ${gammaHalf.toFixed(3)}`);
// Used in probability distributions
const betaFunction = (a: number, b: number) => (gamma(a) * gamma(b)) / gamma(a + b);
console.log(`Beta(2,3) = ${betaFunction(2, 3).toFixed(3)}`);function gammaln(n: number): number;Calculates the natural logarithm of the gamma function. More numerically stable for large values.
Parameters:
n: number - Input valueReturns: number - ln(Γ(n))
import { gammaln } from "simple-statistics";
// For large values, use log form to avoid overflow
const largeGammaLn = gammaln(100); // ln(Γ(100))
console.log(`ln(Γ(100)) = ${largeGammaLn.toFixed(2)}`);
// Convert back if needed (for smaller results)
const largeGamma = Math.exp(largeGammaLn);
console.log(`Γ(100) ≈ ${largeGamma.toExponential(3)}`);function sign(x: number): number;Returns the sign of a number.
Parameters:
x: number - Input numberReturns: number
import { sign } from "simple-statistics";
console.log(`sign(5) = ${sign(5)}`); // 1
console.log(`sign(-3) = ${sign(-3)}`); // -1
console.log(`sign(0) = ${sign(0)}`); // 0
// Direction of price movement
const priceChange = -2.5;
const direction = sign(priceChange) === 1 ? "up" : sign(priceChange) === -1 ? "down" : "flat";
console.log(`Price moved ${direction}`);function numericSort(array: number[]): number[];Sorts an array of numbers in ascending order using proper numeric comparison.
Parameters:
array: number[] - Array of numbers to sortReturns: number[] - New sorted array (original array unchanged)
import { numericSort } from "simple-statistics";
const unsorted = [10, 2, 30, 4, 5];
const sorted = numericSort(unsorted);
console.log(`Sorted: ${sorted}`); // [2, 4, 5, 10, 30]
// Proper numeric sorting vs. lexicographic
const badSort = [10, 2, 30, 4, 5].sort(); // ["10", "2", "30", "4", "5"] - wrong!
const goodSort = numericSort([10, 2, 30, 4, 5]); // [2, 4, 5, 10, 30] - correct!function quickselect<T>(array: T[], k: number, compare?: (a: T, b: T) => number): T;Finds the k-th smallest element in an array using the quickselect algorithm (faster than full sorting).
Parameters:
array: T[] - Array to search ink: number - Index of element to find (0-based)compare?: (a: T, b: T) => number - Optional comparison functionReturns: T - The k-th smallest element
import { quickselect } from "simple-statistics";
const data = [7, 3, 1, 9, 2, 8, 5];
// Find median (middle element) without full sorting
const median = quickselect(data, Math.floor(data.length / 2));
console.log(`Median: ${median}`); // 5
// Find 3rd smallest element
const thirdSmallest = quickselect(data, 2); // 0-based index
console.log(`3rd smallest: ${thirdSmallest}`); // 3
// Custom comparison for objects
const people = [{age: 25}, {age: 30}, {age: 20}, {age: 35}];
const secondYoungest = quickselect(people, 1, (a, b) => a.age - b.age);
console.log(`Second youngest age: ${secondYoungest.age}`); // 25const epsilon: number;Machine epsilon - the smallest representable positive number such that 1 + epsilon ≠ 1 in floating-point arithmetic.
import { epsilon } from "simple-statistics";
console.log(`Machine epsilon: ${epsilon}`);
// Use for floating-point comparisons
function almostEqual(a: number, b: number): boolean {
return Math.abs(a - b) < epsilon * Math.max(Math.abs(a), Math.abs(b));
}
console.log(`0.1 + 0.2 === 0.3: ${0.1 + 0.2 === 0.3}`); // false
console.log(`0.1 + 0.2 ≈ 0.3: ${almostEqual(0.1 + 0.2, 0.3)}`); // truefunction combinations<T>(array: T[], k: number): T[][];Generates all possible combinations of k elements from an array.
Parameters:
array: T[] - Source arrayk: number - Number of elements in each combinationReturns: T[][] - Array of all possible combinations
import { combinations } from "simple-statistics";
const fruits = ['apple', 'banana', 'cherry', 'date'];
const pairs = combinations(fruits, 2);
console.log("All fruit pairs:");
pairs.forEach(pair => console.log(pair.join(' + ')));
// apple + banana, apple + cherry, apple + date, banana + cherry, etc.
// Team selection
const players = ['Alice', 'Bob', 'Charlie', 'David', 'Eve'];
const teams = combinations(players, 3);
console.log(`Possible 3-person teams: ${teams.length}`);function combinationsReplacement<T>(array: T[], k: number): T[][];Generates combinations with replacement - elements can be repeated.
Parameters:
array: T[] - Source arrayk: number - Number of elements in each combinationReturns: T[][] - Array of combinations with replacement
import { combinationsReplacement } from "simple-statistics";
const dice = [1, 2, 3, 4, 5, 6];
const twoRolls = combinationsReplacement(dice, 2);
console.log("Possible dice combinations (with replacement):");
console.log(`Total combinations: ${twoRolls.length}`); // 21 combinationsfunction permutationsHeap<T>(array: T[]): T[][];Generates all permutations of an array using Heap's algorithm.
Parameters:
array: T[] - Source arrayReturns: T[][] - Array of all permutations
import { permutationsHeap } from "simple-statistics";
const letters = ['A', 'B', 'C'];
const perms = permutationsHeap(letters);
console.log("All permutations of ABC:");
perms.forEach(perm => console.log(perm.join('')));
// ABC, ACB, BAC, BCA, CAB, CBA
console.log(`Total permutations: ${perms.length}`); // 6 = 3!function equalIntervalBreaks(values: number[], nClasses: number): number[];Creates equal-width intervals for data binning and histogram creation.
Parameters:
values: number[] - Data valuesnClasses: number - Number of intervals/classesReturns: number[] - Array of break points defining intervals
import { equalIntervalBreaks, min, max } from "simple-statistics";
// Age distribution binning
const ages = [22, 25, 28, 31, 35, 42, 48, 52, 58, 61, 67, 73];
const ageBreaks = equalIntervalBreaks(ages, 4);
console.log("Age group breaks:", ageBreaks);
// Example: [22, 34.75, 47.5, 60.25, 73]
// Create age groups
const ageGroups = [];
for (let i = 0; i < ageBreaks.length - 1; i++) {
const group = ages.filter(age => age >= ageBreaks[i] && age < ageBreaks[i + 1]);
ageGroups.push({
range: `${ageBreaks[i]}-${ageBreaks[i + 1]}`,
count: group.length,
ages: group
});
}
console.log("Age distribution:");
ageGroups.forEach(group => {
console.log(`${group.range}: ${group.count} people`);
});import { bisect, gamma, factorial } from "simple-statistics";
// Solve engineering problems numerically
class EngineeringCalculator {
// Find optimal pipe diameter for fluid flow
static findOptimalDiameter(flowRate: number, maxPressureDrop: number): number {
// Darcy-Weisbach equation simplified
const pressureDrop = (diameter: number) => {
const velocity = flowRate / (Math.PI * diameter * diameter / 4);
return 0.02 * (1 / diameter) * velocity * velocity * 1000 - maxPressureDrop;
};
return bisect(pressureDrop, 0.1, 2.0, 100, 0.001);
}
// Calculate reliability using gamma function
static weibullReliability(time: number, shape: number, scale: number): number {
return Math.exp(-Math.pow(time / scale, shape));
}
// Quality control calculations
static binomialCoefficient(n: number, k: number): number {
return factorial(n) / (factorial(k) * factorial(n - k));
}
}
// Usage examples
const optimalDiameter = EngineeringCalculator.findOptimalDiameter(0.5, 100);
console.log(`Optimal pipe diameter: ${optimalDiameter.toFixed(3)} m`);
const reliability = EngineeringCalculator.weibullReliability(1000, 2, 1500);
console.log(`Reliability after 1000 hours: ${(reliability * 100).toFixed(1)}%`);import { combinations, permutationsHeap, factorial } from "simple-statistics";
// Traveling salesman problem (small scale)
class TSPSolver {
private distances: number[][];
constructor(cities: string[], distanceMatrix: number[][]) {
this.distances = distanceMatrix;
}
// Brute force solution for small instances
solveExact(cities: string[]): { route: string[], distance: number } {
const routes = permutationsHeap(cities.slice(1)); // Fix first city
let bestRoute = cities;
let bestDistance = Infinity;
for (const route of routes) {
const fullRoute = [cities[0], ...route];
const distance = this.calculateRouteDistance(fullRoute);
if (distance < bestDistance) {
bestDistance = distance;
bestRoute = fullRoute;
}
}
return { route: bestRoute, distance: bestDistance };
}
private calculateRouteDistance(route: string[]): number {
let total = 0;
for (let i = 0; i < route.length - 1; i++) {
// Simplified - would need city name to index mapping
total += Math.random() * 100; // Placeholder
}
return total;
}
}
// Portfolio optimization - select best k stocks from n options
function selectOptimalPortfolio(stocks: any[], k: number): any[][] {
const portfolioCombinations = combinations(stocks, k);
// Evaluate each combination (simplified)
return portfolioCombinations.map(portfolio => ({
stocks: portfolio,
expectedReturn: portfolio.reduce((sum, stock) => sum + stock.expectedReturn, 0) / k,
risk: Math.sqrt(portfolio.reduce((sum, stock) => sum + stock.variance, 0) / k)
}));
}import { gamma, gammaln, bisect } from "simple-statistics";
// Statistical distributions using special functions
class Distributions {
// Beta distribution probability density function
static betaPDF(x: number, alpha: number, beta: number): number {
if (x < 0 || x > 1) return 0;
const logBeta = gammaln(alpha) + gammaln(beta) - gammaln(alpha + beta);
return Math.exp((alpha - 1) * Math.log(x) + (beta - 1) * Math.log(1 - x) - logBeta);
}
// Find quantiles using bisection
static betaQuantile(p: number, alpha: number, beta: number): number {
const cdf = (x: number) => {
// Simplified incomplete beta function - would need proper implementation
return this.betaCDF(x, alpha, beta) - p;
};
return bisect(cdf, 0.001, 0.999, 100, 0.0001);
}
private static betaCDF(x: number, alpha: number, beta: number): number {
// Placeholder - would need proper incomplete beta implementation
return Math.pow(x, alpha) * Math.pow(1 - x, beta);
}
}
// Physics simulations
class PhysicsUtils {
// Stirling's approximation for large factorials
static stirlingApproximation(n: number): number {
return Math.sqrt(2 * Math.PI * n) * Math.pow(n / Math.E, n);
}
// Compare with actual gamma function
static compareStirling(n: number): void {
const actual = gamma(n + 1); // Γ(n+1) = n!
const approx = this.stirlingApproximation(n);
const error = Math.abs(actual - approx) / actual * 100;
console.log(`n=${n}: Actual=${actual.toExponential(3)}, Stirling=${approx.toExponential(3)}, Error=${error.toFixed(2)}%`);
}
}
// Test accuracy
[5, 10, 20, 50].forEach(n => PhysicsUtils.compareStirling(n));Install with Tessl CLI
npx tessl i tessl/npm-simple-statistics