A bipartite graph divides its nodes into two sets — call them "top" and "bottom" — where every edge connects a node from one set to a node from the other. No edges exist within a set. This structure naturally models many real-world relationships: movies and the actors in them, users and the products they've purchased, researchers and the papers they've co-authored, or students and the courses they've taken. Analyzing bipartite graphs lets you find dense connections, project onto one mode (e.g., which actors have appeared together), and apply bipartite-specific algorithms. ### Creating a Bipartite Graph NetworkX represents bipartite graphs as ordinary graphs with a `bipartite` node attribute (0 for one set, 1 for the other). The `bipartite` submodule provides specialized tools.
import networkx as nx
from networkx.algorithms import bipartite
import matplotlib.pyplot as plt
B = nx.Graph()
# Top nodes: movies; bottom nodes: actors
movies = ["Inception", "Interstellar", "Dunkirk"]
actors = ["DiCaprio", "Caine", "Murphy", "Hardy"]
B.add_nodes_from(movies, bipartite=0)
B.add_nodes_from(actors, bipartite=1)
B.add_edges_from([
("Inception", "DiCaprio"), ("Inception", "Caine"),
("Interstellar", "Caine"), ("Interstellar", "Murphy"),
("Dunkirk", "Hardy"), ("Dunkirk", "Murphy"),
("Dunkirk", "Caine"),
])
print("Is bipartite:", nx.is_bipartite(B))
print("Nodes:", dict(B.nodes(data="bipartite")))- `add_nodes_from(movies, bipartite=0)` attaches the `bipartite` attribute to each node, marking which partition it belongs to. - `nx.is_bipartite(B)` verifies the structure — useful when you receive a graph from an external source. - Edges only run between movies (0) and actors (1); no actor-actor or movie-movie edges exist. ### Visualizing a Bipartite Graph Bipartite graphs look clearest when the two node sets are drawn on separate horizontal lines.
import networkx as nx
from networkx.algorithms import bipartite
import matplotlib.pyplot as plt
B = nx.Graph()
movies = ["Inception", "Interstellar", "Dunkirk"]
actors = ["DiCaprio", "Caine", "Murphy", "Hardy"]
B.add_nodes_from(movies, bipartite=0)
B.add_nodes_from(actors, bipartite=1)
B.add_edges_from([
("Inception", "DiCaprio"), ("Inception", "Caine"),
("Interstellar", "Caine"), ("Interstellar", "Murphy"),
("Dunkirk", "Hardy"), ("Dunkirk", "Murphy"), ("Dunkirk", "Caine"),
])
pos = {}
for i, m in enumerate(movies):
pos[m] = (i * 2, 1)
for i, a in enumerate(actors):
pos[a] = (i * 1.5, 0)
plt.figure(figsize=(8, 4))
nx.draw(B, pos, with_labels=True, node_color=
["steelblue" if B.nodes[n]["bipartite"] == 0 else "crimson"
for n in B.nodes()],
node_size=1200, font_size=9, edge_color="gray")
plt.title("Movie–Actor Bipartite Graph")
plt.tight_layout()
plt.show()- Manually assigning y=1 to movies and y=0 to actors creates the two-row layout — this is the standard visual convention for bipartite graphs. - Blue nodes (movies) and red nodes (actors) make the partition immediately clear. - Spreading nodes evenly along x avoids overlap while keeping edges readable. ### Bipartite Projection A bipartite projection collapses the graph onto one set of nodes by connecting two nodes if they share a common neighbor in the other set. Projecting the actor set creates an actor-actor network where edges mean "appeared in the same movie".
import networkx as nx
from networkx.algorithms import bipartite
import matplotlib.pyplot as plt
B = nx.Graph()
movies = ["Inception", "Interstellar", "Dunkirk"]
actors = ["DiCaprio", "Caine", "Murphy", "Hardy"]
B.add_nodes_from(movies, bipartite=0)
B.add_nodes_from(actors, bipartite=1)
B.add_edges_from([
("Inception", "DiCaprio"), ("Inception", "Caine"),
("Interstellar", "Caine"), ("Interstellar", "Murphy"),
("Dunkirk", "Hardy"), ("Dunkirk", "Murphy"), ("Dunkirk", "Caine"),
])
# Weighted projection: edge weight = number of shared movies
actor_graph = bipartite.weighted_projected_graph(B, actors)
print("Actor co-appearance network:")
for u, v, w in actor_graph.edges(data="weight"):
print(f" {u} — {v}: {w} shared movie(s)")
pos = nx.spring_layout(actor_graph, seed=1)
widths = [actor_graph[u][v]["weight"] * 2 for u, v in actor_graph.edges()]
plt.figure(figsize=(6, 5))
nx.draw(actor_graph, pos, with_labels=True, node_color="crimson",
node_size=1200, font_size=9, edge_color="gray", width=widths)
plt.title("Actor Co-appearance Network (edge width = shared movies)")
plt.tight_layout()
plt.show()- `bipartite.weighted_projected_graph(B, actors)` creates a graph on just the actor nodes; the `weight` attribute counts how many movies each pair shared. - Edge widths scaled by `weight * 2` make stronger collaborations visually thicker. - `bipartite.projected_graph(B, actors)` gives an unweighted version — useful when you only care about whether a connection exists. ### Bipartite Statistics The `bipartite` module provides degree and centrality functions designed for two-mode networks, where degree is measured only within the relevant partition.
import networkx as nx
from networkx.algorithms import bipartite
B = nx.Graph()
movies = ["Inception", "Interstellar", "Dunkirk"]
actors = ["DiCaprio", "Caine", "Murphy", "Hardy"]
B.add_nodes_from(movies, bipartite=0)
B.add_nodes_from(actors, bipartite=1)
B.add_edges_from([
("Inception", "DiCaprio"), ("Inception", "Caine"),
("Interstellar", "Caine"), ("Interstellar", "Murphy"),
("Dunkirk", "Hardy"), ("Dunkirk", "Murphy"), ("Dunkirk", "Caine"),
])
# Degree in bipartite sense: count edges crossing the partition
deg = bipartite.degree_centrality(B, actors)
print("Actor degree centrality:")
for node in sorted(actors, key=lambda a: deg[a], reverse=True):
print(f" {node}: {deg[node]:.3f}")
print("\nMovie degree centrality:")
deg_movies = bipartite.degree_centrality(B, movies)
for node in sorted(movies, key=lambda m: deg_movies[m], reverse=True):
print(f" {node}: {deg_movies[m]:.3f}")- `bipartite.degree_centrality(B, nodes)` normalizes by the size of the *opposite* partition — the maximum possible number of connections — unlike standard degree centrality which uses n−1. - Michael Caine appears in all three films, so he has the highest actor degree centrality (1.0). - Dunkirk has the most actors (3 out of 4), so it has the highest movie degree centrality. For a deeper look at the projected actor-actor or movie-movie networks, apply [centrality measures](/tutorials/centrality-measures-with-networkx) or [community detection](/tutorials/community-detection-with-networkx) to the projected graph.