This how-to guide demonstrates how to download and process drought duration for a specific administratice (NUTS2) region. In this how-to, we retrieve both historical reanalysis and climate projection data for the region EL64 (Central Greece) and save it in a csv file.
Dataset: European Climate Data Explorer - Climate Indicators. See Copernicus Climate Change Service, Climate Data Store (2024) and the ECDE reference page for more details on the dataset and the drought duration indicator used here.
Settings¶
User settings¶
admin_id = "EL64"Setup of environment¶
import os
import zipfile
import cdsapi
import glob
from pathlib import Path
import xarray as xr
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from functools import partial# Define data directories
data_dir = Path("../data")
ecde_dir = data_dir / "ecde"
reanalysis_dir = ecde_dir / "reanalysis"
proj_dir = ecde_dir / "projections"
output_dir = data_dir / "EL64" / "drought_hazard"
# Create directories if they don't exist
for directory in [ecde_dir, reanalysis_dir, proj_dir, output_dir]:
directory.mkdir(parents=True, exist_ok=True)
print(f"Data will be saved to: {ecde_dir}")
print(f"Output files will be saved to: {output_dir}")Data will be saved to: ../data/ecde
Output files will be saved to: ../data/EL64/drought_hazard
Setup region specifics¶
regions_dir = data_dir / 'regions'
nuts_shp = regions_dir / 'NUTS_RG_20M_2024_4326' / 'NUTS_RG_20M_2024_4326.shp'
nuts_gdf = gpd.read_file(nuts_shp)
# Select the region of interest
sel_gdf = nuts_gdf[nuts_gdf['NUTS_ID'] == admin_id]
print(f"Region: {sel_gdf['NUTS_NAME'].values[0]}")
print(f"NUTS ID: {admin_id}")
print(f"Country: {sel_gdf['CNTR_CODE'].values[0]}")
print(f"Bounding box: {sel_gdf.geometry.total_bounds}")Region: Στερεά Ελλάδα
NUTS ID: EL64
Country: EL
Bounding box: [21.39637798 37.98898161 24.67199242 39.27219519]
Download Hazard Data¶
Helper function to unzip downloaded data¶
The CDS API downloads data as zip files. We need a function to extract and rename the NetCDF files.
def unzip_ecde_data(zipfile_path):
"""
Unzip ECDE data downloaded from CDS and rename to .nc extension.
Parameters:
-----------
zipfile_path : Path or str
Path to the zip file to extract
"""
zipfile_path = Path(zipfile_path)
with zipfile.ZipFile(zipfile_path, 'r') as zip_ref:
names = zip_ref.namelist()
zip_ref.extractall(zipfile_path.parent)
for name in names:
if name.split(".")[-1] == "nc":
# Rename the extracted file to match the zip filename
nc_path = zipfile_path.with_suffix('.nc')
os.rename(zipfile_path.parent / name, nc_path)
print(f" Extracted: {nc_path.name}")
else:
# Remove non-NetCDF files
os.remove(zipfile_path.parent / name)
# Remove the zip file after extraction
os.remove(zipfile_path)
print(f" Removed: {zipfile_path.name}")Download reanalysis drought duration data¶
We’ll download reanalysis drought duration data from the ECDE dataset (1940-2023).
# Define filename and years for reanalysis data
reanalysis_zipfile = reanalysis_dir / "drought_duration_nuts2_reanalysis_1940_2023.zip"
reanalysis_ncfile = reanalysis_zipfile.with_suffix('.nc')
# Download reanalysis data
print("Downloading reanalysis drought duration data (1940-2023)...")
# Skip if already downloaded
if not reanalysis_ncfile.exists():
print(f"Downloading reanalysis data (1940-2023)...")
dataset = "sis-ecde-climate-indicators"
request = {
"variable": ["duration_of_meteorological_droughts"],
"origin": "reanalysis",
"temporal_aggregation": ["yearly"],
"spatial_aggregation": "regional_layer",
"regional_layer": ["nuts_level_2"],
}
try:
client = cdsapi.Client()
client.retrieve(dataset, request).download(str(reanalysis_zipfile))
unzip_ecde_data(reanalysis_zipfile)
print(f" ✓ Download complete")
except Exception as e:
print(f" ✗ Error downloading reanalysis data: {e}")
Downloading reanalysis drought duration data (1940-2023)...
============================================================
Download drought projections¶
We’ll download climate projection data (1950-2100) for multiple Global Climate Models (GCMs), Regional Climate Models (RCMs), scenarios (RCPs), and ensemble members.
We’ll download a subset of available model combinations. You can adjust these lists based on your needs.
# Define model combinations
# Note: Not all combinations are available in the dataset
gcms = ["ec_earth", "hadgem2_es", "ipsl_cm5a_mr", "mpi_esm_lr", "noresm1_m"]
rcms = ["cclm4_8_17", "hirham5", "racmo22e", "rca4", "wrf381p"]
rcps = ["rcp4_5", "rcp8_5"]
enss = ["r12i1p1", "r1i1p1", "r3i1p1"]
print(f"Model combinations to attempt:")
print(f" GCMs: {len(gcms)}")
print(f" RCMs: {len(rcms)}")
print(f" RCPs: {len(rcps)}")
print(f" Ensemble members: {len(enss)}")
print(f" Maximum possible downloads: {len(gcms) * len(rcms) * len(rcps) * len(enss)}")
print(f"\nNote: Not all combinations are available in the dataset; we will check for their availability.")Model combinations to attempt:
GCMs: 5
RCMs: 5
RCPs: 2
Ensemble members: 3
Maximum possible downloads: 150
Note: Not all combinations are available in the dataset; we will check for their availability.
# Download projection data
print("Downloading drought projection data (1950-2100)...")
proj_files = []
download_count = 0
skip_count = 0
error_count = 0
for gcm in gcms:
for rcm in rcms:
for rcp in rcps:
for ens in enss:
# Create filename
proj_zipfile = proj_dir / f"drought_duration_nuts2_{gcm}_{rcm}_{rcp}_{ens}_1950_2100.zip"
proj_ncfile = proj_zipfile.with_suffix('.nc')
# Skip if already downloaded
if proj_ncfile.exists():
skip_count += 1
proj_files.append(proj_ncfile)
continue
model_info = f"{gcm}/{rcm}/{rcp}/{ens}"
#print(f"Downloading: {model_info}")
dataset = "sis-ecde-climate-indicators"
request = {
"variable": ["duration_of_meteorological_droughts"],
"origin": "projections",
"gcm": gcm,
"rcm": rcm,
"experiment": rcp,
"ensemble_member": ens,
"temporal_aggregation": ["yearly"],
"spatial_aggregation": "regional_layer",
"regional_layer": ["nuts_level_2"],
}
try:
client = cdsapi.Client()
client.retrieve(dataset, request).download(str(proj_zipfile))
unzip_ecde_data(proj_zipfile)
proj_files.append(proj_ncfile)
download_count += 1
except Exception as e:
error_count += 1
print("=" * 60)
print(f"Projection data download summary:")
print(f" New downloads: {download_count}")
print(f" Already existed: {skip_count}")
print(f" Errors/unavailable: {error_count}")
print(f" Total projection files: {len(proj_files)}")
Downloading drought projection data (1950-2100)...
============================================================
Projection data download summary:
New downloads: 0
Already existed: 18
Errors/unavailable: 132
Total projection files: 18
Process Hazard Data¶
Process reanalysis data¶
Now we’ll read the reanalysis drought duration data and extract information for our region (EL64).
# Read reanalysis NetCDF file
reanalysis_ncfile = reanalysis_dir / "drought_duration_nuts2_reanalysis_1940_2023.nc"
if reanalysis_ncfile.exists():
# Open reanalysis data
drought_dur_reanalysis = xr.open_dataset(reanalysis_ncfile)
print("\nReanalysis dataset:")
print(drought_dur_reanalysis)
# Extract data for EL64
drought_dur_reanalysis_el64 = drought_dur_reanalysis.sel(nuts=admin_id)
print(f"\nData for {admin_id}:")
print(drought_dur_reanalysis_el64)
else:
print("Reanalysis data file not found. Please download the data first.")
Reanalysis dataset:
<xarray.Dataset> Size: 230kB
Dimensions: (nuts: 334, time: 84)
Coordinates:
* nuts (nuts) <U4 5kB 'DE50' 'DE60' 'DE71' ... 'NO06' 'NO07' 'NO08'
* time (time) datetime64[ns] 672B 1940-01-01 1941-01-01 ... 2023-01-01
realization int64 8B ...
Data variables:
dmd (nuts, time) float64 224kB ...
Data for EL64:
<xarray.Dataset> Size: 1kB
Dimensions: (time: 84)
Coordinates:
* time (time) datetime64[ns] 672B 1940-01-01 1941-01-01 ... 2023-01-01
realization int64 8B ...
nuts <U4 16B 'EL64'
Data variables:
dmd (time) float64 672B ...
Process projection data¶
We’ll read all projection files and organize them by model combination.
# Find all projection NetCDF files
proj_nc_files = sorted(proj_dir.glob("drought_duration_nuts2_*_1950_2100.nc"))
print(f"Found {len(proj_nc_files)} projection data files")
print(f"\nAvailable model combinations:")
for i, file in enumerate(proj_nc_files[:5]):
print(f" {i+1}. {file.stem}")
if len(proj_nc_files) > 5:
print(f" ... and {len(proj_nc_files) - 5} more")Found 18 projection data files
Available model combinations:
1. drought_duration_nuts2_ec_earth_hirham5_rcp4_5_r3i1p1_1950_2100
2. drought_duration_nuts2_ec_earth_hirham5_rcp8_5_r3i1p1_1950_2100
3. drought_duration_nuts2_ec_earth_racmo22e_rcp4_5_r1i1p1_1950_2100
4. drought_duration_nuts2_ec_earth_racmo22e_rcp8_5_r1i1p1_1950_2100
5. drought_duration_nuts2_ec_earth_rca4_rcp4_5_r12i1p1_1950_2100
... and 13 more
# Load projection data for each model file
projection_data = {}
for file in proj_nc_files:
# Extract model info from filename
# Format: drought_duration_nuts2_{gcm}_{rcm}_{rcp}_{ens}_1950_2100.nc
parts = file.stem.split('_')
# Find where RCP starts (it always starts with 'rcp')
rcp_idx = next((i for i, p in enumerate(parts) if p.startswith('rcp')), None)
if rcp_idx:
gcm = '_'.join(parts[3:rcp_idx-1]) # Everything between 'nuts2' and RCM
rcm = parts[rcp_idx-1] # Part just before RCP
rcp = f"{parts[rcp_idx]}_{parts[rcp_idx+1]}" # e.g., rcp4_5 or rcp8_5
ens = parts[rcp_idx+2] # Ensemble member after RCP (before year range)
model_key = f"{gcm}_{rcm}_{rcp}_{ens}"
try:
# Load dataset and extract EL64 data
ds = xr.open_dataset(file)
ds_el64 = ds.sel(nuts=admin_id)
projection_data[model_key] = ds_el64
except Exception as e:
print(f"Error loading {model_key}: {e}")
print(f"\nSuccessfully loaded {len(projection_data)} model combinations")
# Show example
if len(projection_data) > 0:
example_key = list(projection_data.keys())[0]
print(f"\nExample dataset ({example_key}):")
print(projection_data[example_key])
Successfully loaded 18 model combinations
Example dataset (ec_earth_hirham5_rcp4_5_r3i1p1):
<xarray.Dataset> Size: 2kB
Dimensions: (time: 150)
Coordinates:
* time (time) datetime64[ns] 1kB 1951-01-01 1952-01-01 ... 2100-01-01
nuts <U4 16B 'EL64'
Data variables:
dmd (time) float64 1kB ...
Save Regional Hazard Data¶
Finally, we’ll save all the timeseries data to CSV files for further analysis.
# Save reanalysis data to CSV
if 'drought_dur_reanalysis_el64' in locals():
reanalysis_df = drought_dur_reanalysis_el64['dmd'].to_dataframe().reset_index()
reanalysis_df['scenario'] = 'reanalysis'
reanalysis_df['model'] = 'reanalysis'
reanalysis_csv = output_dir / f"drought_duration_reanalysis_{admin_id}.csv"
reanalysis_df.to_csv(reanalysis_csv, index=False)
print(f"Saved reanalysis data to: {reanalysis_csv}")
print(f" Shape: {reanalysis_df.shape}")
print(f" Columns: {list(reanalysis_df.columns)}")Saved reanalysis data to: ../data/EL64/drought_hazard/drought_duration_reanalysis_EL64.csv
Shape: (84, 6)
Columns: ['time', 'realization', 'nuts', 'dmd', 'scenario', 'model']
# Save projection data to CSV
if len(projection_data) > 0:
all_proj_data = []
for model_key, ds in projection_data.items():
# Parse model information from model_key
# Format: {gcm}_{rcm}_{rcp}_{ens}
parts = model_key.split('_')
# Find where RCP starts (it always has format rcp4 or rcp8)
rcp_idx = next(i for i, p in enumerate(parts) if p.startswith('rcp'))
gcm = '_'.join(parts[:rcp_idx-1]) # Everything before RCM
rcm = parts[rcp_idx-1] # Part just before RCP
rcp = f"{parts[rcp_idx]}_{parts[rcp_idx+1]}" # e.g., rcp4_5 or rcp8_5
ens = '_'.join(parts[rcp_idx+2:]) # Everything after RCP (e.g., r1i1p1)
# Convert to dataframe
df = ds['dmd'].to_dataframe().reset_index()
df['scenario'] = rcp.upper() # Use RCP4_5 or RCP8_5
df['gcm'] = gcm
df['rcm'] = rcm
df['ensemble'] = ens
df['model'] = model_key
all_proj_data.append(df)
# Combine all projection data
proj_df = pd.concat(all_proj_data, ignore_index=True)
# Save to CSV
proj_csv = output_dir / f"drought_duration_projections_{admin_id}.csv"
proj_df.to_csv(proj_csv, index=False)
print(f"\nSaved projection data to: {proj_csv}")
print(f" Shape: {proj_df.shape}")
print(f" Columns: {list(proj_df.columns)}")
print(f" Unique models: {proj_df['model'].nunique()}")
print(f" Scenarios: {proj_df['scenario'].unique()}")
Saved projection data to: ../data/EL64/drought_hazard/drought_duration_projections_EL64.csv
Shape: (2585, 8)
Columns: ['time', 'nuts', 'dmd', 'scenario', 'gcm', 'rcm', 'ensemble', 'model']
Unique models: 18
Scenarios: ['RCP4_5' 'RCP8_5']
# Create a combined CSV with both reanalysis and projections
if 'reanalysis_df' in locals() and 'proj_df' in locals():
# Align columns
common_cols = ['time', 'nuts', 'dmd', 'scenario', 'model']
reanalysis_df_clean = reanalysis_df[common_cols].copy()
proj_df_clean = proj_df[['time', 'nuts', 'dmd', 'scenario', 'model']].copy()
combined_df = pd.concat([reanalysis_df_clean, proj_df_clean], ignore_index=True)
combined_df = combined_df.sort_values('time').reset_index(drop=True)
combined_csv = output_dir / f"drought_duration_all_{admin_id}.csv"
combined_df.to_csv(combined_csv, index=False)
print(f"\nSaved combined data to: {combined_csv}")
print(f" Shape: {combined_df.shape}")
print(f" Time range: {combined_df['time'].min()} to {combined_df['time'].max()}")
Saved combined data to: ../data/EL64/drought_hazard/drought_duration_all_EL64.csv
Shape: (2669, 5)
Time range: 1940-01-01 00:00:00 to 2100-01-01 00:00:00
Output summary¶
Output files created:
drought_duration_reanalysis_{admin_id}.csv- Reanalysis drought duration (1940-2023)drought_duration_projections_{admin_id}.csv- All projections of drought duration (1950-2100)drought_duration_all_{admin_id}.csv- Combined reanalysis and projections
These CSV files can now be used for further analysis, risk assessment, or integration with exposure and vulnerability data.
- Copernicus Climate Change Service, Climate Data Store. (2024). Climate indicators for Europe from 1940 to 2100 derived from reanalysis and climate projections [Data set]. Copernicus Climate Change Service (C3S) Climate Data Store (CDS). https://cds.climate.copernicus.eu/datasets/sis-ecde-climate-indicators