Source code for extremeloss.evt.tail_index
from __future__ import annotations
import numpy as np
from ..utils.validation import as_1d_float_array
def _sorted_positive_tail(data) -> np.ndarray:
x = as_1d_float_array(data, name="data")
if np.any(x <= 0.0):
raise ValueError("data must contain only positive values")
return np.sort(x)
[docs]
def hill_estimator(data, k: int) -> float:
r"""Hill estimator of the tail index from the ``k`` largest observations.
For positive data with an approximately Pareto upper tail, the Hill
estimator of the tail index :math:`\gamma = 1/\alpha` (equivalently the
shape parameter :math:`\xi` of the corresponding GPD) is the mean log
spacing of the ``k`` largest order statistics above the ``(k+1)``-th:
.. math::
\hat{\gamma}_k = \frac{1}{k} \sum_{i=1}^{k}
\bigl(\log X_{(n-i+1)} - \log X_{(n-k)}\bigr)
where :math:`X_{(1)} \le \dots \le X_{(n)}` are the sorted values.
The estimate is sensitive to the choice of ``k``: small ``k`` gives high
variance, large ``k`` introduces bias by reaching into the distribution
body. Sweep ``k`` with :func:`hill_curve` and read the tail index from a
stable region of the resulting plot.
Parameters
----------
data : array-like
Strictly positive observations. Order is irrelevant; the values are
sorted internally.
k : int
Number of upper order statistics used, satisfying
``1 <= k < len(data)``.
Returns
-------
float
The Hill tail-index estimate :math:`\hat{\gamma}_k`.
Raises
------
ValueError
If ``data`` contains a non-positive value, or if ``k`` is outside
``1 <= k < len(data)``.
See Also
--------
hill_curve : Hill estimate across a grid of ``k``.
pickands_estimator : Alternative tail-index estimator valid for any
real tail index.
"""
x = _sorted_positive_tail(data)
n = x.size
if k <= 0 or k >= n:
raise ValueError("k must satisfy 1 <= k < len(data)")
x_top = x[-k:]
x_k1 = x[-k - 1]
estimate = np.mean(np.log(x_top) - np.log(x_k1))
return float(estimate)
[docs]
def pickands_estimator(data, k: int) -> float:
r"""Pickands estimator of the tail index from ordered tail spacings.
Unlike the Hill estimator, the Pickands estimator is valid for any real
tail index :math:`\gamma` (light, heavy, or bounded tails) and requires
no positivity of the data beyond the internal ordering. Using the sorted
values :math:`X_{(1)} \le \dots \le X_{(n)}`, it compares spacings at the
``k``-th, ``2k``-th, and ``4k``-th largest observations:
.. math::
\hat{\gamma}_k = \frac{1}{\log 2}\,
\log\!\left(
\frac{X_{(n-k+1)} - X_{(n-2k+1)}}{X_{(n-2k+1)} - X_{(n-4k+1)}}
\right)
As with any tail-index estimator the result depends on ``k``; it is
typically read from a stable region of a plot over ``k``.
Parameters
----------
data : array-like
Strictly positive observations. Order is irrelevant; the values are
sorted internally.
k : int
Order-statistic spacing parameter, satisfying ``4k < len(data) + 1``
so that the ``4k``-th largest observation exists.
Returns
-------
float
The Pickands tail-index estimate :math:`\hat{\gamma}_k`.
Raises
------
ValueError
If ``data`` contains a non-positive value, if ``k`` violates
``4k < len(data) + 1``, or if the ordered tail spacings are not
both strictly positive (which can occur with ties or a short tail).
See Also
--------
hill_estimator : Log-spacing estimator for heavy (positive-index) tails.
"""
x = _sorted_positive_tail(data)
n = x.size
if k <= 0 or 4 * k >= n + 1:
raise ValueError("k must satisfy 4k < len(data) + 1")
x1 = x[-k]
x2 = x[-2 * k]
x4 = x[-4 * k]
numerator = x1 - x2
denominator = x2 - x4
if numerator <= 0.0 or denominator <= 0.0:
raise ValueError("Pickands estimator requires ordered tail spacings to be positive")
return float(np.log(numerator / denominator) / np.log(2.0))
[docs]
def hill_curve(data, k_grid=None) -> dict[str, np.ndarray]:
r"""Hill tail-index estimate across a grid of ``k``, for a Hill plot.
Evaluates :func:`hill_estimator` at each ``k`` in ``k_grid``. Plotting the
returned ``hill`` values against ``k`` produces the standard *Hill plot*;
the tail index is read from a region where the estimate is roughly flat,
balancing the variance of small ``k`` against the bias of large ``k``.
Parameters
----------
data : array-like
Strictly positive observations.
k_grid : array-like of int, optional
Values of ``k`` at which to evaluate the estimator. When ``None``
(the default), uses ``1, 2, ..., max(2, len(data) // 4)`` so the grid
stays within the upper quarter of the sample.
Returns
-------
dict of str -> numpy.ndarray
``k`` -- the grid of order-statistic counts used, and ``hill`` --
the corresponding Hill estimates, aligned elementwise.
Raises
------
ValueError
If ``data`` contains a non-positive value, or if any ``k`` in the
grid violates ``1 <= k < len(data)``.
See Also
--------
hill_estimator : The per-``k`` estimator evaluated here.
"""
x = _sorted_positive_tail(data)
n = x.size
if k_grid is None:
upper = max(2, n // 4)
k_grid = np.arange(1, upper + 1, dtype=int)
else:
k_grid = np.asarray(k_grid, dtype=int)
estimates = np.array([hill_estimator(x, int(k)) for k in k_grid], dtype=float)
return {"k": k_grid, "hill": estimates}