remove gap score calculation --> moved to common

This commit is contained in:
DimitraVeropoulou 2026-02-16 15:05:52 +01:00
parent 35e474cf5c
commit 5f2dca0a92

View file

@ -166,153 +166,153 @@ calculate_status_alert <- function(imminent_prob, age_week, weekly_ci_change, me
NA_character_ NA_character_
} }
#' Calculate Gap Filling Score KPI (2σ method) # #' Calculate Gap Filling Score KPI (2σ method)
#' @param ci_raster Current week CI raster # #' @param ci_raster Current week CI raster
#' @param field_boundaries Field boundaries # #' @param field_boundaries Field boundaries
#' @return Data frame with field-level gap filling scores # #' @return Data frame with field-level gap filling scores
calculate_gap_filling_kpi <- function(ci_raster, field_boundaries) { # calculate_gap_filling_kpi <- function(ci_raster, field_boundaries) {
safe_log("Calculating Gap Filling Score KPI (placeholder)") # safe_log("Calculating Gap Filling Score KPI (placeholder)")
# Handle both sf and SpatVector inputs # # Handle both sf and SpatVector inputs
if (!inherits(field_boundaries, "SpatVector")) { # if (!inherits(field_boundaries, "SpatVector")) {
field_boundaries_vect <- terra::vect(field_boundaries) # field_boundaries_vect <- terra::vect(field_boundaries)
} else { # } else {
field_boundaries_vect <- field_boundaries # field_boundaries_vect <- field_boundaries
} # }
# Ensure field_boundaries_vect is valid and matches field_boundaries dimensions # # Ensure field_boundaries_vect is valid and matches field_boundaries dimensions
n_fields_vect <- length(field_boundaries_vect) # n_fields_vect <- length(field_boundaries_vect)
n_fields_sf <- nrow(field_boundaries) # n_fields_sf <- nrow(field_boundaries)
if (n_fields_sf != n_fields_vect) { # if (n_fields_sf != n_fields_vect) {
warning(paste("Field boundary mismatch: nrow(field_boundaries)=", n_fields_sf, "vs length(field_boundaries_vect)=", n_fields_vect, ". Using actual SpatVector length.")) # warning(paste("Field boundary mismatch: nrow(field_boundaries)=", n_fields_sf, "vs length(field_boundaries_vect)=", n_fields_vect, ". Using actual SpatVector length."))
} # }
field_results <- data.frame() # field_results <- data.frame()
for (i in seq_len(nrow(field_boundaries))) { # for (i in seq_len(nrow(field_boundaries))) {
field_name <- field_boundaries$field[i] # field_name <- field_boundaries$field[i]
sub_field_name <- field_boundaries$sub_field[i] # sub_field_name <- field_boundaries$sub_field[i]
field_vect <- field_boundaries_vect[i] # field_vect <- field_boundaries_vect[i]
# Extract CI values using helper function # # Extract CI values using helper function
ci_values <- extract_ci_values(ci_raster, field_vect) # ci_values <- extract_ci_values(ci_raster, field_vect)
valid_values <- ci_values[!is.na(ci_values) & is.finite(ci_values)] # valid_values <- ci_values[!is.na(ci_values) & is.finite(ci_values)]
if (length(valid_values) > 1) { # if (length(valid_values) > 1) {
# Gap score using 2σ below median to detect outliers # # Gap score using 2σ below median to detect outliers
median_ci <- median(valid_values) # median_ci <- median(valid_values)
sd_ci <- sd(valid_values) # sd_ci <- sd(valid_values)
outlier_threshold <- median_ci - (2 * sd_ci) # outlier_threshold <- median_ci - (2 * sd_ci)
low_ci_pixels <- sum(valid_values < outlier_threshold) # low_ci_pixels <- sum(valid_values < outlier_threshold)
total_pixels <- length(valid_values) # total_pixels <- length(valid_values)
gap_score <- round((low_ci_pixels / total_pixels) * 100, 2) # gap_score <- round((low_ci_pixels / total_pixels) * 100, 2)
# Classify gap severity # # Classify gap severity
gap_level <- dplyr::case_when( # gap_level <- dplyr::case_when(
gap_score < 10 ~ "Minimal", # gap_score < 10 ~ "Minimal",
gap_score < 25 ~ "Moderate", # gap_score < 25 ~ "Moderate",
TRUE ~ "Significant" # TRUE ~ "Significant"
) # )
field_results <- rbind(field_results, data.frame( # field_results <- rbind(field_results, data.frame(
field = field_name, # field = field_name,
sub_field = sub_field_name, # sub_field = sub_field_name,
gap_level = gap_level, # gap_level = gap_level,
gap_score = gap_score, # gap_score = gap_score,
mean_ci = mean(valid_values), # mean_ci = mean(valid_values),
outlier_threshold = outlier_threshold # outlier_threshold = outlier_threshold
)) # ))
} else { # } else {
# Not enough valid data, fill with NA row # # Not enough valid data, fill with NA row
field_results <- rbind(field_results, data.frame( # field_results <- rbind(field_results, data.frame(
field = field_name, # field = field_name,
sub_field = sub_field_name, # sub_field = sub_field_name,
gap_level = NA_character_, # gap_level = NA_character_,
gap_score = NA_real_, # gap_score = NA_real_,
mean_ci = NA_real_, # mean_ci = NA_real_,
outlier_threshold = NA_real_ # outlier_threshold = NA_real_
)) # ))
} # }
} # }
return(list(field_results = field_results)) # return(list(field_results = field_results))
} # }
#' Calculate gap filling scores for all per-field mosaics # #' Calculate gap filling scores for all per-field mosaics
#' This is a wrapper function that processes multiple per-field mosaic files # #' This is a wrapper function that processes multiple per-field mosaic files
#' and calculates gap scores for each field. # #' and calculates gap scores for each field.
#' @param per_field_files Character vector of paths to per-field mosaic TIFFs # #' @param per_field_files Character vector of paths to per-field mosaic TIFFs
#' @param field_boundaries_sf sf object with field geometries # #' @param field_boundaries_sf sf object with field geometries
#' @return data.frame with Field_id and gap_score columns # #' @return data.frame with Field_id and gap_score columns
calculate_gap_scores <- function(per_field_files, field_boundaries_sf) { # calculate_gap_scores <- function(per_field_files, field_boundaries_sf) {
message("\nCalculating gap filling scores (2σ method)...") # message("\nCalculating gap filling scores (2σ method)...")
message(paste(" Using per-field mosaics for", length(per_field_files), "fields")) # message(paste(" Using per-field mosaics for", length(per_field_files), "fields"))
field_boundaries_by_id <- split(field_boundaries_sf, field_boundaries_sf$field) # field_boundaries_by_id <- split(field_boundaries_sf, field_boundaries_sf$field)
process_gap_for_field <- function(field_file) { # process_gap_for_field <- function(field_file) {
field_id <- basename(dirname(field_file)) # field_id <- basename(dirname(field_file))
field_bounds <- field_boundaries_by_id[[field_id]] # field_bounds <- field_boundaries_by_id[[field_id]]
if (is.null(field_bounds) || nrow(field_bounds) == 0) { # if (is.null(field_bounds) || nrow(field_bounds) == 0) {
return(data.frame(Field_id = field_id, gap_score = NA_real_)) # return(data.frame(Field_id = field_id, gap_score = NA_real_))
} # }
tryCatch({ # tryCatch({
field_raster <- terra::rast(field_file) # field_raster <- terra::rast(field_file)
ci_band_name <- "CI" # ci_band_name <- "CI"
if (!(ci_band_name %in% names(field_raster))) { # if (!(ci_band_name %in% names(field_raster))) {
return(data.frame(Field_id = field_id, gap_score = NA_real_)) # return(data.frame(Field_id = field_id, gap_score = NA_real_))
} # }
field_ci_band <- field_raster[[ci_band_name]] # field_ci_band <- field_raster[[ci_band_name]]
names(field_ci_band) <- "CI" # names(field_ci_band) <- "CI"
gap_result <- calculate_gap_filling_kpi(field_ci_band, field_bounds) # gap_result <- calculate_gap_filling_kpi(field_ci_band, field_bounds)
if (is.null(gap_result) || is.null(gap_result$field_results) || nrow(gap_result$field_results) == 0) { # if (is.null(gap_result) || is.null(gap_result$field_results) || nrow(gap_result$field_results) == 0) {
return(data.frame(Field_id = field_id, gap_score = NA_real_)) # return(data.frame(Field_id = field_id, gap_score = NA_real_))
} # }
gap_scores <- gap_result$field_results # gap_scores <- gap_result$field_results
gap_scores$Field_id <- gap_scores$field # gap_scores$Field_id <- gap_scores$field
gap_scores <- gap_scores[, c("Field_id", "gap_score")] # gap_scores <- gap_scores[, c("Field_id", "gap_score")]
stats::aggregate(gap_score ~ Field_id, data = gap_scores, FUN = function(x) mean(x, na.rm = TRUE)) # stats::aggregate(gap_score ~ Field_id, data = gap_scores, FUN = function(x) mean(x, na.rm = TRUE))
}, error = function(e) { # }, error = function(e) {
message(paste(" WARNING: Gap score failed for field", field_id, ":", e$message)) # message(paste(" WARNING: Gap score failed for field", field_id, ":", e$message))
data.frame(Field_id = field_id, gap_score = NA_real_) # data.frame(Field_id = field_id, gap_score = NA_real_)
}) # })
} # }
# Process fields sequentially with progress bar # # Process fields sequentially with progress bar
message(" Processing gap scores for ", length(per_field_files), " fields...") # message(" Processing gap scores for ", length(per_field_files), " fields...")
pb <- utils::txtProgressBar(min = 0, max = length(per_field_files), style = 3, width = 50) # pb <- utils::txtProgressBar(min = 0, max = length(per_field_files), style = 3, width = 50)
results_list <- lapply(seq_along(per_field_files), function(idx) { # results_list <- lapply(seq_along(per_field_files), function(idx) {
result <- process_gap_for_field(per_field_files[[idx]]) # result <- process_gap_for_field(per_field_files[[idx]])
utils::setTxtProgressBar(pb, idx) # utils::setTxtProgressBar(pb, idx)
result # result
}) # })
close(pb) # close(pb)
gap_scores_df <- dplyr::bind_rows(results_list) # gap_scores_df <- dplyr::bind_rows(results_list)
if (!is.null(gap_scores_df) && nrow(gap_scores_df) > 0) { # if (!is.null(gap_scores_df) && nrow(gap_scores_df) > 0) {
gap_scores_df <- gap_scores_df %>% # gap_scores_df <- gap_scores_df %>%
dplyr::group_by(Field_id) %>% # dplyr::group_by(Field_id) %>%
dplyr::summarise(gap_score = mean(gap_score, na.rm = TRUE), .groups = "drop") # dplyr::summarise(gap_score = mean(gap_score, na.rm = TRUE), .groups = "drop")
message(paste(" ✓ Calculated gap scores for", nrow(gap_scores_df), "fields")) # message(paste(" ✓ Calculated gap scores for", nrow(gap_scores_df), "fields"))
message(paste(" Gap score range:", round(min(gap_scores_df$gap_score, na.rm=TRUE), 2), "-", # message(paste(" Gap score range:", round(min(gap_scores_df$gap_score, na.rm=TRUE), 2), "-",
round(max(gap_scores_df$gap_score, na.rm=TRUE), 2), "%")) # round(max(gap_scores_df$gap_score, na.rm=TRUE), 2), "%"))
} else { # } else {
message(" WARNING: No gap scores calculated from per-field mosaics") # message(" WARNING: No gap scores calculated from per-field mosaics")
gap_scores_df <- NULL # gap_scores_df <- NULL
} # }
return(gap_scores_df) # return(gap_scores_df)
} # }
#' Build complete per-field KPI dataframe with all 22 columns #' Build complete per-field KPI dataframe with all 22 columns
#' @param current_stats data.frame with current week statistics from load_or_calculate_weekly_stats #' @param current_stats data.frame with current week statistics from load_or_calculate_weekly_stats