Skip to content

Commit 98b2af8

Browse files
authored
Backport PR #4045 on branch 1.12.x (fix: use weights to calculate modularity) (#4048)
1 parent d348605 commit 98b2af8

9 files changed

Lines changed: 10 additions & 42 deletions

File tree

.github/workflows/check-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
id: changes
6161
with:
6262
filters: | # this is intentionally a string
63-
relnotes: 'docs/release-notes/${{ github.event.pull_request.number }}.*.md'
63+
relnotes: 'docs/release-notes/${{ github.event.pull_request.number }}.${{ (contains(github.event.pull_request.title, '!') && 'breaking') || needs.check-milestone.outputs.type }}.md'
6464
- name: Check if a relevant release fragment is added
6565
uses: flying-sheep/check@v1
6666
with:

docs/release-notes/4045.fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make {func}`scanpy.metrics.modularity` actually use edge weights {smaller}`P Angerer`

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,17 @@ dependencies = [
6363
"numba>=0.60",
6464
"numpy>=2",
6565
"packaging>=25",
66-
"pandas>=2.2.2",
66+
"pandas>=2.3",
6767
"patsy",
6868
"pynndescent>=0.5.13",
69-
"scikit-learn>=1.4.2",
69+
"scikit-learn>=1.6",
7070
"scipy>=1.13",
7171
"seaborn>=0.13.2",
7272
"session-info2",
7373
"statsmodels>=0.14.5",
7474
"tqdm",
7575
"typing-extensions; python_version<'3.13'",
76-
"umap-learn>=0.5.7",
76+
"umap-learn>=0.5.12",
7777
]
7878
urls.Bluesky = "https://bsky.app/profile/scverse.bsky.social"
7979
urls.Discourse = "https://discourse.scverse.org/c/help/scanpy/37"

src/scanpy/metrics/_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def modularity_array(
212212
raise
213213
igraph_mode: str = ig.ADJ_DIRECTED if is_directed else ig.ADJ_UNDIRECTED
214214
graph: ig.Graph = ig.Graph.Weighted_Adjacency(connectivities, mode=igraph_mode)
215-
return graph.modularity(_codes(labels))
215+
return graph.modularity(_codes(labels), "weight")
216216

217217

218218
def _codes(labels: AnyArrayLike) -> AnyArrayLike:

src/testing/scanpy/_pytest/marks.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from __future__ import annotations
22

33
from enum import Enum, auto
4-
from importlib.metadata import version
54
from importlib.util import find_spec
65

76
import pytest
8-
from packaging.version import Version
97

108

119
class QuietMarkDecorator(pytest.MarkDecorator):
@@ -71,9 +69,3 @@ def skip_reason(self) -> str | None:
7169
if self._name_.casefold() != self.mod.casefold().replace("-", "_"):
7270
reason = f"{reason} (`pip install {self.mod}`)"
7371
return reason
74-
75-
76-
# TODO: remove once https://github.com/numba/numba/issues/10319 is fixed
77-
skip_numba_0_63 = pytest.mark.skipif(
78-
Version(version=version("numba")) >= Version("0.63b0"), reason="numba 0.63 bug"
79-
)

tests/test_highly_variable_genes.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from scanpy._compat import CSRBase
2020
from testing.scanpy._helpers import _check_check_values_warnings
2121
from testing.scanpy._helpers.data import pbmc3k, pbmc68k_reduced
22-
from testing.scanpy._pytest.marks import needs, skip_numba_0_63
22+
from testing.scanpy._pytest.marks import needs
2323
from testing.scanpy._pytest.params import ARRAY_TYPES
2424

2525
if TYPE_CHECKING:
@@ -166,7 +166,6 @@ def _check_pearson_hvg_columns(output_df: pd.DataFrame, n_top_genes: int):
166166
assert np.nanmax(output_df["highly_variable_rank"].to_numpy()) <= n_top_genes - 1
167167

168168

169-
@skip_numba_0_63
170169
def test_pearson_residuals_inputchecks(
171170
pbmc3k_parametrized_small: Callable[[], AnnData],
172171
) -> None:
@@ -203,7 +202,6 @@ def test_pearson_residuals_inputchecks(
203202
)
204203

205204

206-
@skip_numba_0_63
207205
@pytest.mark.parametrize("subset", [True, False], ids=["subset", "full"])
208206
@pytest.mark.parametrize(
209207
"clip", [None, np.inf, 30], ids=["noclip", "infclip", "30clip"]
@@ -297,7 +295,6 @@ def test_pearson_residuals_general(
297295
_check_pearson_hvg_columns(output_df, n_top_genes)
298296

299297

300-
@skip_numba_0_63
301298
@pytest.mark.parametrize("subset", [True, False], ids=["subset", "full"])
302299
@pytest.mark.parametrize("n_top_genes", [100, 200], ids=["100n", "200n"])
303300
def test_pearson_residuals_batch(

tests/test_metrics.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@
1111
import pytest
1212
import threadpoolctl
1313
from anndata import AnnData
14-
from packaging.version import Version
1514
from scipy import sparse
1615

1716
import scanpy as sc
18-
from scanpy._compat import pkg_version
1917
from scanpy.metrics import modularity
2018
from testing.scanpy._helpers.data import pbmc3k, pbmc68k_reduced
21-
from testing.scanpy._pytest.context import xfail
2219
from testing.scanpy._pytest.marks import needs
2320
from testing.scanpy._pytest.params import ARRAY_TYPES
2421

@@ -336,15 +333,8 @@ def test_modularity_adata(
336333
with subtests.test("bounds", score=name):
337334
assert 0 <= s <= 1
338335
for (n0, s0), (n1, s1) in combinations(scores.items(), 2):
339-
approx = {n0, n1} != {"update", "calculate"}
340-
with (
341-
subtests.test("equality", l=n0, r=n1),
342-
xfail(
343-
approx and pkg_version("igraph") < Version("1"),
344-
reason="igraph 0.x has different modularity behavior",
345-
),
346-
):
347-
assert pytest.approx(s0, rel=1e-3 if approx else 1e-6) == s1
336+
with subtests.test("equality", l=n0, r=n1):
337+
assert pytest.approx(s0, rel=1e-6) == s1
348338
with subtests.test("update"):
349339
assert adata.uns["leiden"]["modularity"] is scores["update"]
350340

tests/test_neighbors.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,19 @@
55
import numpy as np
66
import pytest
77
from anndata import AnnData
8-
from packaging.version import Version
98
from scipy import sparse
109
from sklearn.neighbors import KNeighborsTransformer
1110

1211
import scanpy as sc
1312
from scanpy import Neighbors
14-
from scanpy._compat import CSBase, pkg_version
13+
from scanpy._compat import CSBase
1514
from testing.scanpy._helpers.data import pbmc68k_reduced
1615

1716
if TYPE_CHECKING:
1817
from typing import Literal
1918

2019
from pytest_mock import MockerFixture
2120

22-
# https://github.com/lmcinnes/umap/issues/1216
23-
XFAIL_IF_UMAP_BROKEN = pytest.mark.xfail(
24-
pkg_version("umap-learn") < Version("0.6a0.dev0")
25-
and pkg_version("numba") >= Version("0.62.0rc1"),
26-
reason="umap<0.6 is broken with numba≥0.62.0rc1",
27-
)
28-
2921
# the input data
3022
X = [[1, 0], [3, 0], [5, 6], [0, 4]]
3123
n_neighbors = 3 # includes data points themselves
@@ -207,7 +199,6 @@ def test_distances_all(neigh: Neighbors, transformer, knn):
207199
connectivities_umap,
208200
transitions_umap,
209201
transitions_sym_umap,
210-
marks=XFAIL_IF_UMAP_BROKEN,
211202
id="umap",
212203
),
213204
pytest.param(

tests/test_normalization.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
check_rep_mutation,
1919
check_rep_results,
2020
)
21-
from testing.scanpy._pytest.marks import skip_numba_0_63
2221

2322
# TODO: Add support for sparse-in-dask
2423
from testing.scanpy._pytest.params import ARRAY_TYPES, ARRAY_TYPES_DENSE
@@ -210,7 +209,6 @@ def _check_pearson_pca_fields(ad, n_cells, n_comps):
210209
), "Wrong shape of PCA output in `X_pca`"
211210

212211

213-
@skip_numba_0_63
214212
@pytest.mark.parametrize("n_hvgs", [100, 200])
215213
@pytest.mark.parametrize("n_comps", [30, 50])
216214
@pytest.mark.parametrize(
@@ -283,7 +281,6 @@ def test_normalize_pearson_residuals_pca(
283281
np.testing.assert_array_equal(adata.obsm["X_pca"], adata_pca.obsm["X_pca"])
284282

285283

286-
@skip_numba_0_63
287284
@pytest.mark.parametrize("n_hvgs", [100, 200])
288285
@pytest.mark.parametrize("n_comps", [30, 50])
289286
def test_normalize_pearson_residuals_recipe(

0 commit comments

Comments
 (0)