Building a network¶
How-to
At a glance
Hand your network to infomap.run() however it already lives in memory:
NetworkX, igraph, a SciPy sparse matrix, an edge index, or raw link tuples. For
non-default loading, build it explicitly with a Network.
Run from the format you already have¶
Your network might be a NetworkX graph from a scraping step, a SciPy sparse
adjacency matrix from a machine-learning pipeline, or a DataFrame from a SQL
query. infomap.run() dispatches on the type of its argument, builds the
same internal flow network whatever the source, and returns an immutable
Result. The partition from a NetworkX graph is therefore
numerically identical to the partition from a SciPy matrix built on the same
edges.
When you need to control how a graph is read, a different edge-weight
attribute or explicit directedness, build the network first with a
Network and run that.
Input routes at a glance¶
Your data |
One call |
Explicit builder |
|---|---|---|
NetworkX |
|
|
igraph |
|
|
SciPy sparse adjacency |
|
|
|
|
|
link rows (NumPy array / tuples) |
|
|
pandas |
|
|
network file |
|
|
Keyword arguments to infomap.run() configure the engine (seed,
num_trials, markov_time, …). Arguments that govern how the input is
read belong to the Network.from_* constructors; passing one to
infomap.run() raises with a pointer to the right constructor rather than
silently building a different graph. For a NetworkX or igraph graph the
directedness is read from the graph object itself, so no directed argument
is needed. For a SciPy matrix or edge index, pass directed= to the matching
from_* constructor. Note that the two routes default differently: a SciPy
matrix is read as undirected unless you say otherwise, while a (2, E) edge
index follows the PyG convention and is read as directed.
Two one-shot helpers return native types instead of a
Result: infomap.find_communities() (a NetworkX-style
list[set]) and infomap.find_igraph_communities() (an
igraph.VertexClustering).
NetworkX¶
import networkx as nx
import infomap
# A small two-community graph: two triangles joined by a weak bridge
g = nx.Graph()
g.add_weighted_edges_from([
(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0), # triangle A
(3, 4, 1.0), (4, 5, 1.0), (5, 3, 1.0), # triangle B
(2, 3, 0.5), # weak bridge
])
result = infomap.run(g, two_level=True, seed=123, num_trials=5, silent=True)
print(f"Modules: {result.num_top_modules}")
print(f"Codelength: {result.codelength:.4f} bits/step")
print(f"Assignment: {result.modules()}") # {node_id: module_id}
Modules: 2
Codelength: 2.0533 bits/step
Assignment: {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2}
Non-integer node labels (strings, compound keys) work out of the box. The
loader maps them to internal integers. For string labels, the cleanest way to
read assignments back in your own labels is the "name" column of the result
DataFrame; for other label types (tuples, frozensets), use
Network and its label mapping as shown below:
g_str = nx.Graph([("alice", "bob"), ("bob", "carol"), ("dave", "eve")])
result = infomap.run(g_str, two_level=True, seed=123, silent=True)
print(result.to_dataframe(["name", "module_id"]).to_string(index=False))
name module_id
bob 1
alice 1
carol 1
dave 2
eve 2
When you need the integer-to-label mapping itself, build a
Network: its node_id_to_label records
it.
from infomap import Network, run
net = Network.from_networkx(g_str)
result = run(net, seed=123, silent=True)
named = {net.node_id_to_label[nid]: mid for nid, mid in result.modules().items()}
print("Named assignments:", named)
Named assignments: {'bob': 1, 'alice': 1, 'carol': 1, 'dave': 2, 'eve': 2}
Directed graphs: pass a DiGraph and the adapter selects the directed-flow
model with teleportation automatically. Weighted edges are read from the
"weight" attribute by default; for a different attribute, build with
Network.from_networkx(g, weight="capacity") and run the network.
One-shot in NetworkX style: infomap.find_communities() returns a
list[set] in your original labels, for quick exploration:
communities = infomap.find_communities(g, two_level=True, seed=123, num_trials=5)
print("Communities:", communities)
Communities: [{0, 1, 2}, {3, 4, 5}]
python-igraph¶
import igraph as ig
edges = [(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)]
g_ig = ig.Graph(n=6, edges=edges)
result = infomap.run(g_ig, two_level=True, seed=123, num_trials=5, silent=True)
print(f"igraph route: {result.num_top_modules} modules, {result.codelength:.4f} bits/step")
igraph route: 2 modules, 2.3207 bits/step
For a weighted igraph graph, name the edge-weight attribute through the builder:
g_ig_w = ig.Graph(n=6, edges=edges)
g_ig_w.es["weight"] = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5]
net = Network.from_igraph(g_ig_w, edge_weights="weight")
result = run(net, two_level=True, seed=123, num_trials=5, silent=True)
print(f"igraph (weighted): {result.num_top_modules} modules, {result.codelength:.4f} bits/step")
igraph (weighted): 2 modules, 2.0533 bits/step
infomap.find_igraph_communities() is the one-shot variant; it returns an
igraph.VertexClustering with a .codelength attribute, matching igraph’s
own community functions.
igraph already has Graph.community_infomap()
python-igraph ships a bundled Infomap under Graph.community_infomap(). The
infomap package is useful when you want the current Infomap release,
Infomap-specific options (multilayer, state networks, convergence control), or a
VertexClustering from it.
SciPy sparse adjacency matrix¶
Graph ML pipelines and spectral methods often produce sparse adjacency matrices
directly. infomap.run() accepts CSR, CSC, COO, and the other SciPy sparse
formats and treats the matrix as undirected:
import scipy.sparse as sp
# The same two-triangle adjacency as a symmetric COO matrix
rows = [0, 1, 1, 2, 2, 0, 3, 4, 4, 5, 5, 3, 2, 3]
cols = [1, 0, 2, 1, 0, 2, 4, 3, 5, 4, 3, 5, 3, 2]
data = [1.0] * 12 + [0.5, 0.5]
A = sp.coo_matrix((data, (rows, cols)), shape=(6, 6))
result = infomap.run(A, two_level=True, seed=123, num_trials=5, silent=True)
print(f"SciPy route: {result.num_top_modules} modules, {result.codelength:.4f} bits/step")
SciPy route: 2 modules, 2.0533 bits/step
How the matrix is read is set on the constructor: directed=True reads
A[i, j] as an edge from row i to column j, and weighted=False
ignores the stored values. Here with the defaults spelled out:
net = Network.from_scipy_sparse_matrix(A, directed=False, weighted=True)
result = run(net, two_level=True, seed=123, num_trials=5, silent=True)
print(f"via Network: {result.num_top_modules} modules")
via Network: 2 modules
Pass node_ids= to give the matrix rows external ids; the mapping is stored
on node_id_to_label, the same pattern as
from_networkx.
Edge lists: pandas, NumPy, tuples¶
A weighted edge list, rows of (source, target, weight), runs directly.
Pandas DataFrames convert in one call with to_numpy():
import pandas as pd
edges_df = pd.DataFrame({
"source": [0, 1, 2, 3, 4, 5, 2],
"target": [1, 2, 0, 4, 5, 3, 3],
"weight": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5],
})
result = infomap.run(
edges_df[["source", "target", "weight"]].to_numpy(),
two_level=True, seed=123, num_trials=5, silent=True,
)
print(f"pandas route: {result.num_top_modules} modules, {result.codelength:.4f} bits/step")
pandas route: 2 modules, 2.0533 bits/step
Source and target columns must hold integers. Convert string labels to integer
codes first (pandas.factorize) and keep the uniques array it returns as
your reverse mapping. This route registers no node names, so the result
DataFrame’s "name" column cannot recover the labels. An equally valid one-call form is an
iterable of tuples: infomap.run([(0, 1, 1.0), (1, 2, 1.0), ...]).
Edge index vs link rows
A two-row integer array is read as a (2, E) edge index (the PyG / GNN
convention) and, following that convention, defaults to a directed flow
model. Pass directed=False to Network.from_edge_index if your edge index
lists an undirected graph. Rows of (source, target, weight) have a
float column and are read as weighted links. For an explicit edge index with
its own options, use
Network.from_edge_index(edge_index, edge_weight=..., directed=...).
Network files¶
Infomap reads its native Pajek-style .net files (and plain link lists)
directly from a path. The example below is the two-triangles network that
ships with Infomap as examples/networks/twotriangles.net: triangles A–B–C
and D–E–F, joined by the single link C–D. Named vertices come back on the
result’s nodes.
import tempfile
from pathlib import Path
import infomap
content = """*Vertices
1 "A"
2 "B"
3 "C"
4 "D"
5 "E"
6 "F"
*Edges
1 2
1 3
2 3
3 4
4 5
4 6
5 6
"""
path = Path(tempfile.mkdtemp()) / "twotriangles.net"
path.write_text(content)
result = infomap.run(str(path), two_level=True, seed=123, num_trials=5, silent=True)
print(f"file route: {result.num_top_modules} modules, {result.codelength:.4f} bits/step")
for node in result.nodes():
print(f" {node.name}: module {node.module_id}")
file route: 2 modules, 2.3207 bits/step
A: module 1
B: module 1
C: module 1
D: module 2
E: module 2
F: module 2
Network.from_file(path) is the two-step form when you want to inspect or
extend the network before running. The
input-format reference on
mapequation.org documents every section a .net file can carry, including
*States, *Bipartite, and *Multilayer. The reference example for each
format also ships pre-loaded in infomap.datasets
(infomap.datasets.two_triangles() returns this very network, ready to run).
Building incrementally with Network¶
When you assemble a network programmatically, read a custom file format, or wire
up a handful of edges, use Network directly. Its add_*
verbs return the network, so calls chain, and infomap.run() takes the
built network:
from infomap import Network, run
net = Network()
net.add_node(0, name="Alice")
net.add_node(1, name="Bob")
net.add_node(2, name="Carol")
net.add_node(3, name="Dave")
net.add_node(4, name="Eve")
net.add_node(5, name="Frank")
net.add_link(0, 1); net.add_link(1, 2); net.add_link(2, 0) # triangle A
net.add_link(3, 4); net.add_link(4, 5); net.add_link(5, 3) # triangle B
net.add_link(2, 3, 0.5) # weak bridge
result = run(net, two_level=True, seed=123, num_trials=5, silent=True)
print(result.to_dataframe(["node_id", "name", "module_id", "flow"]).to_string(index=False))
node_id name module_id flow
0 Alice 1 0.153846
1 Bob 1 0.153846
2 Carol 1 0.192308
3 Dave 2 0.192308
4 Eve 2 0.153846
5 Frank 2 0.153846
add_node(node_id, name=None, teleportation_weight=None) registers a node and
an optional label; add_link creates any node it references
automatically. add_link(source, target, weight=1.0) adds one link, and
add_links(rows) adds many from an iterable or a NumPy (n, 2) / (n, 3)
array. The flow model (directed or undirected) is an engine option on the run,
not a property of individual links.
Same graph, same partition¶
Every route builds the same internal flow network. The codelength is identical across them:
runs = {
"NetworkX": infomap.run(g, two_level=True, seed=123, num_trials=5, silent=True),
"SciPy sparse": infomap.run(A, two_level=True, seed=123, num_trials=5, silent=True),
"edge list": infomap.run(
edges_df[["source", "target", "weight"]].to_numpy(),
two_level=True, seed=123, num_trials=5, silent=True,
),
}
for route, res in runs.items():
print(f" {res.codelength:.4f} {route}")
2.0533 NetworkX
2.0533 SciPy sparse
2.0533 edge list
To draw the partition or write it to disk, see Visualising and exporting.
Pitfalls¶
Read-time options belong on the constructor, not the run.
weight,edge_weights,directed, andweightedgovern how the input is read, so they live on theNetwork.from_*constructors. Passing them toinfomap.run()raises with a pointer to the right constructor rather than silently building a different graph.SciPy and edge-index routes default to different directedness. A SciPy sparse matrix is read as undirected unless you pass
directed=True, while a(2, E)integer edge index follows the PyG convention and is read as directed. NetworkX and igraph instead take directedness from the graph object. Spell the flag out when it matters.A two-row integer array is an edge index, not two links. Rows of
(source, target, weight)need a float weight column to be read as links; a bare 2×E integer array is interpreted as a(2, E)edge index (and so as directed). UseNetwork.from_edge_index(..., directed=False)if that is not what you meant.The raw edge-list route registers no node names. Running a NumPy array or tuples directly stores no labels, so the result’s
"name"column cannot recover string ids. Convert labels withpandas.factorizefirst and keep theuniquesarray as your reverse mapping, or build aNetwork(whosenode_id_to_labelrecords the mapping). Source and target columns must hold integers.
API pointers¶
infomap.run()dispatches on the input type and returns an immutableResult.infomap.Networkbuilds a network explicitly. Its constructorsfrom_networkx(),from_igraph(),from_scipy_sparse_matrix(),from_edge_index(), andfrom_file()take the adapter options thatinfomap.run()does not.infomap.Network.add_node(),infomap.Network.add_link(), andinfomap.Network.add_links()build a network incrementally.infomap.find_communities()andinfomap.find_igraph_communities()are one-shot helpers returning a NetworkXlist[set]and anigraph.VertexClusteringrespectively.infomap.Result.modules()returns{node_id: module_id};infomap.Result.to_dataframe()returns node id, module id, flow, path, and name as a DataFrame.
Going deeper¶
Full API reference: API reference gives complete signatures for every entry point above.
Richer network representations: Flow models & representations covers data with memory, layers, or explicit state nodes.
The survey (§4) treats flow modelling in depth, including undirected versus directed links [Smiljanić et al., 2026].