Skip to content

Commit c63a575

Browse files
authored
(fix): fix igraph on windows (#3745)
1 parent 45446ee commit c63a575

File tree

7 files changed

+73
-97
lines changed

7 files changed

+73
-97
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ jobs:
102102
file: test-data/test-results.xml
103103

104104
- name: Publish debug artifacts
105-
if: matrix.env.test-type == 'coverage'
105+
if: ${{ !cancelled() }}
106106
uses: actions/upload-artifact@v4
107107
with:
108108
name: debug-data-${{ matrix.env.name }}

docs/release-notes/3745.bugfix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix {func}`~scanpy.tl.leiden` with igraph backend on Windows {smaller}`P Angerer`

src/scanpy/_utils/random.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,20 @@
3838

3939

4040
class _RNGIgraph:
41-
"""Random number generator for ipgraph so global seed is not changed.
41+
"""Random number generator for igraph so global seed is not changed.
4242
4343
See :func:`igraph.set_random_number_generator` for the requirements.
4444
"""
4545

4646
def __init__(self, random_state: int | np.random.RandomState = 0) -> None:
4747
self._rng = check_random_state(random_state)
4848

49+
def getrandbits(self, k: int) -> int:
50+
return self._rng.tomaxint() & ((1 << k) - 1)
51+
52+
def randint(self, a: int, b: int) -> int:
53+
return self._rng.randint(a, b + 1)
54+
4955
def __getattr__(self, attr: str):
5056
return getattr(self._rng, "normal" if attr == "gauss" else attr)
5157

753 Bytes
Loading

tests/notebooks/test_pbmc3k.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def test_pbmc3k(image_comparer): # noqa: PLR0915
115115
sc.tl.leiden(
116116
adata,
117117
resolution=0.9,
118-
random_state=0,
118+
random_state=1,
119119
directed=False,
120120
n_iterations=2,
121121
flavor="igraph",
@@ -130,22 +130,18 @@ def test_pbmc3k(image_comparer): # noqa: PLR0915
130130
# Due to incosistency with our test runner vs local, these clusters need to
131131
# be pre-annotated as the numbers for each cluster are not consistent.
132132
marker_genes = [
133-
"RP11-18H21.1",
134-
"GZMK",
135-
"CD79A",
136-
"FCGR3A",
137-
"GNLY",
138-
"S100A8",
139-
"FCER1A",
140-
"PPBP",
133+
*["RP11-18H21.1", "GZMK", "CD79A", "FCGR3A"],
134+
*["GNLY", "S100A8", "FCER1A", "PPBP"],
141135
]
142-
new_labels = ["0", "1", "2", "3", "4", "5", "6", "7"]
143136
data_df = adata[:, marker_genes].to_df()
144137
data_df["leiden"] = adata.obs["leiden"]
145138
max_idxs = data_df.groupby("leiden", observed=True).mean().idxmax()
146-
leiden_relabel = {}
147-
for marker_gene, new_label in zip(marker_genes, new_labels, strict=True):
148-
leiden_relabel[max_idxs[marker_gene]] = new_label
139+
assert not max_idxs[marker_genes][
140+
max_idxs[marker_genes].duplicated(keep=False)
141+
].tolist(), "Not all marker genes are unique per cluster"
142+
leiden_relabel = {
143+
max_idxs[marker_gene]: str(i) for i, marker_gene in enumerate(marker_genes)
144+
}
149145
adata.obs["leiden_old"] = adata.obs["leiden"].copy()
150146
adata.rename_categories(
151147
"leiden", [leiden_relabel[key] for key in sorted(leiden_relabel.keys())]
@@ -175,14 +171,8 @@ def test_pbmc3k(image_comparer): # noqa: PLR0915
175171
# save_and_compare_images('rank_genes_groups_4')
176172

177173
new_cluster_names = [
178-
"CD4 T cells",
179-
"CD8 T cells",
180-
"B cells",
181-
"NK cells",
182-
"FCGR3A+ Monocytes",
183-
"CD14+ Monocytes",
184-
"Dendritic cells",
185-
"Megakaryocytes",
174+
*["CD4 T cells", "CD8 T cells", "B cells", "NK cells"],
175+
*["FCGR3A+ Monocytes", "CD14+ Monocytes", "Dendritic cells", "Megakaryocytes"],
186176
]
187177
adata.rename_categories("leiden", new_cluster_names)
188178

tests/test_clustering.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from functools import partial
44

5+
import pandas as pd
56
import pytest
67
from sklearn.metrics.cluster import normalized_mutual_info_score
78

@@ -71,8 +72,8 @@ def test_leiden_random_state(adata_neighbors, flavor):
7172
directed=is_leiden_alg,
7273
n_iterations=n_iterations,
7374
)
74-
assert (adata_1.obs["leiden"] == adata_1_again.obs["leiden"]).all()
75-
assert (adata_2.obs["leiden"] != adata_1_again.obs["leiden"]).any()
75+
pd.testing.assert_series_equal(adata_1.obs["leiden"], adata_1_again.obs["leiden"])
76+
assert not adata_2.obs["leiden"].equals(adata_1_again.obs["leiden"])
7677

7778

7879
@needs.igraph

tests/test_plotting.py

Lines changed: 50 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
if TYPE_CHECKING:
3030
from collections.abc import Callable
31+
from typing import Any
3132

3233
from matplotlib.axes import Axes
3334

@@ -58,71 +59,53 @@ def test_highest_expr_genes(image_comparer, col, layer):
5859

5960

6061
@needs.leidenalg
61-
def test_heatmap(image_comparer):
62+
@pytest.mark.parametrize(
63+
("params", "key"),
64+
[
65+
pytest.param({}, "heatmap", id="default"),
66+
pytest.param(
67+
dict(swap_axes=True, figsize=(10, 3), cmap="YlGnBu"),
68+
"heatmap_swap_axes",
69+
id="swap",
70+
),
71+
pytest.param(
72+
dict(
73+
groupby="numeric_value",
74+
num_categories=4,
75+
figsize=(4.5, 5),
76+
dendrogram=False,
77+
),
78+
"heatmap2",
79+
id="numeric",
80+
),
81+
pytest.param(
82+
dict(standard_scale="var", layer="test"),
83+
"heatmap_std_scale_var",
84+
id="std_scale=var",
85+
),
86+
pytest.param(
87+
dict(standard_scale="obs"),
88+
"heatmap_std_scale_obs",
89+
id="std_scale=obs",
90+
),
91+
],
92+
)
93+
def test_heatmap(image_comparer, params: dict[str, Any], key: str) -> None:
6294
save_and_compare_images = partial(image_comparer, ROOT, tol=15)
6395

6496
adata = krumsiek11()
65-
sc.pl.heatmap(
66-
adata, adata.var_names, "cell_type", use_raw=False, show=False, dendrogram=True
67-
)
68-
save_and_compare_images("heatmap")
69-
70-
# test swap axes
71-
sc.pl.heatmap(
72-
adata,
73-
adata.var_names,
74-
"cell_type",
75-
use_raw=False,
76-
show=False,
77-
dendrogram=True,
78-
swap_axes=True,
79-
figsize=(10, 3),
80-
cmap="YlGnBu",
81-
)
82-
save_and_compare_images("heatmap_swap_axes")
83-
84-
# test heatmap numeric column():
85-
86-
# set as numeric column the vales for the first gene on the matrix
8797
adata.obs["numeric_value"] = adata.X[:, 0]
88-
sc.pl.heatmap(
89-
adata,
90-
adata.var_names,
91-
"numeric_value",
92-
use_raw=False,
93-
num_categories=4,
94-
figsize=(4.5, 5),
95-
show=False,
96-
)
97-
save_and_compare_images("heatmap2")
98-
99-
# test var/obs standardization and layer
10098
adata.layers["test"] = -1 * adata.X.copy()
101-
sc.pl.heatmap(
102-
adata,
103-
adata.var_names,
104-
"cell_type",
105-
use_raw=False,
106-
dendrogram=True,
107-
show=False,
108-
standard_scale="var",
109-
layer="test",
110-
)
111-
save_and_compare_images("heatmap_std_scale_var")
11299

113-
# test standard_scale_obs
114-
sc.pl.heatmap(
115-
adata,
116-
adata.var_names,
117-
"cell_type",
118-
use_raw=False,
119-
dendrogram=True,
120-
show=False,
121-
standard_scale="obs",
122-
)
123-
save_and_compare_images("heatmap_std_scale_obs")
100+
params = dict(groupby="cell_type", dendrogram=True) | params
101+
sc.pl.heatmap(adata, adata.var_names, **params, use_raw=False, show=False)
102+
save_and_compare_images(key)
103+
104+
105+
@needs.leidenalg
106+
def test_heatmap_var_as_dict(image_comparer) -> None:
107+
save_and_compare_images = partial(image_comparer, ROOT, tol=15)
124108

125-
# test var_names as dict
126109
pbmc = pbmc68k_reduced()
127110
sc.tl.leiden(
128111
pbmc,
@@ -154,8 +137,13 @@ def test_heatmap(image_comparer):
154137
)
155138
save_and_compare_images("heatmap_var_as_dict")
156139

157-
# test that plot elements are well aligned
158-
# small
140+
141+
@needs.leidenalg
142+
@pytest.mark.parametrize("swap_axes", [True, False])
143+
def test_heatmap_alignment(*, image_comparer, swap_axes: bool) -> None:
144+
"""Test that plot elements are well aligned."""
145+
save_and_compare_images = partial(image_comparer, ROOT, tol=15)
146+
159147
a = AnnData(
160148
np.array([[0, 0.3, 0.5], [1, 1.3, 1.5], [2, 2.3, 2.5]]),
161149
obs={"foo": ["a", "b", "c"]},
@@ -166,21 +154,11 @@ def test_heatmap(image_comparer):
166154
a,
167155
var_names=a.var_names,
168156
groupby="foo",
169-
swap_axes=True,
170-
figsize=(4, 4),
171-
show=False,
172-
)
173-
save_and_compare_images("heatmap_small_swap_alignment")
174-
175-
sc.pl.heatmap(
176-
a,
177-
var_names=a.var_names,
178-
groupby="foo",
179-
swap_axes=False,
157+
swap_axes=swap_axes,
180158
figsize=(4, 4),
181159
show=False,
182160
)
183-
save_and_compare_images("heatmap_small_alignment")
161+
save_and_compare_images(f"heatmap_small{'_swap' if swap_axes else ''}_alignment")
184162

185163

186164
@pytest.mark.skipif(

0 commit comments

Comments
 (0)