Compare Infomap, Louvain, and Leiden with igraph

This tutorial shows how to use Infomap in a python-igraph workflow. It keeps the result in igraph’s native VertexClustering shape so it fits 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.12.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.41880341880341876
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 0 0
2 2 Mr. Hi 0 0 0
3 3 Mr. Hi 0 0 0
4 4 Mr. Hi 1 1 1
5 5 Mr. Hi 1 1 1
6 6 Mr. Hi 1 1 1
7 7 Mr. Hi 0 0 0
8 8 Mr. Hi 2 2 2
9 9 Officer 0 0 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.463752 0.489967
2 leiden 0.566666 0.587850

Annotate vertices for downstream igraph use

Use module_attribute and flow_attribute when you want Infomap results stored directly 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

If you use Infomap in published work, cite the Infomap software and the map equation literature. See the repository and Infomap user guide for the current recommended references.