Skip to content

Commit 85f84f2

Browse files
committed
Add spectral.py and spectral_notebook
1 parent 5ab0973 commit 85f84f2

2 files changed

Lines changed: 1802 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# bioneuralnet/clustering/spectral.py
2+
3+
from __future__ import annotations
4+
import numpy as np
5+
import networkx as nx
6+
from sklearn.cluster import SpectralClustering
7+
from bioneuralnet.utils.logger import get_logger
8+
import pandas as pd
9+
from typing import Sequence, Tuple
10+
11+
12+
def spectral_cluster_graph(
13+
G: nx.Graph,
14+
n_clusters: int,
15+
use_edge_weights: bool = True,
16+
random_state: int | None = 0,
17+
) -> Tuple[np.ndarray, Sequence]:
18+
"""
19+
Spectral clustering on a NetworkX graph using its adjacency
20+
as a precomputed affinity matrix.
21+
22+
Parameters
23+
G : networkx.Graph
24+
Input graph. Node labels can be anything hashable.
25+
n_clusters : int
26+
Number of clusters to find.
27+
use_edge_weights : bool, default=True
28+
If True, use the 'weight' attribute on edges (defaulting to 1.0
29+
when missing). If False, treat the graph as unweighted.
30+
random_state : int or None, default=0
31+
Random seed passed to scikit-learn's SpectralClustering.
32+
33+
Returns
34+
labels : np.ndarray, shape (n_nodes,)
35+
Cluster label for each node, in the order of `nodes_order`.
36+
nodes_order : list
37+
List of nodes corresponding to `labels`.
38+
"""
39+
logger = get_logger(__name__)
40+
41+
# fixed node order
42+
nodes_order = list(G.nodes())
43+
n = len(nodes_order)
44+
node_index = {node: i for i, node in enumerate(nodes_order)}
45+
46+
# build dense affinity matrix
47+
A = np.zeros((n, n), dtype=float)
48+
49+
if use_edge_weights:
50+
for u, v, data in G.edges(data=True):
51+
i = node_index[u]
52+
j = node_index[v]
53+
w = float(data.get("weight", 1.0))
54+
A[i, j] = w
55+
A[j, i] = w # assume undirected
56+
else:
57+
for u, v in G.edges():
58+
i = node_index[u]
59+
j = node_index[v]
60+
A[i, j] = 1.0
61+
A[j, i] = 1.0
62+
if not np.any(A):
63+
raise ValueError("Graph has no edges; spectral clustering is undefined.")
64+
65+
logger.info(
66+
f"Running SpectralClustering on graph with {n} nodes, "
67+
f"{G.number_of_edges()} edges, n_clusters={n_clusters}, "
68+
f"use_edge_weights={use_edge_weights}."
69+
)
70+
71+
spec = SpectralClustering(
72+
n_clusters=n_clusters,
73+
affinity="precomputed",
74+
assign_labels="kmeans",
75+
random_state=random_state,
76+
n_init=10,
77+
)
78+
labels = spec.fit_predict(A)
79+
80+
return labels, nodes_order

0 commit comments

Comments
 (0)