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:
| Layer | Package | Role |
|---|---|---|
| Data | xrreader | pull + cache CDS in-situ land stations over Iberia |
| Extremes | xtremax | block-maxima extraction, GEV distribution, return levels |
| Gaussian processes | pyrox | kernels, latent GP fields, variational inference |
| Dynamics | diffrax | ODE/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 ;
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 spatial; 08 adds a spatial scale driven by an elevation covariate; 09 frees the shape 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 (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 docsIt 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, slopeThe 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.ipynbEach 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.