Documentation for levy-stable-jax
levy_stable_jax
Implementation of Levy Stable distributions in JAX.
Params
The different parametrizations that are allowed for the stable distribution:
-
N0: The main parametrization. It is the most favorable from a numerical perspective (the pdf is continuous in the parameters).
-
N1: The parametrization used by scipy by default. The most intuitive for users, since the
locparameter is equal to the mean of the distribution in this parametrization.
Source code in src/levy_stable_jax/_typing.py
logpdf(x, alpha, beta, loc=0.0, scale=1.0, param=Params.N0)
The probability density function for the Levy-Stable distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x |
INPUT_TYPE
|
the values at which to evaluate the PDF |
required |
alpha |
INPUT_TYPE
|
the alpha parameter of the distribution |
required |
beta |
INPUT_TYPE
|
the beta parameter of the distribution |
required |
loc |
INPUT_TYPE
|
the location parameter of the distribution (default 0.0, meaning depends on the parametrization) |
0.0
|
scale |
INPUT_TYPE
|
the scale parameter of the distribution (default 1.0, meaning depends on the parametrization) |
1.0
|
param |
Param
|
the parametrization of the distribution (see Params) |
N0
|
Examples:
Source code in src/levy_stable_jax/distribution.py
param_convert(alpha, beta, loc, scale, param_from, param_to)
Shifts the loc and scale of the alpha-stable distribution from one parametrization to another.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha |
INPUT
|
The stability parameter of the stable distribution (0-2.0]. |
required |
beta |
INPUT
|
The skewness parameter of the stable distribution. |
required |
loc |
INPUT
|
The location parameter of the stable distribution. |
required |
scale |
INPUT
|
The scale parameter of the stable distribution. |
required |
param_from |
Param
|
The initial parametrization of the stable distribution. |
required |
param_to |
Param
|
The requested parametrization |
required |
Returns:
| Type | Description |
|---|---|
Tuple[Array | float, Array | float]
|
(loc, scale) in the requested parametrization. |
Source code in src/levy_stable_jax/_utils.py
pdf(x, alpha, beta, loc=0.0, scale=1.0, param=Params.N0)
The probability density function for the Levy-Stable distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x |
INPUT_TYPE
|
the values at which to evaluate the PDF |
required |
alpha |
INPUT_TYPE
|
the alpha parameter of the distribution |
required |
beta |
INPUT_TYPE
|
the beta parameter of the distribution |
required |
loc |
INPUT_TYPE
|
the location parameter of the distribution (default 0.0, meaning depends on the parametrization) |
0.0
|
scale |
INPUT_TYPE
|
the scale parameter of the distribution (default 1.0, meaning depends on the parametrization) |
1.0
|
param |
Param
|
the parametrization of the distribution |
N0
|
Returns:
| Type | Description |
|---|---|
Array | float
|
the value of the PDF at the given point(s) |
Examples:
Evaluation of the unit Levy-stable distribution at 0.0, with alpha=1.5, beta=0.0.
>>> pdf(0.0, 1.5, 0.0) # doctest: +ELLIPSIS
0.28568...
>>> pdf(0.0, 1.5, 0.0,param=Params.N0) # doctest: +ELLIPSIS
0.28568...
Source code in src/levy_stable_jax/distribution.py
rvs(alpha, beta, prng, loc=0.0, scale=1.0, shape=(), param=Params.N0)
Generate random samples from the Levy-Stable distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha |
INPUT_TYPE
|
the alpha parameter of the distribution |
required |
beta |
INPUT_TYPE
|
the beta parameter of the distribution |
required |
prng |
ArrayLike
|
the pseudo-random number generator key |
required |
loc |
INPUT_TYPE
|
the location parameter of the distribution |
0.0
|
scale |
INPUT_TYPE
|
the scale parameter of the distribution |
1.0
|
shape |
Shape
|
the shape of the output array |
()
|
param |
Param
|
the parametrization of the distribution |
N0
|
Returns: - the generated samples
Examples:
>>> import jax
>>> prng = jax.random.PRNGKey(1)
>>> rvs(alpha=1.5, beta=0.0, loc=0.0, scale=1.0, param=Params.N1,
... shape=(10,), prng=prng) # doctest: +ELLIPSIS
Array([-0.750..., -0.495..., ...], ...)
Source code in src/levy_stable_jax/distribution.py
set_stable(p)
Manages the parametrization of scipy's levy_stable distribution.
Since the parametrization of scipy is at the module level, this function provides a way to temporarily change the parametrization of the distribution.
Example:
from scipy.stats import levy_stable as sp_levy_stable
with set_stable(Params.N0):
# Will be parametrized as N0 / scipy's S0 instead of default S1.
_ = sp_levy_stable.rvs(alpha=1.5, beta=0.0, size=10)
TODO: turn into doctest, there is an issue with indentation
Source code in src/levy_stable_jax/_utils.py
shift_scale(alpha, beta, loc, scale, a, b, param)
Given an alpha-stable distribution X with parameters alpha, beta, loc, scale, returns the distribution Y = a * X + b, in the same parametrization. Since the factor alpha is the same, it returns a tuple of (beta, loc, scale)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha |
INPUT
|
The stability parameter of the stable distribution (0-2.0]. |
required |
beta |
INPUT
|
The skewness parameter of the stable distribution. |
required |
loc |
INPUT
|
The location parameter of the stable distribution. |
required |
scale |
INPUT
|
The scale parameter of the stable distribution. |
required |
a |
INPUT
|
The scaling factor. |
required |
b |
INPUT
|
The shift factor. |
required |
param |
Param
|
The parametrization of the stable distribution. |
required |
Example:
import jax.numpy as jnp
>>> shift_scale(2.0, 0.0, 0.0, 1.0, a=2.0, b=1.0, param=Params.N1) # doctest: +ELLIPSIS
(Array(0., dtype=...), Array(1., dtype=...), Array(2., dtype=...))
Source code in src/levy_stable_jax/_utils.py
sum(alpha, beta, loc=0.0, scale=1.0, param=Params.N0, axis=None)
Computes the sum of independent stable random distributions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha |
INPUT
|
The stability parameter of the stable distribution (0-2.0]. It is fixed for all distributions. |
required |
beta |
INPUT
|
The skewness parameter of the stable distribution. |
required |
loc |
INPUT
|
The location parameter of the stable distribution (exact definition depending on the choice of pametrization) |
0.0
|
scale |
INPUT
|
The scale parameter of the stable distribution. |
1.0
|
param |
Param
|
The parametrization of the stable distribution. |
N0
|
Returns: A tuple of (beta_sum, loc_sum, scale_sum)
Example: the sum of two centered Gaussian distributions is a centered Gaussian distribution with a standard deviation of sqrt(2).
import jax.numpy as jnp
>>> sum(2.0, 0.0, 0.0, jnp.asarray([1.0, 1.0]), Params.N1) # doctest: +ELLIPSIS
(Array(0., dtype=float64), Array(0., dtype=float64), Array(1.4142135...,...))
Example: dot product. Say that x1 ~ S(2, 0, 1, 1) and x2 ~ S(2, 0, 1.1, 1) are independent. We want to compute the distribution z = A . x + b where x = [x1, x2], A is a 3 x 2 matrix, b is a 3-vector, and . is the matrix-vector product.
This can be written by a summing a scale operation:
>>> locs = jnp.asarray([1.0, 1.1])
>>> scales = 2.0
>>> A = jnp.asarray([[1,2],[3,4],[5,6]])
>>> b = jnp.asarray([[2,2,2]]).T
>>> (beta1, loc1, scale1) = shift_scale(2.0, 0.0,locs, scales, A,b,"N1")
>>> print(beta1)
[[0. 0.]
[0. 0.]
[0. 0.]]
>>> print(loc1)
[[3. 4.2]
[5. 6.4]
[7. 8.6]]
>>> print(scale1)
[[ 2. 4.]
[ 6. 8.]
[10. 12.]]
>>> sum(2.0, beta1, loc1, scale1, param="N1", axis=0) # doctest: +ELLIPSIS
(Array([0., 0.], ...), Array([15. , 19.2], ...), Array([11.83215957, 14.96662955], ...))
Source code in src/levy_stable_jax/_utils.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
levy_stable_jax.estimation
Different algorithms to estimate the parameters of a stable distribution.
These algorithms should be all considered as experimental and not part of the standard API: their interface is not likely to change but they may not be as numerically stable.
fit_ll(samples, param, weights=None, alpha=None, beta=None)
Maximum likelihood evaluation for univariate Lévy-Stable distributions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
param |
Param
|
the parametrization of the returned distribution |
required |
samples |
Array
|
a 1-d array with the observed samples. |
required |
weights |
Optional[Array]
|
optional, the weights on each of the samples. |
None
|
alpha |
Optional[float]
|
optional, the value of alpha to be used in the optimization. |
None
|
beta |
Optional[float]
|
optional, the value of beta to be used in the optimization. |
None
|
Examples:
>>> samples = jnp.array([1.0, 2.0, 3.0, 4.0, 5.0])
>>> fit_quantiles(samples, Params.N0) # doctest: +ELLIPSIS
Array([2. , 0. , 3. , 1.048...], dtype=float64)
The first value is alpha, then beta, then the location parameter and then the scale parameter.
Source code in src/levy_stable_jax/estimation.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | |
fit_quantiles(samples, param)
A rough approximation of the distribution parameters based on quantiles.
For now, it is just wrapping the scipy code.
Examples:
>>> samples = jnp.array([1.0, 2.0, 3.0, 4.0, 5.0, 30.0])
>>> fit_quantiles(samples, Params.N0) # doctest: +ELLIPSIS
Array([0.76..., 0.89..., 3.07..., 0.64...], dtype=float64)
The first value is alpha, then beta, then the location parameter and then the scale parameter.
Source code in src/levy_stable_jax/estimation.py
levy_stable_jax.pymc
Monte Carlo sampling of Levy-stable distributions using PyMC.
As far as users are concerned, the only relevant function should be LevyStableN0.
A full example notebook is available here: https://github.com/tjhunter/levy-stable-jax/blob/main/notebooks/pymc_levy.ipynb
LevyStableN0(name, alpha, beta, loc, scale, observed=None)
A Lévy-stable distribution. The parametrization follows the "0" notation from Nolan (2022). It is also known as the "S0" parametrization in scipy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha(tensor) |
the stability parameter. Must be in (1.1, 2]. |
required | |
beta(tensor) |
the skewness parameter. Must be in [-1, 1]. |
required | |
loc(tensor) |
the location parameter (delta in Nolan's notation). |
required | |
scale(tensor) |
the scale parameter (gamma in Nolan's notation). |
required |
This distribution is explicitly implemented and parametrized for the N0
parametrization, because the N1 parametrization is not continuous
for alpha close to 1, and is not recommended in general for numerical work.
Use the shift_scale function if you wish to convert to other parametrizations.
Practical tips
It is currently only implemented for alpha in (1.1 - 2.0]. Smaller values will get trimmed.
In the context of MCMC, it is highly recommended to keep beta in (-0.8, 0.8). The Lévy-stable distribution changes abruptly around values of beta close to 1 or -1. As a result, the sampler will reject many values for small changes around these values. The symptoms will be a high number of divergences and slow progress. It is better to trim the range of accepted values of beta and inspect if the posterior distribution is highly skewed towards the extremes.
You cannot really infer a good value for |beta| > 0.8, unless you have an extremely large amount of points. In addition, all other parameters being equal, the log-likelihood as a function of beta is very flat around |beta| ~ (0.8-0.99) and alpha > 1.5: a very large number of observations would be necessary to observed a rare event in the non-heavy tail that would allow to distinguish between these values.
Because of the rather complex and non-convex shape of the log-likelihood, the sampler may struggle to initially reach a region of high probability. I have found that tune=6000 really helps further convergence.
Since all the code is implemented in JAX, a JAX-based sampler will be much faster.
It is recommended to use pymc.sampling_jax.sample_numpyro_nuts
TODO: turn into a proper distribution.