rán · 然 — "so; correct"
A comprehensive JavaScript library for probability distributions, random variate generation, and statistical analysis.
- 140+ probability distributions — continuous and discrete, each with PDF/PMF, CDF, quantile (
q), hazard, survival, log-likelihood (lnL), AIC/BIC, goodness-of-fit testing, and MLE fitting (fit) - Statistical measures — location (mean, median, mode, …), dispersion (variance, IQR, Gini, …), shape (skewness, kurtosis, …), and dependence (Pearson, Spearman, Kendall, …)
- Hypothesis tests — Bartlett, Levene, Brown–Forsythe, Mann–Whitney U, HSIC
- Reproducible sampling — every distribution accepts an optional seed for deterministic output
- TypeScript support — declaration files generated from JSDoc, covering all public APIs
- Tree-shakeable — import individual distributions without pulling in the full bundle
npm install ranjs<script src="https://unpkg.com/ranjs/dist/ranjs.min.js"></script>The library is exported globally as ranjs.
Import a single distribution for minimal bundle size:
import Normal from 'ranjs/dist/normal'
const n = new Normal(0, 1)
n.pdf(0) // => 0.3989422804014327
n.cdf(1.96) // => 0.9750021048517796
n.sample(5) // => [0.42, -1.03, 0.17, 1.81, -0.55]const ran = require('ranjs')
const skellam = new ran.dist.Skellam(1, 3)
const values = skellam.sample(1e4)
skellam.test(values)
// => { statistics: 14.025360669436635, passed: true }
for (let k = -4; k <= 4; k++) {
console.log(k, skellam.pdf(k), skellam.cdf(k))
}
// => -4 0.10963424740027695 0.21542206959904264
// -3 0.16622843570192460 0.38165050508716936
// -2 0.20277318483535026 0.58442368966117290
// ...Every distribution can be individually seeded:
import Gamma from 'ranjs/dist/gamma'
const g = new Gamma(2, 1)
g.seed(42)
g.sample(3) // always produces the same sequenceconst ran = require('ranjs')
const data = new ran.dist.Skellam(1, 3).sample(1e4)
const fitted = new ran.dist.Skellam(1, 3)
const misfit = new ran.dist.Skellam(1.2, 7.5)
console.log(fitted.aic(data)) // => 41937.67252974663
console.log(misfit.aic(data)) // => 66508.74299363888ranjs closes the full statistical cycle — define a model, generate data, fit parameters from data via MLE, then verify the fit:
import { dist } from 'ranjs'
// 1. Define and sample
const model = new dist.Normal(3, 1).seed(42)
const data = model.sample(500)
// 2. Fit parameters from data via MLE
const fitted = dist.Normal.fit(data)
console.log(fitted.p) // => { mu: 3.000, sigma: 1.000 }
// 3. Test goodness of fit
console.log(fitted.test(data)) // => { statistics: 0.031, passed: true }fit() is a static method called on the class, not on an instance: dist.Normal.fit(data), not model.fit(data). All 141 exported distributions support fit(). Most have a data-aware initial guess for reliable MLE convergence; zero-parameter distributions skip optimization and return a fresh instance.
| Namespace | Contents |
|---|---|
ran.dist |
140+ probability distributions |
ran.location |
Mean, median, mode, geometric mean, harmonic mean, trimean, midrange |
ran.dispersion |
Variance, standard deviation, IQR, Gini coefficient, entropy, CV, … |
ran.shape |
Skewness, kurtosis, quantiles, moments, min, max, rank |
ran.dependence |
Pearson, Spearman, Kendall, distance correlation, Kullback–Leibler, … |
ran.test |
Bartlett, Levene, Brown–Forsythe, Mann–Whitney U, HSIC |
ran.core |
Seeded PRNG (xoshiro128+), uniform float/int/bool generators |
Every distribution exposes a consistent interface:
const d = new ran.dist.Gamma(2, 1)
d.type() // 'continuous' or 'discrete'
d.params() // current parameter object, e.g. { alpha: 2, beta: 1 }
d.support() // [{ value, closed }, { value, closed }] — lower/upper bounds
d.sample(n) // generate n random variates
d.pdf(x) // probability density / mass function
d.cdf(x) // cumulative distribution function
d.q(p) // inverse CDF (quantile function)
d.survival(x) // complementary CDF (1 − CDF)
d.hazard(x) // hazard rate (pdf / survival)
d.cHazard(x) // cumulative hazard (−log survival)
d.lnPdf(x) // log probability density / mass
d.lnL(data) // log-likelihood over an array of observations
d.aic(data) // Akaike information criterion
d.bic(data) // Bayesian information criterion
d.test(data) // KS test (continuous) or chi-squared test (discrete)
d.seed(value) // set PRNG seed; returns the instance
d.save() // serialise PRNG state + parameters to a plain object
d.load(state) // restore from a saved state; returns the instance
ran.dist.Gamma.fit(data) // static — MLE fit; returns a new instanceranjs signals an unusual result through one of four channels, chosen by the kind of situation:
| Situation | What you get |
|---|---|
| Invalid input — missing/NaN parameters, broken constraints, wrong arity, mismatched dimensions | a thrown Error |
| A valid query whose answer is mathematically undefined (e.g. the mean of a Cauchy distribution) | NaN |
| A valid query whose answer diverges (e.g. the variance of a Pareto with shape ≤ 2, any moment of a Lévy) | Infinity (or -Infinity) |
| A correct value that simply equals zero (e.g. a density evaluated outside the support) | 0 |
Functions never return undefined to mean "failed" or "does not exist" — numeric results stay numbers (NaN/Infinity), and genuine misuse throws. NaN and Infinity are kept distinct on purpose: NaN means no value exists, Infinity means the value grows without bound. This mirrors the conventions of SciPy and R.
const ran = require('ranjs')
new ran.dist.Cauchy(0, 1).mean() // => NaN (undefined moment)
new ran.dist.Pareto(1, 2).variance() // => Infinity (divergent moment)
new ran.dist.Normal(0, 1).pdf(-Infinity) // => 0 (outside support)ranjs targets ≤ 1e-14 relative error for all public outputs in non-degenerate parameter regions. Outputs involving deeply composed operations (quantile inversion, extreme parameter regimes) have a documented floor of ~1e-12, looser still for a handful of quantiles computed by numerical root-finding or near-boundary asymptotics (see below).
All reference values in test/dist-cases-continuous.js and test/dist-cases-discrete.js are sourced from external tools — mpmath at mp.dps = 50, scipy.stats, or Wolfram Alpha — never computed from ranjs itself. Use scripts/gen-dist-refs.py to generate reference values when adding a new distribution, and verify at least one value per distribution against an independent source. pdf, cdf, and pmf reference-value assertions enforce 1e-14 relative tolerance by default; distributions that cannot reach 1e-14 in specific regimes use 1e-12 with an explanatory comment.
All 31 discrete distributions are verified against mpmath references at 50 decimal places. BetaBinomial and NegativeHypergeometric sit at the ~2e-14 float64 arithmetic floor. The following distributions cap at 1e-12 at certain parameter settings: Binomial, Hypergeometric, NegativeBinomial, Poisson, Skellam.
All 110 continuous distributions are likewise verified against mpmath references at 50 decimal places (three parameter sets each). pdf/cdf cap at 1e-12–1e-13 at certain parameter settings for: Bates, IrwinHall, Levy, NoncentralBeta, NoncentralChi, NoncentralT, DoublyNoncentralT, SkewNormal, Rice, and R. Quantiles with a closed-form or Halley-refined inverse round-trip to 1e-14; those computed by numerical root-finding (BaldingNichols, Bates, BetaPrime, Davis, FisherZ, Muth, NoncentralChi2, NoncentralF, DoublyNoncentralChi2, DoublyNoncentralT, SkewNormal, Student's t/z, UniformProduct, R) round-trip to ~1e-13–1e-10, and BenktanderII's near-boundary asymptotic branch (b → 1) to ~1e-9.
Full API reference and distribution catalogue: https://synesenom.github.io/ran/