Compare Infomap, Louvain, and Leiden with igraph¶
Run Infomap inside a python-igraph workflow. The result stays in igraph’s native VertexClustering shape, so it sits next to community_multilevel() for Louvain and community_leiden() for Leiden.
Infomap optimizes the map equation, which describes flow on the network. Louvain and Leiden optimize modularity-style objectives. The labels produced by all three methods are local community IDs, so compare partitions by membership, sizes, and downstream behavior rather than by the numeric label values themselves.
import infomap
import igraph as ig
import networkx as nx
import pandas as pd
print("infomap:", infomap.__version__)
print("igraph:", ig.__version__)
print("networkx:", nx.__version__)
print("pandas:", pd.__version__)
infomap: 2.13.0
igraph: 1.0.0
networkx: 3.6.1
pandas: 2.3.3
Build a small igraph graph¶
NetworkX provides the karate club graph as a convenient built-in dataset. The graph is converted to igraph for this tutorial so all community methods use igraph-native graph data.
nx_graph = nx.karate_club_graph()
graph = ig.Graph(edges=list(nx_graph.edges()), directed=False)
graph.vs["name"] = [str(vertex.index) for vertex in graph.vs]
graph.vs["club"] = [nx_graph.nodes[vertex.index]["club"] for vertex in graph.vs]
graph.es["weight"] = [1.0] * graph.ecount()
print(graph.summary())
IGRAPH UNW- 34 78 --
+ attr: club (v), name (v), weight (e)
Run the community methods¶
infomap.find_igraph_communities() returns an igraph.VertexClustering and adds a codelength attribute. Louvain is available as community_multilevel(). Leiden is used through community_leiden() when the installed igraph build supports it.
infomap_clusters = infomap.find_igraph_communities(
graph,
edge_weights="weight",
trials=20,
seed=123,
)
louvain_clusters = graph.community_multilevel(weights="weight")
leiden_clusters = None
leiden_note = None
if not hasattr(graph, "community_leiden"):
leiden_note = "igraph Leiden is not available in this igraph installation."
else:
try:
leiden_clusters = graph.community_leiden(
weights="weight",
objective_function="modularity",
)
except Exception as exc:
leiden_note = f"igraph Leiden skipped: {exc}"
print(
"Infomap communities:",
len(infomap_clusters),
"codelength:",
infomap_clusters.codelength,
)
print(
"Louvain communities:",
len(louvain_clusters),
"modularity:",
louvain_clusters.modularity,
)
if leiden_clusters is None:
print(leiden_note)
else:
print(
"Leiden communities:",
len(leiden_clusters),
"modularity:",
leiden_clusters.modularity,
)
Infomap communities: 3 codelength: 4.3117926458018285
Louvain communities: 4 modularity: 0.39340894148586447
Leiden communities: 4 modularity: 0.41978961209730437
Compare assignments¶
The DataFrame uses vertex names as user-facing node labels. Community IDs are generated independently by each algorithm.
df = pd.DataFrame(
{
"node": graph.vs["name"],
"club": graph.vs["club"],
"infomap": infomap_clusters.membership,
"louvain": louvain_clusters.membership,
}
)
if leiden_clusters is not None:
df["leiden"] = leiden_clusters.membership
df.head(10)
| node | club | infomap | louvain | leiden | |
|---|---|---|---|---|---|
| 0 | 0 | Mr. Hi | 0 | 0 | 0 |
| 1 | 1 | Mr. Hi | 0 | 1 | 0 |
| 2 | 2 | Mr. Hi | 0 | 1 | 0 |
| 3 | 3 | Mr. Hi | 0 | 1 | 0 |
| 4 | 4 | Mr. Hi | 1 | 0 | 1 |
| 5 | 5 | Mr. Hi | 1 | 0 | 1 |
| 6 | 6 | Mr. Hi | 1 | 0 | 1 |
| 7 | 7 | Mr. Hi | 0 | 1 | 0 |
| 8 | 8 | Mr. Hi | 2 | 2 | 2 |
| 9 | 9 | Officer | 0 | 2 | 2 |
df.drop(columns="node").nunique().rename("communities").to_frame()
| communities | |
|---|---|
| club | 2 |
| infomap | 3 |
| louvain | 4 |
| leiden | 4 |
Simple similarity metrics¶
AMI and NMI help summarize how similar each assignment is to the known karate-club split. They are label-comparison metrics and do not depend on the actual numeric community IDs.
from sklearn.metrics import adjusted_mutual_info_score, normalized_mutual_info_score
comparisons = []
for method in ["infomap", "louvain", "leiden"]:
if method not in df:
continue
comparisons.append(
{
"method": method,
"AMI vs truth": adjusted_mutual_info_score(df["club"], df[method]),
"NMI vs truth": normalized_mutual_info_score(df["club"], df[method]),
}
)
metrics = pd.DataFrame(comparisons)
metrics
| method | AMI vs truth | NMI vs truth | |
|---|---|---|---|
| 0 | infomap | 0.551082 | 0.568380 |
| 1 | louvain | 0.558176 | 0.579157 |
| 2 | leiden | 0.566666 | 0.587850 |
Annotate vertices for downstream igraph use¶
Use module_attribute and flow_attribute when you want Infomap results stored on the igraph vertices for plotting, filtering, or export.
annotated = graph.copy()
infomap.find_igraph_communities(
annotated,
edge_weights="weight",
module_attribute="infomap",
flow_attribute="infomap_flow",
trials=20,
seed=123,
)
pd.DataFrame(
{
"node": annotated.vs["name"],
"infomap": annotated.vs["infomap"],
"infomap_flow": annotated.vs["infomap_flow"],
}
).head()
| node | infomap | infomap_flow | |
|---|---|---|---|
| 0 | 0 | 0 | 0.102564 |
| 1 | 1 | 0 | 0.057692 |
| 2 | 2 | 0 | 0.064103 |
| 3 | 3 | 0 | 0.038462 |
| 4 | 4 | 1 | 0.019231 |
Citation and further reading¶
If you use Infomap in published work, mapequation.org recommends citing two things: the map equation paper (Rosvall and Bergstrom, 2008, https://doi.org/10.1073/pnas.0706851105) for the method, and the MapEquation software package (Edler, Holmgren, and Rosvall) for the implementation and version. See How to cite for BibTeX.
See also the quickstart for a first run, the options guide for the parameters used here, and compare-infomap-louvain-networkx for the same comparison in a NetworkX workflow.