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.

Spatial Extremes

A step-by-step curriculum on modelling climate extremes in space: how often will a temperature this high recur, and how does that risk vary across a region? It builds up from a single station to a full spatial model, one concept per notebook, on real station data from the Copernicus Climate Data Store (CDS) over Spain.

Four packages do the heavy lifting, one per layer:

LayerPackageRole
Dataxrreaderpull + cache CDS in-situ land stations over Iberia
Extremesxtremaxblock-maxima extraction, GEV distribution, return levels
Gaussian processespyroxkernels, latent GP fields, variational inference
DynamicsdiffraxODE/SDE integration for the time-varying trends (10–12)

From xtremax the tutorials use temporal_block_maxima to reduce daily series to annual maxima, GeneralizedExtremeValueDistribution as the NumPyro likelihood, gev_log_prob and gev_return_level as pure-JAX primitives, and assemble_nonstationary_gev_fields for the time-varying models.

The build-up

Each notebook is short and adds exactly one idea.

00 — Data. Pull daily near-surface air temperature for Spanish land stations from CDS with xrreader, cache it, and look at it.

01–03 — Extreme-value foundations (one station). 01 turns a daily series into annual maxima (xtremax.extraction); 02 fits a generalized extreme value (GEV) distribution to one station and interprets location/scale/shape (μ,σ,ξ)(\mu, \sigma, \xi); 03 covers the extremal-types theorem, and 03 (return levels) turns the fit into return levels with posterior uncertainty.

04–06 — Pooling and Gaussian processes. 04 fits every station independently with NUTS, and 04b does the same with a fast Laplace approximation, then maps the parameters — the noisy result motivates pooling. 05 pools them with a hierarchical Bayesian model. 06 is a Gaussian-process primer with pyrox: interpolate a field over (lon, lat), then add physical features (elevation, distance-to-coast, slope) and use ARD to see which actually matter.

07–09 — Spatial GEV models. Tie the strands together — the GEV parameters become latent GP fields, inferred with NumPyro. 07 makes the location μ(s)\mu(s) spatial; 08 adds a spatial scale σ(s)\sigma(s) driven by an elevation covariate; 09 frees the shape ξ(s)\xi(s) too, and asks honestly whether the tail carries any recoverable geography.

10–12 — Non-stationary in time (one long station). Switch axes: take the single longest record (Albacete, 1901–2025) and let the GEV location drift as the climate warms, three escalating ways. 10 fits a parametric linear trend μ(t)=μ0+μ1z(t)\mu(t)=\mu_0+\mu_1 z(t) (Coles’ model) and turns it into time-varying return levels. 11 replaces the line with a mechanistic ODE — a forced energy-balance relaxation integrated with diffrax inside NUTS. 12 goes nonparametric with a state-space Gaussian process (a local-linear-trend / integrated random walk, the stochastic sibling of the ODE), shows why a free stationary GP over-fits a short record, and puts all three trends on one set of axes.

Re-running the notebooks

The notebooks share a small helper package, spatial_extremes, that lives next to them in docs/tutorials/spatial_extremes/_helpers/ — it is tutorial glue (data loading, station features, place names, cartopy maps), not part of the installed xtremax package. Each notebook’s first code cell puts _helpers/ on sys.path, so run them from anywhere inside the repository.

Its loader, spatial_extremes.data, serves real CDS data when cached and a deterministic synthetic series otherwise — so the whole curriculum runs offline with no credentials, just on synthetic numbers.

1. Install the tutorial dependencies

The extra stack lives in an optional tutorials dependency group, which a plain uv sync does not install:

uv sync --group tutorials --group docs

It adds xrreader[cds-insitu] and a pinned pre-workspace-split pyrox (both from GitHub, since neither is on PyPI yet), plus cartopy, shapely, pyproj, seaborn, loguru, optax and diffrax.

2. (Optional) Fetch the real CDS data

Accept the licence for the in-situ surface land observations dataset, then provide credentials — exported in the environment, in a .env file, or in ~/.cdsapirc (see docs/tutorials/spatial_extremes/.env.example):

export CDSAPI_URL=https://cds.climate.copernicus.eu/api
export CDSAPI_KEY=<your-key>

Fetch once into the cache the notebooks read, and derive the covariates used from notebook 06 on:

H=docs/tutorials/spatial_extremes/_helpers
uv run python $H/scripts/fetch_cds_insitu.py   # resumable; logs to .../logs/
uv run python $H/scripts/build_features.py     # elevation, distance-to-coast, slope

The cache lands in docs/tutorials/spatial_extremes/data/ (gitignored); set CDS_INSITU_SCRATCH_ROOT to put it elsewhere. build_features.py queries the public OpenTopoData API for elevations, so it needs network access too.

3. Execute

uv run --group tutorials --group docs jupyter nbconvert --to notebook --execute \
  --inplace docs/tutorials/spatial_extremes/01_evt_foundations/01_block_maxima.ipynb

Each notebook reports whether it ran on the real record or the synthetic fallback. Commit re-executed notebooks only when they ran on real data, so the published outputs stay comparable.