-
Notifications
You must be signed in to change notification settings - Fork 266
Expand file tree
/
Copy pathclique_percolation.py
More file actions
126 lines (109 loc) · 3.67 KB
/
clique_percolation.py
File metadata and controls
126 lines (109 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
"""
.. _tutorials-clique-percolation:
==========================
Clique Percolation Method
==========================
This example shows how to detect **overlapping communities** using the
Clique Percolation Method (CPM). Unlike partitioning algorithms, CPM allows
a node to belong to more than one community simultaneously.
"""
import itertools
from collections import Counter
import igraph as ig
import matplotlib.pyplot as plt
# %%
# We construct a graph with three dense cliques that share individual nodes,
# creating natural *overlapping* community boundaries:
g = ig.Graph(9)
g.add_edges(list(itertools.combinations([0, 1, 2, 3], 2))) # clique A
g.add_edges(list(itertools.combinations([3, 4, 5, 6], 2))) # clique B, shares node 3 with A
g.add_edges(list(itertools.combinations([6, 7, 8], 2))) # clique C, shares node 6 with B
# %%
# The CPM algorithm works in three steps:
#
# 1. Find all k-cliques (complete subgraphs of exactly k nodes).
# 2. Build a clique graph : two k-cliques are adjacent when they share k-1 nodes.
# 3. Each connected component of the clique graph is a community — the union
# of all its k-cliques. A node shared between cliques in *different*
# components belongs to multiple communities simultaneously.
k = 3
cliques = [set(c) for c in g.cliques(k, k)]
clique_graph = ig.Graph(len(cliques))
clique_graph.add_edges([
(i, j)
for i, j in itertools.combinations(range(len(cliques)), 2)
if len(cliques[i] & cliques[j]) >= k - 1
])
communities = []
for component in clique_graph.connected_components():
members = set()
for idx in component:
members |= cliques[idx]
communities.append(sorted(members))
# %%
# Nodes that appear in more than one community are *overlapping nodes*:
overlap = [
v for v, count in Counter(v for comm in communities for v in comm).items()
if count > 1
]
print(f"Communities (k={k}): {communities}")
print(f"Overlapping nodes: {overlap}")
# %%
# We visualize the result using :class:`igraph.VertexCover`, which draws a
# shaded hull around each community and handles overlapping nodes naturally:
cover = ig.VertexCover(g, communities)
palette = ig.RainbowPalette(n=len(communities))
fig, ax = plt.subplots(figsize=(6, 5))
ig.plot(
cover,
mark_groups=True,
palette=palette,
vertex_size=25,
vertex_label=list(range(g.vcount())),
vertex_label_size=10,
edge_width=1.5,
target=ax,
)
ax.set_title(
f"Clique Percolation Method (k={k})\n"
f"{len(communities)} communities — overlapping nodes: {overlap}"
)
plt.show()
# %%
# Advanced: effect of k
# ----------------------
# Raising k to 4 requires larger cliques. The 3-clique {6, 7, 8} no longer
# qualifies, so community C disappears and node 6 is no longer in any community:
k = 4
cliques_4 = [set(c) for c in g.cliques(k, k)]
clique_graph_4 = ig.Graph(len(cliques_4))
clique_graph_4.add_edges([
(i, j)
for i, j in itertools.combinations(range(len(cliques_4)), 2)
if len(cliques_4[i] & cliques_4[j]) >= k - 1
])
communities_4 = []
for component in clique_graph_4.connected_components():
members = set()
for idx in component:
members |= cliques_4[idx]
communities_4.append(sorted(members))
print(f"Communities (k=4): {communities_4}")
cover_4 = ig.VertexCover(g, communities_4)
palette_4 = ig.RainbowPalette(n=max(len(communities_4), 1))
fig, ax = plt.subplots(figsize=(6, 5))
ig.plot(
cover_4,
mark_groups=True,
palette=palette_4,
vertex_size=25,
vertex_label=list(range(g.vcount())),
vertex_label_size=10,
edge_width=1.5,
target=ax,
)
ax.set_title(
f"Clique Percolation Method (k=4)\n"
f"{len(communities_4)} communities — nodes 6, 7, 8 no longer qualify"
)
plt.show()