然ranjs

Parameter estimation

What is parameter estimation?

Parameter estimation is the process of finding the distribution parameters that best explain an observed dataset. ranjs uses maximum likelihood estimation (MLE): it finds the parameter values that maximise the probability of seeing the data under the assumed model. This is done by a derivative-free Nelder-Mead simplex optimizer, which makes it robust to distributions with non-smooth or discontinuous likelihoods.

Once parameters are estimated you can inspect them, compare models by AIC/BIC, or run a built-in goodness-of-fit test — all with the same distribution instance returned by fit().

The full workflow

fit() closes the full statistical cycle: define a model, generate (or collect) data, estimate parameters from that data, then test whether the fit is adequate.

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.42, passed: true }

The same pattern works for any distribution in the library. Here is a discrete example fitting a Poisson model to count data:

import { dist } from 'ranjs'

const data   = new dist.Poisson(4.2).seed(42).sample(200)
const fitted = dist.Poisson.fit(data)
console.log(fitted.p)           // { lambda: 4.225 }
console.log(fitted.test(data))  // { statistics: 2.471, passed: true }

Static method

Note: fit() is a static method called on the class, not on an instance. Use dist.Normal.fit(data), not model.fit(data).

CorrectWrong
dist.Normal.fit(data)model.fit(data) // TypeError

All distributions

All 140 exported distributions support fit(). Most have a data-aware static _fitInit(data) override that seeds the Nelder-Mead optimizer from a method-of-moments estimate, improving convergence reliability. The base-class fallback uses random positive-value retries when no override is present.

Zero-parameter distributions (e.g. Gilbrat, HalfLogistic) skip optimisation entirely: Cls.fit(data) simply returns new Cls().

Goodness of fit

The instance returned by fit() is a fully-featured distribution object. You can chain any distribution method on it:

const fitted = dist.Gamma.fit(data)

fitted.test(data)        // Anderson-Darling goodness-of-fit test
fitted.aic(data)         // Akaike information criterion
fitted.bic(data)         // Bayesian information criterion
fitted.pdf(x)            // density at any point
fitted.sample(100)       // generate new variates from the fitted model

See the API reference for the full list of methods available on every distribution instance.

Porting from scipy.stats

This guide maps scipy.stats APIs to their ranjs equivalents for the 20 most-used distributions. Each section shows the constructor parameters side-by-side and a brief usage example in both Python and JavaScript.

Method mapping

All ranjs distribution instances share the same methods regardless of distribution type.

scipy.statsranjsNotes
dist.rvs(size=n)dist.sample(n)returns Array of length n
dist.rvs()dist.sample()returns a single number
dist.pdf(x)dist.pdf(x)same name; also covers discrete pmf
dist.pmf(k)dist.pdf(k)ranjs uses pdf() for both continuous and discrete
dist.cdf(x)dist.cdf(x)same name
dist.ppf(q)dist.q(q)quantile / percent-point function
dist.sf(x)dist.survival(x)survival function = 1 − CDF
dist.logpdf(x)dist.lnPdf(x)log-density
scipy.stats.kstest(data, dist)new Dist(...).test(data)built-in KS goodness-of-fit test

Distributions

Normal

scipyranjsParameter mapping
norm(loc=0, scale=1)new Normal(mu=0, sigma=1)loc → mu, scale → sigma
from scipy.stats import norm
d = norm(loc=2, scale=0.5)
d.pdf(2.0)      # 0.7979
d.rvs(size=5)
const d = new ranjs.dist.Normal(2, 0.5)
d.pdf(2.0)      // 0.7979
d.sample(5)

Uniform

scipyranjsParameter mapping
uniform(loc=0, scale=1)new Uniform(xmin=0, xmax=1)loc → xmin; xmax = loc + scale

⚠ Parameter difference: scipy uses start + width (loc, scale); ranjs uses start + end (xmin, xmax). Convert: xmin = loc, xmax = loc + scale.

from scipy.stats import uniform
d = uniform(loc=1, scale=3)   # support [1, 4]
d.pdf(2.5)
d.rvs(size=5)
const d = new ranjs.dist.Uniform(1, 4)  // support [1, 4]
d.pdf(2.5)
d.sample(5)

Exponential

scipyranjsParameter mapping
expon(loc=0, scale=1)new Exponential(lambda=1)scale = 1/lambda (scipy uses mean; ranjs uses rate)

⚠ Parameter difference: scipy's scale is the mean (1/λ); ranjs lambda is the rate (λ). Convert: lambda = 1 / scale.

from scipy.stats import expon
d = expon(scale=0.5)   # rate = 2
d.pdf(1.0)
d.rvs(size=5)
const d = new ranjs.dist.Exponential(2)  // rate = 2
d.pdf(1.0)
d.sample(5)

Gamma

scipyranjsParameter mapping
gamma(a, loc=0, scale=1)new Gamma(alpha, beta=1)a → alpha; beta = 1/scale (rate parameterization)

⚠ Parameter difference: scipy uses a scale parameter (mean/shape); ranjs uses a rate parameter (beta = 1/scale). Convert: beta = 1 / scale.

from scipy.stats import gamma
d = gamma(a=2, scale=0.5)   # rate = 2
d.pdf(1.0)
d.rvs(size=5)
const d = new ranjs.dist.Gamma(2, 2)  // alpha=2, beta=2 (rate)
d.pdf(1.0)
d.sample(5)

Beta

scipyranjsParameter mapping
beta(a, b)new Beta(alpha, beta)a → alpha, b → beta (rename only)
from scipy.stats import beta
d = beta(a=2, b=5)
d.pdf(0.3)
d.rvs(size=5)
const d = new ranjs.dist.Beta(2, 5)
d.pdf(0.3)
d.sample(5)

Chi-squared

scipyranjsParameter mapping
chi2(df)new Chi2(k)df → k (rename only)
from scipy.stats import chi2
d = chi2(df=4)
d.pdf(2.0)
d.rvs(size=5)
const d = new ranjs.dist.Chi2(4)
d.pdf(2.0)
d.sample(5)

Student's t

scipyranjsParameter mapping
t(df)new StudentT(nu)df → nu (rename only)
from scipy.stats import t
d = t(df=10)
d.pdf(0.0)
d.rvs(size=5)
const d = new ranjs.dist.StudentT(10)
d.pdf(0.0)
d.sample(5)

F

scipyranjsParameter mapping
f(dfn, dfd)new F(d1, d2)dfn → d1, dfd → d2 (rename only)
from scipy.stats import f
d = f(dfn=5, dfd=10)
d.pdf(1.0)
d.rvs(size=5)
const d = new ranjs.dist.F(5, 10)
d.pdf(1.0)
d.sample(5)

Log-normal

scipyranjsParameter mapping
lognorm(s, loc=0, scale=1)new LogNormal(mu=0, sigma=1)s → sigma; mu = log(scale)

⚠ Non-trivial conversion: scipy parameterizes lognorm with s (shape = σ of ln X) and scale (= exp(μ of ln X)). ranjs uses log-space parameters directly: mu = mean of ln X, sigma = std dev of ln X. Convert: mu = Math.log(scale), sigma = s.

from scipy.stats import lognorm
import numpy as np
# X ~ LogNormal with log-mean=1, log-sd=0.5
d = lognorm(s=0.5, scale=np.exp(1))
d.pdf(3.0)
d.rvs(size=5)
// mu=1, sigma=0.5 are log-space parameters
const d = new ranjs.dist.LogNormal(1, 0.5)
d.pdf(3.0)
d.sample(5)

Weibull

scipyranjsParameter mapping
weibull_min(c, loc=0, scale=1)new Weibull(lambda=1, k=1)c → k (shape); scale → lambda (scale) — order reversed

⚠ Parameter order reversed: scipy's first positional argument c is the shape (k in ranjs); scipy's scale is ranjs lambda. Convert: Weibull(lambda=scale, k=c).

from scipy.stats import weibull_min
d = weibull_min(c=1.5, scale=2.0)
d.pdf(1.0)
d.rvs(size=5)
// Weibull(lambda, k): lambda=scale=2.0, k=c=1.5
const d = new ranjs.dist.Weibull(2.0, 1.5)
d.pdf(1.0)
d.sample(5)

Pareto

scipyranjsParameter mapping
pareto(b, loc=0, scale=1)new Pareto(xmin=1, alpha=1)b → alpha (shape); scale → xmin (minimum value)
from scipy.stats import pareto
d = pareto(b=3, scale=1)   # xmin=1, alpha=3
d.pdf(2.0)
d.rvs(size=5)
const d = new ranjs.dist.Pareto(1, 3)  // xmin=1, alpha=3
d.pdf(2.0)
d.sample(5)

Cauchy

scipyranjsParameter mapping
cauchy(loc=0, scale=1)new Cauchy(x0=0, gamma=1)loc → x0, scale → gamma (rename only)
from scipy.stats import cauchy
d = cauchy(loc=0, scale=1)
d.pdf(0.0)
d.rvs(size=5)
const d = new ranjs.dist.Cauchy(0, 1)
d.pdf(0.0)
d.sample(5)

Laplace

scipyranjsParameter mapping
laplace(loc=0, scale=1)new Laplace(mu=0, b=1)loc → mu, scale → b (rename only)
from scipy.stats import laplace
d = laplace(loc=0, scale=1)
d.pdf(0.0)
d.rvs(size=5)
const d = new ranjs.dist.Laplace(0, 1)
d.pdf(0.0)
d.sample(5)

Logistic

scipyranjsParameter mapping
logistic(loc=0, scale=1)new Logistic(mu=0, s=1)loc → mu, scale → s (rename only)
from scipy.stats import logistic
d = logistic(loc=0, scale=1)
d.pdf(0.0)
d.rvs(size=5)
const d = new ranjs.dist.Logistic(0, 1)
d.pdf(0.0)
d.sample(5)

Triangular

scipyranjsParameter mapping
triang(c, loc=0, scale=1)new Triangular(a=0, b=1, c=0.5)a=loc, b=loc+scale, c=a+c_scipy*scale

⚠ Non-trivial conversion: scipy's c is the normalized mode (0–1 within [loc, loc+scale]). ranjs uses absolute bounds a (lower), b (upper), and c (mode). Convert: a = loc, b = loc + scale, c = loc + c_scipy * scale.

from scipy.stats import triang
# lower=1, upper=4, mode=2: c=(2-1)/(4-1)=0.333
d = triang(c=0.333, loc=1, scale=3)
d.pdf(2.0)
d.rvs(size=5)
// Triangular(a, b, c): a=1, b=4, mode=2
const d = new ranjs.dist.Triangular(1, 4, 2)
d.pdf(2.0)
d.sample(5)

Poisson

scipyranjsParameter mapping
poisson(mu)new Poisson(lambda=1)mu → lambda (rename only)
from scipy.stats import poisson
d = poisson(mu=3)
d.pmf(2)
d.rvs(size=5)
const d = new ranjs.dist.Poisson(3)
d.pdf(2)    // pdf() covers pmf for discrete
d.sample(5)

Binomial

scipyranjsParameter mapping
binom(n, p)new Binomial(n, p)identical
from scipy.stats import binom
d = binom(n=10, p=0.3)
d.pmf(3)
d.rvs(size=5)
const d = new ranjs.dist.Binomial(10, 0.3)
d.pdf(3)
d.sample(5)

Negative Binomial

scipyranjsParameter mapping
nbinom(n, p)new NegativeBinomial(r, p)n → r; p_ranjs = 1 − p_scipy (p convention differs)

⚠ p convention differs: scipy nbinom counts failures before n successes, with p = P(success). ranjs NegativeBinomial counts successes before r failures, with p = P(success). The PMFs are duals: scipy uses p^n·(1−p)^k; ranjs uses (1−p)^r·p^x. Convert: r = n, p_ranjs = 1 − p_scipy.

from scipy.stats import nbinom
d = nbinom(n=5, p=0.4)
d.pmf(3)
d.rvs(size=5)
// p_ranjs = 1 - p_scipy = 0.6
const d = new ranjs.dist.NegativeBinomial(5, 0.6)
d.pdf(3)
d.sample(5)

Geometric

scipyranjsParameter mapping
geom(p)new Geometric(p)same p; different support convention

⚠ Different support: scipy geom has support {1, 2, 3, …} and counts trials until the first success. ranjs Geometric has support {0, 1, 2, …} and counts failures before the first success. Convert: ranjs_value = scipy_value − 1.

from scipy.stats import geom
d = geom(p=0.3)   # support {1, 2, 3, ...}
d.pmf(1)          # P(X=1) = p = 0.3
d.rvs(size=5)
const d = new ranjs.dist.Geometric(0.3)  // support {0, 1, 2, ...}
d.pdf(0)          // P(X=0) = p = 0.3
d.sample(5)

Hypergeometric

scipyranjsParameter mapping
hypergeom(M, n, N)new Hypergeometric(N, K, n)M → N (population); n → K (successes in pop.); N → n (draws)

⚠ Parameter name collision: scipy and ranjs both use N, but they mean different things. scipy: M = population size, n = successes in population, N = number of draws. ranjs: N = population size, K = successes in population, n = number of draws. Convert: Hypergeometric(N=M, K=n_scipy, n=N_scipy).

from scipy.stats import hypergeom
# population=20, successes_in_pop=7, draws=5
d = hypergeom(M=20, n=7, N=5)
d.pmf(2)
d.rvs(size=5)
// Hypergeometric(N, K, n): N=20, K=7, n=5
const d = new ranjs.dist.Hypergeometric(20, 7, 5)
d.pdf(2)
d.sample(5)