Simulation, descriptive statistics and parameter estimation for ten usual probability distributions, in the browser.
Each distribution is presented three ways: its analytical properties, a simulation that draws a pseudo-random sample and estimates the parameters back from it, and an application that does the same for a sample you supply as a file.
Every distribution carries two estimators side by side — one closed form, obtained by the method of moments or by solving the likelihood equations explicitly, and one numerical, obtained by gradient ascent on the log-likelihood. Comparing them is the point of the exercise.
This project was written during a first-year internship from the ENSIIE, carried out at the CNAM in the department of mathematics and statistics under the supervision of Dariush Ghorbanzadeh. It ran for eight weeks in June and July 2023, on the subject of implementing an application of the gradient descent method.
This is version 2, a 2026 revision. The statistical content is unchanged. The interface was rebuilt, the code reorganised and translated into English, and several defects in the original estimators were fixed. The 2023 version is preserved in the repository history.
There is no build step and no dependency to install.
npm start # serves public/ on http://localhost:8080
npm run build # copies public/ to dist/, which is what gets deployednpm run build exists only because dist/ is the directory the host publishes; it
is a recursive copy, nothing more.
public/
index.html overview, project context and the catalogue
distributions/ ten distributions x three pages
<slug>-properties.html the law, its moments, and a density plot
<slug>-simulation.html draw a sample, summarise it, estimate from it
<slug>-application.html the same, for an imported sample
assets/
css/theme.css the whole design system, one file
js/inference.js sampling, statistics and estimators
js/ui.js shared Vue components and the page factory
js/site.js the distribution catalogue and the sidebar
js/vendor/ Chart.js, vendored
scripts/
build.mjs public/ -> dist/
serve.mjs development server
Pages are plain HTML. They load Vue 2.7 and MathJax from a pinned CDN, Chart.js from
assets/js/vendor/, and share their behaviour through ui.js rather than repeating
it. Adding a distribution means adding one entry to the catalogue in site.js and
three files to distributions/.
assets/js/inference.js exposes one global, Inference:
Inference.summary(data) // count, min, max, mean, variance, skewness, kurtosis
Inference.bins.discrete(data) // distinct values and their counts
Inference.bins.continuous(data, 24) // equal-width bins
Inference.sample.normal(mu, sigma, n) // and one per distribution
Inference.moment.normal(data) // closed-form estimators
Inference.mle.normal(data) // gradient ascent on the log-likelihood
Inference.special.gamma(x) // gamma and digamma functions
Inference.parseSample(text) // read numbers out of an uploaded fileEvery gradient is averaged over the sample, so a learning rate means the same thing
whatever the sample size, and each iteration reuses precomputed sufficient statistics
instead of re-reading the sample. That makes the estimators O(n + iterations) rather
than O(n × iterations) — the 2023 versions re-read the whole sample on every one of
their fifty thousand steps.
The application pages accept plain text containing numbers separated by line breaks, spaces, commas or semicolons. Anything that does not parse as a number is skipped.
The 2023 code had a number of genuine defects, all verified before being changed:
LIB.jsthrew on load. A straylafter a closing brace raisedReferenceError: l is not definedon every page. Function declarations are hoisted, so the library still worked, but every page logged an error.- Uniform (continuous) estimation crashed. Five functions were named
logLikelihoodand two were namedgradient; the last declaration won. The uniform estimator calledgradient(data, a, b)and reached the Bernoulli version, which returns a number rather than a pair, so destructuring it threw aTypeError. - Both uniform estimators now use the exact maximiser. The likelihood of a uniform law is maximised at the edge of the parameter space, where it is not differentiable, so no gradient method converges to it. The estimator is the sample minimum and maximum.
- The exponential estimator diverged. Its gradient was summed rather than averaged,
so the step size grew with the sample: on 3000 observations it returned about
−74 000 instead of 2. Averaging fixed it, and the same normalisation was applied
throughout, which also fixed gamma and Weibull returning
NaNon large samples. - The geometric estimator returned
1 − p. The two terms of the derivative were transposed. For a truep = 0.25it reported0.74. digammawas wrong, which sent the gamma estimator to a fixed point of the wrong equations — shape8.8where the answer was3. It now uses the standard recurrence and asymptotic expansion, and agrees with reference values to eight decimals.- The Weibull gradient used
^instead of exponentiation. In JavaScript^is bitwise exclusive-or, so the term was meaningless. Its second component was also missing a factor of the shape parameter. The ascent also started from a fixed point regardless of the data, and diverged for a true scale above about 6; it now starts from the closed-form estimate, which holds up to a scale of at least 25. - The binomial estimator was pulled towards
p = 0.5. Its gradient weighted each observation by the binomial coefficient, which does not depend onpand therefore vanishes on differentiation. Since that coefficient peaks atn/2, the estimate was dragged to the middle: a truep = 0.1came back as0.24, andp = 0.9as0.76. It was correct only atp = 0.5. The same weighting overflowed to infinity for a large number of trials, leaving the estimate stuck at its starting value. - Empty samples read as
NaN. Statistics now render as an em dash until a sample exists.
Two things were deliberately not changed: the closed-form estimators, and the learning rates and iteration counts of the estimators that already converged.
The site is static. npm run build produces dist/, which is what the host serves.