# Helper function to format count with years in parentheses
format_with_years <- function(count, years) {
if (count == 0) return("0")
paste0(count, " (", paste(sort(years), collapse = ", "), ")")
}
# ============================================================================
# CDI years for FP and FN
# ============================================================================
cdi_fp_years <- df_cdi$year[cdi_fires & df_cdi$drought_actual == "no"]
cdi_fn_years <- df_cdi$year[!cdi_fires & df_cdi$drought_actual == "yes"]
cdi_fp_str <- format_with_years(cdi_fp, cdi_fp_years)
cdi_fn_str <- format_with_years(cdi_fn, cdi_fn_years)
# ============================================================================
# Configuration A: 1991-2025 (n=35), SEAS5 RP >= 4
# ============================================================================
n_droughts_combined <- sum(df_combined_eval$drought_actual == "yes")
# SEAS5 Only metrics (Config A)
seas5_fires_a <- df_combined_eval$seas5_fires_rp4
n_seas5_triggers_a <- sum(seas5_fires_a, na.rm = TRUE)
seas5_tp_a <- sum(seas5_fires_a & df_combined_eval$drought_actual == "yes", na.rm = TRUE)
seas5_fp_a <- sum(seas5_fires_a & df_combined_eval$drought_actual == "no", na.rm = TRUE)
seas5_fn_a <- sum(!seas5_fires_a & df_combined_eval$drought_actual == "yes", na.rm = TRUE)
seas5_precision_a <- round(seas5_tp_a / (seas5_tp_a + seas5_fp_a), 2)
seas5_recall_a <- round(seas5_tp_a / (seas5_tp_a + seas5_fn_a), 2)
seas5_f1_a <- round(2 * seas5_precision_a * seas5_recall_a / (seas5_precision_a + seas5_recall_a), 2)
seas5_rp_effective_a <- round((n_years_combined + 1) / n_seas5_triggers_a, 1)
# Extract years for Config A SEAS5
seas5_fp_years_a <- df_combined_eval$year[seas5_fires_a & df_combined_eval$drought_actual == "no"]
seas5_fn_years_a <- df_combined_eval$year[!seas5_fires_a & df_combined_eval$drought_actual == "yes"]
seas5_fp_str_a <- format_with_years(seas5_fp_a, seas5_fp_years_a)
seas5_fn_str_a <- format_with_years(seas5_fn_a, seas5_fn_years_a)
# Extract years for Config A Combined
combined_fp_years_a <- df_combined_eval$year[df_combined_eval$combined_fires & df_combined_eval$drought_actual == "no"]
combined_fn_years_a <- df_combined_eval$year[!df_combined_eval$combined_fires & df_combined_eval$drought_actual == "yes"]
combined_fp_str_a <- format_with_years(combined_fp, combined_fp_years_a)
combined_fn_str_a <- format_with_years(combined_fn, combined_fn_years_a)
# ============================================================================
# Configuration B: 1984-2025 (n=42), SEAS5 RP >= 6
# ============================================================================
# Use df_comparison filtered to all years with valid 1984 baseline RP
df_eval_1984 <- df_comparison |>
filter(!is.na(seas5_rp_1984)) |>
mutate(
cdi_fires = cdi >= cdi_threshold,
seas5_fires_rp6 = seas5_rp_1984 >= 6,
combined_fires = cdi_fires | seas5_fires_rp6
)
n_years_1984 <- nrow(df_eval_1984)
n_droughts_1984 <- sum(df_eval_1984$drought_actual == "yes")
# SEAS5 Only metrics (Config B: RP >= 6)
seas5_fires_b <- df_eval_1984$seas5_fires_rp6
n_seas5_triggers_b <- sum(seas5_fires_b, na.rm = TRUE)
seas5_tp_b <- sum(seas5_fires_b & df_eval_1984$drought_actual == "yes", na.rm = TRUE)
seas5_fp_b <- sum(seas5_fires_b & df_eval_1984$drought_actual == "no", na.rm = TRUE)
seas5_fn_b <- sum(!seas5_fires_b & df_eval_1984$drought_actual == "yes", na.rm = TRUE)
seas5_precision_b <- round(seas5_tp_b / (seas5_tp_b + seas5_fp_b), 2)
seas5_recall_b <- round(seas5_tp_b / (seas5_tp_b + seas5_fn_b), 2)
seas5_f1_b <- round(2 * seas5_precision_b * seas5_recall_b / (seas5_precision_b + seas5_recall_b), 2)
seas5_rp_effective_b <- round((n_years_1984 + 1) / n_seas5_triggers_b, 1)
# Combined metrics (Config B)
n_combined_triggers_b <- sum(df_eval_1984$combined_fires, na.rm = TRUE)
combined_tp_b <- sum(df_eval_1984$combined_fires & df_eval_1984$drought_actual == "yes", na.rm = TRUE)
combined_fp_b <- sum(df_eval_1984$combined_fires & df_eval_1984$drought_actual == "no", na.rm = TRUE)
combined_fn_b <- sum(!df_eval_1984$combined_fires & df_eval_1984$drought_actual == "yes", na.rm = TRUE)
combined_precision_b <- round(combined_tp_b / (combined_tp_b + combined_fp_b), 2)
combined_recall_b <- round(combined_tp_b / (combined_tp_b + combined_fn_b), 2)
combined_f1_b <- round(2 * combined_precision_b * combined_recall_b / (combined_precision_b + combined_recall_b), 2)
combined_rp_effective_b <- round((n_years_1984 + 1) / n_combined_triggers_b, 1)
# Extract years for Config B SEAS5
seas5_fp_years_b <- df_eval_1984$year[seas5_fires_b & df_eval_1984$drought_actual == "no"]
seas5_fn_years_b <- df_eval_1984$year[!seas5_fires_b & df_eval_1984$drought_actual == "yes"]
seas5_fp_str_b <- format_with_years(seas5_fp_b, seas5_fp_years_b)
seas5_fn_str_b <- format_with_years(seas5_fn_b, seas5_fn_years_b)
# Extract years for Config B Combined
combined_fp_years_b <- df_eval_1984$year[df_eval_1984$combined_fires & df_eval_1984$drought_actual == "no"]
combined_fn_years_b <- df_eval_1984$year[!df_eval_1984$combined_fires & df_eval_1984$drought_actual == "yes"]
combined_fp_str_b <- format_with_years(combined_fp_b, combined_fp_years_b)
combined_fn_str_b <- format_with_years(combined_fn_b, combined_fn_years_b)
# ============================================================================
# Configuration C: 1991-2025 (n=35), SEAS5 RP >= 6
# ============================================================================
df_eval_1991_rp6 <- df_same_denom |>
mutate(
seas5_fires_rp6 = seas5_rp_1991_same >= 6,
combined_fires_c = cdi_fires | seas5_fires_rp6
)
# SEAS5 Only metrics (Config C: 1991 baseline, RP >= 6)
seas5_fires_c <- df_eval_1991_rp6$seas5_fires_rp6
n_seas5_triggers_c <- sum(seas5_fires_c, na.rm = TRUE)
seas5_tp_c <- sum(seas5_fires_c & df_eval_1991_rp6$drought_actual == "yes", na.rm = TRUE)
seas5_fp_c <- sum(seas5_fires_c & df_eval_1991_rp6$drought_actual == "no", na.rm = TRUE)
seas5_fn_c <- sum(!seas5_fires_c & df_eval_1991_rp6$drought_actual == "yes", na.rm = TRUE)
seas5_precision_c <- round(seas5_tp_c / (seas5_tp_c + seas5_fp_c), 2)
seas5_recall_c <- round(seas5_tp_c / (seas5_tp_c + seas5_fn_c), 2)
seas5_f1_c <- round(2 * seas5_precision_c * seas5_recall_c / (seas5_precision_c + seas5_recall_c), 2)
seas5_rp_effective_c <- round((n_years_combined + 1) / n_seas5_triggers_c, 1)
# Combined metrics (Config C)
n_combined_triggers_c <- sum(df_eval_1991_rp6$combined_fires_c, na.rm = TRUE)
combined_tp_c <- sum(df_eval_1991_rp6$combined_fires_c & df_eval_1991_rp6$drought_actual == "yes", na.rm = TRUE)
combined_fp_c <- sum(df_eval_1991_rp6$combined_fires_c & df_eval_1991_rp6$drought_actual == "no", na.rm = TRUE)
combined_fn_c <- sum(!df_eval_1991_rp6$combined_fires_c & df_eval_1991_rp6$drought_actual == "yes", na.rm = TRUE)
combined_precision_c <- round(combined_tp_c / (combined_tp_c + combined_fp_c), 2)
combined_recall_c <- round(combined_tp_c / (combined_tp_c + combined_fn_c), 2)
combined_f1_c <- round(2 * combined_precision_c * combined_recall_c / (combined_precision_c + combined_recall_c), 2)
combined_rp_effective_c <- round((n_years_combined + 1) / n_combined_triggers_c, 1)
# Extract years for Config C SEAS5
seas5_fp_years_c <- df_eval_1991_rp6$year[seas5_fires_c & df_eval_1991_rp6$drought_actual == "no"]
seas5_fn_years_c <- df_eval_1991_rp6$year[!seas5_fires_c & df_eval_1991_rp6$drought_actual == "yes"]
seas5_fp_str_c <- format_with_years(seas5_fp_c, seas5_fp_years_c)
seas5_fn_str_c <- format_with_years(seas5_fn_c, seas5_fn_years_c)
# Extract years for Config C Combined
combined_fp_years_c <- df_eval_1991_rp6$year[df_eval_1991_rp6$combined_fires_c & df_eval_1991_rp6$drought_actual == "no"]
combined_fn_years_c <- df_eval_1991_rp6$year[!df_eval_1991_rp6$combined_fires_c & df_eval_1991_rp6$drought_actual == "yes"]
combined_fp_str_c <- format_with_years(combined_fp_c, combined_fp_years_c)
combined_fn_str_c <- format_with_years(combined_fn_c, combined_fn_years_c)
# ============================================================================
# Calculate FPs ADDED by SEAS5 (beyond CDI) for interpretation
# ============================================================================
# Config A: SEAS5 FPs that are not already CDI FPs
seas5_fp_added_a <- sum(seas5_fires_a & !df_combined_eval$cdi_fires & df_combined_eval$drought_actual == "no", na.rm = TRUE)
# Config C: SEAS5 FPs that are not already CDI FPs
seas5_fp_added_c <- sum(seas5_fires_c & !df_eval_1991_rp6$cdi_fires & df_eval_1991_rp6$drought_actual == "no", na.rm = TRUE)
# ============================================================================
# Build combined comparison table
# ============================================================================
tibble(
Metric = c(
"Evaluation period",
"Drought years",
"Years triggered",
"True positives",
"False positives",
"Missed droughts",
"Precision",
"Recall",
"F1 Score",
"Effective RP"
),
# CDI Only (same for both configs)
cdi = c(
paste0(n_years_total, " (1984-2025)"),
as.character(n_droughts_total),
as.character(n_cdi_triggers),
as.character(cdi_tp),
cdi_fp_str,
cdi_fn_str,
as.character(cdi_precision),
as.character(cdi_recall),
as.character(cdi_f1),
paste0("~", cdi_rp_effective, " yr")
),
# Config A: 1991-2025, RP4
seas5_a = c(
paste0(n_years_combined, " (1991-2025)"),
as.character(n_droughts_combined),
as.character(n_seas5_triggers_a),
as.character(seas5_tp_a),
seas5_fp_str_a,
seas5_fn_str_a,
as.character(seas5_precision_a),
as.character(seas5_recall_a),
as.character(seas5_f1_a),
paste0("~", seas5_rp_effective_a, " yr")
),
combined_a = c(
paste0(n_years_combined, " (1991-2025)"),
as.character(n_droughts_combined),
as.character(n_combined_triggers),
as.character(combined_tp),
combined_fp_str_a,
combined_fn_str_a,
as.character(combined_precision),
as.character(combined_recall),
as.character(combined_f1),
paste0("~", combined_rp_effective, " yr")
),
# Config B: 1984-2025, RP6
seas5_b = c(
paste0(n_years_1984, " (1984-2025)"),
as.character(n_droughts_1984),
as.character(n_seas5_triggers_b),
as.character(seas5_tp_b),
seas5_fp_str_b,
seas5_fn_str_b,
as.character(seas5_precision_b),
as.character(seas5_recall_b),
as.character(seas5_f1_b),
paste0("~", seas5_rp_effective_b, " yr")
),
combined_b = c(
paste0(n_years_1984, " (1984-2025)"),
as.character(n_droughts_1984),
as.character(n_combined_triggers_b),
as.character(combined_tp_b),
combined_fp_str_b,
combined_fn_str_b,
as.character(combined_precision_b),
as.character(combined_recall_b),
as.character(combined_f1_b),
paste0("~", combined_rp_effective_b, " yr")
),
# Config C: 1991-2025, RP6
seas5_c = c(
paste0(n_years_combined, " (1991-2025)"),
as.character(n_droughts_combined),
as.character(n_seas5_triggers_c),
as.character(seas5_tp_c),
seas5_fp_str_c,
seas5_fn_str_c,
as.character(seas5_precision_c),
as.character(seas5_recall_c),
as.character(seas5_f1_c),
paste0("~", seas5_rp_effective_c, " yr")
),
combined_c = c(
paste0(n_years_combined, " (1991-2025)"),
as.character(n_droughts_combined),
as.character(n_combined_triggers_c),
as.character(combined_tp_c),
combined_fp_str_c,
combined_fn_str_c,
as.character(combined_precision_c),
as.character(combined_recall_c),
as.character(combined_f1_c),
paste0("~", combined_rp_effective_c, " yr")
)
) |>
gt() |>
tab_header(
title = "Trigger Performance Comparison",
subtitle = "Three configurations: 1991 RP≥4, 1984 RP≥6, 1991 RP≥6"
) |>
tab_spanner(
label = "CDI Only (April)",
columns = cdi
) |>
tab_spanner(
label = "1991, RP ≥ 4",
columns = c(seas5_a, combined_a)
) |>
tab_spanner(
label = "1984, RP ≥ 6",
columns = c(seas5_b, combined_b)
) |>
tab_spanner(
label = "1991, RP ≥ 6",
columns = c(seas5_c, combined_c)
) |>
# Column group colors - softer palette
tab_style(
style = cell_fill(color = "#D2F2F0"), # light teal for CDI
locations = cells_body(columns = cdi)
) |>
tab_style(
style = cell_fill(color = "#D2F2F0"),
locations = cells_column_labels(columns = cdi)
) |>
tab_style(
style = cell_fill(color = "#FFF8E1"), # very light amber for Config A
locations = cells_body(columns = c(seas5_a, combined_a))
) |>
tab_style(
style = cell_fill(color = "#FFF8E1"),
locations = cells_column_labels(columns = c(seas5_a, combined_a))
) |>
tab_style(
style = cell_fill(color = "#F3E5F5"), # light violet for Config B
locations = cells_body(columns = c(seas5_b, combined_b))
) |>
tab_style(
style = cell_fill(color = "#F3E5F5"),
locations = cells_column_labels(columns = c(seas5_b, combined_b))
) |>
# Config C styling - light blue for third config
tab_style(
style = cell_fill(color = "#E3F2FD"), # light blue for Config C
locations = cells_body(columns = c(seas5_c, combined_c))
) |>
tab_style(
style = cell_fill(color = "#E3F2FD"),
locations = cells_column_labels(columns = c(seas5_c, combined_c))
) |>
# Style the spanner headers
tab_style(
style = list(
cell_fill(color = "#1EBFB3"),
cell_text(weight = "bold", size = px(13)),
cell_borders(sides = c("left", "right"), color = "white", weight = px(2))
),
locations = cells_column_spanners(spanners = "CDI Only (April)")
) |>
tab_style(
style = list(
cell_fill(color = "#FFCC80"),
cell_text(weight = "bold", size = px(13)),
cell_borders(sides = c("left", "right"), color = "white", weight = px(2))
),
locations = cells_column_spanners(spanners = "1991, RP ≥ 4")
) |>
tab_style(
style = list(
cell_fill(color = "#CE93D8"),
cell_text(weight = "bold", size = px(13)),
cell_borders(sides = c("left", "right"), color = "white", weight = px(2))
),
locations = cells_column_spanners(spanners = "1984, RP ≥ 6")
) |>
tab_style(
style = list(
cell_fill(color = "#64B5F6"), # blue for Config C spanner
cell_text(weight = "bold", size = px(13)),
cell_borders(sides = c("left", "right"), color = "white", weight = px(2))
),
locations = cells_column_spanners(spanners = "1991, RP ≥ 6")
) |>
# Add vertical borders between column groups
tab_style(
style = cell_borders(sides = "right", color = "#9E9E9E", weight = px(2)),
locations = cells_body(columns = cdi)
) |>
tab_style(
style = cell_borders(sides = "right", color = "#9E9E9E", weight = px(2)),
locations = cells_body(columns = combined_a)
) |>
tab_style(
style = cell_borders(sides = "right", color = "#9E9E9E", weight = px(2)),
locations = cells_body(columns = combined_b)
) |>
cols_label(
Metric = "",
cdi = "CDI",
seas5_a = "SEAS5",
combined_a = "Combined",
seas5_b = "SEAS5",
combined_b = "Combined",
seas5_c = "SEAS5",
combined_c = "Combined"
)