Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

GPD via Peaks-Over-Threshold

Open In Colab

This notebook walks the canonical peaks-over-threshold (POT) pipeline. Instead of one observation per year (the block-maxima approach of the previous three notebooks), POT keeps every exceedance over a high threshold uu and models the conditional excesses with a Generalized Pareto Distribution.

POT is the workhorse parameterisation in finance, hydrology and reliability whenever you have more data than you would get from annual maxima alone — which is essentially always.

What we cover

  1. Theory. The Pickands–Balkema–de Haan theorem; the GPD log-likelihood; threshold-stability and the mean-residual-life plot.

  2. Threshold selection. quantile_threshold at the 95th percentile, plus a sensitivity sweep to show how the posterior on ξ\xi depends on uu.

  3. Declustering. Why naïve POT fails on temporally correlated data, and how decluster_runs enforces independence.

  4. Bayesian fit. NumPyro NUTS over (σ,ξ)(\sigma, \xi) with weakly informative priors.

  5. Return levels. The Poisson-GPD compound that gives the per-year return level.

Python implementation: CPython
Python version       : 3.12.3
IPython version      : 9.10.0

jax       : 0.10.1
numpy     : 2.4.4
matplotlib: 3.10.8
numpyro   : 0.21.0
xarray    : 2026.4.0
xtremax   : 0.0.0

Compiler    : GCC 13.3.0
OS          : Linux
Release     : 6.18.5
Machine     : x86_64
Processor   : x86_64
CPU cores   : 4
Architecture: 64bit

1. Pickands–Balkema–de Haan and the GPD

Suppose X1,X2,…X_1, X_2, \ldots are i.i.d. from a parent CDF FF in the GEVD domain of attraction with shape ξ\xi. Define the conditional excess distribution at threshold uu:

Fu(y)  :=  P(X−u≤y∣X>u)  =  F(u+y)−F(u)1−F(u),y≥0.F_u(y) \;:=\; \mathbb{P}(X - u \le y \mid X > u) \;=\; \frac{F(u + y) - F(u)}{1 - F(u)},\qquad y \ge 0.

The Pickands–Balkema–de Haan (1974/1975) theorem says: there exists a positive function σu\sigma_u such that, as uu approaches the right endpoint xFx_F,

sup⁡0≤y<xF−u∣Fu(y)−H(y; σu,ξ)∣  →u→xF  0,\sup_{0 \le y < x_F - u} \bigl| F_u(y) - H(y;\,\sigma_u, \xi) \bigr| \;\xrightarrow[u \to x_F]{} \;0,

where HH is the Generalized Pareto CDF

H(y; σ,ξ)  =  {1−(1+ξy/σ)−1/ξ,ξ≠0,1−e−y/σ,ξ=0,H(y;\,\sigma, \xi) \;=\; \begin{cases} 1 - \bigl(1 + \xi y/\sigma\bigr)^{-1/\xi}, & \xi \neq 0, \\[4pt] 1 - e^{-y/\sigma}, & \xi = 0, \end{cases}

defined on y≥0y \ge 0 (and additionally y≤−σ/ξy \le -\sigma/\xi if ξ<0\xi < 0).

Three crucial implications.

  1. The shape parameter is shared. The ξ\xi in the GPD is the same ξ\xi that appears in the GEVD limit of FF. Block-maxima fits and POT fits should agree on ξ\xi (and they often do — that is a nice sanity check).

  2. Threshold stability. If X−u1X - u_1 is GPD with parameters (σu1,ξ)(\sigma_{u_1}, \xi) and u2>u1u_2 > u_1, then X−u2X - u_2 is also GPD with the same ξ\xi and shifted scale

    σu2  =  σu1+ξ(u2−u1).\sigma_{u_2} \;=\; \sigma_{u_1} + \xi(u_2 - u_1).

    A non-trivial slope of σu\sigma_u vs uu in fitted parameters is a sign that the asymptotic POT regime has not kicked in — raise the threshold.

  3. The mean excess is linear. Define e(u):=E[X−u∣X>u]e(u) := \mathbb{E}[X - u \mid X > u]. For a GPD with ξ<1\xi < 1,

    e(u)  =  σ+ξu1−ξ,e(u) \;=\; \frac{\sigma + \xi u}{1 - \xi},

    a linear function of the threshold. The empirical mean-excess plot should therefore be linear above the asymptotic threshold — a famous diagnostic.

Log-likelihood. Given exceedances y1,…,yky_1, \ldots, y_k,

ℓ(σ,ξ)  =  −k log⁡σ  −  (1/ξ+1)∑i=1klog⁡ ⁣(1+ξyi/σ).\ell(\sigma, \xi) \;=\; -k\,\log\sigma \;-\; \bigl(1/\xi + 1\bigr)\sum_{i=1}^{k} \log\!\bigl(1 + \xi y_i/\sigma\bigr).

This is what GeneralizedParetoDistribution.log_prob evaluates inside NumPyro.

2. Synthetic temporally correlated daily series

We build a daily-stage process with: a smooth seasonal cycle, AR(1) persistence (so adjacent days are correlated — not i.i.d.), and Gaussian noise. POT pretends the exceedances are independent; we will see how this assumption fails on the raw series and how decluster_runs fixes it.

<Figure size 1100x300 with 1 Axes>

3. Threshold selection at the 95th percentile

95th percentile threshold u = 18.774
Raw (non-declustered) exceedances: 1460
<Figure size 1100x300 with 1 Axes>

Why declustering matters

The plot above shows the issue. Adjacent exceedance days come from the same extreme weather event, and they are highly correlated — fitting a GPD to all of them violates the i.i.d. assumption and produces a spuriously narrow posterior. The classical fix is the runs method: collapse each contiguous run of exceedances into a single “event” (typically the maximum within the run), keeping only one observation per cluster.

Declustered exceedances: 847
Cluster reduction:       1460 → 847 (58.0%)

4. Mean-residual-life (mean-excess) plot

The mean-excess function e(u)=E[X−u∣X>u]e(u) = \mathbb{E}[X - u \mid X > u] should be linear in uu above the asymptotic threshold. We plot the empirical e^(u)\hat e(u) over a sweep of candidate thresholds; we want to identify a value above which the curve is roughly linear.

<Figure size 800x450 with 1 Axes>

Reading this:

  • Linear region above some threshold: the asymptotic GPD regime has kicked in. The slope is ξ/(1−ξ)\xi/(1-\xi) and the intercept is σ0/(1−ξ)\sigma_0/(1-\xi).

  • Curved or noisy region below: the threshold is too low (non-tail observations contaminate) or above (variance is too high — fewer data).

  • The plotted 95% confidence band widens to the right because the right-tail samples thin out; this is the small-sample end.

The chosen uu should sit comfortably inside the linear region and have enough exceedances above it for a stable fit — there is genuine art here, and the plot is a sanity check, not a recipe.

5. Bayesian GPD fit

Model. With independent declustered excesses yi=zi−uy_i = z_i - u,

yi∣σ,ξ  ∼  GPD(σ,ξ).y_i \mid \sigma, \xi \;\sim\; \mathrm{GPD}(\sigma, \xi).

Priors.

  • σ∼HalfNormal(2yˉ)\sigma \sim \mathrm{HalfNormal}(2 \bar y), where yˉ\bar y is the empirical mean excess. Half-normal is positive; the scale is comfortably wider than any plausible value.

  • ξ∼N(0,0.25)\xi \sim \mathcal{N}(0, 0.25), a weakly informative prior covering the physically reasonable range [−0.5,+0.5][-0.5, +0.5].


                mean       std    median      2.5%     97.5%     n_eff     r_hat
     sigma      2.54      0.10      2.54      2.34      2.75   1095.19      1.00
        xi     -0.18      0.02     -0.19     -0.23     -0.14   1113.05      1.00

Number of divergences: 0

Number of divergences: 0
<Figure size 1100x360 with 2 Axes>

6. Threshold-sensitivity sweep

How robust is the inference to the choice of uu? We re-fit the GPD at a sweep of candidate thresholds and plot the posterior median (with 95% CI) of ξ\xi as a function of uu. Threshold stability says ξ\xi should be approximately constant above the asymptotic threshold; a flat plateau is evidence that the chosen uu is in the right regime.

<Figure size 900x450 with 1 Axes>

A flat curve says the asymptotic regime has kicked in and the chosen threshold sits in a stable region. A trending curve says you should pick a higher threshold (until the trend disappears or until the credible band blows up from too-few-exceedances). For our AR(1) Gaussian-tail process we expect ξ≈0\xi \approx 0 across the upper plateau.

7. Return levels via Poisson–GPD compound

A pure GPD models excess sizes, not the frequency of exceedances. To get the per-year return level you need the rate λu\lambda_u of exceedances per year and combine the two as

zT  =  u+σξ[(Tλu)ξ−1],ξ≠0,z_T \;=\; u + \frac{\sigma}{\xi}\Bigl[(T \lambda_u)^{\xi} - 1\Bigr], \qquad \xi \neq 0,

i.e. “the level exceeded with probability 1/(Tλu)1/(T \lambda_u) given a threshold exceedance.” We use the empirical declustered rate

λ^u  =  (declustered exceedances)/(years of data).\hat\lambda_u \;=\; \text{(declustered exceedances)} / \text{(years of data)}.
Declustered events per year: λ_u = 10.59
<Figure size 800x500 with 1 Axes>
100-year return level (posterior median): 28.70
95% credible interval:                    [28.01, 29.82]

8. PP plot for the GPD fit

Same diagnostic as for the GEVD: order the excesses y(1)≤…≤y(k)y_{(1)} \le \ldots \le y_{(k)}, plot F^(y(i))\hat F(y_{(i)}) vs i/(k+1)i/(k+1).

<Figure size 1100x450 with 2 Axes>

Takeaways

  • POT uses every exceedance, not just one per year. With AR(1) data and a 95th-percentile threshold we get hundreds of declustered events; the GPD posterior is correspondingly tight.

  • Decluster before fitting. Naïve POT on raw exceedances violates i.i.d. and produces a falsely narrow posterior. decluster_runs collapses each contiguous run into one event.

  • Validate the threshold. The mean-residual-life plot and the threshold-sensitivity sweep both target the same question — is uu high enough that the asymptotic regime has kicked in? — and give complementary visual answers.

  • Return levels need a rate. GPD on its own has no per-year scale; the Poisson-GPD compound multiplies the exceedance rate λu\lambda_u into the return-period formula.

The next notebook generalises threshold selection to non-stationary data via XarrayQuantileRegressor, fitting a covariate-dependent threshold and feeding the residual exceedances into the same GPD.