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.