Graphs and clustering¶
find_neighbors builds both graphs Seurat builds — the directed KNN graph and
the shared-nearest-neighbour graph on top of it — and find_clusters runs
community detection over the SNN.
Four details here were wrong before the integration tutorial went looking, and
each mattered downstream: the KNN graph is stored directed rather than
symmetrized, the SNN keeps its diagonal and is computed in float64, run_umap
zeroes the diagonal when handed a graph, and singletons are folded into their
nearest community the way GroupSingletons does. They are noted in the
docstrings because each one changes cluster assignments, not just internals.
Neighbour graphs¶
find_neighbors
¶
find_neighbors(seurat, dims: Optional[Union[list[int], range]] = None, k_param: int = 20, assay: Optional[str] = None, reduction: str = 'pca', graph_name: Optional[str] = None, nn_name: Optional[str] = None, prune_snn: float = 1 / 15, seed: int = 42) -> None
Build KNN and SNN graphs from a low-dimensional embedding.
Mirrors R's FindNeighbors(pbmc, dims = 1:10). Stores Graph objects in seurat.graphs[graph_name + '_nn'] and seurat.graphs[graph_name + '_snn'].
Parameters:
-
dims(Optional[Union[list[int], range]], default:None) –which PCs to use (0-indexed; default all available)
-
k_param(int, default:20) –number of nearest neighbors
-
reduction(str, default:'pca') –which reduction to use ('pca' by default)
-
graph_name(Optional[str], default:None) –prefix for graph names (defaults to active assay name)
-
prune_snn(float, default:1 / 15) –edges with Jaccard index below this are pruned (Seurat default 1/15)
Source code in truecell/neighbors.py
find_multi_modal_neighbors
¶
find_multi_modal_neighbors(seurat, reduction_list: Sequence[str] = ('pca', 'apca'), dims_list: Optional[Sequence[Sequence[int]]] = None, k_nn: int = 20, l2_norm: bool = True, knn_graph_name: str = 'wknn', snn_graph_name: str = 'wsnn', knn_range: int = 200, prune_snn: float = 1 / 15, sd_scale: float = 1.0, cross_constant: Optional[float] = None, smooth: bool = False, seed: int = 42) -> None
Compute weighted-nearest-neighbour graphs across modalities.
Mirrors R's FindMultiModalNeighbors(obj, reduction.list =
list("pca","apca"), dims.list = list(1:30, 1:18)). Stores:
seurat.graphs[knn_graph_name]— joint KNN graphseurat.graphs[snn_graph_name]— joint SNN graph (feed tofind_clusters(graph_name=...)orrun_umap(graph=...))seurat.meta_data["<modality>.weight"]— per-cell modality weights
Parameters:
-
reduction_list(Sequence[str], default:('pca', 'apca')) –reductions to combine, one per modality
-
dims_list(Optional[Sequence[Sequence[int]]], default:None) –dims (0-indexed) to use from each reduction; default all
-
k_nn(int, default:20) –number of joint neighbours to keep per cell
-
l2_norm(bool, default:True) –L2-normalise each embedding first (R's
l2.norm) -
knn_range(int, default:200) –candidate neighbours each modality nominates before the joint re-ranking (R's
knn.range) -
prune_snn(float, default:1 / 15) –Jaccard prune threshold for the joint SNN graph
-
sd_scale(float, default:1.0) –scaling on the per-cell kernel bandwidth (R's
sd.scale) -
cross_constant(Optional[float], default:None) –denominator guard in the modality score; default 1e-4
-
smooth(bool, default:False) –average each cell's modality score over its neighbours
Notes
The weights are stored under each reduction's assay_used name, so an RNA
"pca" and an ADT "apca" produce RNA.weight and ADT.weight —
the same columns Seurat writes, and readable by the plotting functions as if
they were features.
Source code in truecell/multimodal.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
Community detection¶
find_clusters
¶
find_clusters(seurat, resolution: Union[float, Sequence[float]] = 0.5, algorithm: int = 1, graph_name: Optional[str] = None, random_seed: int = 0, n_iterations: int = -1, group_singletons: bool = True, cluster_name: Optional[Union[str, Sequence[str]]] = None) -> None
Apply Louvain or Leiden clustering on the SNN graph.
Mirrors R's FindClusters(pbmc, resolution = 0.5), including its
vector form FindClusters(pbmc, resolution = c(0.4, 0.8, 1.2)).
Each resolution is written to its own metadata column, named
{graph_name}_res.{resolution} as Seurat names it. seurat_clusters and
the active identities are set from the last resolution in the sequence —
last as given, not largest, which is what Seurat does.
Parameters:
-
resolution(Union[float, Sequence[float]], default:0.5) –higher values give more / finer clusters. A sequence runs each in turn, as R's
resolution = c(...)does. -
algorithm(int, default:1) –1 = Louvain, 2 = Louvain (multilevel, igraph's default), 4 = Leiden. (3 = SLM is not implemented.)
-
graph_name(Optional[str], default:None) –SNN graph to use (defaults to '{assay}_snn')
-
random_seed(int, default:0) –for reproducibility
-
n_iterations(int, default:-1) –Leiden iterations (-1 = until stable)
-
group_singletons(bool, default:True) –absorb size-1 clusters into their best-connected neighbour, as Seurat's
GroupSingletonsdoes. WithFalsethey are all pooled into one"singleton"cluster instead — again matching Seurat. -
cluster_name(Optional[Union[str, Sequence[str]]], default:None) –override the generated column name(s); one name, or one per resolution. Seurat's
cluster.name.seurat_clustersis still written either way.
Notes
Seurat runs its own modularity optimiser with n.start = 10 restarts and
keeps the highest-modularity partition; this runs a single pass of igraph's
multilevel Louvain. On the same graph that makes truecell's partition land in
a slightly shallower optimum — measurably so, but not necessarily a worse
one. See the clustering section of tutorials/integration_vignette.md.
Each resolution is clustered from the same seed rather than from a running
RNG stream, so a partition does not depend on which resolutions preceded it
or on the order they were given in. Verified against Seurat 5.5.1, where
resolution 0.8 gives the same partition alone, in c(0.4, 0.8, 1.2), and
in c(1.2, 0.8, 0.4).
Source code in truecell/clustering.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |