event_skill <- function(ref_df, target_df, var, threshold,
direction = c(">", ">=", "<", "<=")) {
direction <- match.arg(direction)
joined <- inner_join(
ref_df |> select(Date, ref = all_of(var)),
target_df |> select(Date, tgt = all_of(var)),
by = "Date"
) |>
drop_na(ref, tgt)
ref_event <- switch(
direction,
">" = joined$ref > threshold,
">=" = joined$ref >= threshold,
"<" = joined$ref < threshold,
"<=" = joined$ref <= threshold
)
tgt_event <- switch(
direction,
">" = joined$tgt > threshold,
">=" = joined$tgt >= threshold,
"<" = joined$tgt < threshold,
"<=" = joined$tgt <= threshold
)
hits <- sum(ref_event & tgt_event, na.rm = TRUE)
miss <- sum(ref_event & !tgt_event, na.rm = TRUE)
fa <- sum(!ref_event & tgt_event, na.rm = TRUE)
cn <- sum(!ref_event & !tgt_event, na.rm = TRUE)
pod <- if ((hits + miss) > 0) hits / (hits + miss) else NA_real_
far <- if ((hits + fa) > 0) fa / (hits + fa) else NA_real_
csi <- if ((hits + miss + fa) > 0) hits / (hits + miss + fa) else NA_real_
bias_score <- if ((hits + miss) > 0) (hits + fa) / (hits + miss) else NA_real_
tibble(
n = nrow(joined),
hits = hits, misses = miss, false_alarms = fa, correct_neg = cn,
POD = pod, FAR = far, CSI = csi, bias_score = bias_score
)
}
event_skill_all_targets <- function(ref_df, targets_list, var, threshold,
direction = ">=",
ref_name = "Airport_1770") {
imap_dfr(targets_list, ~ {
event_skill(ref_df, .x, var = var, threshold = threshold, direction = direction) |>
mutate(target = .y, var = var, threshold = threshold, ref = ref_name, .before = 1)
})
}
# --- Plot: stacked counts of event outcomes per target ---
plot_event_outcomes <- function(skill_tbl, title = NULL) {
long <- skill_tbl |>
select(target, hits, misses, false_alarms, correct_neg) |>
pivot_longer(-target, names_to = "outcome", values_to = "count")
ggplot(long, aes(target, count, fill = outcome)) +
geom_col() +
coord_flip() +
labs(
title = title %||% "Event agreement outcomes",
x = NULL, y = "Count (days)", fill = NULL
) +
theme_bw()
}
# --- Plot: POD/FAR/CSI per target (quick skill comparison) ---
plot_event_skill_scores <- function(skill_tbl, title = NULL) {
long <- skill_tbl |>
select(target, POD, FAR, CSI, bias_score) |>
pivot_longer(-target, names_to = "metric", values_to = "value")
ggplot(long, aes(target, value)) +
geom_col() +
facet_wrap(~ metric, ncol = 2, scales = "free_y") +
coord_flip() +
labs(
title = title %||% "Event skill scores",
x = NULL, y = NULL
) +
theme_bw()
}
# --- Magnitude when disagreement occurs (precip example) ---
# "Ref dry but target wet": how much rain do they report?
plot_false_alarm_magnitude <- function(ref_df, targets_list,
threshold_mm = 1,
ref_name = "Airport_1770") {
# Build paired dataset
paired <- imap_dfr(
targets_list,
~ inner_join(
ref_df |> select(Date, ref = Precip_mm),
.x |> select(Date, tgt = Precip_mm),
by = "Date"
) |>
drop_na(ref, tgt) |>
mutate(target = .y)
)
fa <- paired |>
filter(ref <= threshold_mm, tgt > threshold_mm)
ggplot(fa, aes(tgt, fill = target)) +
geom_histogram(bins = 40, alpha = 0.6, position = "identity") +
scale_x_continuous(trans = "log1p") +
scale_fill_manual(values = target_colors) +
labs(
title = paste0("False alarms magnitude: ref ≤ ", threshold_mm, " mm but target > ", threshold_mm, " mm"),
x = "Target precip (mm/day) [log1p]", y = "Count", fill = "Dataset"
) +
theme_bw()
}
# --- Example calls (precip threshold default = 0.1 mm; easy to change) ---
precip_event_threshold <- 1 # change to 1 or 5 if you want sensitivity tests later
skill_precip <- event_skill_all_targets(
ref_df, targets_list,
var = "Precip_mm",
threshold = precip_event_threshold,
direction = ">=")