diff --git a/docs/release-notes/blank.md b/docs/release-notes/blank.md index 9ce44aab..b6b04f12 100644 --- a/docs/release-notes/blank.md +++ b/docs/release-notes/blank.md @@ -2,6 +2,8 @@ ```{rubric} Features ``` +* ``tl.leiden`` and ``tl.louvain`` now record the final modularity value in ``adata.uns[key_added]["modularity"]`` +(scalar for a single resolution, list for multiple resolutions) {pr}`648` {smaller}`J Pintar` ```{rubric} Performance ``` @@ -13,6 +15,9 @@ ```{rubric} Misc ``` +* ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution +is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple +resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar` ```{rubric} Removals ``` diff --git a/src/rapids_singlecell/tools/_clustering.py b/src/rapids_singlecell/tools/_clustering.py index f92261b8..9082441a 100644 --- a/src/rapids_singlecell/tools/_clustering.py +++ b/src/rapids_singlecell/tools/_clustering.py @@ -229,8 +229,9 @@ def leiden( resolutions = [resolution] else: resolutions = resolution + modularities = [] for resolution in resolutions: - leiden_parts, _ = culeiden( + leiden_parts, modularity = culeiden( g, resolution=resolution, random_state=random_state, @@ -241,6 +242,7 @@ def leiden( leiden_parts = leiden_parts.to_backend("pandas").compute() else: leiden_parts = leiden_parts.to_pandas() + modularities.append(modularity) # Format output groups = leiden_parts.sort_values("vertex")[["partition"]].to_numpy().ravel() @@ -270,10 +272,13 @@ def leiden( # store information on the clustering parameters adata.uns[key_added] = {} adata.uns[key_added]["params"] = { - "resolution": resolutions, + "resolution": resolutions if len(resolutions) > 1 else resolutions[0], "random_state": random_state, "n_iterations": n_iterations, } + adata.uns[key_added]["modularity"] = ( + modularities if len(modularities) > 1 else modularities[0] + ) return adata if copy else None @@ -383,8 +388,9 @@ def louvain( resolutions = [resolution] else: resolutions = resolution + modularities = [] for resolution in resolutions: - louvain_parts, _ = culouvain( + louvain_parts, modularity = culouvain( g, resolution=resolution, max_level=n_iterations, @@ -394,6 +400,7 @@ def louvain( louvain_parts = louvain_parts.to_backend("pandas").compute() else: louvain_parts = louvain_parts.to_pandas() + modularities.append(modularity) # Format output groups = louvain_parts.sort_values("vertex")[["partition"]].to_numpy().ravel() @@ -422,10 +429,13 @@ def louvain( Comms.destroy() adata.uns[key_added] = {} adata.uns[key_added]["params"] = { - "resolution": resolutions, + "resolution": resolutions if len(resolutions) > 1 else resolutions[0], "n_iterations": n_iterations, "threshold": threshold, } + adata.uns[key_added]["modularity"] = ( + modularities if len(modularities) > 1 else modularities[0] + ) return adata if copy else None diff --git a/tests/test_clustering.py b/tests/test_clustering.py index 9276cb37..4209a4c0 100644 --- a/tests/test_clustering.py +++ b/tests/test_clustering.py @@ -78,8 +78,13 @@ def test_clustering_resolution(adata_neighbors, clustering_function, resolution) if isinstance(resolution, list): for r in resolution: assert f"test_clustering_{r}" in adata.obs.columns + assert isinstance(adata.uns["test_clustering"]["modularity"], list) + assert len(adata.uns["test_clustering"]["modularity"]) == len(resolution) + assert adata.uns["test_clustering"]["params"]["resolution"] == resolution else: assert "test_clustering" in adata.obs.columns + assert isinstance(adata.uns["test_clustering"]["modularity"], float) + assert adata.uns["test_clustering"]["params"]["resolution"] == resolution def test_kmeans_basic(adata_neighbors):