from __future__ import annotations
import numpy as np
from ..estimation.metrics import empirical_tvar, empirical_var, exceedance_probability
from ..results import BootstrapResult
from .validation import as_1d_float_array, validate_alpha, validate_positive
[docs]
def bootstrap_statistic(
data,
statistic,
*,
n_resamples: int = 1000,
alpha: float = 0.05,
random_state: int | np.random.Generator | None = None,
) -> BootstrapResult:
r"""Nonparametric bootstrap of an arbitrary statistic, with a percentile CI.
Resamples ``data`` with replacement ``n_resamples`` times, evaluates
``statistic`` on each resample, and summarizes the bootstrap distribution
with a point estimate (the statistic on the original sample), a
percentile confidence interval, and a bootstrap standard error. The
interval is the equal-tailed percentile interval with endpoints at the
``alpha/2`` and ``1 - alpha/2`` quantiles of the resampled statistics.
Parameters
----------
data : array-like
The sample to resample.
statistic : callable
A function mapping a 1-D array to a float, e.g. ``np.mean`` or a
tail measure. It is called once on the original data for the point
estimate and once per resample.
n_resamples : int, optional
Number of bootstrap resamples (default 1000). Must be positive.
alpha : float, optional
Significance level for the two-sided percentile interval (default
0.05, i.e. a 95% interval). Must lie in ``(0, 1)``.
random_state : int, numpy.random.Generator, or None, optional
Seed or generator controlling the resampling. An existing
``Generator`` is used as given; anything else is passed to
:func:`numpy.random.default_rng`. Fix it for reproducible intervals.
Returns
-------
BootstrapResult
Carries ``estimate`` (statistic on the original sample),
``bootstrap_estimates`` (the array of resampled values), ``ci``
(the ``(low, high)`` percentile bounds), ``stderr`` (standard
deviation of the resampled values, ``ddof=1``), ``method``
(``"percentile"``), and ``alpha``.
Raises
------
ValueError
If ``n_resamples`` is not positive or ``alpha`` is not in ``(0, 1)``.
Notes
-----
The percentile interval is simple and distribution-free but can
under-cover for strongly skewed sampling distributions -- the same
caveat that applies to Wald tail intervals elsewhere in the package.
See Also
--------
bootstrap_var : Bootstrap CI for value-at-risk.
bootstrap_tvar : Bootstrap CI for tail value-at-risk.
bootstrap_tail_probability : Bootstrap CI for an exceedance probability.
"""
validate_positive(n_resamples, name="n_resamples")
validate_alpha(alpha)
x = as_1d_float_array(data, name="data")
rng = (
random_state
if isinstance(random_state, np.random.Generator)
else np.random.default_rng(random_state)
)
point = float(statistic(x))
boot = np.empty(int(n_resamples), dtype=float)
n = x.size
for i in range(int(n_resamples)):
sample = x[rng.integers(0, n, size=n)]
boot[i] = float(statistic(sample))
ci = tuple(np.quantile(boot, [alpha / 2.0, 1.0 - alpha / 2.0]).tolist())
stderr = float(np.std(boot, ddof=1)) if boot.size > 1 else 0.0
return BootstrapResult(
estimate=point,
bootstrap_estimates=boot,
method="percentile",
ci=(float(ci[0]), float(ci[1])),
stderr=stderr,
alpha=float(alpha),
)
[docs]
def bootstrap_tail_probability(losses, threshold: float, **kwargs) -> BootstrapResult:
r"""Bootstrap confidence interval for an exceedance probability.
Convenience wrapper over :func:`bootstrap_statistic` with the statistic
fixed to the empirical probability :math:`P(X > u)` of exceeding
``threshold``.
Parameters
----------
losses : array-like
Loss sample to resample.
threshold : float
The level :math:`u` whose exceedance probability is bootstrapped.
**kwargs
Forwarded to :func:`bootstrap_statistic` (``n_resamples``, ``alpha``,
``random_state``).
Returns
-------
BootstrapResult
As returned by :func:`bootstrap_statistic`, for the exceedance
probability.
See Also
--------
bootstrap_statistic : The general routine this delegates to.
"""
return bootstrap_statistic(losses, lambda x: exceedance_probability(x, threshold), **kwargs)
[docs]
def bootstrap_var(losses, q: float, **kwargs) -> BootstrapResult:
r"""Bootstrap confidence interval for value-at-risk (a loss quantile).
Convenience wrapper over :func:`bootstrap_statistic` with the statistic
fixed to the empirical value-at-risk at level ``q`` -- the ``q``-quantile
of the loss distribution.
Parameters
----------
losses : array-like
Loss sample to resample.
q : float
VaR level in ``(0, 1)``, e.g. ``0.99`` for the 99% VaR.
**kwargs
Forwarded to :func:`bootstrap_statistic` (``n_resamples``, ``alpha``,
``random_state``).
Returns
-------
BootstrapResult
As returned by :func:`bootstrap_statistic`, for the VaR.
See Also
--------
bootstrap_tvar : Bootstrap CI for the tail value-at-risk at the same level.
bootstrap_statistic : The general routine this delegates to.
"""
return bootstrap_statistic(losses, lambda x: empirical_var(x, q), **kwargs)
[docs]
def bootstrap_tvar(losses, q: float, **kwargs) -> BootstrapResult:
r"""Bootstrap confidence interval for tail value-at-risk.
Convenience wrapper over :func:`bootstrap_statistic` with the statistic
fixed to the empirical tail value-at-risk at level ``q`` -- the mean loss
in the worst ``1 - q`` fraction of outcomes (also called CVaR or expected
shortfall).
Parameters
----------
losses : array-like
Loss sample to resample.
q : float
TVaR level in ``(0, 1)``, e.g. ``0.99`` for the 99% TVaR.
**kwargs
Forwarded to :func:`bootstrap_statistic` (``n_resamples``, ``alpha``,
``random_state``).
Returns
-------
BootstrapResult
As returned by :func:`bootstrap_statistic`, for the TVaR.
See Also
--------
bootstrap_var : Bootstrap CI for the value-at-risk at the same level.
bootstrap_statistic : The general routine this delegates to.
"""
return bootstrap_statistic(losses, lambda x: empirical_tvar(x, q), **kwargs)