From e10f5bcae1e692f214780ede5cd9a974b741ec60 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 28 Jul 2026 13:57:30 +0200 Subject: [PATCH 1/2] stop losing downstream scores to NA * generate_cosine(): filter the Moran's I values rather than the objects holding them. `real[!is.na(real) & !is.na(sim)]` operates on a list, so it never dropped anything and one NaN took the whole metric with it -- 32 of 99 runs came out NA, while generate_mantel() passes na.rm and lost none. * calculate_precision(): report 0 when the simulation has no spatially variable genes at all. That is a bad simulation rather than a missing result, and reporting NA left both negative controls without a score on any dataset -- 8 of 30 expected control scores -- so nothing anchored the bottom of the scale. * calculate_recall(): left as NA on an empty denominator, and said why. There it is the real dataset that has no spatially variable genes, so there is nothing for any simulator to recover. --- src/helpers/utils.R | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/helpers/utils.R b/src/helpers/utils.R index b9ec2703..28e58c96 100644 --- a/src/helpers/utils.R +++ b/src/helpers/utils.R @@ -21,9 +21,20 @@ generate_moransI <- function(adata) { generate_cosine <- function(real, sim) { requireNamespace("lsa", quietly = TRUE) - real_new <- real[!is.na(real) & !is.na(sim)] - sim_new <- sim[!is.na(real) & !is.na(sim)] - similarity <- lsa::cosine(lsa::as.textmatrix(cbind(as.vector(real_new$Morans.I), as.vector(sim_new$Morans.I)))) + + real_i <- as.vector(real$Morans.I) + sim_i <- as.vector(sim$Morans.I) + + # drop the pairs where either side is missing. This used to filter `real` and + # `sim` themselves, which are lists, so it never dropped anything and a single + # NaN took the whole metric with it -- 32 of 99 runs came out NA, while + # generate_mantel() passes na.rm and lost none. + keep <- is.finite(real_i) & is.finite(sim_i) + if (sum(keep) < 2) { + return(NA_real_) + } + + similarity <- lsa::cosine(lsa::as.textmatrix(cbind(real_i[keep], sim_i[keep]))) return(mean(similarity)) } @@ -57,7 +68,10 @@ calculate_precision <- function(real_svg, sim_svg) { filtered_compared_data <- dplyr::filter(sim_svg$res_mtest, adjustedPval < 0.05) tp <- length(intersect(row.names(filtered_real_data), row.names(filtered_compared_data))) fp <- length(setdiff(row.names(filtered_compared_data), row.names(filtered_real_data))) - precision <- ifelse((tp + fp) > 0, tp / (tp + fp), NA) + # a simulation that produces no spatially variable genes at all is a bad + # simulation, not a missing result. Reporting NA meant both negative controls + # had no score on any dataset, so nothing anchored the bottom of the scale. + precision <- ifelse((tp + fp) > 0, tp / (tp + fp), 0) return(precision) } @@ -66,6 +80,9 @@ calculate_recall <- function(real_svg, sim_svg) { filtered_compared_data <- dplyr::filter(sim_svg$res_mtest, adjustedPval < 0.05) tp <- length(intersect(row.names(filtered_real_data), row.names(filtered_compared_data))) fn <- length(setdiff(row.names(filtered_real_data), row.names(filtered_compared_data))) + # unlike precision, an empty denominator here says something about the real + # dataset rather than about the simulation: there are no spatially variable + # genes to recover, so no simulator can be scored on it. NA is right. recall <- ifelse((tp + fn) > 0, tp / (tp + fn), NA) return(recall) } From 2ef211f766f952fcab22a76d97defee0b0f51f04 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 28 Jul 2026 14:19:28 +0200 Subject: [PATCH 2/2] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3468069..57f4a7c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # task_spatial_simulators dev Bug fixes: + - `generate_cosine()`: filter the Moran's I values rather than the objects + holding them, so that a single NaN no longer takes the whole metric with + it. `crosscor_cosine` was NA for 32 of 99 runs. + - `calculate_precision()`: report 0 rather than NA when a simulation has no + spatially variable genes at all. Both negative controls were left without a + score on any dataset, so nothing anchored the bottom of the scale. - `run_benchmark`: raise `uns_length_cutoff` from 15 to 50, so that `extract_uns_metadata` no longer drops the `metric_ids` of components that emit more than 15 metrics. All 28 `ks_statistic_gene_cell` metrics were