From c52558e4ea1d1a791a41671270cfa913c376a9be Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 28 Jul 2026 13:53:16 +0200 Subject: [PATCH 1/4] order scdesign2 input by spatial cluster `simulate_count_scDesign2()` writes its output one cell type at a time, in contiguous blocks (`new_count[, llim:ulim] <- ...`), but scdesign2 was pulling the coordinates from the input in its original order. The datasets are not sorted by spatial_cluster -- MOBNEW starts 1,2,3,3,4,2,4,1 -- so every spot got the counts of an unrelated spot and the spatial signal was destroyed before the metrics ever saw it. It shows: scdesign2 came last on morans_I (25.7, against 1.8 for scdesign3_nb) and on svg_precision. Every other method already does `input[order(input$obs$spatial_cluster)]` first; this one now does too. Also make cell_type_sel and cell_type_prop agree: they are matched by position, but were built with `unique()` and `table()` respectively, which only coincide when the labels happen to sort the same way. Both now come from `unique()` on the sorted data, which also matches the block order for 10 or more clusters, where numeric and character sorting disagree. --- src/methods/scdesign2/script.R | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/methods/scdesign2/script.R b/src/methods/scdesign2/script.R index aac648d2..09497d10 100644 --- a/src/methods/scdesign2/script.R +++ b/src/methods/scdesign2/script.R @@ -18,13 +18,24 @@ if (par$base != "domain") { } cat("scDesign2 simulation start\n") -counts <- Matrix::t(input$layers[["counts"]]) -colnames(counts) <- as.character(input$obs$spatial_cluster) -spatial_cluster_prop <- table(input$obs$spatial_cluster) +# simulate_count_scDesign2() returns the cells grouped per cell type, so order +# the spots by spatial cluster first -- otherwise the coordinates we attach +# below belong to different spots than the counts we attach them to +ordered_indices <- order(input$obs$spatial_cluster) +input_ordered <- input[ordered_indices] + +counts <- Matrix::t(input_ordered$layers[["counts"]]) +colnames(counts) <- as.character(input_ordered$obs$spatial_cluster) + +# cell_type_prop is matched to the fitted models by position, so both have to be +# in the same order -- and that order has to be the one the spots are sorted in, +# which unique() gives us since the columns are already grouped per cluster +cell_types <- unique(colnames(counts)) +spatial_cluster_prop <- prop.table(table(colnames(counts))[cell_types]) copula_result <- scDesign2::fit_model_scDesign2( data_mat = counts, - cell_type_sel = unique(colnames(counts)), + cell_type_sel = cell_types, sim_method = "copula", ncores = ifelse(!is.null(meta$cpus), meta$cpus, 8L) ) @@ -33,11 +44,11 @@ sim_out <- scDesign2::simulate_count_scDesign2( copula_result, ncol(counts), sim_method = "copula", - cell_type_prop = prop.table(spatial_cluster_prop) + cell_type_prop = spatial_cluster_prop ) -rownames(sim_out) <- input$var_names -colnames(sim_out) <- input$obs_names +rownames(sim_out) <- input_ordered$var_names +colnames(sim_out) <- input_ordered$obs_names cat("Generating output\n") @@ -45,10 +56,10 @@ output <- anndataR::AnnData( layers = list( counts = Matrix::t(sim_out) ), - obs = input$obs[c("row", "col")], - var = input$var, + obs = input_ordered$obs[c("row", "col")], + var = input_ordered$var, uns = c( - input$uns, + input_ordered$uns, list( method_id = meta$name ) From 3889039481ddbecb37eef7604c6dbe5b8df6860d Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 28 Jul 2026 14:20:49 +0200 Subject: [PATCH 2/4] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3468069..b32f320f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # task_spatial_simulators dev Bug fixes: + - `scdesign2`: order the input by `spatial_cluster` before simulating. + `simulate_count_scDesign2()` returns cells grouped per cell type, so the + coordinates taken from the unsorted input belonged to different spots than + the counts they were attached to. Also make `cell_type_sel` and + `cell_type_prop` agree in order, which they did not for 10 or more clusters. - `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 From 7b5a93e1ceb38116d62e77b14cc43eb4a21daac6 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 28 Jul 2026 15:00:55 +0200 Subject: [PATCH 3/4] restore the input spot order in scdesign2 Sorting for the simulation is only half of it: the result has to be sorted back, or scdesign2 joins the group of methods whose output is in a different spot order than the real dataset and whose clustering_ari is therefore scored against mismatched spots. See #31. --- src/methods/scdesign2/script.R | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/methods/scdesign2/script.R b/src/methods/scdesign2/script.R index 09497d10..ee061fd5 100644 --- a/src/methods/scdesign2/script.R +++ b/src/methods/scdesign2/script.R @@ -50,16 +50,21 @@ sim_out <- scDesign2::simulate_count_scDesign2( rownames(sim_out) <- input_ordered$var_names colnames(sim_out) <- input_ordered$obs_names +# put the spots back in the order they came in. The metrics compare the +# simulated dataset against the real one spot for spot -- ARI and NMI are +# invariant to a permutation of the labels, but not of the samples. +restore_order <- order(ordered_indices) + cat("Generating output\n") output <- anndataR::AnnData( layers = list( - counts = Matrix::t(sim_out) + counts = Matrix::t(sim_out)[restore_order, , drop = FALSE] ), - obs = input_ordered$obs[c("row", "col")], - var = input_ordered$var, + obs = input$obs[c("row", "col")], + var = input$var, uns = c( - input_ordered$uns, + input$uns, list( method_id = meta$name ) From 438e84c1b75add1c4c3476535a7b53ee4bc643db Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 28 Jul 2026 15:00:55 +0200 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b32f320f..37828406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Bug fixes: coordinates taken from the unsorted input belonged to different spots than the counts they were attached to. Also make `cell_type_sel` and `cell_type_prop` agree in order, which they did not for 10 or more clusters. + The simulated spots are returned in the order they came in. - `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