diff --git a/docs/release-notes/4031.fix.md b/docs/release-notes/4031.fix.md new file mode 100644 index 0000000000..7bf2a2692a --- /dev/null +++ b/docs/release-notes/4031.fix.md @@ -0,0 +1 @@ +{func}`scanpy.tl.umap` no longer silently mutates `adata.obsp['connectivities']` via a shared sparse buffer {smaller}`N Justice` diff --git a/src/scanpy/tools/_umap.py b/src/scanpy/tools/_umap.py index a1848173fa..74565595b2 100644 --- a/src/scanpy/tools/_umap.py +++ b/src/scanpy/tools/_umap.py @@ -216,7 +216,7 @@ def umap( # noqa: PLR0913, PLR0915 n_epochs = default_epochs if maxiter is None else maxiter x_umap, _ = simplicial_set_embedding( data=x, - graph=neighbors["connectivities"].tocoo(), + graph=neighbors["connectivities"].tocoo(copy=True), n_components=n_components, initial_alpha=alpha, a=a, diff --git a/tests/test_embedding.py b/tests/test_embedding.py index 692157a084..c99cbba8ea 100644 --- a/tests/test_embedding.py +++ b/tests/test_embedding.py @@ -75,6 +75,21 @@ def test_umap_init_paga(layout): sc.tl.umap(pbmc, init_pos="paga") +def test_umap_preserves_connectivities(): + # https://github.com/scverse/scanpy/issues/4028 + pbmc = pbmc68k_reduced()[:100, :].copy() + conn = pbmc.obsp["connectivities"] + data_before = conn.data.copy() + nnz_before = conn.nnz + + sc.tl.umap(pbmc) + + assert_array_equal(data_before, conn.data) + assert conn.nnz == nnz_before + assert (conn.data == 0).sum() == 0, "CSR should have no explicit zeros" + assert "X_umap" in pbmc.obsm + + def test_diffmap(): pbmc = pbmc68k_reduced()