Skip to content

Low-level API

lmo.constants

Mathematical constants.

lmo.constants.theta_m: Final[float] = 0.9553166181245093 module-attribute

Magic angle \( \theta_m = \arctan \sqrt 2 \).

See also

lmo.constants.theta_m_bar: Final[float] = 0.1520433619923482 module-attribute

Magic number of turns \( \bar{\theta}_m = \theta_m / (2 \pi) \).

See also

lmo.linalg

Linear algebra and linearized orthogonal polynomials.

lmo.linalg.sandwich(A, X, /, dtype=np.float64)

Calculates the “sandwich” matrix product (A @ X @ A.T) along the specified X axis.

Parameters:

Name Type Description Default
A Array[tuple[_K, _R], floating[Any]]

2-D array of shape (k, r), the “bread”.

required
X Array[tuple[_R, *tuple[_R, ...]], floating[Any]]

Array of shape (r, r, ...).

required
dtype _DType[_TF]

The data type of the result.

float64

Returns:

Name Type Description
C Array[tuple[_K, *tuple[_K, ...]], _TF]

Array of shape (k, k, ...).

See Also

lmo.linalg.pascal(k, /, dtype=np.int64, *, inv=False)

Construct the lower-diagonal Pascal matrix \(L_{k \times k\)}$, or its matrix inverse \(L^{-1}\).

\[ \begin{align} L_{ij} &= \binom{i}{j} \\ L^{-1}_{ij} &= (-1)^{i - j} L_{ij} \end{align} \]

Implemented using recursion, unlike the slow naive implementation from the equivalent scipy.linalg.pascal and scipy.linalg.invpascal functions using kind='lower'. By using the binomial recurrence relation, assuming \(0 < j < i\), \(\binom{i}{j} = \frac{i}{j} \binom{i-1}{j-1}\), the following recursive definition is obtained:

\[ L_{ij} = \begin{cases} 0 & \text{if } i < j \text{,} \\ 1 & \text{if } i = j \vee j = 0 \text{, and} \\ (i \, L_{i-1,\, j-1}) / j & \text{otherwise.} \end{cases} \]

Examples:

>>> import numpy as np
>>> pascal(4, dtype=np.int_)
array([[1, 0, 0, 0],
       [1, 1, 0, 0],
       [1, 2, 1, 0],
       [1, 3, 3, 1]])
>>> pascal(4, dtype=np.int_, inv=True)
array([[ 1,  0,  0,  0],
       [-1,  1,  0,  0],
       [ 1, -2,  1,  0],
       [-1,  3, -3,  1]])
>>> np.rint(np.linalg.inv(pascal(4))).astype(np.int_)
array([[ 1,  0,  0,  0],
       [-1,  1,  0,  0],
       [ 1, -2,  1,  0],
       [-1,  3, -3,  1]])

Now, let’s compare with scipy:

>>> import scipy.linalg
>>> scipy.linalg.invpascal(4, kind='lower').astype(np.int_)
array([[ 1,  0,  0,  0],
       [-1,  1,  0,  0],
       [ 1, -2,  1,  0],
       [-1,  3, -3,  1]])

lmo.linalg.ir_pascal(k, /, dtype)

Inverse regulatized lower-diagonal Pascal matrix, \(\bar{L}_{ij} = L^{-1}_ij / i\).

Used to linearly combine order statistics order statistics into L-moments.

lmo.linalg.sh_legendre(k, /, dtype=np.int64)

Shifted Legendre polynomial coefficient matrix \(\widetilde{P}\) of shape (k, k).

The \(j\)-th coefficient of the shifted Legendre polynomial of degree \(k\) is at \((k, j)\):

\[ \widetilde{p}_{k, j} = (-1)^{k - j} \binom{k}{j} \binom{k + j}{j} \]

Useful for transforming probability-weighted moments into L-moments.

Danger

For \(k \ge 29\), all 64-bits dtypes (default is int64) will overflow, which results in either an OverflowError (if you’re lucky), or will give incorrect results. Similarly, all 32-bits dtypes (e.g. np.int_ on Windows) already overflow when \(k \ge 16\).

This is not explicitly checked – so be sure to select the right dtype depending on k.

One option is to use dtype=np.object_, which will use Python-native int. However, this is a lot slower, and is likely to fail. For instance, when multiplied together with some float64 array, a TypeError is raised.

Parameters:

Name Type Description Default
k _K

The size of the matrix, and the max degree of the shifted Legendre polynomial.

required
dtype _DType[_TI]

Desired output data type, e.g, numpy.float64. Must be signed. The default is numpy.int64.

int64

Returns:

Name Type Description
P _Square[_K, _TI]

2-D array of the lower-triangular square matrix of size \(k^2\)`.

Examples:

Calculate \(\widetilde{P}_{4 \times 4}\):

>>> from lmo.linalg import sh_legendre
>>> sh_legendre(4, dtype=int)
array([[  1,   0,   0,   0],
       [ -1,   2,   0,   0],
       [  1,  -6,   6,   0],
       [ -1,  12, -30,  20]])
See Also

lmo.linalg.sh_jacobi(k, a, b, /, dtype=np.float64)

Shifted Jacobi polynomial coefficient matrix \(\widetilde{P}^{(a,b)}\) of shape (k, k).

The \(j\)-th coefficient of the shifted Jacobi polynomial of degree \(k\) is at \((k, j)\):

The “shift” refers to the change of variables \(x \mapsto 2x - 1\) in the (unshifted) Jacobi (or hypergeometric) polynomials.

The (shifted) Jacobi polynomials \(\widetilde{P}^{(a,b)}\) generalize the (shifted) Legendre polynomials \(\widetilde{P}\): \(\widetilde{P}^{(0, 0)} = \widetilde{P}\)

Parameters:

Name Type Description Default
k _K

The size of the matrix, and the max degree of the polynomial.

required
a float

The \(\alpha\) parameter, must be \(\ge 0\).

required
b float

The \(\beta\) parameter, must be \(\ge 0\).

required
dtype _DType[_TF]

Desired output data type, e.g, numpy.float64. Default is numpy.int64 if a and b are integers, otherwise np.float64.

float64

Returns:

Name Type Description
P _Square[_K, _TF]

2-D array of the lower-triangular square matrix of size \(k^2\)`.

Examples:

Calculate \(\widetilde{P}^{(1, 1)}_{4 \times 4}\):

>>> from lmo.linalg import sh_jacobi
>>> sh_jacobi(4, 1, 1, dtype=int)
array([[  1,   0,   0,   0],
       [ -2,   4,   0,   0],
       [  3, -15,  15,   0],
       [ -4,  36, -84,  56]])

Let’s compare \(\widetilde{P}^(1, \pi)_3\) with the scipy Jacobi poly1d. This requires manual shifting \(x \mapsto f(x)\), with \(f(x) = 2x - 1\):

>>> import numpy as np
>>> import scipy.special as sc
>>> f_x = np.poly1d([2, -1])  # f(x) = 2*x + 1
>>> sc.jacobi(3, 1, np.pi)(f_x)
poly1d([ 125.80159497, -228.55053774,  128.54584648,  -21.79690371])
>>> sh_jacobi(4, 1, np.pi)[3]
array([ -21.79690371,  128.54584648, -228.55053774,  125.80159497])

Apart from the reversed coefficients of numpy.poly1d (an awkward design choice, but it’s fixed in the new numpy.polynomial module.)

See Also

lmo.linalg.succession_matrix(c)

A toeplitz-like transformation matrix construction, that prepends \(i\) zeroes to \(i\)-th row, so that the input shape is mapped from (n, k) to (n, k + n).

So all values \(i > j \vee i + j \ge k\) are zero in the succession matrix.

Parameters:

Name Type Description Default
c Array[tuple[_K, int], _T] | Array[tuple[_K], _T]

Dense matrix of shape (n, k).

required

Returns:

Name Type Description
S Array[tuple[_K, int], _T]

Matrix of shape (n, k + n)

Examples:

>>> from lmo.linalg import succession_matrix
>>> c = np.arange(1, 9).reshape(4, 2)
>>> c
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])
>>> succession_matrix(c)
array([[1, 2, 0, 0, 0],
       [0, 3, 4, 0, 0],
       [0, 0, 5, 6, 0],
       [0, 0, 0, 7, 8]])

lmo.linalg.trim_matrix(r, /, trim, dtype=np.float64)

Linearization of the trimmed L-moment recurrence relations, following the (corrected) derivation by Hosking (2007) from the (shifted) Jacobi Polynomials.

This constructs a \(r \times r + t_1 + t_2\) matrix \(T^{(t_1, t_2)}\) that “trims” conventional L-moments. E.g. the first 3 \((1, 1)\) trimmed L-moments can be obtained from the first \(3+1+1=5\) (untrimmed) L-moments (assuming they exist) with trim_matrix(3, (1, 1)) @ l_moment(x, np.ogrid[:5] + 1).

The big “L” in “L-moment”, referring to it being a Linear combination of order statistics, has been prominently put in the name by Hosking (1990) for a good reason. It means that transforming order statistics to a bunch of L-moments, can be done using a single matrix multiplication (see lmo.linalg.sh_legendre). By exploiting liniarity, it can easily be chained with this trim matrix, to obtain a reusable order-statistics -> trimmed L-moments transformation (matrix).

Note that these linear transformations can be used in exactly the same way to e.g. calculate several population TL-moments of some random varianble, using nothing but its theoretical probablity-weighted moments (PWMs).

Parameters:

Name Type Description Default
r _R

The max (trimmed) L-moment order.

required
trim tuple[int, int]

Left- and right-trim orders \((t_1, t_2)\), integers \(\ge 0\). If set to (0, 0), the identity matrix is returned.

required
dtype _DType[_TF]

Desired output data type, e.g, numpy.float64 (default).

float64

Returns:

Type Description
Array[tuple[_R, int], _TF]

Toeplitz-like matrix of shape \((r, r + t_1 + t_2)\).

Examples:

>>> from lmo.linalg import trim_matrix
>>> trim_matrix(3, (0, 1))
array([[ 1.        , -1.        ,  0.        ,  0.        ],
       [ 0.        ,  0.75      , -0.75      ,  0.        ],
       [ 0.        ,  0.        ,  0.66666667, -0.66666667]])
>>> trim_matrix(3, (1, 0))
array([[1.        , 1.        , 0.        , 0.        ],
       [0.        , 0.75      , 0.75      , 0.        ],
       [0.        , 0.        , 0.66666667, 0.66666667]])
References

lmo.special

Mathematical “special” functions, extending scipy.special.

lmo.special.fpow(x, n, /, out=None)

fpow(x: _Real_in, n: _Real_in, /, out: None = None) -> _F8
fpow(x: _RealND_in, n: _Real_in | _RealND_in, /, out: None = None) -> _F8ND
fpow(x: _Real_in, n: _RealND_in, /, out: None = None) -> _F8ND
fpow(x: _RealND_in, n: _Real_in | _RealND_in, /, out: _ArrayT) -> _ArrayT
fpow(x: _Real_in, n: _RealND_in, /, out: _ArrayT) -> _ArrayT

Factorial power, or falling factorial.

It is defined as

\[ \ffact{x}{n} = \frac{\Gamma(x + 1)}{\Gamma(x - n + 1)} \]

Parameters:

Name Type Description Default
x _Real_in | _RealND_in

Real-valued array-like or scalar.

required
n _Real_in | _RealND_in

Real valued array-like or scalar.

required
out _ArrayT | None

Optional output array for the function results

None

Returns:

Name Type Description
out _ArrayT | _F8 | _F8ND

Array or scalar with the value(s) of the function.

See Also

lmo.special.gamma2(a, x, /, out=None)

gamma2(a: _Real_in, x: _Real_in, /, out: None = None) -> _F8
gamma2(a: _Real_in, x: _RealND_in, /, out: None = None) -> _F8ND
gamma2(a: _Real_in, x: _RealND_in, /, out: _ArrayT) -> _ArrayT

Incomplete (upper) gamma function.

It is defined as

\[ \Gamma(a,\ x) = \int_x^\infty t^{a-1} e^{-t} \mathrm{d}t \]

for \( a \ge 0 \) and \( x \ge 0 \).

Parameters:

Name Type Description Default
a _Real_in

Real-valued non-negative scalar.

required
x _Real_in | _RealND_in

Real-valued non-negative array-like.

required
out _ArrayT | None

Optional output array for the results.

None

Returns:

Name Type Description
out _ArrayT | _F8 | _F8ND

Scalar of array with the values of the incomplete gamma function.

See Also

lmo.special.harmonic(n, /, out=None)

harmonic(n: _Real_in, /, out: None = None) -> float
harmonic(n: _RealND_in, /, out: None = None) -> _F8ND
harmonic(n: _RealND_in, /, out: _ArrayT) -> _ArrayT

Harmonic number \( H_n = \sum_{k=1}^{n} 1 / k \), extended for real and complex argument via analytic contunuation.

Examples:

>>> harmonic(0)
0.0
>>> harmonic(1)
1.0
>>> harmonic(2)
1.5
>>> harmonic(42)
4.32674
>>> harmonic(np.pi)
1.87274
>>> harmonic(-1 / 12)
-0.146106

Parameters:

Name Type Description Default
n _Real_in | _RealND_in

Real- or complex- valued parameter, as array-like or scalar.

required
out _ArrayT | None

Optional real or complex output array for the results.

None

Returns:

Name Type Description
out _ArrayT | float | _F8ND

Array or scalar with the value(s) of the function.

See Also

lmo.special.norm_sh_jacobi(n, alpha, beta)

norm_sh_jacobi(n: AnyOrder, alpha: float, beta: float) -> _F8
norm_sh_jacobi(n: AnyOrderND, alpha: float, beta: float) -> _F8ND

Evaluate the (weighted) \( L^2 \)-norm of a shifted Jacobi polynomial.

Specifically,

\[ \| p_n \|^2 = \braket{p_n | p_n} = \int_0^1 |p_n|^2 \mathrm{d}x = \frac{1}{2 n + \alpha + \beta + 1} \frac {\Gamma(n + \alpha + 1) \Gamma(n + \beta + 1)} {n! \ \Gamma(n + \alpha + \beta + 1)} \]

with

\[ p_n(x) \equiv x^{\beta / 2} \ (1 - x)^{\alpha / 2} \ \shjacobi{n}{\alpha}{\beta}{x} \]

the normalized Jacobi polynomial on \( [0, 1] \).

lmo.special.fourier_jacobi(x, c, a, b)

fourier_jacobi(x: _Real_in, c: _RealND_in, a: float, b: float) -> _F8
fourier_jacobi(x: _RealND_in, c: _RealND_in, a: float, b: float) -> _F8ND

Evaluate the Fourier-Jacobi series, using the Clenshaw summation algorithm.

If \( c \) is of length \( n + 1 \), this function returns the value:

\[ c_0 \cdot \jacobi{0}{a}{b}{x} + c_1 \cdot \jacobi{1}{a}{b}{x} + \ldots + c_n \cdot \jacobi{n}{a}{b}{x} \]

Here, \( \jacobi{n}{a}{b}{x} \) is a Jacobi polynomial of degree \( n = |\vec{c}| \), which is orthogonal iff \( (a, b) \in (-1,\ \infty)^2 \) and \( x \in [0,\ 1] \).

Tip

Trailing zeros in the coefficients will be used in the evaluation, so they should be avoided if efficiency is a concern.

Parameters:

Name Type Description Default
x _Real_in | _RealND_in

Scalar or array-like with input data.

required
c _RealND_in

Array-like of coefficients, ordered from low to high. All coefficients to the right are considered zero.

For instance, [4, 3, 2] gives \( 4 \jacobi{0}{a}{b}{x} + 3 \jacobi{1}{a}{b}{x} + 2 \jacobi{2}{a}{b}{x} \).

required
a float

Jacobi parameter \( a > -1 \).

required
b float

Jacobi parameter \( a > -1 \).

required

Returns:

Name Type Description
out _F8 | _F8ND

Scalar or array of same shape as x.

See Also

lmo.theoretical

Theoretical (population) L-moments of known univariate probability distributions.

lmo.theoretical.cdf_from_ppf(ppf)

Numerical inversion of the PPF.

Note

This function isn’t vectorized.

lmo.theoretical.entropy_from_qdf(qdf, /, *args, **kwds)

entropy_from_qdf(qdf: _QDF[[]]) -> float
entropy_from_qdf(qdf: _QDF[_Tss], /, *args: _Tss.args, **kwds: _Tss.kwargs) -> float

Evaluate the (differential / continuous) entropy \( H(X) \) of a univariate random variable \( X \), from its quantile density function (QDF), \( q(u) = \frac{\mathrm{d} F^{-1}(u)}{\mathrm{d} u} \), with \( F^{-1} \) the inverse of the CDF, i.e. the PPF / quantile function.

The derivation follows from the identity \( f(x) = 1 / q(F(x)) \) of PDF \( f \), specifically:

\[ h(X) = \E[-\ln f(X)] = \int_\mathbb{R} \ln \frac{1}{f(x)} \mathrm{d} x = \int_0^1 \ln q(u) \mathrm{d} u \]

Parameters:

Name Type Description Default
qdf (float, *Tss.args, **Tss.kwargs) -> float

The quantile distribution function (QDF).

required
*args args

Optional additional positional arguments to pass to qdf.

()
**kwds kwargs

Optional keyword arguments to pass to qdf.

{}

Returns:

Type Description
float

The differential entropy \( H(X) \).

See Also

lmo.theoretical.l_comoment_from_pdf(pdf, cdfs, r, /, trim=0, *, supports=None, quad_opts=None)

Evaluate the theoretical L-comoment matrix of a multivariate probability distribution, using the joint PDF \(f(\vec x) \equiv f(x_1, x_2, \ldots, x_n)\) of random vector \(\vec{X}\), and the marginal CDFs \(F_k\) of its \(k\)-th random variable.

The L-comoment matrix is defined as

\[ \Lambda_{r}^{(s, t)} = \left[ \lambda_{r [ij]}^{(s, t)} \right]_{n \times n} \;, \]

with elements

\[ \begin{align*} \lambda_{r [ij]}^{(s, t)} &= c^{(s,t)}_r \int_{\mathbb{R^n}} x_i \ u_j^s \ (1 - u_j)^t \ \widetilde{P}^{(t, s)}_{r - 1} (u_j) \ f(\vec{x}) \ \mathrm{d} \vec{x} \\ &= c^{(s,t)}_r \, \mathbb{E}_{\vec{X}} \left[ X_i \ U_j^s \ (1 - U_j)^t \ \widetilde{P}^{(t, s)}_{r - 1}(U_j) \right] \, , \end{align*} \]

where \(U_j = F_j(X_j)\) and \(u_j = F_j(x_j)\) denote the (marginal) probability integral transform of \(X_j\) and \(x_j \sim X_j\). Furthermore, \(\widetilde{P}^{(\alpha, \beta)}_k\) is a shifted Jacobi polynomial, and

\[ c^{(s,t)}_r = \frac{r + s + t}{r} \frac{\B(r,\ r + s + t)}{\B(r + s,\ r + t)} \; , \]

a positive constant.

For \(r \ge 2\), it can also be expressed as

\[ \lambda_{r [ij]}^{(s, t)} = c^{(s,t)}_r \mathrm{Cov} \left[ X_i, \; U_j^s \ (1 - U_j)^t \ \widetilde{P}^{(t, s)}_{r - 1}(U_j) \right] \; , \]

and without trim (\(s = t = 0\)), this simplifies to

\[ \lambda_{r [ij]} = \mathrm{Cov} \left[ X_i ,\; \widetilde{P}_{r - 1} (U_j) \right] \; , \]

with \(\tilde{P}_n = \tilde{P}^{(0, 0)}_n\) the shifted Legendre polynomial. This last form is precisely the definition introduced by Serfling & Xiao (2007).

Note that the L-comoments along the diagonal, are equivalent to the (univariate) L-moments, i.e.

\[ \lambda_{r [ii]}^{(s, t)}\big( \vec{X} \big) = \lambda_{r}^{(s, t)}\big( X_i \big) \;. \]
Notes

At the time of writing, trimmed L-comoments have not been explicitly defined in the literature. Instead, the author (@jorenham) derived it by generizing the (untrimmed) L-comoment definition by Serfling & Xiao (2007), analogous to the generalization of L-moments into TL-moments by Elamir & Seheult (2003).

Examples:

Find the L-coscale and TL-coscale matrices of the multivariate Student’s t distribution with 4 degrees of freedom:

>>> from scipy.stats import multivariate_t
>>> df = 4
>>> loc = np.array([0.5, -0.2])
>>> cov = np.array([[2.0, 0.3], [0.3, 0.5]])
>>> X = multivariate_t(loc=loc, shape=cov, df=df)
>>> from scipy.special import stdtr
>>> std = np.sqrt(np.diag(cov))
>>> cdf0 = lambda x: stdtr(df, (x - loc[0]) / std[0])
>>> cdf1 = lambda x: stdtr(df, (x - loc[1]) / std[1])
>>> l_cov = l_comoment_from_pdf(X.pdf, (cdf0, cdf1), 2)
>>> l_cov.round(4)
array([[1.0413, 0.3124],
       [0.1562, 0.5207]])
>>> tl_cov = l_comoment_from_pdf(X.pdf, (cdf0, cdf1), 2, trim=1)
>>> tl_cov.round(4)
array([[0.4893, 0.1468],
       [0.0734, 0.2447]])

The (Pearson) correlation coefficient can be recovered in several ways:

>>> cov[0, 1] / np.sqrt(cov[0, 0] * cov[1, 1])  # "true" correlation
0.3
>>> l_cov[0, 1] / l_cov[0, 0]
0.3
>>> l_cov[1, 0] / l_cov[1, 1]
0.3
>>> tl_cov[0, 1] / tl_cov[0, 0]
0.3
>>> tl_cov[1, 0] / tl_cov[1, 1]
0.3

Parameters:

Name Type Description Default
pdf Callable[[_ArrF8], float] | Callable[[_ArrF8], float64]

Joint Probability Distribution Function (PDF), that accepts a float vector of size \(n\), and returns a scalar in \([0, 1]\).

required
cdfs Sequence[_Fn1]

Sequence with \(n\) marginal CDF’s.

required
r AnyOrder

Non-negative integer \(r\) with the L-moment order.

required
trim AnyTrim

Left- and right- trim, either as a \((s, t)\) tuple with \(s, t > -1/2\), or \(t\) as alias for \((t, t)\).

0

Other Parameters:

Name Type Description
supports Sequence[_Pair[float]] | None

A sequence with \(n\) 2-tuples, corresponding to the marginal integration limits. Defaults to \([(-\infty, \infty), \dots]\).

quad_opts QuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

Returns:

Name Type Description
lmbda _ArrF8

The population L-comoment matrix with shape \(n \times n\).

References

lmo.theoretical.l_coratio_from_pdf(pdf, cdfs, r, r0=2, /, trim=0, *, supports=None, quad_opts=None)

Evaluate the theoretical L-comoment ratio matrix of a multivariate probability distribution, using the joint PDF \(f_{\vec{X}}(\vec{x})\) and \(n\) marginal CDFs \(F_X(x)\) of random vector \(\vec{X}\).

\[ \tilde \Lambda_{r,r_0}^{(s, t)} = \left[ \left. \lambda_{r [ij]}^{(s, t)} \right/ \lambda_{r_0 [ii]}^{(s, t)} \right]_{n \times n} \]
See Also

lmo.theoretical.l_moment_from_cdf(cdf, r, /, trim=0, *, support=None, quad_opts=None, alpha=ALPHA, ppf=None)

l_moment_from_cdf(
    cdf: _Fn1 | Callable[[float], float],
    r: lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] | None = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
    ppf: _Fn1 | None = ...,
) -> _ArrF8
l_moment_from_cdf(
    cdf: _Fn1 | Callable[[float], float],
    r: lmt.AnyOrder,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] | None = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
    ppf: _Fn1 | None = ...,
) -> np.float64

Evaluate the population L-moment of a continuous probability distribution, using its Cumulative Distribution Function (CDF) \(F_X(x) = P(X \le x)\).

\[ \lambda^{(s, t)}_r = \begin{cases} 1 & r = 0 \\ \displaystyle \int_{\mathbb{R}} \left(H(x) - I_u(s + 1,\ t + 1)\right) \ \mathrm{d} x & r = 1 \\ \displaystyle \frac{c^{(s,t)}_r}{r} \int_{\mathbb{R}} u^{s + 1} \left(1 - u\right)^{t + 1} \ \widetilde{P}^{(t + 1, s + 1)}_{r - 2}(u) \ \mathrm{d} x & r > 1 \; , \end{cases} \]

where,

\[ c^{(s,t)}_r = \frac{r + s + t}{r} \frac{\B(r,\ r + s + t)}{\B(r + s,\ r + t)} \; , \]

\(\widetilde{P}^{(\alpha, \beta)}_k(x)\) the shifted (\(x \mapsto 2x-1\)) Jacobi polynomial, \(H(x)\) the Heaviside step function, and \(I_x(\alpha, \beta)\) the regularized incomplete gamma function, and \(u = F_X(x)\) the probability integral transform of \(x \sim X\).

Notes

Numerical integration is performed with scipy.integrate.quad, which cannot verify whether the integral exists and is finite. If it returns an error message, an IntegrationWarning is issues, and nan is returned (even if quad returned a finite result).

Examples:

Evaluate the first 4 L- and TL-moments of the standard normal distribution:

>>> from scipy.special import ndtr  # standard normal CDF
>>> l_moment_from_cdf(ndtr, [1, 2, 3, 4])
array([0.        , 0.56418958, 0.        , 0.06917061])
>>> l_moment_from_cdf(ndtr, [1, 2, 3, 4], trim=1)
array([0.        , 0.29701138, 0.        , 0.01855727])

Evaluate the first 4 TL-moments of the standard Cauchy distribution:

>>> def cdf_cauchy(x: float) -> float:
...     return np.arctan(x) / np.pi + 1 / 2
>>> l_moment_from_cdf(cdf_cauchy, [1, 2, 3, 4], trim=1)
array([0.        , 0.69782723, 0.        , 0.23922105])

Parameters:

Name Type Description Default
cdf _Fn1 | Callable[[float], float]

Cumulative Distribution Function (CDF), \(F_X(x) = P(X \le x)\). Must be a continuous monotone increasing function with signature (float) -> float, whose return value lies in \([0, 1]\).

required
r AnyOrder | AnyOrderND

L-moment order(s), non-negative integer or array-like of integers.

required
trim AnyTrim

Left- and right- trim, either as a \((s, t)\) tuple with \(s, t > -1/2\), or \(t\) as alias for \((t, t)\).

0

Other Parameters:

Name Type Description
support _Pair[float] | None

The subinterval of the nonzero domain of cdf. Generally it’s not needed to provide this, as it will be guessed automatically.

quad_opts QuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

alpha float

Split the integral into integrals with limits \([a, F^{-1}(\alpha)]\), \([F(\alpha), F^{-1}(1 - \alpha)]\) and \([F^{-1}(1 - \alpha), b]\) to improve numerical stability. So \(\alpha\) can be consideresd the size of the tail. Numerical experiments have found 0.05 to give good results for different distributions.

ppf _Fn1 | None

The inverse of the cdf, used with alpha to calculate the integral split points (if provided).

Raises:

Type Description
TypeError

r is not integer-valued or negative

ValueError

r is negative

Returns:

Name Type Description
lmbda float64 | _ArrF8

The population L-moment(s), a scalar or float array like r. If nan, consult the related IntegrationWarning message.

References
See Also

lmo.theoretical.l_moment_from_ppf(ppf, r, /, trim=0, *, support=(0, 1), quad_opts=None, alpha=ALPHA)

l_moment_from_ppf(
    ppf: _Fn1 | Callable[[float], float],
    r: lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> _ArrF8
l_moment_from_ppf(
    ppf: _Fn1 | Callable[[float], float],
    r: lmt.AnyOrder,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> np.float64

Evaluate the population L-moment of a univariate probability distribution, using its Percentile Function (PPF), \(x(F)\), also commonly known as the quantile function, which is the inverse of the Cumulative Distribution Function (CDF).

\[ \lambda^{(s, t)}_r = c^{(s, t)}_r \int_0^1 F^s (1 - F)^t \ \widetilde{P}^{(t, s)}_{r - 1}(F) \ x(F) \ \mathrm{d} F \; , \]

where

\[ c^{(s,t)}_r = \frac{r+s+t}{r} \frac{B(r,\,r+s+t)}{B(r+s,\,r+t)} \;, \]

and \(\widetilde{P}^{(\alpha, \beta)}_k(x)\) the shifted (\(x \mapsto 2x-1\)) Jacobi polynomial.

Notes

Numerical integration is performed with scipy.integrate.quad, which cannot verify whether the integral exists and is finite. If it returns an error message, an IntegrationWarning is issues, and nan is returned (even if quad returned a finite result).

Examples:

Evaluate the L- and TL-location and -scale of the standard normal distribution:

>>> from scipy.special import ndtri  # standard normal inverse CDF
>>> l_moment_from_ppf(ndtri, [1, 2])
array([0.        , 0.56418958])
>>> l_moment_from_ppf(ndtri, [1, 2], trim=1)
array([0.        , 0.29701138])

Parameters:

Name Type Description Default
ppf _Fn1 | Callable[[float], float]

The quantile function \(x(F)\), a monotonically continuous increasing function with signature (float) -> float, that maps a probability in \([0, 1]\), to the domain of the distribution.

required
r AnyOrder | AnyOrderND

L-moment order(s), non-negative integer or array-like of integers. E.g. 0 gives 1, 1 the L-location, 2 the L-scale, etc.

required
trim AnyTrim

Left- and right- trim, either as a \((s, t)\) tuple with \(s, t > -1/2\), or \(t\) as alias for \((t, t)\).

0

Other Parameters:

Name Type Description
support _Pair[float]

Integration limits. Defaults to (0, 1), as it should. There is no need to change this to anything else, and only exists to make the function signature consistent with the *_from_cdf analogue.

quad_opts QuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

alpha float

Split the integral into integrals with limits \([0, \alpha]\), \([\alpha, 1-\alpha]\) and \([1-\alpha, 0]\) to improve numerical stability. So \(\alpha\) can be consideresd the size of the tail. Numerical experiments have found 0.1 to give good results for different distributions.

Raises:

Type Description
TypeError

Invalid r or trim types.

ValueError

Invalid r or trim values.

Returns:

Name Type Description
lmbda float64 | _ArrF8

The population L-moment(s), a scalar or float array like r. If nan, consult the related IntegrationWarning message.

References
See Also

lmo.theoretical.l_moment_from_qdf(qdf, r, /, trim=0, *, support=(0, 1), quad_opts=None, alpha=ALPHA)

l_moment_from_qdf(
    qdf: _Fn1 | Callable[[float], float],
    r: lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> _ArrF8
l_moment_from_qdf(
    qdf: _Fn1 | Callable[[float], float],
    r: lmt.AnyOrder,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> np.float64

Evaluate the population L-moments \( \tlmoment{s, t}{r} \) for \( r > 1 \) from the quantile distribution function (QDF), which is the derivative of the PPF (quantile function).

lmo.theoretical.l_ratio_from_cdf(cdf, r, s, /, trim=0, *, support=None, quad_opts=None, alpha=ALPHA, ppf=None)

l_ratio_from_cdf(
    cdf: _Fn1,
    r: lmt.AnyOrderND,
    s: lmt.AnyOrder | lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] | None = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
    ppf: _Fn1 | None = ...,
) -> _ArrF8
l_ratio_from_cdf(
    cdf: _Fn1,
    r: lmt.AnyOrder | lmt.AnyOrderND,
    s: lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] | None = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
    ppf: _Fn1 | None = ...,
) -> _ArrF8
l_ratio_from_cdf(
    cdf: _Fn1,
    r: lmt.AnyOrder,
    s: lmt.AnyOrder,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] | None = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> np.float64

Population L-ratio’s from a CDF.

See Also

lmo.theoretical.l_ratio_from_ppf(ppf, r, s, /, trim=0, *, support=(0, 1), quad_opts=None, alpha=ALPHA)

l_ratio_from_ppf(
    ppf: _Fn1,
    r: lmt.AnyOrderND,
    s: lmt.AnyOrder | lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> _ArrF8
l_ratio_from_ppf(
    ppf: _Fn1,
    r: lmt.AnyOrder | lmt.AnyOrderND,
    s: lmt.AnyOrderND,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> _ArrF8
l_ratio_from_ppf(
    ppf: _Fn1,
    r: lmt.AnyOrder,
    s: lmt.AnyOrder,
    /,
    trim: lmt.AnyTrim = ...,
    *,
    support: _Pair[float] = ...,
    quad_opts: lspt.QuadOptions | None = ...,
    alpha: float = ...,
) -> np.float64

Population L-ratio’s from a PPF.

See Also

lmo.theoretical.l_stats_from_cdf(cdf, num=4, /, trim=0, *, support=None, quad_opts=None, alpha=ALPHA, ppf=None)

Calculates the theoretical- / population- L-moments (for \(r \le 2\)) and L-ratio’s (for \(r > 2\)) of a distribution, from its CDF.

By default, the first num = 4 population L-stats are calculated:

  • \(\lambda^{(s,t)}_1\) - L-location
  • \(\lambda^{(s,t)}_2\) - L-scale
  • \(\tau^{(s,t)}_3\) - L-skewness coefficient
  • \(\tau^{(s,t)}_4\) - L-kurtosis coefficient

This function is equivalent to l_ratio_from_cdf(cdf, [1, 2, 3, 4], [0, 0, 2, 2], *, **).

Note

This should not be confused with the term L-statistic, which is sometimes used to describe any linear combination of order statistics.

See Also

lmo.theoretical.l_stats_from_ppf(ppf, num=4, /, trim=0, *, support=(0, 1), quad_opts=None, alpha=ALPHA)

Calculates the theoretical- / population- L-moments (for \(r \le 2\)) and L-ratio’s (for \(r > 2\)) of a distribution, from its quantile function.

By default, the first num = 4 population L-stats are calculated:

  • \(\lambda^{(s,t)}_1\) - L-location
  • \(\lambda^{(s,t)}_2\) - L-scale
  • \(\tau^{(s,t)}_3\) - L-skewness coefficient
  • \(\tau^{(s,t)}_4\) - L-kurtosis coefficient

This function is equivalent to l_ratio_from_cdf(cdf, [1, 2, 3, 4], [0, 0, 2, 2], *, **).

Note

This should not be confused with the term L-statistic, which is sometimes used to describe any linear combination of order statistics.

See Also

lmo.theoretical.l_moment_cov_from_cdf(cdf, r_max, /, trim=0, *, support=None, quad_opts=None)

L-moments that are estimated from \(n\) samples of a distribution with CDF \(F\), converge to the multivariate normal distribution as the sample size \(n \rightarrow \infty\).

\[ \sqrt{n} \left( \vec{l}^{(s, t)} - \vec{\lambda}^{(s, t)} \right) \sim \mathcal{N}( \vec{0}, \mathbf{\Lambda}^{(s, t)} ) \]

Here, \(\vec{l}^{(s, t)} = \left[l^{(s, t)}_r, \dots, l^{(s, t)}_{r_{max}} \right]^T\) is a vector of estimated sample L-moments, and \(\vec{\lambda}^{(s, t)}\) its theoretical (“true”) counterpart.

This function calculates the covariance matrix

\[ \begin{align*} \bf{\Lambda}^{(s,t)}_{k, r} &= \mathrm{Cov}[l^{(s, t)}_k, l^{(s, t)}_r] \\ &= c_k c_r \iint\limits_{x < y} \left( p^{(s, t)}_k(u) \ p^{(s, t)}_r(v) + p^{(s, t)}_r(u) \ p^{(s, t)}_k(v) \right) \ w^{(s + 1,\ t)}(u) \ w^{(s,\ t + 1)}(v) \ \mathrm{d} x \ \mathrm{d} y \; , \end{align*} \]

where \(u = F_X(x)\) and \(v = F_Y(y)\) (marginal) probability integral transforms, and

\[ c_n = \frac{\Gamma(n) \Gamma(n+s+t+1)}{n \Gamma(n+s) \Gamma(n+t)}\;, \]

the shifted Jacobi polynomial \(p^{(s, t)}_n(u) = P^{(t, s)}_{n-1}(2u - 1)\), \(P^{(t, s)}_m\), and \(w^{(s, t)}(u) = u^s (1 - u)^t\) its weight function.

Notes

This function uses scipy.integrate.nquad for numerical integration. Unexpected results may be returned if the integral does not exist, or does not converge. The results are rounded to match the order of magnitude of the absolute error of scipy.integrate.nquad.

This function is not vectorized or parallelized.

For small sample sizes (\(n < 100\)), the covariances of the higher-order L-moments (\(r > 2\)) can be biased. But this bias quickly disappears at roughly \(n > 200\) (depending on the trim- and L-moment orders).

Parameters:

Name Type Description Default
cdf _Fn1

Cumulative Distribution Function (CDF), \(F_X(x) = P(X \le x)\). Must be a continuous monotone increasing function with signature (float) -> float, whose return value lies in \([0, 1]\).

required
r_max AnyOrder

The amount of L-moment orders to consider. If for example r_max = 4, the covariance matrix will be of shape (4, 4), and the columns and rows correspond to the L-moments of order \(r = 1, \dots, r_{max}\).

required
trim AnyTrim

Left- and right- trim. Must be a tuple of two non-negative ints or floats.

0

Other Parameters:

Name Type Description
support _Pair[float] | None

The subinterval of the nonzero domain of cdf. Generally it’s not needed to provide this, as it will be guessed automatically.

quad_opts NQuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

Returns:

Name Type Description
cov _ArrF8

Covariance matrix, with shape (r_max, r_max).

Raises:

Type Description
RuntimeError

If the covariance matrix is invalid.

See Also
References

lmo.theoretical.l_stats_cov_from_cdf(cdf, /, num=4, trim=0, *, support=None, quad_opts=None, alpha=ALPHA, ppf=None)

Similar to l_moment_from_cdf, but for the lmo.l_stats.

As the sample size \(n \rightarrow \infty\), the L-moment ratio’s are also distributed (multivariate) normally. The L-stats are defined to be L-moments for \(r\le 2\), and L-ratio coefficients otherwise.

The corresponding covariance matrix has been found to be

\[ \bf{T}^{(s, t)}_{k, r} = \begin{cases} \bf{\Lambda}^{(s, t)}_{k, r} & k \le 2 \wedge r \le 2 \\ \frac{ \bf{\Lambda}^{(s, t)}_{k, r} - \tau_r \bf{\Lambda}^{(s, t)}_{k, 2} }{ \lambda^{(s,t)}_{2} } & k \le 2 \wedge r > 2 \\ \frac{ \bf{\Lambda}^{(s, t)}_{k, r} - \tau_k \bf{\Lambda}^{(s, t)}_{2, r} - \tau_r \bf{\Lambda}^{(s, t)}_{k, 2} + \tau_k \tau_r \bf{\Lambda}^{(s, t)}_{2, 2} }{ \Big( \lambda^{(s,t)}_{2} \Big)^2 } & k > 2 \wedge r > 2 \end{cases} \]

where \(\bf{\Lambda}^{(s, t)}\) is the covariance matrix of the L-moments from l_moment_cov_from_cdf, and \(\tau^{(s,t)}_r = \lambda^{(s,t)}_r / \lambda^{(s,t)}_2\) the population L-ratio.

Parameters:

Name Type Description Default
cdf _Fn1

Cumulative Distribution Function (CDF), \(F_X(x) = P(X \le x)\). Must be a continuous monotone increasing function with signature (float) -> float, whose return value lies in \([0, 1]\).

required
num AnyOrder

The amount of L-statistics to return. Defaults to 4.

4
trim AnyTrim

Left- and right- trim. Must be a tuple of two non-negative ints or floats.

0

Other Parameters:

Name Type Description
support _Pair[float] | None

The subinterval of the nonzero domain of cdf. Generally it’s not needed to provide this, as it will be guessed automatically.

quad_opts QuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

alpha float

Two-sided quantile to split the integral at.

ppf _Fn1 | None

Quantile function, for calculating the split integral limits.

References

lmo.theoretical.l_moment_influence_from_cdf(cdf, r, /, trim=0, *, support=None, l_moment=None, quad_opts=None, alpha=ALPHA, tol=1e-08)

Influence Function (IF) of a theoretical L-moment.

\[ \psi_{\lambda^{(s, t)}_r | F}(x) = c^{(s,t)}_r \, F(x)^s \, \big( 1-{F}(x) \big)^t \, \tilde{P}^{(s,t)}_{r-1} \big( F(x) \big) \, x - \lambda^{(s,t)}_r \;, \]

with \(F\) the CDF, \(\tilde{P}^{(s,t)}_{r-1}\) the shifted Jacobi polynomial, and

\[ c^{(s,t)}_r = \frac{r+s+t}{r} \frac{B(r, \, r+s+t)}{B(r+s, \, r+t)} \;, \]

where \(B\) is the (complete) Beta function.

The proof is trivial, because population L-moments are linear functionals.

Notes

The order parameter r is not vectorized.

Parameters:

Name Type Description Default
cdf _Fn1

Vectorized cumulative distribution function (CDF).

required
r AnyOrder

The L-moment order. Must be a non-negative integer.

required
trim AnyTrim

Left- and right- trim lengths. Defaults to (0, 0).

0

Other Parameters:

Name Type Description
support _Pair[float] | None

The subinterval of the nonzero domain of cdf.

quad_opts QuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

l_moment float | float64 | None

The relevant L-moment to use. If not provided, it is calculated from the CDF.

alpha float

Two-sided quantile to split the integral at.

tol float

Zero-roundoff absolute threshold.

Returns:

Name Type Description
influence_function Callable[[_T_x], _T_x]

The influence function, with vectorized signature () -> ().

See Also

lmo.theoretical.l_ratio_influence_from_cdf(cdf, r, k=2, /, trim=0, *, support=None, l_moments=None, quad_opts=None, alpha=ALPHA, tol=1e-08)

Construct the influence function of a theoretical L-moment ratio.

\[ \psi_{\tau^{(s, t)}_{r,k}|F}(x) = \frac{ \psi_{\lambda^{(s, t)}_r|F}(x) - \tau^{(s, t)}_{r,k} \, \psi_{\lambda^{(s, t)}_k|F}(x) }{ \lambda^{(s,t)}_k } \;, \]

where the generalized L-moment ratio is defined as

\[ \tau^{(s, t)}_{r,k} = \frac{ \lambda^{(s, t)}_r }{ \lambda^{(s, t)}_k } \;. \]

Because IF’s are a special case of the general Gâteuax derivative, the L-ratio IF is derived by applying the chain rule to the L-moment IF.

Parameters:

Name Type Description Default
cdf _Fn1

Vectorized cumulative distribution function (CDF).

required
r AnyOrder

L-moment ratio order, i.e. the order of the numerator L-moment.

required
k AnyOrder

Denominator L-moment order, defaults to 2.

2
trim AnyTrim

Left- and right- trim lengths. Defaults to (0, 0).

0

Other Parameters:

Name Type Description
support _Pair[float] | None

The subinterval of the nonzero domain of cdf.

l_moments _Pair[float] | None

The L-moments corresponding to \(r\) and \(k\). If not provided, they are calculated from the CDF.

quad_opts QuadOptions | None

Optional dict of options to pass to scipy.integrate.quad.

alpha float

Two-sided quantile to split the integral at.

tol float

Zero-roundoff absolute threshold.

Returns:

Name Type Description
influence_function Callable[[_T_x], _T_x]

The influence function, with vectorized signature () -> ().

See Also

lmo.theoretical.ppf_from_l_moments(lmbda, /, trim=0, *, support=(-np.inf, np.inf), validate=True, extrapolate=False)

Return a PPF (quantile function, or inverse CDF), with the specified. L-moments \( \tlmoment{s, t}{1}, \tlmoment{s, t}{2}, \ldots, \tlmoment{s, t}{R} \). Other L-moments are considered zero.

For \( R \) L-moments, this function returns

\[ \hat{Q}_R(u) = \sum_{r=1}^{R} r \frac{2r + s + t - 1}{r + s + t} \tlmoment{s, t}{r} \shjacobi{r - 1}{t}{s}{u}, \]

where \( \shjacobi{n}{a}{b}{x} \) is an \( n \)-th degree shifted Jacobi polynomial, which is orthogonal for \( (a, b) \in (-1, \infty)^2 \) on \( u \in [0, 1] \).

This nonparametric quantile function estimation method was first described by J.R.M. Hosking in 2007. However, his derivation contains a small, but obvious error, resulting in zero-division for \( r = 1 \). So Lmo derived this correct version himself, by using the fact that L-moments are the disguised coefficients of the PPF’s generalized Fourier-Jacobi series expansion.

With Parseval’s theorem it can be shown that, if the probability-weighted moment \( M_{2,s,t} \) (which is the variance if \( s = t = 0 \)) is finite, then \( \hat{Q}_R(u) = Q(u) \) as \( R \to \infty \).

Parameters:

Name Type Description Default
lmbda AnyVectorFloat

1-d array-like of L-moments \( \tlmoment{s,t}{r} \) for \( r = 1, 2, \ldots, R \). At least 2 L-moments are required. All remaining L-moments with \( r > R \) are considered zero.

required
trim AnyTrim

The trim-length(s) of L-moments lmbda.

0
support _Pair[float]

A tuple like (x_min, x_max). If provided, the PPF results will be clipped to within this interval.

(-inf, inf)
validate bool

If True (default), a ValueError will be raised if the resulting PPF is invalid (non-monotonic), which can be solved by increasing the trim.

True
extrapolate bool

If set to True, a simple moving average of \( R \) and \( R - 1 \) will be returned. This generally results in a smoother and more accurate PPF, but its L-moments will not be equal to lmda. Defaults to False.

False

Returns:

Name Type Description
ppf _Fn1

A vectorized PPF (quantile function). Its extra optional keyword argument r_max: int can be used to “censor” trailing L-moments, i.e. truncating the degree of the polynomial.

lmo.theoretical.qdf_from_l_moments(lmbda, /, trim=0, *, validate=True, extrapolate=False)

Return the QDF (quantile density function, the derivative of the PPF), with the specified L-moments \( \tlmoment{s, t}{1}, \tlmoment{s, t}{2}, \ldots, \tlmoment{s, t}{R} \). Other L-moments are considered zero.

This function returns

\[ \begin{align*} \hat{q}_R(u) &= \frac{\dd{\hat{Q}_R(u)}}{\dd{u}} \\ &= \sum_{r=2}^{R} r (2r + s + t - 1) \tlmoment{s, t}{r} \shjacobi{r - 2}{t + 1}{s + 1}{u}, \end{align*} \]

where \( \shjacobi{n}{a}{b}{x} \) is an \( n \)-th degree shifted Jacobi polynomial, which is orthogonal for \( (a, b) \in (-1, \infty)^2 \) on \( u \in [0, 1] \).

See ppf_from_l_moments for options.

lmo.inference

Statistical inference for parametric probability distributions.

lmo.inference.fit(ppf, args0, n_obs, l_moments, r=None, trim=0, *, k=None, k_max=50, l_tol=0.0001, l_moment_fn=None, n_mc_samples=9999, random_state=None, **kwds)

Fit the distribution parameters using the (Generalized) Method of L-Moments (L-(G)MM).

The goal is to find the “true” parameters \(\bm{\theta^*}\) of the distribution. In practise, this is done using a reasonably close estimate, \(\bm{\hat\theta}\).

In the (non-Generalized) Method of L-moments (L-MM), this is done by solving the system of equations \(\ell^{(s, t)}_r = \lambda^{(s, t)}_r\), for \(r = 1, \dots, n\), with \(n\) the number of free parameters. Because the amount of parameters matches the amount of L-moment conditions, the solution is point-defined, and can be found using simple least squares.

L-GMM extends L-MM by allowing more L-moment conditions than there are free parameters, \(m > n\). This requires solving an over-identified system of \(m\) equations:

\[ \bm{\hat\theta} = \mathop{\arg \min} \limits_{\theta \in \Theta} \Bigl\{ \left[ \bm{\lambda}^{(s, t)}(X_\theta) - \bm{\ell}^{(s, t)} \right]^T W_m \left[ \bm{\lambda}^{(s, t)}(X_\theta) - \bm{\ell}^{(s, t)} \right] \Bigr\} \, , \]

where \(W_m\) is a \(m \times m\) weight matrix.

The weight matrix is initially chosen as the matrix inverse of the non-parametric L-moment covariance matrix, see lmo.l_moment_cov. These weights are then plugged into the the equation above, and fed into scipy.optimize.minimize, to obtain the initial parameter estimates.

In the next step(s), Monte-Carlo sampling is used to draw samples from the distribution (using the current parameter estimates), with sample sizes matching that of the data. The L-moments of these samples are consequently used to to calculate the new weight matrix.

Todo
  • Raise on minimization error, warn on failed k-step convergence
  • Optional integrality kwarg with boolean mask for integral params.
  • Implement CUE: Continuously Updating GMM (i.e. implement and use _loss_cue(), then run with k=1), see #299.

Parameters:

Name Type Description Default
ppf _Fn1

The (vectorized) quantile function of the probability distribution, with signature (q: T, *theta: float) -> T.

required
args0 AnyVectorFloat

Initial estimate of the distribution’s parameter values.

required
n_obs int

Amount of observations.

required
l_moments AnyVectorFloat

Estimated sample L-moments. Must be a 1-d array-like s.t. len(l_moments) >= len(args0).

required
r AnyOrderND | None

The orders of l_moments. Defaults to [1, ..., len(l_moments)].

None
trim int | tuple[int, int]

The L-moment trim-length(s) to use. Currently, only integral trimming is supported.

0

Other Parameters:

Name Type Description
k int | None

If set to a positive integer, exactly \(k\) steps will be run. Will be ignored if n_extra=0.

k_max int

Maximum amount of steps to run while not reaching convergence. Will be ignored if \(k\) is specified or if n_extra=0.

l_tol float

Error tolerance in the parametric L-moments (unit-standardized). Will be ignored if \(k\) is specified or if n_extra=0.

l_moment_fn Callable[..., _ArrF8] | None

Function for parametric L-moment calculation, with signature: (r: intp[:], *theta: float, trim: tuple[int, int]) -> float64[:].

n_mc_samples int

The number of Monte-Carlo (MC) samples drawn from the distribution to to form the weight matrix in step \(k > 1\). Will be ignored if n_extra=0.

random_state Seed | None

A seed value or numpy.random.Generator instance, used for weight matrix estimation in step \(k > 1\). Will be ignored if n_extra=0.

**kwds Any

Additional keyword arguments to be passed to scipy.optimize.minimize.

Raises:

Type Description
ValueError

Invalid arguments.

Returns:

Name Type Description
result GMMResult

An instance of [GMMResult][lmo.inference.GMMResult].

References

lmo.inference.GMMResult

Bases: NamedTuple

Represents the Generalized Method of L-Moments (L-GMM) results. See lmo.inference.fit for details.

n_arg: int property

The number of model parameters.

n_con: int property

The amount of L-moment conditions of the model.

n_extra: int property

The number of over-identifying L-moment conditions. For L-MM this is zero, otherwise, for L-GMM, it is strictly positive.

j_test: HypothesisTestResult property

Sargan-Hansen J-test for over-identifying restrictions; a hypothesis test for the invalidity of the model.

The test is defined through two hypotheses:

  • \(H_0\): The data satisfies the L-moment conditions, i.e. the model is “valid”.
  • \(H_1\): The data does not satisfy the L-moment conditions, i.e. the model is “invalid”.
References

AIC: float property

Akaike Information Criterion, based on the p-value of the J-test. Requires over-identified L-moment conditions, i.e. n_extra > 0.

The AIC is useful for model selection, e.g. for finding the most appropriate probability distribution from the data (smaller is better).

References

AICc: float property

A modification of the AIC that includes a bias-correction small sample sizes.

References