Source code for ts2net.dynamic.communities

"""
Temporal community tracking across graph windows.
"""

from __future__ import annotations

import networkx as nx
import numpy as np


[docs] def community_labels(G: nx.Graph) -> dict[int, int]: """ Assign community ids using connected components (fallback for sparse graphs). For denser graphs with networkx >= 2.8, uses greedy modularity communities when available. """ if G.number_of_nodes() == 0: return {} try: from networkx.algorithms.community import greedy_modularity_communities if G.number_of_edges() > 0: comms = list(greedy_modularity_communities(G)) labels: dict[int, int] = {} for cid, comm in enumerate(comms): for node in comm: labels[node] = cid return labels except Exception: pass labels = {} for cid, component in enumerate(nx.connected_components(G)): for node in component: labels[node] = cid return labels
[docs] def track_communities( graphs: list[nx.Graph], ) -> dict[str, object]: """ Track community structure across graph windows. Returns ------- dict ``labels_per_window`` (list of node->community dicts), ``n_communities`` (array), ``stability`` (mean Jaccard overlap of consecutive community partitions). """ if not graphs: return { "labels_per_window": [], "n_communities": np.array([], dtype=np.int64), "stability": np.array([], dtype=np.float64), } labels_per_window = [community_labels(G) for G in graphs] n_communities = np.array( [len(set(lbls.values())) for lbls in labels_per_window], dtype=np.int64, ) stability: list[float] = [] for i in range(len(labels_per_window) - 1): a = labels_per_window[i] b = labels_per_window[i + 1] common = set(a) & set(b) if not common: stability.append(0.0) continue same = sum(1 for n in common if a[n] == b[n]) stability.append(same / len(common)) return { "labels_per_window": labels_per_window, "n_communities": n_communities, "stability": np.asarray(stability, dtype=np.float64), }