Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions src/scanpy/_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
from typing import Any

from anndata import AnnData
from igraph import Graph
from numpy.typing import ArrayLike, NDArray
from pandas._typing import Dtype as PdDtype

Expand Down Expand Up @@ -268,30 +267,6 @@ def check_use_raw(
return adata.raw is not None


# --------------------------------------------------------------------------------
# Graph stuff
# --------------------------------------------------------------------------------


def get_igraph_from_adjacency(adjacency: CSBase, *, directed: bool = False) -> Graph:
"""Get igraph graph from adjacency matrix."""
import igraph as ig

sources, targets = adjacency.nonzero()
weights = dematrix(adjacency[sources, targets]).ravel() if len(sources) else []
g = ig.Graph(directed=directed)
g.add_vertices(adjacency.shape[0]) # this adds adjacency.shape[0] vertices
g.add_edges(list(zip(sources, targets, strict=True)))
with suppress(KeyError):
g.es["weight"] = weights
if g.vcount() != adjacency.shape[0]:
logg.warning(
f"The constructed graph has only {g.vcount()} nodes. "
"Your adjacency matrix contained redundant nodes."
)
return g


# --------------------------------------------------------------------------------
# Group stuff
# --------------------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions src/scanpy/neighbors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from packaging.version import Version
from scipy import sparse

from .. import _utils
from .. import logging as logg
from .._compat import CSBase, CSRBase, SpBase, pkg_version, warn
from .._docs import doc_rng
Expand Down Expand Up @@ -578,7 +577,9 @@ def distances_dpt(self) -> OnFlySymMatrix:

def to_igraph(self) -> Graph:
"""Generate igraph from connectiviies."""
return _utils.get_igraph_from_adjacency(self.connectivities)
import igraph as ig

return ig.Graph.Weighted_Adjacency(self.connectivities, mode=ig.ADJ_UNDIRECTED)

@_doc_params(n_pcs=doc_n_pcs, use_rep=doc_use_rep)
@_accepts_legacy_random_state(0)
Expand Down
10 changes: 8 additions & 2 deletions src/scanpy/plotting/_tools/paga.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,21 @@ def _compute_pos( # noqa: PLR0912
)
raise ValueError(msg)
else: # igraph layouts
import igraph as ig

if isinstance(rng, _LegacyRng): # backwards compat
random.seed(rng.bytes(8))
ctx = nullcontext()
else:
ctx = _set_igraph_rng(rng)
g = _sc_utils.get_igraph_from_adjacency(adjacency_solid)
g: ig.Graph = ig.Graph.Weighted_Adjacency(
adjacency_solid, mode=ig.ADJ_UNDIRECTED
)
with ctx:
if "rt" in layout:
g_tree = _sc_utils.get_igraph_from_adjacency(adj_tree)
g_tree: ig.Graph = ig.Graph.Weighted_Adjacency(
adj_tree, mode=ig.ADJ_UNDIRECTED
)
pos_list = g_tree.layout(
layout, root=root if isinstance(root, list) else [root]
).coords
Expand Down
5 changes: 3 additions & 2 deletions src/scanpy/tools/_draw_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import numpy as np

from .. import _utils
from .. import logging as logg
from .._docs import doc_rng
from .._utils import _choose_graph, _doc_params, get_literal_vals
Expand Down Expand Up @@ -150,7 +149,9 @@ def draw_graph( # noqa: PLR0913
if layout == "fa":
positions = np.array(fa2_positions(adjacency, init_coords, **kwds))
else:
g = _utils.get_igraph_from_adjacency(adjacency)
import igraph as ig

g: ig.Graph = ig.Graph.Weighted_Adjacency(adjacency, mode=ig.ADJ_UNDIRECTED)
with _igraph_rng_compat(rng):
if layout in {"fr", "drl", "kk", "grid_fr"}:
ig_layout = g.layout(layout, seed=init_coords.tolist(), **kwds)
Expand Down
10 changes: 7 additions & 3 deletions src/scanpy/tools/_leiden.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,10 @@ def leiden( # noqa: PLR0913
to calculate a score independent of `flavor`.

"""
flavor = _validate_flavor(flavor, partition_type=partition_type, directed=directed)
_utils.ensure_igraph()
import igraph as ig

flavor = _validate_flavor(flavor, partition_type=partition_type, directed=directed)
clustering_args = dict(clustering_args)
rng = np.random.default_rng(rng)
meta_random_state = (
Expand Down Expand Up @@ -170,7 +172,9 @@ def leiden( # noqa: PLR0913
if resolution is not None:
clustering_args["resolution_parameter"] = resolution
directed = True if directed is None else directed
g = _utils.get_igraph_from_adjacency(adjacency, directed=directed)
g: ig.Graph = ig.Graph.Weighted_Adjacency(
adjacency, mode=ig.ADJ_DIRECTED if directed else ig.ADJ_UNDIRECTED
)
if partition_type is None:
partition_type = leidenalg.RBConfigurationVertexPartition
if use_weights:
Expand All @@ -186,7 +190,7 @@ def leiden( # noqa: PLR0913
leidenalg.find_partition(g, partition_type, seed=seed, **clustering_args),
)
else:
g = _utils.get_igraph_from_adjacency(adjacency, directed=False)
g: ig.Graph = ig.Graph.Weighted_Adjacency(adjacency, mode=ig.ADJ_UNDIRECTED)
if use_weights:
clustering_args["weights"] = "weight"
if resolution is not None:
Expand Down
7 changes: 5 additions & 2 deletions src/scanpy/tools/_louvain.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from packaging.version import Version
from scverse_misc import Deprecation, deprecated

from .. import _utils
from .. import logging as logg
from .._compat import pkg_version
from .._utils import _choose_graph, _doc_params
Expand Down Expand Up @@ -143,13 +142,17 @@ def louvain( # noqa: PLR0912, PLR0913, PLR0915
adjacency=adjacency,
)
if flavor in {"vtraag", "igraph"}:
import igraph as ig

if flavor == "igraph" and resolution is not None:
logg.warning('`resolution` parameter has no effect for flavor "igraph"')
if directed and flavor == "igraph":
directed = False
if not directed:
logg.debug(" using the undirected graph")
g = _utils.get_igraph_from_adjacency(adjacency, directed=directed)
g: ig.Graph = ig.Graph.Weighted_Adjacency(
adjacency, mode=ig.ADJ_DIRECTED if directed else ig.ADJ_UNDIRECTED
)
weights = np.array(g.es["weight"]).astype(np.float64) if use_weights else None
if flavor == "vtraag":
import louvain
Expand Down
22 changes: 12 additions & 10 deletions src/scanpy/tools/_paga.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ def _compute_connectivities_v1_2(self):
ones = self._neighbors.distances.copy()
ones.data = np.ones(len(ones.data))
# should be directed if we deal with distances
g = _utils.get_igraph_from_adjacency(ones, directed=True)
g: igraph.Graph = igraph.Graph.Weighted_Adjacency(
ones, mode=igraph.ADJ_DIRECTED
)
vc = igraph.VertexClustering(
g, membership=self._adata.obs[self._groups_key].cat.codes.values
)
Expand Down Expand Up @@ -212,7 +214,9 @@ def _compute_connectivities_v1_0(self):

ones = self._neighbors.connectivities.copy()
ones.data = np.ones(len(ones.data))
g = _utils.get_igraph_from_adjacency(ones)
g: igraph.Graph = igraph.Graph.Weighted_Adjacency(
ones, mode=igraph.ADJ_DIRECTED
)
vc = igraph.VertexClustering(
g, membership=self._adata.obs[self._groups_key].cat.codes.values
)
Expand Down Expand Up @@ -289,9 +293,8 @@ def compute_transitions(self):
# raise ValueError(msg)
import igraph

g = _utils.get_igraph_from_adjacency(
self._adata.uns[vkey].astype("bool"),
directed=True,
g: igraph.Graph = igraph.Graph.Adjacency(
self._adata.uns[vkey].astype("bool"), mode=igraph.ADJ_DIRECTED
)
vc = igraph.VertexClustering(
g, membership=self._adata.obs[self._groups_key].cat.codes.values
Expand Down Expand Up @@ -324,19 +327,18 @@ def compute_transitions(self):
def compute_transitions_old(self):
import igraph

g = _utils.get_igraph_from_adjacency(
self._adata.uns["velocyto_transitions"],
directed=True,
g: igraph.Graph = igraph.Graph.Weighted_Adjacency(
self._adata.uns["velocyto_transitions"], mode=igraph.ADJ_DIRECTED
)
vc = igraph.VertexClustering(
g, membership=self._adata.obs[self._groups_key].cat.codes.values
)
# this stores all single-cell edges in the cluster graph
cg_full = vc.cluster_graph(combine_edges=False)
# this is the boolean version that simply counts edges in the clustered graph
g_bool = _utils.get_igraph_from_adjacency(
g_bool = igraph.Graph.Adjacency(
self._adata.uns["velocyto_transitions"].astype("bool"),
directed=True,
mode=igraph.ADJ_DIRECTED,
)
vc_bool = igraph.VertexClustering(
g_bool, membership=self._adata.obs[self._groups_key].cat.codes.values
Expand Down
Binary file modified tests/_images/heatmap_var_as_dict/expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/_images/paga/expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/_images/paga_continuous/expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/_images/paga_continuous_multiple/expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/_images/paga_continuous_obs/expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/_images/paga_pie/expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/test_clustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def test_leiden_random_state(
n_iterations=n_iterations,
**{rng_arg: seed},
)
for seed in (1, 1, 42)
for seed in (1, 1, 3)
)
with subtests.test("reproducible"):
pd.testing.assert_series_equal(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def test_modularity_adata(
assert 0 <= s <= 1
for (n0, s0), (n1, s1) in combinations(scores.items(), 2):
with subtests.test("equality", l=n0, r=n1):
assert pytest.approx(s0, rel=1e-6) == s1
assert s0 == s1
with subtests.test("update"):
assert adata.uns["leiden"]["modularity"] is scores["update"]

Expand Down
Loading