From 9ce8135f845fad7dfb6b705347a1336de4394f3e Mon Sep 17 00:00:00 2001 From: Jacopo888 Date: Sun, 22 Mar 2026 19:42:27 +0100 Subject: [PATCH] Add gallery example: Clique Percolation Method for overlapping communities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new example to the sphinx-gallery demonstrating the Clique Percolation Method (CPM) for overlapping community detection. The example shows both k=3 and k=4, visualized with VertexCover. Closes #819 (partially — clique percolation item) --- .../clique_percolation.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 doc/examples_sphinx-gallery/clique_percolation.py diff --git a/doc/examples_sphinx-gallery/clique_percolation.py b/doc/examples_sphinx-gallery/clique_percolation.py new file mode 100644 index 000000000..0a6cfcb94 --- /dev/null +++ b/doc/examples_sphinx-gallery/clique_percolation.py @@ -0,0 +1,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()