npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@veenie/risk

v0.2.0

Published

Monte Carlo & Latin Hypercube Risk Simulation Engine — 17 distributions, Iman-Conover correlation, machine-precision quantiles

Readme

@veenie/risk

npm license bundle size tests

A dependency-free Monte Carlo & Latin Hypercube risk simulation engine in TypeScript. 17 probability distributions, Iman–Conover rank correlation (the @RISK method), machine-precision quantile functions, and finance statistics (VaR, CVaR, tornado sensitivity) — in one small package with zero runtime dependencies.

Built to power MonteSheet, the Monte Carlo simulator for Google Sheets. Play with every distribution live at montesheet.com/distributions — interactive sandboxes running 10,000 simulations of this exact package in your browser.

Sibling package: @veenie/aero (lifting-line wing aerodynamics, powers AeroSheet). Both from veenie.space.

Quick start

npm install @veenie/risk
import { generateCorrelatedSamples, computeStats } from '@veenie/risk';

const variables = [
  { distId: 'normal', base: 100, params: { spread: 0.1 } },          // revenue
  { distId: 'pert',   base: 50,  params: { min: 30, mode: 50, max: 90 } } // cost
];

// Optional: how they move together
const correlation = [
  [1.0, 0.8],
  [0.8, 1.0]
];

// 100k iterations, reproducible, in milliseconds
const samples = generateCorrelatedSamples(100_000, variables, correlation, 'seed-123');

const revenue = samples.map(row => row[0]);
const stats = computeStats(revenue);
console.log(stats.p10, stats.p50, stats.p90);

Latin Hypercube + Iman–Conover (pro mode)

const samples = generateCorrelatedSamples(50_000, variables, correlation, 'seed-123', {
  method: 'lhs',                 // stratified sampling — same accuracy, ~10x fewer sims
  copula: 'iman-conover',        // rank reordering: marginals preserved EXACTLY
  correlationInput: 'spearman',  // treat matrix entries as rank correlations
});

With plain Monte Carlo, 10,000 sims of a normal(100, 10) gives you a mean somewhere in 100 ± 0.2. With LHS the strata force one sample per probability slice, so low-order moments converge dramatically faster — the same trick @RISK and Crystal Ball sell as a headline feature.

Finance statistics

import { expectedShortfall, probabilityAbove, quantile, tailMean } from '@veenie/risk';

expectedShortfall(pnl, 0.05);      // CVaR₉₅: average of the worst 5% of outcomes
probabilityAbove(npv, 0);          // P(NPV > 0)
quantile(pnl, 0.01);               // 1st percentile (VaR₉₉, sign convention yours)
tailMean(returns, 0.10, 'upper');  // average of the best decile

Sensitivity / tornado charts

import { spearmanSensitivity, contributionToVariance } from '@veenie/risk';

// Rank correlation of each input against the output
spearmanSensitivity(inputSamples, outputSamples, [
  { id: 'rev', label: 'Revenue' },
  { id: 'cogs', label: 'COGS' },
]);

// Squared-rho normalized to 100% — the "% of variance explained" view
contributionToVariance(inputSamples, outputSamples, meta);

CLI

The package ships a CLI for quick sanity checks and piping stats into scripts:

# PERT cost estimate, 50k sims
npx @veenie/risk --dist pert --base 100 --min 50 --mode 100 --max 150 --sims 50000

# Latin Hypercube instead of plain Monte Carlo
npx @veenie/risk --dist lognormal --base 100 --spread 0.3 --sims 10000 --method lhs

# Heavy-tailed: Pareto with α=3, reproducible seed
npx @veenie/risk --dist pareto --base 100 --alpha 3 --seed q3-forecast --sims 100000

# Discrete: how many defects in 500 units at 2% failure rate?
npx @veenie/risk --dist binomial --n 500 --p 0.02 --base 500

# Student-t for fat-tailed returns (df=5)
npx @veenie/risk --dist student-t --base 100 --spread 0.15 --df 5

# JSON output for piping
npx @veenie/risk --dist gamma --base 100 --spread 0.5 --json | jq .p90

Flags: --dist --base --sims --seed --method (random|lhs) --json, plus whatever params the distribution takes (--spread --min --max --mode --lambda --p --n --r --df --alpha --shape --mean --center --concentration).

The 17 distributions

Every distribution is base-anchored: you give it a base value (your spreadsheet cell) and shape params, and the distribution centers its mean on the base. This is the core MonteSheet UX contract — swap a normal for a lognormal and your expected value doesn't move.

| Tier | Distribution | Params | Use it for | |------|-------------|--------|-----------| | free | normal | spread (CV) | the default; symmetric uncertainty | | free | triangular | min mode max | quick 3-point estimates | | free | uniform | min max | "anywhere in this range" | | free | discrete-uniform | min max | integer ranges (units, headcount) | | pro | lognormal | spread (CV) | prices, durations — positive & right-skewed | | pro | pert | min mode max | expert 3-point estimates, smoother than triangular | | pro | beta | center concentration | rates & percentages on [0,1] | | pro | gamma | spread (CV) | waiting times, insurance claims | | pro | weibull | shape | failure rates, wind speeds | | pro | exponential | mean | time between events | | pro | student-t | spread df | fat-tailed returns | | pro | pareto | alpha | power laws: wealth, losses, city sizes | | pro | poisson | lambda | event counts | | pro | binomial | n p | successes in n trials | | pro | bernoulli | p | single yes/no events | | pro | geometric | p | trials until first success | | pro | negative-binomial | r p | failures before r-th success |

Each one implements sample, pdf, cdf, and icdf — the CDF being available on everything means P(X > target) queries work uniformly.

What's under the hood

  • RNG: Mulberry32 with djb2 string-seed hashing. Same seed → bit-identical results, forever. No global state anywhere (v0.1 had a Box–Muller cache that broke this — see changelog).
  • Special functions (src/math/special.ts): Lanczos log-gamma, regularized incomplete gamma (series + continued fraction) and incomplete beta to ~1e-15. Normal CDF via erfc is accurate to machine precision including relative accuracy deep in the tails (Φ(−8) good to 6+ digits), normal ICDF is Acklam's algorithm plus one Halley refinement step.
  • Correlation: Gaussian copula (NORTA) via Cholesky, with automatic repair of non-positive-definite matrices (shrink toward identity, warn with the shrinkage %). Or Iman–Conover rank reordering, which permutes rather than transforms — marginals and LHS stratification survive exactly.
  • Spearman↔Pearson: correlationInput: 'spearman' converts via the exact Gaussian-copula relation ρ = 2·sin(πρₛ/6).
  • Tests: 140 tests, 40k+ assertions. Every distribution is pinned to closed-form analytic moments, round-tripped through CDF∘ICDF on a 999-point grid, and its PDF numerically integrated back to 1.

Quirks & conventions (read before shipping on top of this)

  • stdDev is population (÷n), matching computeStats since 0.1. Use sampleStdDev for Excel STDEV.S (÷n−1) semantics.
  • Discrete quantiles use the standard right-continuous inverse: icdf(p) returns the smallest integer k with CDF(k) ≥ p.
  • Probabilities are clamped to [1e-12, 1−1e-12] before inverse transforms, so unbounded distributions never return ±Infinity from the sampler. normalICDF(0) directly still returns −Infinity, as it should.
  • Seed streams changed in 0.2.0. Same seed gives different (but distributionally identical) samples than 0.1.x, because the copula path now consumes uniforms via inverse transform instead of polar sampling. Statistical results are equivalent; exact sample values are not. Pin 0.1.x if you froze golden outputs.
  • Non-PD correlation matrices are repaired, not dropped. 0.1.x silently fell back to independent sampling; 0.2.0 shrinks the matrix toward identity just enough to factor, and warns.
  • Correlation matrices are symmetrized (upper/lower averaged) and off-diagonals clamped to ±0.9999 before use.
  • Base-anchoring edge cases: pareto requires α > 1 (else the mean doesn't exist — the param floor enforces this), student-t requires df > 2 for the spread to mean what you think it means.

Why this exists

If you build financial models, project cost estimators, or engineering tolerance sims in JS/TS, the alternative is stitching together five unmaintained math libraries with inconsistent parameterizations. @veenie/risk puts the hard parts — inverse CDF sampling, matrix decomposition, correlation induction, tail-accurate special functions — behind one tested, seeded, tree-shakeable API. It feeds directly into headless calc engines like HyperFormula, web workers, or interactive calculators.

Want it inside your spreadsheet instead of your codebase? That's MonteSheet=MS_NORMAL(100, 0.1) in a cell and go.

License

MIT. See LICENSE. Built by Veenie.