Relative Drought hazard and risk visualization#

Aims of the workflow#

This workflow aims for vizualising and exploring the relative drought risk. It includes maps of relative drought risk at NUTS3 for different European countries.

Caution

The workflow is not applicable to the following countries, as they do not have NUTS3 level: Montenegro (ME), Cyprus (CY), Malta (MT), Lithuania (LI), Luxemburg (LU); or to Iceland (IS) due to missing exposure data.

Preliminaries#

Load libraries#

import os
import urllib
import pooch
os.environ['USE_PYGEOS'] = '0'
import pandas as pd
import geopandas as gpd
import plotly.express as px
import plotly.graph_objects as go

Define working environment and global parameters#

This workflow relies on pre-proceessed data. The user will define the path to the data folder and the code below will create a folder for outputs.

# Set working environment
workflow_folder = "./sample_data_nuts3"

Access to sample dataset#

Load the file registry for the droughtrisk_sample_nuts3 dataset in the CLIMAAX cloud storage with pooch.

sample_data_pooch = pooch.create(
    path=workflow_folder,
    base_url="https://object-store.os-api.cci1.ecmwf.int/climaax/droughtrisk_sample_nuts3/"
)
sample_data_pooch.load_registry("files_registry.txt")

If any files requested below were downloaded before, pooch will inspect the local file contents and skip the download if the contents match expectations.

Load NUTS3 spatial data and define regions of interest#

NUTS3 data is available in various resolutions: 1:1M (01M), 1:3M (03M), 1:10M (10M), 1:20M (20M) and 1:60M (60M).

nuts3_resolution = "10M"

nuts = None
while nuts is None:
    try:
        nuts = gpd.read_file(
            'https://gisco-services.ec.europa.eu/distribution/v2/nuts/geojson/'
            f'NUTS_RG_{nuts3_resolution}_2021_4326_LEVL_3.geojson'
        )
    except urllib.error.HTTPError as e:
        # Retry download for 503 errors
        if e.code != 503:
            raise e

nuts['Location'] = nuts['CNTR_CODE'] + ': ' + nuts['NAME_LATN']
nuts = nuts.set_index('Location')

Select country codes#

print("Choose country code from: ", nuts['CNTR_CODE'].unique())
Choose country code from:  ['BG' 'CH' 'AL' 'AT' 'BE' 'DE' 'CY' 'CZ' 'DK' 'EE' 'EL' 'FI' 'FR' 'ES'
 'HU' 'HR' 'LT' 'IE' 'IS' 'IT' 'NL' 'LU' 'LV' 'ME' 'MK' 'LI' 'PL' 'NO'
 'MT' 'SK' 'TR' 'RS' 'SE' 'SI' 'PT' 'RO' 'UK']
# set country code
ccode = "IT" # choose from the country codes printed above
# validate country selection and subset regions
if not nuts['CNTR_CODE'].str.contains(ccode).any():
    print("Country code: ", ccode, " is not valid; please choose a valid country code.")
else:
    nuts = nuts.query('CNTR_CODE in @ccode')

Loading hazard data and concatenate historic with future datasets#

Hazard data is provided in the format required at the NUTS3 level for EU countries in the sample_data folder: e.g. “droughthazard_ES_historic.csv”. The workflow for calculating the hazard data is available in the Hazard Assessment notebook.

# Load the CSV files into a DataFrame

# Set scenarios to be loaded #####
data = ['historic',  'ssp126_nf', 'ssp126_ff',  'ssp370_nf', 'ssp370_ff', 'ssp585_nf', 'ssp585_ff']

count = 0

for d in data:
    df_file = sample_data_pooch.fetch(f"outputs_hazards/droughthazard_{ccode}_{d}.csv")
    df = pd.read_csv(df_file)
    # Print the first 5 rows of the DataFrame
    df['data']=f"{d}" #create a new column with the data specification

    if count == 0:
        df_ =  df
        count = 1
    else:
        df_ = pd.concat([df_, df], axis=0) #concatenate the  data in a single dataframe

Choose the focal (NUTS2) area#

Choose and select a NUTS2 region within the selected country as a focal area. This allows users to visualize results on drought hazard and drought risk for a specific regions of interest.

#create a group column for the NUTS2 regions
df_['NUTS2'] = df_['NUTS_ID'].str.slice(0,4)

# list NUTS2 region:
print("Choose nuts2 region from: ", df_['NUTS2'].unique())
Choose nuts2 region from:  ['ITC1' 'ITC2' 'ITC3' 'ITC4' 'ITF1' 'ITF2' 'ITF3' 'ITF4' 'ITF5' 'ITF6'
 'ITG1' 'ITG2' 'ITH1' 'ITH2' 'ITH3' 'ITH4' 'ITH5' 'ITI1' 'ITI2' 'ITI3'
 'ITI4']
# Set the focal area
focal = 'ITF1'  # choose from the nuts2 printed above
# Subset dataset for the focal area
focal_area = df_['NUTS_ID'].str.slice(0,4) == focal
df_focal_area = df_[focal_area][["NUTS_ID", "wasp_raw_mean", "wasp_raw_q25", 'wasp_raw_median', "wasp_raw_q75", "wasp_raw_count", "hazard_raw", 'data']]
# Create additional columns for localities names
nmes = nuts.loc[nuts['NUTS_ID'].str.slice(0,4) == focal, ['NUTS_ID', 'NAME_LATN', 'NUTS_NAME']]
df_focal_area = df_focal_area.merge(nmes, on='NUTS_ID')

Loading drought risk data and concatenate historic risk and with future risk datasets#

Drought risk data is provided in the format required at the NUTS3 level for EU countries in the sample_data folder: e.g. “droughtrisk_ES_historic.csv”. The workflow for calculating the hazard data is available in the Drought Risk assessment notebook.

count = 0

for d in data:
    df_file = sample_data_pooch.fetch(f"outputs/droughtrisk_{ccode}_{d}.csv")
    df = pd.read_csv(df_file)
    # Print the first 5 rows of the DataFrame
    df['data']=f"{d}" #create a new column with the data specification
    if count == 0:
        df_r = df
        count = 1
    else:
        df_r = pd.concat([df_r, df], axis=0) #concatenate the  data in a single dataframe

loc_ = pd.DataFrame(nuts[['NUTS_ID']])
loc_['Location'] = list(pd.DataFrame(nuts[['NUTS_ID']]).index)
df_r = loc_.merge(df_r, on='NUTS_ID')
print (df_r)
    NUTS_ID                Location  hazard_raw  exposure_raw  \
0     ITF65  IT: Reggio di Calabria       1.000         0.681   
1     ITF65  IT: Reggio di Calabria       0.050         0.927   
2     ITF65  IT: Reggio di Calabria       0.091         0.829   
3     ITF65  IT: Reggio di Calabria       1.000         0.935   
4     ITF65  IT: Reggio di Calabria       0.727         0.903   
..      ...                     ...         ...           ...   
744   ITG2F            IT: Cagliari       0.182         0.737   
745   ITG2F            IT: Cagliari       0.800         0.885   
746   ITG2F            IT: Cagliari       0.783         0.839   
747   ITG2F            IT: Cagliari       0.300         0.736   
748   ITG2F            IT: Cagliari       0.900         0.841   

     vulnerability_raw  risk_raw  risk_cat       data  
0                0.865     0.589         3   historic  
1                0.367     0.017         1  ssp126_nf  
2                0.429     0.032         1  ssp126_ff  
3                0.258     0.241         2  ssp370_nf  
4                0.260     0.171         1  ssp370_ff  
..                 ...       ...       ...        ...  
744              0.369     0.049         1  ssp126_ff  
745              0.147     0.104         1  ssp370_nf  
746              0.148     0.097         1  ssp370_ff  
747              0.261     0.058         1  ssp585_nf  
748              0.406     0.307         2  ssp585_ff  

[749 rows x 8 columns]

How do the absolute drought hazard (WASP value) for the NUTS3 change in the future?#

Compare the WASP values (median, q25 and q75) between NUTS3 level for historic and future scenarios in the focal (NUTS2) area. Absolute higher values mean more severe precipitation deficit for a region compared to the others in the same dataset. Drought hazard metrics are absolute and comparable among datasets, and can thus help users to understand if changes on relative drough risk of NUTS3 regions are accompanied by an increasing drought hazard. Changes in exposure and vulnerability also affect drought risk, and users are encouraged to carefully select exposure and vulnerability indicators, adapting them to their regional context and data availability. For more detailes on how these values are calculated, please see the Hazard Assessment notebook.

fig = go.Figure()

# change WASP values in absolute values
# needed in order to simplify the visualization
df_focal_area['wasp_raw_mean'] = abs(df_focal_area['wasp_raw_mean'])
df_focal_area['wasp_raw_median']= abs(df_focal_area['wasp_raw_median'])
df_focal_area['wasp_raw_q75']= abs(df_focal_area['wasp_raw_q75'])
df_focal_area['wasp_raw_q25']= abs(df_focal_area['wasp_raw_q25'])

fig.add_trace(go.Bar(
  x = [df_focal_area['NAME_LATN'],
       df_focal_area['data']],
  y = df_focal_area['wasp_raw_median'],
  marker_color = '#8CAED2',
  name = "Median"
))

fig.add_trace(go.Scatter(
  x = [df_focal_area['NAME_LATN'],
       df_focal_area['data']],
  y = df_focal_area['wasp_raw_q25'],
  name = "Quantile-75%",
  marker_color = "#9cbd7e",
  mode = "markers"
))

fig.add_trace(go.Scatter(
  x = [df_focal_area['NAME_LATN'],
       df_focal_area['data']],
  y = df_focal_area['wasp_raw_q75'],
  name = "Quantile-25%",
  marker_color = "#e4bace",
  mode = "markers"
))

fig.update_layout(title="WASP Indices values for historic and future scenarios",
                 xaxis=dict(
                 tickangle=90,  # Rotate the labels by 45 degrees
                 tickfont=dict(
                      size=10  # Adjust font size
                  )))

fig.show()

What is the relative drought risk in each NUTS3 of the selected country?#

This map allows to compare the drought risk category between NUTS3 regions within a selected country. It also allows to see how the relative drought risk category of the regions changes in relation to each other in the different datasets (e.g. historical, ssp126 near future, ssp126 far future, etc.).

Note

Results on risk category for each region are always relative to the other regions considered in the workflow (here: country level) and therefore should not be considered as absolute risk level. This means that a region showing a higher projected risk score relative to the historical baseline, does not necessarily experience higher drought risk, rather it is more at risk relative to other areas in its country. Please refer to the risk assessment workflow for more details on how drought risk is calculated.

x_nuts, y_nuts = gpd.GeoSeries(nuts.geometry).unary_union.centroid.xy

# Update time ref.
df_r['data'] = df_r['data'].replace({"_nf": ", 2050", "_ff": ", 2080"}, regex=True)

fig = px.choropleth_mapbox(df_r, geojson=nuts.geometry, locations='Location', color='risk_cat',\
                  animation_frame = 'data', color_continuous_scale="reds", range_color = [1,5],\
                           mapbox_style="open-street-map")

# Customize line properties for selected polygons
fig.update_geos(fitbounds="locations", visible=False)

fig.update_layout(title="Current and projected drought risk",
                  mapbox_center = {"lat": list(y_nuts)[0], "lon": list(x_nuts)[0]},
                  mapbox_zoom=4,
                  height=700,
                 coloraxis_colorbar=dict(
                    title= "Risk category",
                    tickvals = [1, 2, 3, 4, 5],
                    ticktext = [1, 2, 3, 4, 5]
                 ))

fig.show()

How do the relative drought risk for the NUTS3 change in the future?#

The relative drought risk in each NUTS3 under different scenario can be benchmarked against the risk of neighboring areas.

Note

Results on risk category for each region are always relative to the other regions considered in the workflow (here: country level) and therefore should not be considered as absolute risk level. This means that a region showing a higher projected risk score relative to the historical baseline, does not necessarily experience higher drought risk, rather it is more at risk relative to other areas in its country. Please refer to the risk assessment workflow for more details on how drought risk is calculated.

print('Line chart for historic and future relative drought risk in the focal area (NUTS2)')

###maybe make it costumizabe for users to choose what to chose on the x axis###
df_r['NUTS2'] = df_r['NUTS_ID'].str.slice(0,4)
df_r_focal_area = df_r[df_r['NUTS2'] == focal]
nmes = nuts.loc[nuts['NUTS_ID'].str.slice(0,4) == focal, ['NUTS_ID', 'NAME_LATN', 'NUTS_NAME']]
df_r_focal_area = df_r_focal_area.merge(nmes, on='NUTS_ID')

fig4 = go.Figure()

for nuts3_ in df_r_focal_area['NAME_LATN'].unique():
    df_r_focal_area_ = df_r_focal_area.loc[df_r_focal_area['NAME_LATN'] == nuts3_, ]
    fig4.add_trace(go.Bar(
      x = [df_r_focal_area_['data'],
           df_r_focal_area_['NAME_LATN']],
      y = df_r_focal_area_['risk_raw'],
      name = nuts3_
    ))

fig4.show()
Line chart for historic and future relative drought risk in the focal area (NUTS2)

Conclusions#

The above workflow is used to visualise the relative drought hazard and drought risk of European NUTS3 regions within a selected country and a focal NUTS2 area. The change in drought hazard and relative (not absolute, see warning above) drought risk between different NUTS3 regions can be compared between different scenarios and timeframes.

Preprocessed data at European NUTS3 level is available for the following scenarios:

  • historical (1981-2015)

  • SSP1-RCP2.6 near future (2031-2060)

  • SSP1-RCP2.6 far future (2071-2100)

  • SSP3-RCP7.0 near future (2031-2060)

  • SSP3-RCP2.0 far future (2071-2100)

  • SSP5-RCP8.5 near future (2031-2060)

  • SSP5-RCP8.5 far future (2071-2100)

Contributors#

The workflow has beend developed by Silvia Artuso and Dor Fridman from IIASA’s Water Security Research Group, and supported by Michaela Bachmann from IIASA’s Systemic Risk and Reslience Research Group.