HANZE flood types across Europe¶
HANZE classifies every historical European flood by type, and the type turns out to explain a lot about who
gets hurt and how much it costs. This notebook uses the hanze backend's flood_type= facet and its catalog to
compare the four types across the whole continent — which are most frequent, which are deadliest, which are most
expensive, and how their frequency has changed over time — then maps where one type clusters.
The four types (HANZE's own vocabulary, no Compound): River (fluvial), Flash (pluvial), Coastal
(storm-surge), and River/Coastal (compound). By the end you will know how to read the type vocabulary from the
catalog, pull a Europe-wide event table in one call, and slice it by type — including the geometry attach.
See the hanze reference for the backend, and the
quickstart notebook for the single-country walkthrough.
import tempfile
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from earthlens.core import EarthLens
from earthlens.hanze import Catalog
# A stable scratch dir keeps the small (~3 MB) source files cached between runs.
OUT = Path(tempfile.gettempdir()) / "earthlens-hanze-demo"
OUT.mkdir(exist_ok=True)
1. The type vocabulary, from the catalog¶
Before touching any data, the bundled catalog tells you exactly which flood types exist and what each means — so
a flood_type= request never has to guess. It also carries the pinned Zenodo release and its citation.
cat = Catalog()
vocab = {t: cat.get_flood_type(t).description for t in cat.flood_types()}
print(
f"HANZE {cat.record.version} (Zenodo record {cat.record.record}, {cat.record.license})"
)
pd.DataFrame({"flood_type": vocab.keys(), "description": vocab.values()})
2. One call for the whole continent¶
The backend is facet-only: omit country= and you get every country. A single download() reads the 618 KB
events CSV once (cached under path) and returns a pandas.DataFrame — no per-country loop needed. We take
1950–2020, the period with the most complete impact record.
events = EarthLens("hanze", start="1950", end="2020", path=str(OUT)).download(
progress_bar=False
)
events["loss"] = pd.to_numeric(events["Losses (real value)"], errors="coerce")
events["fatalities"] = pd.to_numeric(events["Fatalities"], errors="coerce")
print(
f"{len(events)} floods across {events['Country code'].nunique()} countries, 1950-2020"
)
events[["Country code", "Year", "Type", "fatalities", "loss"]].head()
3. Which type is frequent, which is deadly, which is costly?¶
Aggregating by type separates three different stories. Watch the columns move independently — the most common type is not the most lethal, and neither is the most expensive.
by_type = events.groupby("Type").agg(
events=("ID", "size"),
fatalities=("fatalities", "sum"),
median_loss_meur=("loss", lambda s: s.median() / 1e6),
)
by_type
fig, axes = plt.subplots(1, 3, figsize=(13, 4))
metrics = [
("events", "Number of events", "#4C72B0"),
("fatalities", "Total fatalities", "#C44E52"),
("median_loss_meur", "Median loss (M EUR, 2025)", "#55A868"),
]
for ax, (col, title, colour) in zip(axes, metrics):
by_type[col].sort_values().plot.barh(ax=ax, color=colour)
ax.set_title(title)
ax.set_ylabel("")
fig.suptitle("HANZE flood impacts by type, Europe 1950-2020")
fig.tight_layout()
plt.show()
What to notice. River and Flash floods are roughly equally frequent, but they diverge on impact:
River floods carry by far the highest median loss (big rivers flood expensive floodplains), while Flash
floods take the most lives (they arrive with little warning). Coastal floods are rare — a few dozen in
seventy years — yet their total fatalities are high relative to their count: a single storm surge can be
catastrophic. River/Coastal compound events are the rarest of all.
4. Are floods getting more frequent?¶
Binning the same events by decade and type shows the trend behind the totals. Because every event already carries
a Year, this is just a crosstab — no re-download.
by_decade = pd.crosstab(events["Year"] // 10 * 10, events["Type"])
ax = by_decade.plot.bar(stacked=True, figsize=(10, 5), colormap="viridis")
ax.set_title("HANZE flood events per decade by type (Europe)")
ax.set_xlabel("Decade")
ax.set_ylabel("Number of events")
ax.tick_params(axis="x", rotation=0)
plt.show()
What to notice. Recorded flood frequency climbs steadily and peaks in the 2000s, driven mostly by River and
Flash events (the 2020 bar is a partial decade). Part of this is more floods, part is better reporting in recent
decades — HANZE'S own documentation cautions that the pre-1990 record is sparser. Coastal events stay roughly
flat, consistent with their dependence on rarer storm-surge conditions.
5. Slice by type — and attach the geometry¶
flood_type= narrows the request to one type; with_geometry=True then returns the affected NUTS-3 regions
as a pyramids FeatureCollection (reprojected to WGS84, one polygon per region with an n_events count). Here we
map where Europe's coastal floods concentrate.
coastal = EarthLens(
"hanze", start="1950", end="2020", flood_type="Coastal", path=str(OUT)
).download(progress_bar=False)
print(
"top coastal-flood countries:",
coastal["Country code"].value_counts().head(5).to_dict(),
)
coastal_regions = EarthLens(
"hanze",
start="1950",
end="2020",
flood_type="Coastal",
with_geometry=True,
path=str(OUT),
).download(progress_bar=False)
print(
f"{len(coastal_regions)} affected NUTS-3 regions; CRS EPSG:{coastal_regions.crs.to_epsg()}"
)
coastal_regions[["nuts3_code", "region_name", "n_events"]].sort_values(
"n_events", ascending=False
).head()
ax = coastal_regions.plot(
column="n_events",
cmap="PuBu",
legend=True,
edgecolor="0.6",
linewidth=0.3,
figsize=(9, 9),
legend_kwds={"label": "Coastal-flood events, 1950-2020", "shrink": 0.6},
)
ax.set_title("Where Europe's coastal floods hit (affected NUTS-3 regions)")
ax.set_axis_off()
plt.show()
What to notice. The coastal-flood regions trace Europe's exposed coastlines — the North Sea (UK, the Netherlands, Denmark, Germany) and stretches of the Mediterranean and Atlantic. This is the same events table as section 2, now filtered to one type and joined to boundary polygons: the tabular and vector views are two faces of one request.
Recap¶
- The catalog (
Catalog().flood_types()) is the source of truth for the type vocabulary and the pinned release — read it before filtering. - A single
EarthLens("hanze", start=, end=).download()returns the whole continent; slice it inpandas. - Flood type explains impact:
Riveris costliest,Flashis deadliest and most frequent,Coastalis rare but severe,River/Coastalis the compound tail. flood_type=+with_geometry=Truemaps where a given type strikes, as a per-regionn_eventschoropleth.- All figures are reported impacts (nominal + inflation-adjusted), not exposure-normalised — HANZE
v3.0.1-beta (record
20478847, CC-BY-4.0; cite Paprotny et al.).