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.

GEVD: Annual Maxima from Block-Maxima

Open In Colab

This notebook walks the canonical block-maxima → GEVD pipeline end-to-end on a synthetic 60-year daily series:

  1. Theory recap — why block maxima converge to a GEVD (Fisher–Tippett–Gnedenko) and how the resulting parameters are interpretable.

  2. Synthetic data — a daily-temperature-like series with a known parent distribution.

  3. Block extraction — xtremax.extraction.temporal_block_maxima to get one observation per year.

  4. Bayesian fit — NumPyro NUTS over (μ,σ,ξ)(\mu, \sigma, \xi) with a weakly informative prior on ξ\xi.

  5. Diagnostics — trace plots, posterior summary, PP/QQ checks, and the canonical return-level plot with credible bands.

Math-heavy throughout. Read the markdown cells in order; the code is mostly bookkeeping.

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. Why block maxima converge to a GEVD

Suppose X1,…,XnX_1, \ldots, X_n are i.i.d. from a parent CDF FF and let

Mn=max⁡(X1,…,Xn).M_n = \max(X_1, \ldots, X_n).

The CDF of MnM_n is just F(x)nF(x)^n, which collapses to a step at the right endpoint as n→∞n \to \infty — useless on its own. The trick is to look for normalising sequences an>0a_n > 0 and bn∈Rb_n \in \mathbb{R} such that

P ⁣(Mn−bnan≤x)=F(anx+bn)n  →n→∞  G(x),\mathbb{P}\!\left(\frac{M_n - b_n}{a_n} \le x\right) = F(a_n x + b_n)^n \;\xrightarrow[n \to \infty]{}\; G(x),

for a non-degenerate GG. The Fisher–Tippett–Gnedenko theorem states that if such GG exists, it must be a member of the GEVD family,

G(x;μ,σ,ξ)={exp⁡ ⁣{−[1+ξ x−μσ]−1/ξ},ξ≠0,exp⁡ ⁣(−exp⁡(−x−μσ)),ξ=0,G(x;\mu,\sigma,\xi) = \begin{cases} \exp\!\left\{-\bigl[1 + \xi\,\tfrac{x-\mu}{\sigma}\bigr]^{-1/\xi}\right\}, & \xi \neq 0,\\ \exp\!\bigl(-\exp(-\tfrac{x-\mu}{\sigma})\bigr), & \xi = 0, \end{cases}

defined where 1+ξ(x−μ)/σ>01 + \xi(x-\mu)/\sigma > 0. The shape ξ\xi is determined by the tail of FF:

  • ξ>0\xi > 0: FF has a regularly varying upper tail (heavy tails, Fˉ(x)∼x−1/ξ\bar F(x) \sim x^{-1/\xi}).

  • ξ=0\xi = 0: FF has a tail that decays “exponentially” (Gaussian, exponential, gamma, …).

  • ξ<0\xi < 0: FF has a finite right endpoint.

Practical consequence. Block the data into long-enough chunks (annual maxima of daily data, hourly maxima of 10-minute readings, …), keep the maximum of each block, and fit a GEVD. The result is a small three-parameter approximation to the tail of FF — precisely the part you care about for risk assessment.

Block size as a bias-variance trade-off. Too short → the limit theorem hasn’t kicked in and the fitted ξ\xi is biased; too long → too few maxima and the fit has wide uncertainty. A year is the standard choice for daily meteorological / hydrological data, and the rationale is the same as elsewhere in statistics: seasonality, regulatory cadence, and a comfortable n≈365n \approx 365 within each block.

2. Synthetic 60-year daily series

We generate a daily-temperature-like series whose annual maxima we know are approximately Gumbel: the parent is Gaussian with a small positive seasonal trend. Sixty years is a typical length for a high-quality station record.

<Figure size 1100x320 with 1 Axes>

3. Block-maxima extraction

temporal_block_maxima is a thin wrapper over xarray’s resample-.max(). We resample to annual end frequency ("YE") and require at least 300 valid days per year — an industry-standard data-quality cut.

Number of annual maxima: 60
Range: 31.01 to 45.33
<Figure size 1100x320 with 1 Axes>

4. The Bayesian GEVD model

We fit

zi∼iidGEVD(μ,σ,ξ),i=1,…,n,z_i \overset{\text{iid}}{\sim} \mathrm{GEVD}(\mu, \sigma, \xi), \qquad i = 1, \ldots, n,

treating the annual maxima as exchangeable. The Bayesian formulation needs priors on (μ,σ,ξ)(\mu, \sigma, \xi):

  • Location μ∼N(zˉ,5)\mu \sim \mathcal{N}(\bar z, 5) centred at the empirical mean of the maxima. The likelihood is highly informative for μ\mu (it acts like the location of a shifted distribution) so a moderately tight prior reduces NUTS warm-up time without biasing the posterior.

  • Scale σ∼HalfNormal(2sz)\sigma \sim \mathrm{HalfNormal}(2 s_z) where szs_z is the empirical standard deviation. Half-normal is positive by construction; a scale of 2sz2 s_z is comfortably wider than any plausible value.

  • Shape ξ∼N(0,0.25)\xi \sim \mathcal{N}(0, 0.25): this is the delicate parameter. Coles (2001) recommends ξ∈[−0.5,0.5]\xi \in [-0.5, 0.5] as the “physically reasonable” range; values outside this band are rare in real geophysical data. The prior puts ∼95%\sim 95\% of its mass in [−0.5,+0.5][-0.5, +0.5].

NUTS settings and divergences. GEVD likelihoods have awkward geometry near the support boundaries (the term 1+ξ(z−μ)/σ1 + \xi(z-\mu)/\sigma vanishes), so the default NUTS step-size adaptation produces divergences. We push the sampler hard — target_accept_prob=0.995, init_strategy=init_to_median (initialise at the prior median), max_tree_depth=12, and a long num_warmup=4000 — which keeps the divergence count to a handful out of 4000 post-warmup samples (the exact count is printed below; it is single digits, ≪0.5%\ll0.5\%). It does not reach exactly zero on this run: the residue is intrinsic to the GEVD upper-boundary geometry, which here sits close to the largest observed maximum because the fitted shape is mildly negative (ξ≈−0.12\xi\approx-0.12, a bounded upper tail). We tried additional remedies — pushing target_accept_prob to 0.995, standardising the data, and a dense mass matrix — and none drove the count to zero; the few remaining divergences persist regardless. Caveat: a small number of divergences means the listed locations of the trajectory are not fully reliable, so treat the long-period return levels below as indicative rather than exact. They do not appear to bias the bulk posterior on this run — r^\hat r stays at 1.00 for every parameter and the pairs plot is unimodal — but they are a real limitation, not something the sampler settings “cure”.

Why be strict about the ξ\xi prior? The likelihood for ξ\xi is often weakly identified with only a few decades of data, so the posterior is prior-sensitive. A flat prior on ξ\xi can put substantial mass on ∣ξ∣>1|\xi| > 1, where the GEVD has no mean. Constraining the prior improves both the posterior diagnostics and the credibility of the resulting return levels.

Likelihood. The log-PDF (computed inside NumPyro by GeneralizedExtremeValueDistribution.log_prob) is

ℓi=−log⁡σ−(1/ξ+1)log⁡ ⁣[1+ξ ti]−[1+ξ ti]−1/ξ,ti=(zi−μ)/σ,\ell_i = -\log\sigma - (1/\xi + 1)\log\!\bigl[1 + \xi\,t_i\bigr] - \bigl[1 + \xi\, t_i\bigr]^{-1/\xi},\qquad t_i = (z_i - \mu)/\sigma,

for ξ≠0\xi \neq 0, with the Gumbel limit handled by jnp.where inside gev_log_prob. The total log-likelihood is ∑iℓi\sum_i \ell_i.

/tmp/claude-0/ipykernel_20640/2096466666.py:26: UserWarning: There are not enough devices to run parallel chains: expected 2 but got 1. Chains will be drawn sequentially. If you are running MCMC in CPU, consider using `numpyro.set_host_device_count(2)` at the beginning of your program. You can double-check how many devices are available in your system using `jax.local_device_count()`.
  mcmc = MCMC(kernel, num_warmup=4000, num_samples=2000, num_chains=2, progress_bar=False)

                mean       std    median      2.5%     97.5%     n_eff     r_hat
        mu     36.41      0.31     36.40     35.79     37.01   2167.12      1.00
     sigma      2.25      0.21      2.23      1.87      2.67   2397.89      1.00
        xi     -0.12      0.06     -0.12     -0.23      0.00   1536.72      1.00

Number of divergences: 3
Number of divergences: 3

5. Posterior diagnostics

Marginal posteriors. A look at the per-parameter marginals tells us which way the data is pulling. For this Gaussian-parent series we expect ξ\xi to concentrate near zero (Gumbel regime) and (μ,σ)(\mu, \sigma) to recover the moments of the annual-maxima distribution.

<Figure size 1400x360 with 3 Axes>

Joint structure. (μ,σ)(\mu, \sigma) is typically negatively correlated; σ\sigma and ξ\xi are also correlated because shifting weight into the tail can be done by either widening the scale or steepening the shape. The pairwise scatter makes this concrete.

<Figure size 1300x420 with 3 Axes>

6. PP and QQ plots

Probability plot (PP) and quantile plot (QQ) are the canonical GEVD goodness-of-fit diagnostics. Order the data z(1)≤…≤z(n)z_{(1)} \le \ldots \le z_{(n)} and let pi=i/(n+1)p_i = i/(n+1) be the empirical plotting position.

  • PP plot: F^(z(i))\hat F(z_{(i)}) vs pip_i — should land on the diagonal.

  • QQ plot: z(i)z_{(i)} vs F^−1(pi)\hat F^{-1}(p_i) — should also land on the diagonal, but is more sensitive to tail behaviour.

We use the posterior median of (μ,σ,ξ)(\mu, \sigma, \xi) for the fitted CDF.

<Figure size 1100x450 with 2 Axes>

Both diagnostics should hug the diagonal for a well-fit Gumbel-regime GEVD. Deviations in the tail (top-right of QQ) are diagnostic for shape misspecification — those are the points the model is likely under- or over-predicting at long return periods.

7. Return levels and credible bands

The TT-block return level is the level exceeded with probability 1/T1/T in any one block:

zT=F−1(1−1/T)={μ+σξ[(−log⁡(1−1/T))−ξ−1],ξ≠0,μ−σ log⁡(−log⁡(1−1/T)),ξ=0.z_T = F^{-1}(1 - 1/T) = \begin{cases} \mu + \tfrac{\sigma}{\xi}\bigl[(-\log(1-1/T))^{-\xi} - 1\bigr], & \xi \neq 0,\\ \mu - \sigma\,\log(-\log(1-1/T)), & \xi = 0. \end{cases}

In the Bayesian setting we get zTz_T for every posterior sample, then summarise as a median curve plus an α\alpha%-credible band — a far richer summary than a single MLE point estimate.

<Figure size 800x500 with 1 Axes>
100-yr return level (posterior median):  44.29
100-yr return level (95% CI):            [42.76, 47.61]

Reading the plot:

  • The empirical points should fall mostly inside the credible band — they are the training data, but the band is generally wider than the spread of empirical points if the sample size is small.

  • The slope on this log-x axis encodes ξ\xi: linear (ξ=0\xi=0, Gumbel), concave (ξ<0\xi<0, bounded), convex (ξ>0\xi>0, heavy).

  • For 60 years of data, the 100-year return level has surprisingly wide credible bounds — that is the honest picture, not a bug.

8. Posterior predictive check

A final sanity check: simulate a new 60-year sequence of annual maxima from the posterior and overlay its histogram on the data. The two should agree on overall shape; gross mismatch is a sign of a misspecified model.

<Figure size 900x450 with 1 Axes>

Takeaways

  • Block maxima → GEVD is the single canonical recipe for stationary annual extremes. Three parameters (μ,σ,ξ)(\mu, \sigma, \xi), one per-block likelihood evaluation, NUTS handles the rest.

  • The shape parameter does the work. Once you have a posterior on ξ\xi you can read off the regime: ξ≈0\xi \approx 0 here, consistent with a Gaussian parent.

  • Return levels are posterior summaries, not point estimates. The credible band tells you how much the data actually pin down the long-period extreme; for short records this width is the dominant uncertainty.

  • Use a constrained ξ\xi prior. Without it, MCMC can wander into ∣ξ∣>1|\xi| > 1 regions where moments are infinite and the posterior is weakly identified.

The next two notebooks repeat this flow on parents whose tails are not Gaussian, so we land in the Fréchet (ξ>0\xi > 0) and Weibull (ξ<0\xi < 0) regimes deliberately.