mrkeyoor.com_
Thu 06 Aug 00:57 UTC
PyPIDataupdated 05 Aug 2026

networkx

NetworkX is a pure-Python library for building and analysing graphs. A graph is a dict of dicts under the hood, so any hashable Python object can be a node, every node and edge carries an arbitrary attribute dictionary, and you build structures by calling add_edge rather than by declaring a schema. On top of that sit several hundred implemented algorithms: shortest paths, centrality measures, community detection, flow, matching, clique finding, isomorphism, traversal, and generators for classic graph families. It converts to and from pandas DataFrames, SciPy sparse arrays, and NumPy matrices, reads and writes GraphML, GEXF, edge lists, and JSON, and since 3.x it can dispatch algorithm calls to compiled backends such as GPU or parallel implementations without changing your code.

Verdict

The default graph library in Python and the right first choice for analysis, prototyping, and anything under roughly a hundred thousand edges, because no other library has this algorithm coverage or this gentle an API. Move to rustworkx or igraph when the graph outgrows the interpreter, and keep NetworkX around for the algorithms they do not have.

API stability4/5The Graph, DiGraph, and MultiGraph interfaces have barely changed in a decade and 3.x removals come with a deprecation cycle documented per release. Small defaults still shift, such as node_link_data now emitting an 'edges' key where it used to emit 'links'.
Docs5/5networkx.org gives every public function a page with parameters, notes, references to the source paper, and a runnable example, plus a tutorial and a large gallery. Finding the right algorithm is genuinely easy.
Maintenance5/5Pushed the same day this was written, 141 open issues (324 counting the 183 open PRs), steady 3.x releases, and a funded team with a public meeting calendar rather than a single maintainer.
Ecosystem5/5Roughly 69 million weekly downloads and a dependency of scikit-image, dask, and much of the scientific Python stack; the backend dispatch system means cuGraph, GraphBLAS, and parallel implementations now target its API directly.

Use it if

  • You are exploring a graph rather than serving one: notebooks, one-off analyses, and research code where the breadth of ready algorithms saves you more time than raw speed would
  • Your nodes are real Python objects with attributes: strings, tuples, dataclasses, and per-edge dictionaries all work directly, with no id mapping layer to maintain
  • You need an algorithm that only exists here: the coverage of community detection, flow, matching, and isomorphism variants is wider than any compiled alternative
  • You want an easy install with no compiler: the base package has zero required dependencies and works anywhere Python runs, including restricted build environments
  • You are writing something teachable or reviewable, where reading nx.betweenness_centrality in the diff communicates intent better than a hand-rolled traversal
Skip it if

Setup reality

pip install networkx pulls nothing at all, which is the nice part and also the trap: converting to a SciPy sparse array, reading a DataFrame, or drawing anything raises ImportError until you install the optional stack. pip install networkx[default] adds numpy, scipy, matplotlib, and pandas and is what most people actually want. Graphviz layouts need pygraphviz, which compiles against system Graphviz headers and is the one dependency here that regularly fails to build; pydot is the easier fallback. Python support is 3.11 or newer, and the current release explicitly excludes 3.14.1. Two behaviours surprise newcomers: add_edge silently creates any node it has not seen, so a typo in a node name quietly adds a node instead of raising, and Graph collapses parallel edges, so keeping duplicates means choosing MultiGraph up front.

Patterns

Create a graph and attach attributesbuild-a-graph

import networkx as nx

G = nx.Graph()
G.add_node("alice", role="admin")
G.add_edge("alice", "bob", weight=4.0, since=2019)
G.add_edges_from([("bob", "carol", {"weight": 2.0})])

print(G.number_of_nodes(), G.number_of_edges())
print(G["alice"]["bob"]["weight"])   # 4.0
print(G.nodes["alice"]["role"])       # admin

add_edge creates missing nodes without complaining, so a misspelled name becomes a new node rather than an error. On a plain Graph, adding the same pair twice updates the existing edge instead of adding a second one.

Build a graph from a pandas DataFrameload-from-dataframe

import pandas as pd, networkx as nx

df = pd.read_csv("calls.csv")   # columns: caller, callee, minutes
G = nx.from_pandas_edgelist(
    df, source="caller", target="callee",
    edge_attr=["minutes"], create_using=nx.DiGraph,
)
back = nx.to_pandas_edgelist(G)

create_using must be the class, not an instance, or the direction is silently dropped. Needs pandas, which the bare install does not include.

Find shortest paths, weighted and unweightedshortest-paths

nx.shortest_path(G, "alice", "dave")                    # hop count, BFS
nx.shortest_path(G, "alice", "dave", weight="weight")   # Dijkstra
nx.shortest_path_length(G, "alice", weight="weight")    # dict from alice

for target, path in nx.single_source_dijkstra_path(G, "alice", cutoff=3).items():
    print(target, path)

Dijkstra rejects negative weights; use bellman_ford_path when costs can be negative. Missing endpoints raise NodeNotFound and disconnected ones raise NetworkXNoPath, so catch both rather than assuming a path exists.

Rank nodes by centralitycentrality

deg = nx.degree_centrality(G)
pr = nx.pagerank(G, alpha=0.85, weight="weight")
btw = nx.betweenness_centrality(G, k=500, seed=42, normalized=True)

top = sorted(pr, key=pr.get, reverse=True)[:10]

Exact betweenness runs a shortest-path search from every node, so it becomes impractical well before other measures do. The k argument samples that many source nodes and needs a seed to be reproducible.

Split a graph into componentscomponents-and-connectivity

for comp in sorted(nx.connected_components(G), key=len, reverse=True):
    print(len(comp))

largest = G.subgraph(max(nx.connected_components(G), key=len)).copy()

# directed graphs
list(nx.weakly_connected_components(D))
list(nx.strongly_connected_components(D))

connected_components raises NetworkXNotImplemented on a directed graph; pick the weak or strong variant deliberately. subgraph returns a read-only view over the original, so call .copy() if you plan to mutate it.

Topologically sort a dependency graphdag-ordering

D = nx.DiGraph([("compile", "test"), ("test", "package"), ("lint", "package")])

if not nx.is_directed_acyclic_graph(D):
    print(nx.find_cycle(D))

order = list(nx.topological_sort(D))
for batch in nx.topological_generations(D):
    print("can run in parallel:", batch)

topological_sort raises NetworkXUnfeasible on a cycle, and find_cycle raises NetworkXNoCycle when there is none, so both need handling. topological_generations is the one you want for scheduling parallel work.

Convert to a SciPy sparse array for linear algebrasparse-matrix-conversion

import numpy as np, networkx as nx

nodes = list(G)
A = nx.to_scipy_sparse_array(G, nodelist=nodes, weight="weight", format="csr")
deg = np.asarray(A.sum(axis=1)).ravel()

H = nx.from_scipy_sparse_array(A, create_using=nx.DiGraph)

Always pass nodelist and keep it: row order follows it, and without it you cannot map matrix indices back to node names. from_scipy_sparse_array returns integer-labelled nodes, so relabel with nx.relabel_nodes if you need the original names.

Detect communitiescommunities

from networkx.algorithms.community import louvain_communities, modularity

comms = louvain_communities(G, weight="weight", resolution=1.0, seed=42)
print(len(comms), modularity(G, comms))

Louvain is randomized: without seed you get different partitions on every run, which makes results look unstable when they are not. Raise resolution for more, smaller communities.

Save and load graphsserialize

import json, networkx as nx

nx.write_graphml(G, "graph.graphml")
G2 = nx.read_graphml("graph.graphml")

data = nx.node_link_data(G, edges="edges")
json.dump(data, open("graph.json", "w"))
G3 = nx.node_link_graph(data, edges="edges")

The node_link key for edges is now 'edges'; older files and older code use 'links', so pass edges= explicitly on both sides when reading anything you saved before NetworkX 3.4. GraphML stringifies node ids, so integers come back as strings.

Draw a small graph with matplotlibdraw-a-graph

import matplotlib.pyplot as plt, networkx as nx

pos = nx.spring_layout(G, seed=42, k=0.3)
nx.draw_networkx_nodes(G, pos, node_size=300)
nx.draw_networkx_edges(G, pos, alpha=0.4)
nx.draw_networkx_labels(G, pos, font_size=8)
plt.axis("off")
plt.savefig("graph.png", dpi=200, bbox_inches="tight")

spring_layout is random unless you pass a seed, so the same graph looks different every run. Past a few hundred nodes the output is an unreadable ball; export to Graphviz or Gephi instead.

Test two graphs for isomorphismcompare-graphs

from networkx.algorithms import isomorphism

nx.vf2pp_is_isomorphic(G1, G2, node_label="role")

matcher = isomorphism.GraphMatcher(
    G1, G2, node_match=lambda a, b: a["role"] == b["role"])
if matcher.is_isomorphic():
    print(matcher.mapping)

Isomorphism is expensive in the general case and attribute matching narrows the search, so pass node_match or node_label when you can. vf2pp is the faster implementation for labelled graphs.

Dispatch an algorithm to a faster backenduse-a-backend

import networkx as nx

# per call
nx.betweenness_centrality(G, backend="parallel")

# or globally, with fallback to pure NetworkX
nx.config.backend_priority = ["cugraph"]
nx.config.fallback_to_nx = True
nx.config.cache_converted_graphs = True

Backends such as nx-parallel, nx-cugraph, and graphblas-algorithms are separate installs and each covers only some algorithms. Graph conversion happens per call unless you turn on cache_converted_graphs, which can wipe out the speedup on small graphs.

Alternatives

PackageRegistryPick it when
rustworkxPyPIYou need the same kinds of algorithms on much larger graphs and can accept integer node indices; it is a Rust implementation with a deliberately similar API.
igraphPyPIYou want a mature C core, fast community detection, and much better built-in plotting, and do not mind a less Pythonic interface.
networkitPyPIYou are analysing very large networks and want parallel C++ algorithms for centrality and community detection across many cores.
graphblas-algorithmsPyPIYou want to keep writing NetworkX calls but run them over sparse linear algebra; it plugs in as a NetworkX backend.