Code
library(tidyverse)
library(gghdx)
library(glue)
library(janitor)
gghdx()
box::use(
cumulus
)
# Priority provinces (without Bamyan)
PROVINCES_AOI <- c(
"Faryab",
"Sar-e-Pul",
"Badghis",
"Takhar",
"Badakhshan"
)This chapter examines multi-year consecutive drought events using ERA5 MAM (March-April-May) precipitation data. We quantify the probability of consecutive drought years and develop return periods for multi-year drought events.
library(tidyverse)
library(gghdx)
library(glue)
library(janitor)
gghdx()
box::use(
cumulus
)
# Priority provinces (without Bamyan)
PROVINCES_AOI <- c(
"Faryab",
"Sar-e-Pul",
"Badghis",
"Takhar",
"Badakhshan"
)Load ERA5 precipitation directly from the database and join with polygon table to get province names.
# Connect to database
con <- cumulus::pg_con()
# Get province names from polygon table
df_provinces <- tbl(con, "polygon") |>
filter(iso3 == "AFG", adm_level == 1) |>
select(pcode, name) |>
distinct() |>
collect() |>
clean_names()
# Load ERA5 data for Afghanistan admin1
df_era5_raw <- tbl(con, "era5") |>
filter(iso3 == "AFG", adm_level == 1) |>
collect() |>
clean_names() |>
# Join with province names
left_join(df_provinces, by = "pcode")
cat("=== ERA5 Data Loaded ===\n")=== ERA5 Data Loaded ===
cat("Date range:", as.character(min(df_era5_raw$valid_date)), "to",
as.character(max(df_era5_raw$valid_date)), "\n")Date range: 1981-01-01 to 2025-12-01
cat("Provinces:", length(unique(df_era5_raw$pcode)), "\n")Provinces: 34
# Process to MAM totals per province per year
df_era5_mam <- df_era5_raw |>
mutate(
year = year(valid_date),
month = month(valid_date)
) |>
# Filter to MAM months
filter(month %in% c(3, 4, 5)) |>
# Use the 'mean' column (mean precipitation in mm)
group_by(name, pcode, year) |>
summarise(
mam_precip = sum(mean, na.rm = TRUE),
n_months = n(),
.groups = "drop"
) |>
# Only keep complete years (all 3 months)
filter(n_months == 3) |>
# Filter to priority provinces
filter(name %in% PROVINCES_AOI) |>
arrange(name, year)
# Summary
cat("\n=== ERA5 MAM Precipitation Summary ===\n")
=== ERA5 MAM Precipitation Summary ===
cat("Years:", min(df_era5_mam$year), "-", max(df_era5_mam$year), "\n")Years: 1981 - 2025
cat("Provinces:", paste(unique(df_era5_mam$name), collapse = ", "), "\n")Provinces: Badakhshan, Badghis, Faryab, Sar-e-Pul, Takhar
cat("Records per province:\n")Records per province:
df_era5_mam |> count(name) |> print()# A tibble: 5 × 2
name n
<chr> <int>
1 Badakhshan 45
2 Badghis 45
3 Faryab 45
4 Sar-e-Pul 45
5 Takhar 45
We define a “drought year” as MAM precipitation below the 5-year return period threshold (20th percentile) for each province.
# Configuration
DROUGHT_RP <- 5 # 5-year return period = 20th percentile
# Calculate province-specific drought thresholds
df_thresholds <- df_era5_mam |>
group_by(name) |>
summarise(
n_years = n(),
mean_precip = mean(mam_precip),
sd_precip = sd(mam_precip),
# 20th percentile for 5-year RP drought
threshold_5yr = quantile(mam_precip, probs = 1/DROUGHT_RP),
threshold_3yr = quantile(mam_precip, probs = 1/3),
.groups = "drop"
)
cat("Drought Thresholds (", DROUGHT_RP, "-year RP):\n\n", sep = "")Drought Thresholds (5-year RP):
df_thresholds |>
mutate(across(where(is.numeric), ~round(.x, 1))) |>
print()# A tibble: 5 × 6
name n_years mean_precip sd_precip threshold_5yr threshold_3yr
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Badakhshan 45 8.8 1.7 7.5 8.1
2 Badghis 45 5.8 1.9 3.8 5
3 Faryab 45 5.8 1.8 4.1 5.1
4 Sar-e-Pul 45 6.6 1.7 5.4 6.1
5 Takhar 45 9.6 2.3 8.1 8.9
# Classify each year as drought or normal
df_drought_classified <- df_era5_mam |>
left_join(df_thresholds |> select(name, threshold_5yr), by = "name") |>
mutate(
is_drought = mam_precip < threshold_5yr,
drought_flag = as.integer(is_drought)
)
# Summary by province
cat("\nDrought Years by Province:\n")
Drought Years by Province:
df_drought_classified |>
group_by(name) |>
summarise(
n_years = n(),
n_drought = sum(is_drought),
pct_drought = round(100 * mean(is_drought), 1),
empirical_rp = round(n_years / n_drought, 1),
.groups = "drop"
) |>
print()# A tibble: 5 × 5
name n_years n_drought pct_drought empirical_rp
<chr> <int> <int> <dbl> <dbl>
1 Badakhshan 45 9 20 5
2 Badghis 45 9 20 5
3 Faryab 45 9 20 5
4 Sar-e-Pul 45 9 20 5
5 Takhar 45 9 20 5
ggplot(df_drought_classified, aes(x = year, y = mam_precip)) +
geom_col(aes(fill = is_drought), width = 0.8) +
geom_hline(
data = df_thresholds,
aes(yintercept = threshold_5yr),
linetype = "dashed", color = "red", linewidth = 0.8
) +
facet_wrap(~name, scales = "free_y", ncol = 2) +
scale_fill_manual(
values = c("FALSE" = "#56B4E9", "TRUE" = "#D55E00"),
labels = c("FALSE" = "Normal", "TRUE" = "Drought"),
name = NULL
) +
labs(
title = "MAM Precipitation by Province with Drought Classification",
subtitle = glue("Red dashed line = {DROUGHT_RP}-year RP drought threshold (20th percentile)"),
x = "Year",
y = "MAM Precipitation (mm)"
) +
theme(legend.position = "bottom")
Count consecutive drought years in the historical record.
# Function to identify runs of consecutive droughts
identify_runs <- function(drought_vector) {
rle_result <- rle(drought_vector)
# Get runs of TRUE (drought)
drought_runs <- tibble(
run_length = rle_result$lengths[rle_result$values == TRUE],
is_drought = TRUE
)
if (nrow(drought_runs) == 0) {
return(tibble(run_length = integer(), count = integer()))
}
drought_runs |>
count(run_length) |>
rename(count = n)
}
# Calculate runs for each province
df_runs_by_province <- df_drought_classified |>
arrange(name, year) |>
group_by(name) |>
summarise(
runs = list(identify_runs(is_drought)),
.groups = "drop"
) |>
unnest(runs)
cat("Drought Runs by Province:\n")Drought Runs by Province:
df_runs_by_province |>
pivot_wider(names_from = run_length, values_from = count,
names_prefix = "run_", values_fill = 0) |>
print()# A tibble: 5 × 4
name run_1 run_2 run_3
<chr> <int> <int> <int>
1 Badakhshan 7 1 0
2 Badghis 4 1 1
3 Faryab 4 1 1
4 Sar-e-Pul 5 2 0
5 Takhar 5 2 0
# Aggregate across provinces
df_runs_total <- df_runs_by_province |>
group_by(run_length) |>
summarise(total_count = sum(count), .groups = "drop")
cat("\n\nTotal Drought Run Distribution (all provinces):\n")
Total Drought Run Distribution (all provinces):
df_runs_total |>
mutate(
description = case_when(
run_length == 1 ~ "Single drought year",
run_length == 2 ~ "2 consecutive years",
run_length == 3 ~ "3 consecutive years",
TRUE ~ paste0(run_length, " consecutive years")
)
) |>
print()# A tibble: 3 × 3
run_length total_count description
<int> <int> <chr>
1 1 25 Single drought year
2 2 7 2 consecutive years
3 3 2 3 consecutive years
Estimate transition probabilities to understand drought persistence.
# Calculate transitions across all provinces (pooled)
df_transitions <- df_drought_classified |>
arrange(name, year) |>
group_by(name) |>
mutate(prev_drought = lag(is_drought)) |>
filter(!is.na(prev_drought)) |>
ungroup()
# Count transitions
n_dd <- sum(df_transitions$prev_drought & df_transitions$is_drought)
n_dn <- sum(df_transitions$prev_drought & !df_transitions$is_drought)
n_nd <- sum(!df_transitions$prev_drought & df_transitions$is_drought)
n_nn <- sum(!df_transitions$prev_drought & !df_transitions$is_drought)
# Calculate probabilities
p_dd <- n_dd / (n_dd + n_dn) # P(drought | previous drought)
p_nd <- n_nd / (n_nd + n_nn) # P(drought | previous normal)
cat("=== Markov Transition Analysis (Pooled Across Provinces) ===\n\n")=== Markov Transition Analysis (Pooled Across Provinces) ===
cat("Transition Counts:\n")Transition Counts:
cat(" D → D:", n_dd, "\n") D → D: 11
cat(" D → N:", n_dn, "\n") D → N: 29
cat(" N → D:", n_nd, "\n") N → D: 34
cat(" N → N:", n_nn, "\n\n") N → N: 146
cat("Transition Probabilities:\n")Transition Probabilities:
cat(" P(Drought | Previous Drought) = p_dd =", round(p_dd, 3), "\n") P(Drought | Previous Drought) = p_dd = 0.275
cat(" P(Drought | Previous Normal) = p_nd =", round(p_nd, 3), "\n\n") P(Drought | Previous Normal) = p_nd = 0.189
cat("Persistence Analysis:\n")Persistence Analysis:
cat(" Persistence ratio (p_dd / p_nd) =", round(p_dd / p_nd, 2), "\n") Persistence ratio (p_dd / p_nd) = 1.46
cat(" (Ratio > 1 indicates positive autocorrelation / drought persistence)\n") (Ratio > 1 indicates positive autocorrelation / drought persistence)
# Calculate lag-1 autocorrelation for each province
df_autocorr <- df_drought_classified |>
group_by(name) |>
arrange(year) |>
summarise(
lag1_corr_precip = cor(mam_precip, lag(mam_precip), use = "complete.obs"),
lag1_corr_drought = cor(drought_flag, lag(drought_flag), use = "complete.obs"),
.groups = "drop"
)
cat("Lag-1 Autocorrelation by Province:\n")Lag-1 Autocorrelation by Province:
df_autocorr |>
mutate(across(where(is.numeric), ~round(.x, 3))) |>
print()# A tibble: 5 × 3
name lag1_corr_precip lag1_corr_drought
<chr> <dbl> <dbl>
1 Badakhshan 0.085 -0.093
2 Badghis 0.125 0.199
3 Faryab 0.112 0.199
4 Sar-e-Pul 0.127 0.053
5 Takhar 0.199 0.053
# Visualize year-to-year persistence
df_drought_classified |>
arrange(name, year) |>
group_by(name) |>
mutate(prev_precip = lag(mam_precip)) |>
filter(!is.na(prev_precip)) |>
ggplot(aes(x = prev_precip, y = mam_precip)) +
geom_point(aes(color = is_drought), alpha = 0.7, size = 2) +
geom_smooth(method = "lm", se = TRUE, color = "black", linetype = "dashed") +
facet_wrap(~name, scales = "free") +
scale_color_manual(
values = c("FALSE" = "#56B4E9", "TRUE" = "#D55E00"),
labels = c("FALSE" = "Normal", "TRUE" = "Drought")
) +
labs(
title = "Year-to-Year Precipitation Persistence",
subtitle = "Dashed line = linear fit showing lag-1 autocorrelation",
x = "Previous Year MAM Precipitation (mm)",
y = "Current Year MAM Precipitation (mm)",
color = "Current Year"
) +
theme(legend.position = "bottom")`geom_smooth()` using formula = 'y ~ x'

Using the estimated transition probabilities, calculate theoretical probabilities of consecutive drought years.
# Stationary probability of drought
# pi_D = p_nd / (p_nd + (1 - p_dd))
pi_D <- p_nd / (p_nd + (1 - p_dd))
# Probability of n consecutive drought years
# P(run of n starting from drought) = pi_D * p_dd^(n-1)
calc_consecutive_prob <- function(n, pi_D, p_dd) {
pi_D * p_dd^(n-1)
}
# Under independence assumption for comparison
p_drought_independent <- mean(df_drought_classified$is_drought)
# Build comparison table
df_multiyear_prob <- tibble(
consecutive_years = 1:5
) |>
mutate(
# Markov model (accounts for persistence)
prob_markov = map_dbl(consecutive_years, ~calc_consecutive_prob(.x, pi_D, p_dd)),
rp_markov = 1 / prob_markov,
# Independence assumption (no persistence)
prob_independent = p_drought_independent^consecutive_years,
rp_independent = 1 / prob_independent
)
cat("=== Multi-Year Drought Probabilities ===\n\n")=== Multi-Year Drought Probabilities ===
cat("Stationary P(Drought) =", round(pi_D, 3), "\n")Stationary P(Drought) = 0.207
cat("Persistence P(D|D) =", round(p_dd, 3), "\n")Persistence P(D|D) = 0.275
cat("Marginal P(Drought) =", round(p_drought_independent, 3), "\n\n")Marginal P(Drought) = 0.2
df_multiyear_prob |>
mutate(
`P (Markov)` = paste0(round(prob_markov * 100, 2), "%"),
`RP (Markov)` = paste0(round(rp_markov, 1), " yr"),
`P (Independent)` = paste0(round(prob_independent * 100, 2), "%"),
`RP (Independent)` = paste0(round(rp_independent, 1), " yr")
) |>
select(consecutive_years, `P (Markov)`, `RP (Markov)`, `P (Independent)`, `RP (Independent)`) |>
print()# A tibble: 5 × 5
consecutive_years `P (Markov)` `RP (Markov)` `P (Independent)`
<int> <chr> <chr> <chr>
1 1 20.67% 4.8 yr 20%
2 2 5.68% 17.6 yr 4%
3 3 1.56% 64 yr 0.8%
4 4 0.43% 232.6 yr 0.16%
5 5 0.12% 846 yr 0.03%
# ℹ 1 more variable: `RP (Independent)` <chr>
df_plot_probs <- df_multiyear_prob |>
select(consecutive_years, prob_markov, prob_independent) |>
pivot_longer(
cols = c(prob_markov, prob_independent),
names_to = "model",
values_to = "probability"
) |>
mutate(
model = case_when(
model == "prob_markov" ~ "Markov (with persistence)",
model == "prob_independent" ~ "Independence (no persistence)"
)
)
ggplot(df_plot_probs, aes(x = factor(consecutive_years), y = probability * 100, fill = model)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
geom_text(
aes(label = paste0(round(probability * 100, 1), "%")),
position = position_dodge(width = 0.8),
vjust = -0.5, size = 3.5
) +
scale_fill_manual(values = c("Markov (with persistence)" = "#E69F00",
"Independence (no persistence)" = "#56B4E9")) +
scale_y_continuous(limits = c(0, max(df_plot_probs$probability) * 100 * 1.3)) +
labs(
title = "Probability of Consecutive Drought Years",
subtitle = glue("Drought defined as MAM precipitation below {DROUGHT_RP}-year RP threshold"),
x = "Number of Consecutive Drought Years",
y = "Probability (%)",
fill = NULL
) +
theme(legend.position = "bottom")
Instead of just counting consecutive drought years, we can examine the accumulated precipitation deficit over 2-3 year windows.
# Calculate rolling 2-year and 3-year MAM precipitation totals
df_accumulated <- df_era5_mam |>
arrange(name, year) |>
group_by(name) |>
mutate(
precip_2yr = zoo::rollsum(mam_precip, k = 2, fill = NA, align = "right"),
precip_3yr = zoo::rollsum(mam_precip, k = 3, fill = NA, align = "right")
) |>
ungroup()
# Calculate thresholds for accumulated deficits
df_accum_thresholds <- df_accumulated |>
group_by(name) |>
summarise(
# Climatological means
mean_2yr = mean(precip_2yr, na.rm = TRUE),
mean_3yr = mean(precip_3yr, na.rm = TRUE),
# 5-year RP thresholds (20th percentile)
threshold_2yr_5rp = quantile(precip_2yr, probs = 0.2, na.rm = TRUE),
threshold_3yr_5rp = quantile(precip_3yr, probs = 0.2, na.rm = TRUE),
.groups = "drop"
)
cat("Accumulated Precipitation Thresholds (5-year RP):\n")Accumulated Precipitation Thresholds (5-year RP):
df_accum_thresholds |>
mutate(across(where(is.numeric), ~round(.x, 1))) |>
print()# A tibble: 5 × 5
name mean_2yr mean_3yr threshold_2yr_5rp threshold_3yr_5rp
<chr> <dbl> <dbl> <dbl> <dbl>
1 Badakhshan 17.6 26.4 15.6 23.4
2 Badghis 11.6 17.4 9.4 14.3
3 Faryab 11.6 17.3 9.3 14.7
4 Sar-e-Pul 13.4 20.1 11.9 18
5 Takhar 19.3 28.9 16.6 24.9
# Plot 2-year accumulated precipitation
df_accumulated |>
filter(!is.na(precip_2yr)) |>
left_join(
df_accum_thresholds |> select(name, threshold_2yr_5rp, mean_2yr),
by = "name"
) |>
mutate(below_threshold = precip_2yr < threshold_2yr_5rp) |>
ggplot(aes(x = year, y = precip_2yr)) +
geom_col(aes(fill = below_threshold), width = 0.8) +
geom_hline(aes(yintercept = threshold_2yr_5rp), linetype = "dashed", color = "red") +
geom_hline(aes(yintercept = mean_2yr), linetype = "solid", color = "gray40", alpha = 0.7) +
facet_wrap(~name, scales = "free_y", ncol = 2) +
scale_fill_manual(
values = c("FALSE" = "#56B4E9", "TRUE" = "#D55E00"),
labels = c("FALSE" = "Normal", "TRUE" = "Deficit")
) +
labs(
title = "2-Year Accumulated MAM Precipitation",
subtitle = "Red dashed = 5-year RP threshold | Gray solid = climatological mean",
x = "Year (end of 2-year window)",
y = "2-Year Accumulated Precipitation (mm)",
fill = NULL
) +
theme(legend.position = "bottom")
cat("=" |> rep(60) |> paste(collapse = ""), "\n")============================================================
cat("MULTI-YEAR DROUGHT ANALYSIS SUMMARY\n")MULTI-YEAR DROUGHT ANALYSIS SUMMARY
cat("=" |> rep(60) |> paste(collapse = ""), "\n\n")============================================================
cat("DATA:\n")DATA:
cat(" - ERA5 MAM (Mar-Apr-May) precipitation, ", min(df_era5_mam$year), "-", max(df_era5_mam$year), "\n", sep = "") - ERA5 MAM (Mar-Apr-May) precipitation, 1981-2025
cat(" - ", length(unique(df_era5_mam$name)), " priority provinces\n\n", sep = "") - 5 priority provinces
cat("DROUGHT DEFINITION:\n")DROUGHT DEFINITION:
cat(" - Single year: MAM precip below ", DROUGHT_RP, "-year RP threshold (20th percentile)\n\n", sep = "") - Single year: MAM precip below 5-year RP threshold (20th percentile)
cat("PERSISTENCE (Markov Model):\n")PERSISTENCE (Markov Model):
cat(" - P(Drought | Previous Drought) = ", round(p_dd * 100, 1), "%\n", sep = "") - P(Drought | Previous Drought) = 27.5%
cat(" - P(Drought | Previous Normal) = ", round(p_nd * 100, 1), "%\n", sep = "") - P(Drought | Previous Normal) = 18.9%
cat(" - Persistence ratio = ", round(p_dd / p_nd, 2), " (>1 = positive autocorrelation)\n\n", sep = "") - Persistence ratio = 1.46 (>1 = positive autocorrelation)
cat("MULTI-YEAR DROUGHT PROBABILITIES (Markov Model):\n")MULTI-YEAR DROUGHT PROBABILITIES (Markov Model):
for (i in 1:3) {
prob <- calc_consecutive_prob(i, pi_D, p_dd)
cat(" - ", i, " consecutive year(s): ", round(prob * 100, 2),
"% (RP = ", round(1/prob, 1), " years)\n", sep = "")
} - 1 consecutive year(s): 20.67% (RP = 4.8 years)
- 2 consecutive year(s): 5.68% (RP = 17.6 years)
- 3 consecutive year(s): 1.56% (RP = 64 years)
cat("\nKEY INSIGHT:\n")
KEY INSIGHT:
if (p_dd > p_nd) {
cat(" Drought years show POSITIVE persistence (p_dd > p_nd).\n")
cat(" Multi-year droughts are MORE likely than independence would suggest.\n")
} else {
cat(" No significant drought persistence detected.\n")
} Drought years show POSITIVE persistence (p_dd > p_nd).
Multi-year droughts are MORE likely than independence would suggest.
Given a new MAM rainfall observation, what’s the probability of multi-year drought?
# Function to assess multi-year drought risk given current year status
assess_multiyear_risk <- function(current_year_drought, p_dd, p_nd) {
if (current_year_drought) {
cat("Current year: DROUGHT\n")
cat(" P(Next year drought) = ", round(p_dd * 100, 1), "%\n", sep = "")
cat(" P(2 consecutive from here) = ", round(p_dd * 100, 1), "%\n", sep = "")
cat(" P(3 consecutive from here) = ", round(p_dd^2 * 100, 1), "%\n", sep = "")
} else {
cat("Current year: NORMAL\n")
cat(" P(Next year drought) = ", round(p_nd * 100, 1), "%\n", sep = "")
cat(" P(2 consecutive starting next year) = ", round(p_nd * p_dd * 100, 2), "%\n", sep = "")
}
}
cat("SCENARIO 1: Current year is a DROUGHT year\n")SCENARIO 1: Current year is a DROUGHT year
cat("-" |> rep(40) |> paste(collapse = ""), "\n")----------------------------------------
assess_multiyear_risk(TRUE, p_dd, p_nd)Current year: DROUGHT
P(Next year drought) = 27.5%
P(2 consecutive from here) = 27.5%
P(3 consecutive from here) = 7.6%
cat("\n\nSCENARIO 2: Current year is NORMAL\n")
SCENARIO 2: Current year is NORMAL
cat("-" |> rep(40) |> paste(collapse = ""), "\n")----------------------------------------
assess_multiyear_risk(FALSE, p_dd, p_nd)Current year: NORMAL
P(Next year drought) = 18.9%
P(2 consecutive starting next year) = 5.19%
Given any arbitrary year, what is the probability that it begins a 2-year consecutive drought?
\[P(\text{2 consecutive droughts starting year } t) = P(D_t) \times P(D_{t+1} | D_t) = \pi_D \times p_{dd}\]
# P(this year AND next year are both droughts)
p_2yr_consecutive <- pi_D * p_dd
rp_2yr_consecutive <- 1 / p_2yr_consecutive
cat("=== Probability of 2 Consecutive Drought Years (from any given year) ===\n\n")=== Probability of 2 Consecutive Drought Years (from any given year) ===
cat("P(Year t is drought) = π_D = ", round(pi_D * 100, 2), "%\n", sep = "")P(Year t is drought) = π_D = 20.67%
cat("P(Year t+1 is drought | Year t is drought) = p_dd = ", round(p_dd * 100, 2), "%\n\n", sep = "")P(Year t+1 is drought | Year t is drought) = p_dd = 27.5%
cat("P(2 consecutive droughts starting any year) = π_D × p_dd\n")P(2 consecutive droughts starting any year) = π_D × p_dd
cat(" = ", round(p_2yr_consecutive * 100, 2), "%\n\n", sep = "") = 5.68%
cat("RETURN PERIOD: ", round(rp_2yr_consecutive, 1), " years\n", sep = "")RETURN PERIOD: 17.6 years
What single-year drought threshold (RP) would give us a specific 2-consecutive-year return period?
Under independence: \(P(\text{2 consecutive}) = p^2\) where \(p = 1/\text{RP}_{\text{single}}\)
Therefore: \(\text{RP}_{\text{single}} = \sqrt{\text{RP}_{\text{2-consecutive}}}\)
target_2yr_rps <- c(3, 4, 5, 6, 7, 8)
df_theoretical <- tibble(
target_2yr_rp = target_2yr_rps,
single_yr_rp_independent = sqrt(target_2yr_rps),
single_yr_prob_independent = 1 / single_yr_rp_independent
)
cat("Theoretical Single-Year Thresholds (Independence Assumption):\n\n")Theoretical Single-Year Thresholds (Independence Assumption):
df_theoretical |>
mutate(
`Target 2-Consec RP` = paste0(target_2yr_rp, " yr"),
`Single-Year RP` = round(single_yr_rp_independent, 2),
`Single-Year Prob` = paste0(round(single_yr_prob_independent * 100, 1), "%"),
`Percentile Threshold` = paste0(round(single_yr_prob_independent * 100, 1), "th")
) |>
select(`Target 2-Consec RP`, `Single-Year RP`, `Single-Year Prob`, `Percentile Threshold`) |>
print()# A tibble: 6 × 4
`Target 2-Consec RP` `Single-Year RP` `Single-Year Prob`
<chr> <dbl> <chr>
1 3 yr 1.73 57.7%
2 4 yr 2 50%
3 5 yr 2.24 44.7%
4 6 yr 2.45 40.8%
5 7 yr 2.65 37.8%
6 8 yr 2.83 35.4%
# ℹ 1 more variable: `Percentile Threshold` <chr>
Sweep through different single-year thresholds and calculate the actual 2-consecutive RP accounting for Markov persistence.
# Function to calculate 2-consecutive RP for a given single-year RP threshold
calc_2yr_rp_for_threshold <- function(data, single_yr_rp) {
# Calculate threshold as percentile
threshold_prob <- 1 / single_yr_rp
# Apply threshold per province
df_classified <- data |>
group_by(name) |>
mutate(
threshold = quantile(mam_precip, probs = threshold_prob),
is_drought = mam_precip < threshold
) |>
ungroup()
# Calculate Markov transitions
df_trans <- df_classified |>
arrange(name, year) |>
group_by(name) |>
mutate(prev_drought = lag(is_drought)) |>
filter(!is.na(prev_drought)) |>
ungroup()
n_dd <- sum(df_trans$prev_drought & df_trans$is_drought)
n_dn <- sum(df_trans$prev_drought & !df_trans$is_drought)
n_nd <- sum(!df_trans$prev_drought & df_trans$is_drought)
n_nn <- sum(!df_trans$prev_drought & !df_trans$is_drought)
# Handle edge cases
if ((n_dd + n_dn) == 0 || (n_nd + n_nn) == 0) {
return(list(p_2yr = NA, rp_2yr = NA, p_dd = NA, p_nd = NA, pi_D = NA))
}
p_dd <- n_dd / (n_dd + n_dn)
p_nd <- n_nd / (n_nd + n_nn)
# Stationary probability
pi_D <- p_nd / (p_nd + (1 - p_dd))
# 2-consecutive probability
p_2yr <- pi_D * p_dd
rp_2yr <- 1 / p_2yr
list(
p_2yr = p_2yr,
rp_2yr = rp_2yr,
p_dd = p_dd,
p_nd = p_nd,
pi_D = pi_D
)
}
# Sweep through single-year RPs from 1.5 to 10
single_yr_rps_sweep <- seq(1.5, 10, by = 0.25)
df_sweep <- tibble(single_yr_rp = single_yr_rps_sweep) |>
mutate(
results = map(single_yr_rp, ~calc_2yr_rp_for_threshold(df_era5_mam, .x))
) |>
unnest_wider(results)
cat("Empirical Relationship: Single-Year RP → 2-Consecutive RP\n\n")Empirical Relationship: Single-Year RP → 2-Consecutive RP
df_sweep |>
filter(!is.na(rp_2yr)) |>
mutate(across(where(is.numeric), ~round(.x, 2))) |>
select(single_yr_rp, pi_D, p_dd, rp_2yr) |>
filter(single_yr_rp %in% c(2, 2.5, 3, 4, 5, 6, 8, 10)) |>
print()# A tibble: 8 × 4
single_yr_rp pi_D p_dd rp_2yr
<dbl> <dbl> <dbl> <dbl>
1 2 0.49 0.48 4.32
2 2.5 0.4 0.36 6.8
3 3 0.34 0.33 8.94
4 4 0.25 0.3 13.2
5 5 0.21 0.28 17.6
6 6 0.18 0.23 23.9
7 8 0.14 0.22 32.6
8 10 0.11 0.22 40.1
# Interpolate to find single-year RPs for target 2-consecutive RPs
find_single_rp_for_target <- function(target_2yr_rp, sweep_data) {
# Linear interpolation
sweep_clean <- sweep_data |> filter(!is.na(rp_2yr))
approx(x = sweep_clean$rp_2yr, y = sweep_clean$single_yr_rp, xout = target_2yr_rp)$y
}
df_target_thresholds <- tibble(
target_2yr_rp = target_2yr_rps
) |>
mutate(
single_yr_rp_empirical = map_dbl(target_2yr_rp, ~find_single_rp_for_target(.x, df_sweep)),
single_yr_rp_independent = sqrt(target_2yr_rp),
percentile_empirical = 1 / single_yr_rp_empirical,
percentile_independent = 1 / single_yr_rp_independent
)Warning: There were 6 warnings in `mutate()`.
The first warning was:
ℹ In argument: `single_yr_rp_empirical = map_dbl(target_2yr_rp,
~find_single_rp_for_target(.x, df_sweep))`.
Caused by warning in `regularize.values()`:
! collapsing to unique 'x' values
ℹ Run `dplyr::last_dplyr_warnings()` to see the 5 remaining warnings.
cat("\n=== Single-Year Thresholds for Target 2-Consecutive RPs ===\n\n")
=== Single-Year Thresholds for Target 2-Consecutive RPs ===
df_target_thresholds |>
mutate(
`Target 2-Yr RP` = paste0(target_2yr_rp, " yr"),
`Single RP (Empirical)` = round(single_yr_rp_empirical, 2),
`Single RP (Independent)` = round(single_yr_rp_independent, 2),
`Percentile (Empirical)` = paste0(round(percentile_empirical * 100, 1), "%")
) |>
select(`Target 2-Yr RP`, `Single RP (Empirical)`, `Single RP (Independent)`, `Percentile (Empirical)`) |>
print()# A tibble: 6 × 4
`Target 2-Yr RP` `Single RP (Empirical)` `Single RP (Independent)`
<chr> <dbl> <dbl>
1 3 yr 1.76 1.73
2 4 yr 1.94 2
3 5 yr 2.17 2.24
4 6 yr 2.36 2.45
5 7 yr 2.53 2.65
6 8 yr 2.7 2.83
# ℹ 1 more variable: `Percentile (Empirical)` <chr>
# Plot the relationship
ggplot(df_sweep |> filter(!is.na(rp_2yr), rp_2yr < 50), aes(x = single_yr_rp, y = rp_2yr)) +
geom_line(linewidth = 1.2, color = "#E69F00") +
geom_point(
data = df_target_thresholds,
aes(x = single_yr_rp_empirical, y = target_2yr_rp),
size = 4, color = "#D55E00"
) +
geom_text(
data = df_target_thresholds,
aes(x = single_yr_rp_empirical, y = target_2yr_rp,
label = paste0(target_2yr_rp, "-yr")),
vjust = -1, size = 3.5
) +
geom_abline(
aes(intercept = 0, slope = 1, linetype = "x² (independence)"),
color = "gray50"
) +
scale_linetype_manual(values = "dashed", name = NULL) +
labs(
title = "Single-Year RP Required for Target 2-Consecutive Drought RP",
subtitle = "Orange line = empirical (with persistence) | Gray dashed = theoretical independence",
x = "Single-Year Drought RP Threshold",
y = "Resulting 2-Consecutive Drought RP"
) +
theme(legend.position = "bottom")
cat("\n" , rep("=", 70) |> paste(collapse = ""), "\n", sep = "")
======================================================================
cat("RECOMMENDED SINGLE-YEAR THRESHOLDS FOR 2-CONSECUTIVE DROUGHT RPs\n")RECOMMENDED SINGLE-YEAR THRESHOLDS FOR 2-CONSECUTIVE DROUGHT RPs
cat(rep("=", 70) |> paste(collapse = ""), "\n\n", sep = "")======================================================================
df_target_thresholds |>
mutate(
threshold_mm = map_dbl(single_yr_rp_empirical, function(rp) {
# Get average threshold across provinces
df_era5_mam |>
group_by(name) |>
summarise(thresh = quantile(mam_precip, probs = 1/rp), .groups = "drop") |>
pull(thresh) |>
mean()
})
) |>
transmute(
`2-Consecutive RP` = paste0(target_2yr_rp, " years"),
`Single-Year RP` = paste0(round(single_yr_rp_empirical, 2), " years"),
`Percentile` = paste0(round(percentile_empirical * 100, 1), "%"),
`Avg Threshold (mm)` = round(threshold_mm, 1)
) |>
print()# A tibble: 6 × 4
`2-Consecutive RP` `Single-Year RP` Percentile `Avg Threshold (mm)`
<chr> <chr> <chr> <dbl>
1 3 years 1.76 years 56.8% 7.7
2 4 years 1.94 years 51.5% 7.5
3 5 years 2.17 years 46.1% 7.3
4 6 years 2.36 years 42.3% 7.1
5 7 years 2.53 years 39.5% 6.9
6 8 years 2.7 years 37% 6.8