Note
Click here to download the full example code
Every detector on one graph¶
Run all ten detector entry points at their default parameters on Zachary's karate club, draw the ten partitions on one shared layout, and score each against the club's recorded split.
Run the ten detectors¶
Every detector takes the graph as its only required argument and returns a
dict mapping each node to a community id. Two are the library's own methods,
pymocd.rimpso and pymocd.hpmocd; the
other eight entry points implement the seven re-implemented baselines
(Shi-MOCD has two, mocd_q and mocd_d):
pymocd.cdrme, pymocd.mmcomo,
pymocd.ccm, pymocd.krm,
pymocd.gdpso, pymocd.mocd_q,
pymocd.mocd_d and pymocd.moga_net.
The algorithms overview describes each one.
Each detector runs with its own defaults (population size, number of generations and so on differ between them), so this page is a tour of the API, not a benchmark.
import matplotlib.pyplot as plt
import networkx as nx
import pymocd
G = nx.karate_club_graph()
truth = {v: int(G.nodes[v]["club"] != "Mr. Hi") for v in G}
detectors = {
"rimpso": pymocd.rimpso,
"hpmocd": pymocd.hpmocd,
"cdrme": pymocd.cdrme,
"mmcomo": pymocd.mmcomo,
"ccm": pymocd.ccm,
"krm": pymocd.krm,
"gdpso": pymocd.gdpso,
"mocd_q": pymocd.mocd_q,
"mocd_d": pymocd.mocd_d,
"moga_net": pymocd.moga_net,
}
partitions = {name: detect(G) for name, detect in detectors.items()}
Draw the partitions¶
All panels share one layout. Community ids are arbitrary, so each partition
is renumbered in order of its lowest node id before coloring: the community
that holds node 0 is always blue, the next one found is orange, and so on.
Marker shape shows the recorded split (the club node attribute).
def by_lowest_node(partition):
"""Renumber communities in the order their lowest node id appears."""
order = {}
for v in sorted(partition):
order.setdefault(partition[v], len(order))
return {v: order[c] for v, c in partition.items()}
pos = nx.spring_layout(G, seed=7)
cmap = plt.colormaps["tab10"]
shapes = {0: "o", 1: "s"}
fig, axes = plt.subplots(2, 5, figsize=(13, 6))
for ax, (name, partition) in zip(axes.flat, partitions.items()):
colors = by_lowest_node(partition)
k = len(set(partition.values()))
ami = pymocd.ami(partition, truth)
nx.draw_networkx_edges(G, pos, ax=ax, width=0.7, alpha=0.35)
for side, marker in shapes.items():
nodes = [v for v in G if truth[v] == side]
nx.draw_networkx_nodes(
G,
pos,
nodelist=nodes,
node_color=[cmap(colors[v]) for v in nodes],
node_shape=marker,
node_size=90,
edgecolors="white",
linewidths=0.6,
ax=ax,
)
ax.set_title(f"{name}\n{k} communities, AMI {ami:.2f}", fontsize=11)
ax.set_axis_off()
handles = [
plt.Line2D([], [], marker="o", linestyle="", color="0.35", label="Mr. Hi's faction"),
plt.Line2D([], [], marker="s", linestyle="", color="0.35", label="Officer's faction"),
]
fig.legend(handles=handles, loc="lower center", ncol=2, frameon=False, fontsize=10)
fig.suptitle("Zachary's karate club: ten detectors at their default parameters", fontsize=13)
fig.tight_layout(rect=(0, 0.05, 1, 0.97))
plt.show()

Compare against the club split¶
pymocd.gt_metrics returns NMI, AMI, ARI and pairwise
F1 against the recorded split in one call.
print(f"{'detector':<10} {'k':>3} {'NMI':>6} {'AMI':>6} {'ARI':>6} {'F1':>6}")
for name, partition in partitions.items():
k = len(set(partition.values()))
nmi, ami, ari, f1 = pymocd.gt_metrics(partition, truth)
print(f"{name:<10} {k:>3} {nmi:>6.3f} {ami:>6.3f} {ari:>6.3f} {f1:>6.3f}")
Out:
detector k NMI AMI ARI F1
rimpso 3 0.568 0.551 0.591 0.764
hpmocd 4 0.490 0.464 0.392 0.598
cdrme 2 0.837 0.833 0.882 0.939
mmcomo 4 0.588 0.567 0.465 0.646
ccm 4 0.588 0.567 0.465 0.646
krm 4 0.593 0.571 0.483 0.664
gdpso 3 0.568 0.551 0.591 0.764
mocd_q 4 0.588 0.567 0.465 0.646
mocd_d 3 0.568 0.551 0.591 0.764
moga_net 4 0.498 0.470 0.429 0.634
Reproducibility¶
rimpso (its docstring), cdrme and gdpso (their module READMEs) are
deterministic: the same graph, with its nodes and edges listed in the same
order, and the same parameters give the same partition (see
Input order). The
module READMEs of the other seven describe them as nondeterministic, so their
panels and rows can change from one build of this page to the next. A single
run on a 34-node graph says little about how detectors compare; see
Choosing a detector and the
benchmark examples for that.
Total running time of the script: ( 0 minutes 1.444 seconds)
Download Python source code: plot_detector_tour.py