Climate Extremes Analysis - Run Theory
Overview
Run theory provides a framework for analyzing climate extreme events based on continuous periods beyond a threshold. This package implements comprehensive event analysis for both drought (dry) and wet (flood/excess) conditions with three operational modes.
Based on: Yevjevich (1967) run theory Applies to: Both dry events (drought) and wet events (flooding/excess precipitation) Provides: Event identification, time-series monitoring, period statistics
📘 Important Note on Terminology
All functions are neutral and work for both dry and wet extremes. This guide uses “drought” in some examples for historical consistency with run theory literature, but the same functions and concepts apply to wet events by simply using positive thresholds instead of negative ones.
Threshold Direction:
- Negative threshold (e.g., -1.2): Identifies dry events (drought)
- Positive threshold (e.g., +1.2): Identifies wet events (flooding/excess)
The mathematics and event characteristics (duration, magnitude, intensity, peak) work identically for both extremes.
Run Theory Concept
Figure 1: Run Theory Applied to Climate Extremes
This illustration demonstrates run theory using dry events (drought) as an example, where events occur when the index falls below the threshold (x₀).
⚠️ Important: The same framework applies identically to wet events (flooding/excess) by using a positive threshold—wet events occur when the index rises above the threshold. The mathematics, algorithms, and characteristic calculations remain unchanged.
Key characteristics:
- Duration (D): Length of continuous period beyond threshold (D₁, D₂, D₃)
- Magnitude (M): Total accumulated deviation from threshold (M₁, M₂, M₃)
- Intensity (I): Average severity per time unit = M/D
- Inter-arrival Time (T): Time between consecutive events (T₁, T₂)
The “Wet” and “Dry” regions are labeled to show that both extremes can be analyzed using the same methodology.
Three Modes of Analysis
Mode 1: Event-Based Analysis
Purpose: Identify and analyze complete climate extreme events
from runtheory import identify_events
events = identify_events(spi_ts, threshold=-1.2, min_duration=3)
# Returns: DataFrame with one row per eventUse for: Historical analysis, event comparison, detailed investigation
Mode 2: Time-Series Monitoring
Purpose: Month-by-month tracking with varying characteristics
from runtheory import calculate_timeseries
ts = calculate_timeseries(spi_ts, threshold=-1.2)
# Returns: DataFrame with one row per time stepUse for: Real-time monitoring, event evolution tracking, operational systems
Mode 3: Period Statistics (Gridded)
Purpose: Answer “What happened in 2023?” or “2021-2025?”
from runtheory import calculate_period_statistics
stats = calculate_period_statistics(spi, threshold=-1.2,
start_year=2020, end_year=2024)
# Returns: xarray Dataset with (lat, lon) dimensionsUse for: Decision-making, reporting, regional analysis, mapping
Quick Start
Drought (Dry) Events
import xarray as xr
from runtheory import identify_events, calculate_period_statistics
from visualization import plot_events
# Load SPI
spi = xr.open_dataset('output/netcdf/spi_12.nc')['spi_gamma_12_month']
# Drought events (negative threshold)
spi_location = spi.isel(lat=50, lon=100)
dry_events = identify_events(spi_location, threshold=-1.2)
print(f"Found {len(dry_events)} dry events")
# Gridded dry event statistics
dry_stats = calculate_period_statistics(spi, threshold=-1.2,
start_year=2023, end_year=2023)
dry_stats.num_events.plot(title='Dry Events 2023')Wet (Flood/Excess) Events
# Wet events (positive threshold) - SAME FUNCTIONS!
wet_events = identify_events(spi_location, threshold=+1.2)
print(f"Found {len(wet_events)} wet events")
# Gridded wet event statistics
wet_stats = calculate_period_statistics(spi, threshold=+1.2,
start_year=2023, end_year=2023)
wet_stats.num_events.plot(title='Wet Events 2023')💡 Key Point: Same functions, different thresholds:
- Dry (drought):
threshold < 0(e.g., -1.2, -1.5, -2.0) - Wet (flood/excess):
threshold > 0(e.g., +1.2, +1.5, +2.0)
Event Characteristics
| Characteristic | Formula | Meaning |
|---|---|---|
| Duration (D) | end - start + 1 | Months in event |
| Magnitude (Cumulative) | Σ(threshold - SPI) | Total water deficit |
| Magnitude (Instantaneous) | threshold - SPI[t] | Current severity |
| Intensity (I) | Magnitude / Duration | Average severity |
| Peak (P) | min(SPI) | Worst moment |
| Inter-arrival (T) | start[n+1] - start[n] | Time between events |
Magnitude: Two Types
Cumulative (blue in plots):
- Total accumulated deficit
- Always increases during event
- Like debt accumulation
- Use for: Total impact, event comparison
Instantaneous (red in plots):
- Current month’s severity
- Varies with SPI (rises-peaks-falls)
- Like NDVI crop phenology
- Use for: Monitoring evolution, peak detection
See Magnitude Explained for detailed comparison.
Mode 1: Event-Based Analysis
Basic Usage
from runtheory import identify_events
# Single location
spi_ts = spi.isel(lat=50, lon=100)
events = identify_events(spi_ts, threshold=-1.2, min_duration=3)
print(events)
# Columns: event_id, start_date, end_date, duration, magnitude,
# intensity, peak, peak_date, interarrivalEvent Summary Statistics
from runtheory import summarize_events
summary = summarize_events(events)
print(f"Total events: {summary['num_events']}")
print(f"Mean duration: {summary['mean_duration']:.1f} months")
print(f"Max magnitude: {summary['max_magnitude']:.2f}")
print(f"Most severe peak: {summary['most_severe_peak']:.2f}")Visualization
from visualization import plot_events, plot_event_characteristics
# Timeline with events highlighted
plot_events(spi_ts, events, threshold=-1.2)
# Event characteristics analysis
plot_event_characteristics(events, characteristic='magnitude')Mode 2: Time-Series Monitoring
Real-Time Tracking
from runtheory import calculate_timeseries
ts = calculate_timeseries(spi_ts, threshold=-1.2)
# Check current status
current = ts.tail(1)
if current['is_event'].values[0]:
print(f"IN DRY EVENT")
print(f"Duration: {current['duration'].values[0]} months")
print(f"Cumulative magnitude: {current['magnitude_cumulative'].values[0]:.2f}")
print(f"Current severity: {current['magnitude_instantaneous'].values[0]:.2f}")
else:
print("No event detected")Monitoring Evolution
# Is event worsening or easing?
recent = ts['magnitude_instantaneous'].tail(3)
if recent.is_monotonic_decreasing:
print("EASING - severity decreasing")
elif recent.is_monotonic_increasing:
print("WORSENING - severity increasing")Visualization
from visualization import plot_event_timeline
# Shows both cumulative and instantaneous magnitude
plot_event_timeline(ts)Mode 3: Period Statistics
Specific Time Period
from runtheory import calculate_period_statistics
# What happened in 2023?
stats_2023 = calculate_period_statistics(spi, threshold=-1.2,
start_year=2023, end_year=2023)
# Map number of events
stats_2023.num_events.plot(cmap='YlOrRd')
# Map worst severity
stats_2023.worst_peak.plot(cmap='YlOrRd_r')Year-by-Year Analysis
from runtheory import calculate_annual_statistics
# Calculate for each year
annual = calculate_annual_statistics(spi, threshold=-1.2)
# Access specific year
stats_2015 = annual.sel(year=2015)
# Trend over time
annual.num_events.mean(dim=['lat', 'lon']).plot()Period Comparison
from runtheory import compare_periods
# Compare decades
comparison = compare_periods(
spi,
periods=[(1991, 2000), (2001, 2010), (2011, 2020), (2021, 2024)],
period_names=['1990s', '2000s', '2010s', '2020s']
)
# Calculate change
diff = comparison.sel(period='2020s') - comparison.sel(period='1990s')
diff.num_events.plot(title='Change in Events: 2020s vs 1990s', cmap='RdBu_r')Output Variables (Period Statistics)
All period statistics functions return xarray Dataset with:
| Variable | Description | Units |
|---|---|---|
num_events |
Number of dry/wet events | count |
total_event_months |
Total months in events | months |
total_magnitude |
Sum of all event magnitudes | index units |
mean_magnitude |
Average magnitude per event | index units |
max_magnitude |
Largest single event | index units |
worst_peak |
Most severe SPI/SPEI value | index units |
mean_intensity |
Average intensity | index units/month |
max_intensity |
Maximum intensity | index units/month |
pct_time_in_event |
% of time in events | % |
Decision-Maker Questions
“How many dry events in 2023?”
stats_2023 = calculate_period_statistics(spi, start_year=2023, end_year=2023)
stats_2023.num_events.plot(title='Dry Events in 2023')“Where was the worst dry event in last 5 years?”
stats = calculate_period_statistics(spi, start_year=2020, end_year=2024)
stats.worst_peak.plot(title='Worst Severity 2020-2024', cmap='YlOrRd_r')“How does recent compare to historical?”
comparison = compare_periods(spi,
periods=[(1991, 2020), (2021, 2024)],
period_names=['Historical', 'Recent'])
diff = comparison.sel(period='Recent') - comparison.sel(period='Historical')
diff.total_magnitude.plot(title='Change in Total Magnitude')“Which year had the most events?”
annual = calculate_annual_statistics(spi)
worst_year = int(annual.year[annual.num_events.mean(dim=['lat','lon']).argmax()])
print(f"Worst year: {worst_year}")Thresholds
Common dry event thresholds (use positive values for wet events):
| Threshold | Category | Use |
|---|---|---|
| -0.5 | Incipient dry | Early warning |
| -0.8 | Mild dry | Agricultural concern |
| -1.0 | Moderate dry | Standard threshold |
| -1.2 | Moderate-severe | Conservative threshold |
| -1.5 | Severe dry | Significant impacts |
| -2.0 | Extreme dry | Crisis level |
Recommendation: -1.0 or -1.2 for operational monitoring
Minimum Duration
Filter short-term fluctuations:
# Include all dry periods
events_all = identify_events(spi_ts, min_duration=1)
# Only sustained events
events_sustained = identify_events(spi_ts, min_duration=3)
# Long-term events
events_longterm = identify_events(spi_ts, min_duration=6)Recommendation: min_duration=3 for SPI-12 (captures sustained events)
Performance
| Function | Input Size | Time | Notes |
|---|---|---|---|
identify_events() |
Single location | <0.1 sec | Very fast |
calculate_timeseries() |
Single location | <0.1 sec | Very fast |
calculate_period_statistics() |
165×244 grid | ~30 sec | Acceptable |
calculate_annual_statistics() |
165×244, 10 years | ~5 min | Loops over years |
compare_periods() |
165×244, 2 periods | ~1 min | Parallelizable |
Optimization tips:
- Use
min_durationto filter events - Process regional subsets when possible
- Save and reuse outputs
Complete Workflow Example
import xarray as xr
from runtheory import (identify_events,
calculate_period_statistics,
compare_periods)
from visualization import (plot_events,
plot_event_timeline)
# 1. Load SPI
spi = xr.open_dataset('output/netcdf/spi_12.nc')['spi_gamma_12_month']
# 2. Single location analysis
lat, lon = 50, 100
spi_loc = spi.isel(lat=lat, lon=lon)
events = identify_events(spi_loc, threshold=-1.2, min_duration=3)
ts = calculate_timeseries(spi_loc, threshold=-1.2)
# 3. Visualize
plot_events(spi_loc, events, threshold=-1.2)
plot_event_timeline(ts)
# 4. Regional statistics
stats_2020s = calculate_period_statistics(spi, threshold=-1.2,
start_year=2020, end_year=2024)
# 5. Compare with historical
comparison = compare_periods(spi,
periods=[(1991, 2020), (2021, 2024)],
period_names=['Historical', 'Recent'])
# 6. Save outputs
events.to_csv(f'output/csv/dry_events_lat{lat}_lon{lon}.csv')
stats_2020s.to_netcdf('output/netcdf/dry_stats_2020-2024.nc')
# 7. Create maps
stats_2020s.num_events.plot()References
Yevjevich, V. (1967). An objective approach to definitions and investigations of continental hydrologic droughts. Hydrology Papers 23, Colorado State University.
Sheffield, J., Wood, E.F., Roderick, M.L. (2012). Little change in global drought over the past 60 years. Nature, 491(7424), 435-438.
See Also
- Magnitude Explained - Cumulative vs Instantaneous
- Visualization Guide - All plotting options
- SPI Guide - Index calculation
- SPEI Guide - Temperature-inclusive index