Mapping current global drought events#

Welcome back! 😀 In the previous notebook, we focused on identifying drought events globally over time using the SPEI index. Now we add another tool to our toolbox, considering even the SPI index and trying to identify current drought events.

Purpose of the notebook#

This Jupyter notebook is developed to provide a real-time analysis of current global drought conditions by leveraging the Standardized Precipitation Index (SPI) and the Standardized Precipitation Evapotranspiration Index (SPEI). As the impacts of climate change become more pronounced, the ability to monitor and respond to drought events in real-time is crucial for mitigating their adverse effects on agriculture, water resources, and ecosystems.

The primary objectives of this notebook are to:

  1. Map current global drought conditions using up-to-date SPI and SPEI data.

  2. Identify regions where droughts are currently developing or intensifying.

  3. Generate a report summarizing the severity and geographical distribution of these drought events.

Data used#

Standardized Precipitation Index (SPI)#

The SPI measures deviations in precipitation from the historical average over specified accumulation periods, ranging from 1 month to several years. For this analysis, SPI data for the most recent accumulation periods (e.g., 1, 3, 6, 12 months) will be used to identify regions experiencing current drought conditions.

Standardized Precipitation Evapotranspiration Index (SPEI)#

The SPEI extends the SPI by including temperature data, providing a more comprehensive assessment of drought conditions under varying climatic conditions. SPEI data for similar accumulation periods will be analyzed to capture droughts influenced by both precipitation deficits and increased evapotranspiration due to higher temperatures.

Source of data#

The data utilized in this notebook comes from the European Centre for Medium-Range Weather Forecasts (ECMWF).

These datasets offer the temporal granularity needed to assess and visualize current drought events worldwide.

Outcome#

By the end of this notebook, users will be able to generate a global map highlighting current drought events and produce a detailed report on the regions experiencing these conditions, providing valuable insights for stakeholders involved in climate adaptation and risk management.

Understanding SPI and SPEI#

The Standardized Precipitation Index (SPI) and Standardized Precipitation Evapotranspiration Index (SPEI) are indices used to characterize meteorological droughts.

  • SPI measures the precipitation deficit over various time scales.

  • SPEI takes into account both precipitation and potential evapotranspiration, making it more sensitive to changes in temperature and is therefore considered a more comprehensive drought indicator.

Accumulation periods (1, 3, 6, 12, 24, 48 months) provide insight into short-term and long-term drought conditions. Typically, lower SPI/SPEI values indicate more severe drought conditions, while positive values indicate wet conditions.

Understanding drought types by development duration#

Introduction#

Droughts vary significantly depending on how long they have been developing. Different types of droughts impact various aspects of the environment and society. By understanding these types, we can better assess and respond to the drought conditions currently affecting different regions around the globe. This section provides an overview of the main types of droughts categorized by their development duration, using the Standardized Precipitation Index (SPI) and Standardized Precipitation Evapotranspiration Index (SPEI).

Types of droughts#

1. Meteorological drought (Short-term drought)#

  • Timescale: 1 to 3 months

  • Description:

    • Meteorological drought is characterized by a short-term deficit in precipitation, typically observed over 1 to 3 months. This drought type is often the first indicator of potential drought conditions and can quickly escalate if dry conditions persist.

  • Impacts:

    • Primarily affects weather patterns, leading to dry spells and short-term water shortages. Early impacts include stress on vegetation, reduced soil moisture, and increased wildfire risk.

2. Agricultural drought (Medium-term drought)#

  • Timescale: 3 to 6 months

  • Description:

    • Agricultural drought develops when there is insufficient soil moisture to meet the needs of crops during their growing season. It usually manifests over 3 to 6 months and can have direct consequences on food production.

  • Impacts:

    • Affects agricultural productivity, leading to reduced crop yields, pasture degradation, and higher costs for irrigation. This type of drought can cause financial strain for farmers and lead to rising food prices.

3. Hydrological drought (Long-term drought)#

  • Timescale: 6 to 12 months

  • Description:

    • Hydrological drought is associated with extended periods of below-average precipitation that lead to diminished water supply in rivers, reservoirs, and groundwater. This type of drought typically develops over 6 to 12 months.

  • Impacts:

    • Impacts water supply systems, reducing water availability for municipal, industrial, and agricultural use. It can also affect hydropower generation and aquatic ecosystems.

4. Socioeconomic drought (Very long-term drought)#

  • Timescale: 12 to 24 months or longer

  • Description:

    • Socioeconomic drought occurs when the demand for water exceeds supply over a prolonged period due to persistent dry conditions. It usually persists for over a year, leading to significant societal and economic impacts.

  • Impacts:

    • Causes long-term water shortages, economic losses, and social challenges, such as water rationing and migration. It can lead to severe economic downturns, particularly in water-dependent regions.

5. Ecological drought (Chronic drought)#

  • Timescale: 24 months to several years

  • Description:

    • Ecological drought refers to prolonged and chronic water scarcity that negatively impacts ecosystems’ structure and function. This type of drought can last several years, often leading to irreversible environmental changes.

  • Impacts:

    • Long-term degradation of ecosystems, loss of biodiversity, and shifts in species composition. It can cause desertification, increased wildfire frequency, and damage to forests and wetlands.

Application in the notebook#

To categorize and analyze these types of droughts using the SPI and SPEI indices, we will examine the data across different accumulation periods:

  • 1-3 months: Focus on detecting meteorological droughts.

  • 3-6 months: Identify agricultural droughts.

  • 6-12 months: Assess hydrological droughts.

  • 12-24 months: Monitor for socioeconomic droughts.

  • 24+ months: Evaluate the presence of ecological droughts.

By analyzing the SPI and SPEI data over these periods, we can map and report on the current drought events globally, classifying them by type and identifying the regions most affected.

Loading the data#

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import geopandas as gpd
import glob
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd
from shapely.geometry import Point
import xarray as xr

import warnings
warnings.filterwarnings('ignore')
def get_spi_dataset(acc_period: str = 1, years: list = [2020]):
    data_root_folder = '/data1/drought_dataset/spi/'
    spi_folder = os.path.join(data_root_folder, f'spi{acc_period}')
    spi_paths = []

    for year in years:
        spi_paths.extend(sorted(glob.glob(
            f'{data_root_folder}spi{acc_period}/SPI{acc_period}_gamma_global_era5_moda_ref1991to2020_{year}*.nc')))

    return xr.open_mfdataset(spi_paths, chunks={'time': "auto"}, concat_dim="time", combine='nested', parallel=False)


def get_spei_dataset(acc_period: str = 1, years: list = [2020]):
    data_root_folder = '/data1/drought_dataset/spei/'
    spi_folder = os.path.join(data_root_folder, f'spi{acc_period}')
    spi_paths = []

    for year in years:
        spi_paths.extend(sorted(glob.glob(
            f'{data_root_folder}spei{acc_period}/SPEI{acc_period}_genlogistic_global_era5_moda_ref1991to2020_{year}*.nc')))

    return xr.open_mfdataset(spi_paths, chunks={'time': "auto"}, concat_dim="time", combine='nested', parallel=False)


def mask_invalid_values(ds, index, value=-9999):
    ds[index] = ds[index].where(ds[index] != value, np.nan)
    return ds

def get_spei_significance_dataset(index='SPEI1', year=2020):
    data_root_folder='/data1/drought_dataset/spei/'
    quality_paths = []
    for month in range(1, 13):
        month_str = f'{month:02d}'
        quality_paths.append(f'{data_root_folder}{index.lower()}/parameter/{index}_significance_global_era5_moda_{year}{month_str}_ref1991to2020.nc')
    return xr.open_mfdataset(quality_paths, concat_dim="time", combine='nested', parallel=False)


def get_spi_significance_dataset(index='SPI1', year=2020):
    data_root_folder='/data1/drought_dataset/spi/'
    quality_paths = []
    for month in range(1, 13):
        month_str = f'{month:02d}'
        quality_paths.append(f'{data_root_folder}{index.lower()}/parameter/{index}_significance_global_era5_moda_{year}{month_str}_ref1991to2020.nc')
    return xr.open_mfdataset(quality_paths, concat_dim="time", combine='nested', parallel=False)


def create_drought_dataset(years: list):
    spi1 = get_spi_dataset(acc_period=1, years=years)
    spi3 = get_spi_dataset(acc_period=3, years=years)
    spi6 = get_spi_dataset(acc_period=6, years=years)
    spi12 = get_spi_dataset(acc_period=12, years=years)
    spi24 = get_spi_dataset(acc_period=24, years=years)
    spi48 = get_spi_dataset(acc_period=48, years=years)
    
    spei1 = get_spei_dataset(acc_period=1, years=years)
    spei3 = get_spei_dataset(acc_period=3, years=years)
    spei6 = get_spei_dataset(acc_period=6, years=years)
    spei12 = get_spei_dataset(acc_period=12, years=years)
    spei24 = get_spei_dataset(acc_period=24, years=years)
    spei48 = get_spei_dataset(acc_period=48, years=years)
    
    spei_significance = get_spei_significance_dataset(year=2020)
    spi_significance = get_spi_significance_dataset(year=2020)
    
    drought_dataset = xr.Dataset()

    for key, ds in {
        'SPI1': spi1,
        'SPI3': spi3,
        'SPI6': spi6,
        'SPI12': spi12,
        'SPI24': spi24,
        'SPI48': spi48,
        'SPEI1': spei1,
        'SPEI3': spei3,
        'SPEI6': spei6,
        'SPEI12': spei12,
        'SPEI24': spei24,
        'SPEI48': spei48,
        'SPEI_significance': spei_significance,
        'SPI_significance': spi_significance
    }.items():
        for var in ds.data_vars:
            drought_dataset[f"{key}"] = ds[var]
    
    return drought_dataset
drought_dataset = create_drought_dataset(years = [2017, 2018, 2019, 2020, 2021, 2022, 2023])

Defining drought categories#

Drought classification#

Drought conditions can be classified based on SPI/SPEI values:

  • Extreme Drought: SPI/SPEI < -2

  • Severe Drought: -2 ≤ SPI/SPEI < -1.5

  • Moderate Drought: -1.5 ≤ SPI/SPEI < -1

  • Mild Drought: -1 ≤ SPI/SPEI < -0.5

  • Normal: -0.5 ≤ SPI/SPEI ≤ 0.5

  • Wet Conditions: SPI/SPEI > 0.5

def classify_drought(spei_value):
    if spei_value < -2:
        return 5  # Extreme Drought
    elif -2 <= spei_value < -1.5:
        return 4  # Severe Drought
    elif -1.5 <= spei_value < -1:
        return 3  # Moderate Drought
    elif -1 <= spei_value < -0.5:
        return 2  # Mild Drought
    elif -0.5 <= spei_value <= 0.5:
        return 1  # Normal
    else:
        return 0  # Wet Conditions
def visualize_global_drought_map(index, target_year, target_month):
    
    months = ["January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", "December"]
    
    spei_data = drought_dataset[index].sel(time=f'{target_year}-{target_month:2d}', method='nearest').squeeze()
    classified_spei = xr.apply_ufunc(classify_drought, spei_data, vectorize=True, dask='parallelized')
    
    cmap = ListedColormap(['#5AB1A7', '#B6E3DC', '#EFDDAF', '#CEA053', '#995D12', '#543005'])
    levels = [-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5]
    
    # Plotting the classified SPEI data on a global map
    plt.figure(figsize=(15, 10))
    ax = plt.axes(projection=ccrs.PlateCarree())
    
    # Plot the classified SPEI data using the custom colormap and levels
    im = classified_spei.plot(ax=ax, transform=ccrs.PlateCarree(), cmap=cmap, levels=levels, 
                         extend='neither',
                         cbar_kwargs={'label': 'Drought Category', 
                                      'ticks': [0, 1, 2, 3, 4, 5],
                                      # 'tick_labels': ['Wet Conditions', 'Normal', 'Mild Drought', 'Moderate Drought', 'Severe Drought', 'Extreme Drought'],
                                      'format': '%d',
                                      'shrink': 0.5
                                     }, 
                         zorder=1)
    
    # Add coastlines, borders, and ocean masking
    ax.coastlines(zorder=3)
    ax.add_feature(cfeature.BORDERS, zorder=4)
    ax.add_feature(cfeature.OCEAN, color='white', zorder=2)

    # Add latitude and longitude grid lines
    gl = ax.gridlines(draw_labels=True, linewidth=0.5, color='gray', alpha=0.7, linestyle='--', zorder=5)
    gl.top_labels = False  # Disable top labels
    gl.right_labels = False  # Disable right labels
    gl.xlocator = plt.FixedLocator(range(-180, 181, 30))  # Set longitude grid line intervals
    gl.ylocator = plt.FixedLocator(range(-90, 91, 15))  # Set latitude grid line intervals
    gl.xlabel_style = {'size': 10, 'color': 'black'}
    gl.ylabel_style = {'size': 10, 'color': 'black'}
    
    im.colorbar.set_ticklabels(['Wet Conditions', 'Normal', 'Mild Drought', 'Moderate Drought', 'Severe Drought', 'Extreme Drought'])
    ax.set_title(f'Global Drought Conditions ({index}) for {months[target_month-1]} {target_year}')
    plt.show()
import ipywidgets as widgets
from ipywidgets import interact
# Define the interactive controls
index_widget = widgets.Dropdown(
    options=[('SPEI1', 'SPEI1'), ('SPEI3', 'SPEI3'), ('SPEI6', 'SPEI6'), ('SPEI12', 'SPEI12'), ('SPEI24', 'SPEI24'), ('SPEI48', 'SPEI48')],
    value='SPEI12',
    description='Index:',
    disabled=False,
)

year_widget = widgets.IntSlider(
    value=2023,
    min=2000,
    max=2023,
    step=1,
    description='Year:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
)

month_widget = widgets.IntSlider(
    value=1,
    min=1,
    max=12,
    step=1,
    description='Month:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
)

# Create an interactive function that calls `visualize_global_drought_map`
interactive_plot = interact(
    visualize_global_drought_map,
    index=index_widget,
    target_year=year_widget,
    target_month=month_widget
)
def list_countries_with_extreme_droughts(target_year, target_month, first_x=10, index='SPEI12'):
    spei_data = drought_dataset[index].sel(time=f'{target_year}-{target_month:2d}', method='nearest').squeeze()
    classified_spei = xr.apply_ufunc(classify_drought, spei_data, vectorize=True, dask='parallelized')

    world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
    world = world.to_crs(epsg=4326)
    
    lats = classified_spei['lat'].values
    lons = classified_spei['lon'].values
    lon_grid, lat_grid = np.meshgrid(lons, lats)
    
    # Flatten the grid and SPEI data
    flat_lons = lon_grid.flatten()
    flat_lats = lat_grid.flatten()
    flat_spei = classified_spei.values.flatten()
    
    # Create a DataFrame for easy manipulation
    df = pd.DataFrame({
        'lon': flat_lons,
        'lat': flat_lats,
        'spei_class': flat_spei
    })
    
    # Convert DataFrame to a GeoDataFrame
    geometry = [Point(xy) for xy in zip(df['lon'], df['lat'])]
    gdf = gpd.GeoDataFrame(df, geometry=geometry)
    
    # Set the coordinate reference system (CRS) to match the shapefile (assumed to be WGS84)
    # gdf.crs = {'init': 'epsg:4326'}
    gdf.set_crs(epsg=4326, inplace=True)
    
    # Spatial join to match points with countries
    gdf_with_country = gpd.sjoin(gdf, world, how='left', op='intersects')
    
    # Filter for Extreme Drought (spei_class == 5)
    extreme_drought_gdf = gdf_with_country[gdf_with_country['spei_class'] == 5]
    
    # Group by country and count occurrences
    extreme_drought_countries = extreme_drought_gdf.groupby('name').size().sort_values(ascending=False)
    
    # Display the countries facing extreme drought
    # print(extreme_drought_countries.head(first_x))
    countries_extreme_drought = extreme_drought_countries.head(first_x).index.tolist()
    print(f'\nList of countries currently experiencing extreme drought (Top {first_x}):\n')
    for country_name in countries_extreme_drought:
        print(f'- {country_name}')
    
    return countries_extreme_drought
countries_extreme_drought = list_countries_with_extreme_droughts(2023, 12, first_x=20)
List of countries currently experiencing extreme drought (Top 20):

- Brazil
- Canada
- Russia
- China
- Sudan
- Mexico
- Algeria
- Chad
- Mali
- S. Sudan
- Antarctica
- Turkmenistan
- United States of America
- Uzbekistan
- Colombia
- Bolivia
- Argentina
- Iran
- Venezuela
- Central African Rep.
def show_country_drought_map(index, target_year, target_month, country_name):

    months = ["January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", "December"]
    
    world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
    world = world.to_crs(epsg=4326)
    country_shape = world[world['name'] == country_name]
    
    spei_data = drought_dataset[index].sel(time=f'{target_year}-{target_month:2d}', method='nearest').squeeze()
    spei_data = spei_data.rio.write_crs("EPSG:4326", inplace=True)
    spei_data_country = spei_data.rio.clip(country_shape.geometry, world.crs, drop=True)
    
    plt.figure(figsize=(10, 10))
    ax = plt.axes(projection=ccrs.PlateCarree())
    
    # Plot the SPEI data for the country
    im = spei_data_country.plot(ax=ax, 
                                transform=ccrs.PlateCarree(), 
                                cmap='BrBG', 
                                vmin=-2, vmax=2,
                                add_colorbar=True,
                                cbar_kwargs={
                                    'label': index, 
                                    'shrink': 0.5})
    
    # Plot the country's boundary
    ax.add_geometries(world[world['name'] == country_name].geometry, ccrs.PlateCarree(),
                      edgecolor='black', facecolor='none', linewidth=1.5)
    
    # Add coastlines and borders for context
    ax.coastlines()
    ax.add_feature(cfeature.BORDERS)
    
    # Set the extent to focus on the country
    ax.set_extent([spei_data_country.lon.min(), spei_data_country.lon.max(),
                   spei_data_country.lat.min(), spei_data_country.lat.max()],
                  crs=ccrs.PlateCarree())
    
    # Title for each plot
    ax.set_title(f'{index} for {country_name} ({months[target_month-1]} {target_year})')
    
    plt.show()
# Define the interactive controls
# Load the world shapefile and extract the list of countries
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world = world.to_crs(epsg=4326)
country_list = world['name'].unique().tolist()
country_list.sort()  # Sort the list alphabetically

# Create widgets for interactive selection
index_widget = widgets.Dropdown(
    options=[('SPEI1', 'SPEI1'), ('SPEI3', 'SPEI3'), ('SPEI6', 'SPEI6'), ('SPEI12', 'SPEI12'), ('SPEI24', 'SPEI24'), ('SPEI48', 'SPEI48')],
    value='SPEI12',
    description='Index:',
    disabled=False,
)

year_widget = widgets.IntSlider(
    value=2023,
    min=2000,
    max=2023,
    step=1,
    description='Year:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
)

month_widget = widgets.IntSlider(
    value=1,
    min=1,
    max=12,
    step=1,
    description='Month:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
)

country_widget = widgets.Dropdown(
    options=country_list,
    value='S. Sudan',  # Default value
    description='Country:',
    disabled=False,
)

# Create an interactive function that calls `show_country_drought_map`
interactive_map = interact(
    show_country_drought_map,
    index=index_widget,
    target_year=year_widget,
    target_month=month_widget,
    country_name=country_widget
)

This chart concludes the second leg of our journey into understanding the characteristics of drought. In the next stage, we will delve deeper into the methods for determining the severity of a drought event. We look forward to seeing you.

References#