MCPcopy Create free account
hub / github.com/Graphify-Labs/graphify / cluster

Function cluster

graphify/cluster.py:134–236  ·  view source on GitHub ↗

Run Leiden community detection. Returns {community_id: [node_ids]}. Community IDs are stable across runs: 0 = largest community after splitting. Oversized communities (> 25% of graph nodes, min 10) are split by running a second Leiden pass on the subgraph. Accepts directed or undir

(
    G: nx.Graph,
    resolution: float = 1.0,
    exclude_hubs_percentile: float | None = None,
)

Source from the content-addressed store, hash-verified

132
133
134def cluster(
135 G: nx.Graph,
136 resolution: float = 1.0,
137 exclude_hubs_percentile: float | None = None,
138) -> dict[int, list[str]]:
139 """Run Leiden community detection. Returns {community_id: [node_ids]}.
140
141 Community IDs are stable across runs: 0 = largest community after splitting.
142 Oversized communities (> 25% of graph nodes, min 10) are split by running
143 a second Leiden pass on the subgraph.
144
145 Accepts directed or undirected graphs. DiGraphs are converted to undirected
146 internally since Louvain/Leiden require undirected input.
147
148 resolution: passed to Leiden/Louvain. >1.0 = more smaller communities,
149 <1.0 = fewer larger communities. Default 1.0.
150 exclude_hubs_percentile: if set (0-100), nodes whose degree exceeds this
151 percentile are excluded from partitioning and reattached to their
152 majority-vote neighbour community afterwards. Useful for staging/utility
153 super-hubs that inflate god-node rankings (#919).
154 """
155 if G.number_of_nodes() == 0:
156 return {}
157 if G.is_directed():
158 G = G.to_undirected()
159 if G.number_of_edges() == 0:
160 return {i: [n] for i, n in enumerate(sorted(G.nodes))}
161
162 # Compute hub exclusion set before removing anything so degree is based on full graph
163 hub_nodes: set[str] = set()
164 if exclude_hubs_percentile is not None:
165 degrees = sorted(d for _, d in G.degree())
166 if degrees:
167 idx = max(0, int(len(degrees) * exclude_hubs_percentile / 100) - 1)
168 threshold = degrees[idx]
169 hub_nodes = {n for n, d in G.degree() if d > threshold}
170
171 # Leiden warns and drops isolates - handle them separately
172 # Also exclude hub nodes from partitioning so they don't pull unrelated
173 # subsystems into the same community
174 excluded = hub_nodes
175 isolates = [n for n in G.nodes() if G.degree(n) == 0 and n not in excluded]
176 connected_nodes = [n for n in G.nodes() if G.degree(n) > 0 and n not in excluded]
177 connected = G.subgraph(connected_nodes)
178
179 raw: dict[int, list[str]] = {}
180 if connected.number_of_nodes() > 0:
181 partition = _partition(connected, resolution=resolution)
182 for node, cid in partition.items():
183 raw.setdefault(cid, []).append(node)
184
185 # Each isolate becomes its own single-node community
186 next_cid = max(raw.keys(), default=-1) + 1
187 for node in isolates:
188 raw[next_cid] = [node]
189 next_cid += 1
190
191 # Reattach excluded hubs by majority-vote neighbour community

Calls 5

_partitionFunction · 0.85
_split_communityFunction · 0.70
cohesion_scoreFunction · 0.70
itemsMethod · 0.45
getMethod · 0.45