Core Distributions¶
heavy_tails
¶
Cauchy
dataclass
¶
Bases: InverseTransformSampling
Cauchy(location x0, scale gamma>0). PDF: f(x) = [1/πgamma] * [1 / (1 + ((x-x0)/gamma)^2)] CDF: F(x) = 0.5 + (1/π) * arctan((x - x0)/gamma) PPF: x = x0 + gamma * tan(π(u - 0.5))
Examples:
Symmetric, and heavy enough to have no mean at all -- the sample mean of Cauchy draws is itself Cauchy, so more data does not help.
>>> cauchy = Cauchy(x0=0.0, gamma=1.0)
>>> cauchy.cdf(0.0)
0.5
>>> round(cauchy.ppf(0.75), 10)
1.0
>>> round(cauchy.pdf(0.0), 6)
0.31831
The tail decays like 1/x, so the survival function times x
tends to 1/pi:
cdf
¶
Distribution function.
For very negative z the arctangent approaches -pi/2 and adding
one half cancels it away, leaving about seven digits of a probability
that is the whole point of the left tail. arctan(-1/z) is the same
number with its argument near zero, where it is exact.
Source code in heavytails/heavy_tails.py
pdf
¶
ppf
¶
Quantile function.
Returns inf when the quantile exceeds the float range. At extreme
parameters the true value is genuinely not representable, and reporting
it is more useful than raising, which aborts a parameter sweep at the
first point that overflows.
Uses the cotangent form in the tails. The textbook
tan(pi (u - 1/2)) puts its argument next to +-pi/2, where the
tangent is arbitrarily steep, so a rounding error of one ulp in the
argument becomes an enormous error in the result: at u = 1e-9 it
returns -318309868.8 where the answer is -318309886.2, wrong in the
eighth digit. cot(pi u) has its argument near zero instead, where
it is computed exactly, and the tail is the only part of a Cauchy
anyone cares about.
Source code in heavytails/heavy_tails.py
sf
¶
Survival function 1 - CDF.
Computed as atan(1/z)/pi in the upper tail rather than as
1 - cdf(x). For large z the latter subtracts two numbers that agree
to every displayed digit and collapses to exactly zero, whereas
atan(1/z) -> 1/z keeps full relative precision.
Source code in heavytails/heavy_tails.py
Frechet
dataclass
¶
Bases: InverseTransformSampling
Fréchet(alpha, s, m): heavy-tailed extreme-value distribution. Support x > m. alpha>0 (shape), s>0 (scale), m (location). CDF: F(x) = exp( - ((x - m)/s)^(-alpha) ), x>m PDF: f(x) = (alpha/s) * ((x - m)/s)^(-alpha-1) * exp( - ((x - m)/s)^(-alpha) ), x>m PPF: x = m + s * [ -ln(u) ]^{-1/alpha}
Examples:
The limit law for maxima of Pareto-tailed data, so cdf(m + s) is
exp(-1) whatever the shape:
>>> round(Frechet(alpha=2.0, s=1.0, m=0.0).cdf(1.0), 6)
0.367879
>>> round(Frechet(alpha=5.0, s=1.0, m=0.0).cdf(1.0), 6)
0.367879
>>> round(Frechet(alpha=2.0, s=1.0, m=0.0).ppf(0.5), 6)
1.201122
ppf
¶
Quantile function.
Returns inf when the quantile exceeds the float range. At extreme
parameters the true value is genuinely not representable, and reporting
it is more useful than raising, which aborts a parameter sweep at the
first point that overflows.
Source code in heavytails/heavy_tails.py
sf
¶
Survival function 1 - CDF, via -expm1 for tail accuracy.
In the upper tail the exponent tends to zero and exp of it rounds to
exactly 1.0, so 1 - cdf(x) would yield 0. -expm1(t) is accurate
for small t and preserves the true decay.
Source code in heavytails/heavy_tails.py
GEV_Frechet
dataclass
¶
Bases: InverseTransformSampling
Generalized Extreme Value (Fréchet-type) with xi>0, mu (loc), sigma>0 (scale). Heavy-tailed when xi>0.
CDF: F(x) = exp( -[1 + xi ( (x-mu)/sigma )]^(-1/xi) ), for 1 + xi (x-mu)/sigma > 0 PDF: f(x) = (1/sigma) * [1 + xi z]^(-1/xi - 1) * exp( -[1 + xi z]^(-1/xi) ), z=(x-mu)/sigma PPF: x = mu + (sigma/xi) * ( (-ln u)^(-xi) - 1 )
Examples:
The generalized extreme value distribution in its heavy-tailed branch,
parameterised by xi = 1 / alpha:
>>> gev = GEV_Frechet(xi=0.5, mu=0.0, sigma=1.0)
>>> round(gev.cdf(1.0), 6)
0.64118
>>> round(gev.ppf(0.5), 6)
0.402245
Its support starts at mu - sigma / xi, and nothing falls below it:
ppf
¶
Quantile function.
Returns inf when the quantile exceeds the float range. At extreme
parameters the true value is genuinely not representable, and reporting
it is more useful than raising, which aborts a parameter sweep at the
first point that overflows.
Source code in heavytails/heavy_tails.py
sf
¶
Survival function 1 - CDF, via -expm1 for tail accuracy.
See :meth:Frechet.sf: forming 1 - exp(-t) for small t loses every
significant digit, while -expm1(-t) does not.
Source code in heavytails/heavy_tails.py
InverseTransformSampling
¶
Bases: Samplable
Sampling by feeding uniforms through the quantile function.
Draws the uniforms in a loop -- the generator is Python's and each draw depends on the one before, so there is nothing to vectorise there -- and then inverts all of them in a single call.
That second half is where the time was going. Inverting 50,000 uniforms
one at a time costs about 0.30s, because it is 50,000 separate dispatches
through :func:~heavytails._array.as_array and back; inverting them in one
call costs about 0.8ms.
The uniforms are the same either way, so a seeded sample is reproducible
and its distribution is unchanged. The variates are almost the same:
NumPy's vectorised loops for log and pow round differently from its
scalar ones, so a few draws land one ULP away from what the scalar path
gave. For Weibull(k=0.5) that is 14 values in 20,000, worst case a relative
1.99e-16, with the sample mean unchanged to twelve digits. Tests that pin
seeded variates to the last bit will see it; nothing measuring a
distribution will.
Examples:
>>> Pareto(alpha=2.0, xm=1.0).rvs(3, seed=7) == Pareto(
... alpha=2.0, xm=1.0
... ).rvs(3, seed=7)
True
ppf
¶
The quantile function, which every family mixing this in defines.
Declared here because the batched sampler below is written in terms of it: the mixin is not merely a helper, it is a statement that the family samples by inversion and therefore has a quantile function to invert.
Source code in heavytails/heavy_tails.py
LogNormal
dataclass
¶
Bases: Samplable
LogNormal with underlying Normal(mu, sigma^2), sigma>0. PDF: f(x) = [1/(x sigma sqrt(2π))] * exp( -(ln x - mu)^2 / (2sigma^2) ), x>0 CDF: F(x) = 0.5 * [1 + erf( (ln x - mu) / (sigma sqrt(2)) )], x>0
Examples:
The median is exp(mu) exactly:
>>> lognormal = LogNormal(mu=0.0, sigma=1.0)
>>> lognormal.ppf(0.5)
1.0
>>> round(lognormal.cdf(1.0), 10)
0.5
Heavy-tailed but not regularly varying: the tail decays faster than any power, so a tail index estimator applied to it returns something that does not mean what it usually means.
cdf
¶
Distribution function.
Evaluated one point at a time: it needs the complementary error
function and NumPy has none, so the loop is the implementation rather
than a stand-in. :meth:pdf is elementary and does vectorise.
Source code in heavytails/heavy_tails.py
pdf
¶
Density. Elementary, so this one vectorises.
Source code in heavytails/heavy_tails.py
ppf
¶
Quantile function.
Returns inf when the quantile exceeds the float range rather than
raising. For large mu the answer is genuinely not representable --
the median of LogNormal(mu=1000) is exp(1000) -- and inf is
the correct value to report for it.
Source code in heavytails/heavy_tails.py
sf
¶
Survival function 1 - CDF, computed with erfc for tail accuracy.
1 - cdf(x) collapses to exactly zero once cdf(x) rounds to 1.0,
which happens well inside the range of interest. erfc is accurate
for large arguments and keeps the true decay.
Source code in heavytails/heavy_tails.py
ParameterError
¶
Bases: ValueError
Raised when distribution parameters are invalid.
Pareto
dataclass
¶
Bases: InverseTransformSampling
Pareto Type I with scale xm>0 and shape alpha>0. PDF: f(x) = alpha x_m^alpha / x^{alpha+1}, x >= x_m CDF: F(x) = 1 - (x_m / x)^alpha PPF: F^{-1}(u) = x_m * (1 - u)^{-1/alpha}
Examples:
The defining property is a straight line on a log-log tail plot: the
survival function falls by a factor of 10 ** alpha per decade.
>>> pareto = Pareto(alpha=2.0, xm=1.0)
>>> round(pareto.sf(10.0), 10)
0.01
>>> round(pareto.sf(100.0), 10)
0.0001
>>> pareto.cdf(2.0)
0.75
>>> round(pareto.ppf(0.5), 6)
1.414214
cdf
¶
Distribution function.
Source code in heavytails/heavy_tails.py
pdf
¶
Density. A number in gives a number out, a sequence gives an array.
Source code in heavytails/heavy_tails.py
ppf
¶
Quantile function.
Returns inf where the quantile exceeds the float range. At extreme
parameters the true value is genuinely not representable, and reporting
it is more useful than raising, which aborts a parameter sweep at the
first point that overflows.
Raises:
| Type | Description |
|---|---|
ValueError
|
If any probability is outside (0, 1). The whole input is checked before any work starts, so the message can name the offending value instead of failing part way through. |
Source code in heavytails/heavy_tails.py
sf
¶
Survival function.
Computed directly rather than as 1 - cdf, which returns exactly
zero once the survival probability drops below the spacing of one.
Source code in heavytails/heavy_tails.py
RNG
¶
Thin wrapper around random.Random for reproducibility and isolation.
Attributes¶
rng : random.Random Underlying random number generator.
Examples:
Seeded, which is what makes a simulation study checkable rather than merely plausible:
Source code in heavytails/heavy_tails.py
chisquare
¶
gamma
¶
X ~ Gamma(k, θ) with k>0, θ>0 using Marsaglia-Tsang (2000). Works for all k>0 (uses boost for k<1).
References¶
G. Marsaglia and W. W. Tsang (2000). A Simple Method for Generating Gamma Variables. ACM Transactions on Mathematical Software 26(3):363-372.
Source code in heavytails/heavy_tails.py
standard_normal
¶
uniform_0_1
¶
U ~ Uniform(0,1) in (0,1), clipped away from exact 0 and 1 for log/ppf stability.
Source code in heavytails/heavy_tails.py
Samplable
¶
Mixin to provide vectorized sampling with a given RNG.
Examples:
Every distribution here draws through it, so the same seed gives the same sample:
>>> Pareto(alpha=2.0, xm=1.0).rvs(3, seed=1) == Pareto(
... alpha=2.0, xm=1.0
... ).rvs(3, seed=1)
True
rvs
¶
Draw n IID variates. Subclasses implement ._rvs_one(rng), or ._rvs_many(rng, n) when the whole sample can be drawn at once.
Source code in heavytails/heavy_tails.py
StudentT
dataclass
¶
Bases: Samplable
Student's t with degrees of freedom nu>0. PDF: f(x) = Gamma((nu+1)/2) / [ sqrt(nuπ) Gamma(nu/2) ] * (1 + x2/nu)(-(nu+1)/2) Sampling: X = Z / sqrt(Y/nu) with Z~N(0,1), Y~χ²(nu)
The CDF, survival function and quantile function are expressed through the
regularized incomplete beta function in heavytails._special, so no
third-party dependency is required.
Examples:
The tail index is nu, so moments below it exist and the rest do
not. One degree of freedom is the Cauchy exactly:
>>> round(StudentT(nu=1.0).cdf(1.0), 10)
0.75
>>> round(Cauchy(x0=0.0, gamma=1.0).cdf(1.0), 10)
0.75
More degrees of freedom means a lighter tail:
cdf
¶
Distribution function, via the regularized incomplete beta.
Evaluated one point at a time. NumPy has no incomplete beta, so there
is no expression to vectorise and the loop is the implementation
rather than a stand-in for one. :meth:pdf is elementary and does
vectorise.
Source code in heavytails/heavy_tails.py
pdf
¶
Density. Elementary, so this one vectorises.
Source code in heavytails/heavy_tails.py
ppf
¶
Quantile function, obtained by inverting the incomplete beta.
Uses the symmetry of the Student-t about zero so that only the upper
half is solved, then inverts I_y(nu/2, 1/2) = 2(1-u) directly.
Inverting in y rather than in x is what keeps the far tail
accurate: a quantile solver working to an absolute tolerance on the CDF
loses most of its digits where the density is vanishingly small.
Source code in heavytails/heavy_tails.py
sf
¶
Weibull
dataclass
¶
Bases: InverseTransformSampling
Weibull(k, lambda_) with shape k>0 and scale lambda_>0. PDF: f(x) = (k/lambda_) (x/lambda_)^{k-1} exp(-(x/lambda_)^k), x>=0 CDF: F(x) = 1 - exp(-(x/lambda_)^k), x>=0 PPF: x = lambda_ * (-ln(1-u))^{1/k} Heavy-tailed for k in (0,1) (subexponential, slower than exponential decay).
Examples:
Shape one is the exponential distribution, whose survival function at
the scale is 1/e:
Below one it is heavy-tailed, and the density is unbounded at the origin:
cdf
¶
Distribution function, via -expm1 so the lower tail survives.
Source code in heavytails/heavy_tails.py
pdf
¶
Density, which diverges at the origin for shape below one.
Source code in heavytails/heavy_tails.py
ppf
¶
Quantile function.
Returns inf when the quantile exceeds the float range. At extreme
parameters the true value is genuinely not representable, and reporting
it is more useful than raising, which aborts a parameter sweep at the
first point that overflows.
Source code in heavytails/heavy_tails.py
sf
¶
Survival function: 1 - CDF(x).