Skip to main content

PageRank Algorithm: Link Analysis and Graph Ranking

Created: March 16, 2026 Larry Qu 9 min read

Introduction

When Larry Page and Sergey Brin founded Google in 1998, they revolutionized web search with a simple but powerful insight: the importance of a web page can be determined by the links pointing to it. This insight became the PageRank algorithm—a mathematical method for measuring the importance of nodes in a network based on the structure of incoming links.

PageRank transformed web search and remains fundamental to understanding network data. In 2026, its applications extend far beyond search engines—to social network analysis, recommendation systems, biology, and neuroscience. This article explores the PageRank algorithm from mathematical foundations to practical implementation.

The Core Idea

Conceptual Understanding

PageRank views the web as a directed graph where:

  • Nodes: Web pages
  • Edges: Hyperlinks between pages

A page is important if other important pages link to it. This creates a recursive definition: a page’s importance depends on the importance of pages that reference it.

Random Surfer Model

The algorithm models a random web surfer:

  1. Start at a random page
  2. Follow random links with probability d (damping factor)
  3. Jump to random page with probability (1-d)
  4. Repeat indefinitely
  5. The fraction of time spent on each page is its PageRank

This approach handles:

  • Dangling nodes: Pages with no outgoing links
  • Sink nodes: Pages that form cycles without outgoing links
  • Spider traps: Pages that trap the surfer in a local region

Mathematical Foundation

Transition Matrix

Given a graph with n pages, create an n×n transition matrix M:

M[i][j] = 1/outdegree(j) if page j links to page i, else 0

Each column sums to 1 (except dangling nodes).

Eigenvector Formulation

PageRank is the stationary distribution of the random walk:

π = M × π

This is the eigenvector of M with eigenvalue 1.

Damping Factor

The damping factor d (typically 0.85) accounts for random jumps:

P = d × M + (1-d) × J/n

where J is an all-ones matrix. This ensures the Markov chain is ergodic.

The PageRank Equation

The complete PageRank formula:

PR(i) = (1-d)/n + d × Σ(j→i) PR(j) / outdegree(j)

where the sum is over all pages j that link to i.

Implementation

Basic PageRank

import numpy as np

def pagerank(adjacency_matrix, damping=0.85, max_iterations=100, 
             tolerance=1e-6):
    n = len(adjacency_matrix)
    
    # Create transition matrix
    out_degree = adjacency_matrix.sum(axis=0)
    out_degree[out_degree == 0] = 1  # Handle dangling nodes
    
    M = adjacency_matrix / out_degree
    
    # Add damping factor
    P = damping * M + (1 - damping) / n
    
    # Power iteration
    pr = np.ones(n) / n
    
    for _ in range(max_iterations):
        new_pr = P @ pr
        diff = np.linalg.norm(new_pr - pr, 1)
        pr = new_pr
        
        if diff < tolerance:
            break
    
    return pr

Sparse Matrix Implementation

For large graphs, use sparse matrices:

import scipy.sparse as sp

def pagerank_sparse(adjacency_list, n, damping=0.85, max_iter=100, 
                    tol=1e-6):
    # Create sparse transition matrix
    row_indices = []
    col_indices = []
    data = []
    
    for src, targets in adjacency_list.items():
        if targets:
            weight = 1.0 / len(targets)
            for target in targets:
                row_indices.append(target)
                col_indices.append(src)
                data.append(weight)
    
    M = sp.csr_matrix((data, (row_indices, col_indices)), 
                       shape=(n, n))
    
    # Damping
    P = damping * M + (1 - damping) / n
    
    # Power iteration with sparse matrix
    pr = np.ones(n) / n
    
    for _ in range(max_iter):
        new_pr = P @ pr
        diff = np.abs(new_pr - pr).sum()
        pr = new_pr
        
        if diff < tol:
            break
    
    return pr

Iterative Implementation

def pagerank_iterative(links, damping=0.85, max_iterations=100,
                       min_diff=1e-5):
    """
    links: dict mapping page_id to list of linked page_ids
    """
    pages = set(links.keys())
    for linked_pages in links.values():
        pages.update(linked_pages)
    
    n = len(pages)
    page_to_idx = {page: i for i, page in enumerate(pages)}
    
    # Initialize PageRank
    pr = {page: 1.0 / n for page in pages}
    
    for iteration in range(max_iterations):
        new_pr = {}
        damping_factor = (1 - damping) / n
        
        for page in pages:
            rank = damping_factor
            for linking_page in pages:
                if page in links.get(linking_page, []):
                    out_links = links[linking_page]
                    if out_links:  # Avoid division by zero
                        rank += damping * pr[linking_page] / len(out_links)
            new_pr[page] = rank
        
        # Check convergence
        diff = sum(abs(new_pr[page] - pr[page]) for page in pages)
        pr = new_pr
        
        if diff < min_diff:
            print(f"Converged after {iteration + 1} iterations")
            break
    
    # Normalize
    total = sum(pr.values())
    pr = {page: rank / total for page, rank in pr.items()}
    
    return pr

NetworkX Implementation

import networkx as nx

def pagerank_networkx(G, alpha=0.85):
    return nx.pagerank(G, alpha=alpha)

# Example usage
G = nx.DiGraph()
G.add_edges_from([
    (0, 1), (0, 2), (0, 3),
    (1, 0), (1, 2),
    (2, 0), (2, 1), (2, 3),
    (3, 0), (3, 2)
])

ranks = pagerank_networkx(G)
for node, rank in sorted(ranks.items(), key=lambda x: x[1], reverse=True):
    print(f"Node {node}: {rank:.4f}")

Variations and Extensions

Personalized PageRank

Bias toward specific nodes for personalized rankings:

def personalized_pagerank(links, teleport_set, damping=0.85, 
                         iterations=100):
    """
    teleport_set: nodes to teleport to (personalization)
    """
    pages = set(links.keys())
    for linked in links.values():
        pages.update(linked)
    
    n = len(pages)
    page_to_idx = {p: i for i, p in enumerate(pages)}
    
    # Personalized teleportation
    teleport_prob = {page: 1.0/len(teleport_set) 
                    if page in teleport_set else 0 
                    for page in pages}
    
    pr = {page: 1.0/n for page in pages}
    
    for _ in range(iterations):
        new_pr = {}
        
        for page in pages:
            rank = (1 - damping) * teleport_prob[page]
            
            for linking in pages:
                if page in links.get(linking, []):
                    out_links = links[linking]
                    if out_links:
                        rank += damping * pr[linking] / len(out_links)
            
            new_pr[page] = rank
        
        pr = new_pr
    
    return pr

Weighted PageRank

Incorporates edge weights:

def weighted_pagerank(links, weights, damping=0.85, iterations=100):
    """
    weights: dict of (src, dst) -> weight
    """
    pages = set(links.keys())
    for linked in links.values():
        pages.update(linked)
    
    n = len(pages)
    pr = {p: 1.0/n for p in pages}
    
    for _ in range(iterations):
        new_pr = {}
        
        for page in pages:
            rank = (1 - damping) / n
            
            for linking in pages:
                if page in links.get(linking, []):
                    out_links = links[linking]
                    if out_links:
                        # Weight contribution
                        w = weights.get((linking, page), 1)
                        w_sum = sum(weights.get((linking, l), 1) 
                                   for l in out_links)
                        rank += damping * pr[linking] * w / w_sum
                    
            new_pr[page] = rank
        
        pr = new_pr
    
    return pr

TrustRank

Identifies trusted pages to combat web spam:

def trustrank(links, seed_pages, damping=0.85, iterations=100):
    """
    seed_pages: known trusted pages
    """
    pages = set(links.keys())
    for linked in links.values():
        pages.update(linked)
    
    n = len(pages)
    
    # Seed pages have equal trust
    seed_prob = 1.0 / len(seed_pages) if seed_pages else 0
    
    trust = {page: seed_prob if page in seed_pages else 0 
             for page in pages}
    
    for _ in range(iterations):
        new_trust = {}
        
        for page in pages:
            rank = 0
            for linking in pages:
                if page in links.get(linking, []):
                    out_links = links[linking]
                    if out_links:
                        rank += damping * trust[linking] / len(out_links)
            new_trust[page] = rank
        
        trust = new_trust
    
    return trust

Social Network Analysis

def social_influence_ranking(social_graph):
    """
    social_graph: directed graph of following relationships
    """
    return nx.pagerank(social_graph)

Recommendation Systems

def item_recommendation(user_item_matrix, damping=0.85):
    """
    user_item_matrix: binary or rating matrix
    """
    # Item-item similarity via PageRank on bipartite graph
    n_items = user_item_matrix.shape[1]
    item_graph = np.zeros((n_items, n_items))
    
    for user_ratings in user_item_matrix:
        rated_items = np.where(user_ratings > 0)[0]
        for i in rated_items:
            for j in rated_items:
                if i != j:
                    item_graph[i, j] += 1
    
    return pagerank(item_graph, damping)

Biology

  • Protein-Protein Interaction Networks: Identify essential proteins
  • Gene Regulatory Networks: Find master regulators
  • Metabolic Networks: Identify key metabolites

Neuroscience

def brain_region_importance(connectome, threshold=0.1):
    """
    connectome: brain connectivity matrix
    """
    # Threshold weak connections
    strong_connections = (connectome > threshold).astype(float)
    strong_connections[connectome < threshold] = 0
    
    return pagerank(strong_connections)

Finance

  • Stock Market Networks: Identify systemically important companies
  • Credit Networks: Detect important entities in financial systems

Handling Practical Issues

Dead Ends and Spider Traps

The damping factor handles these, but explicit handling improves results:

def pagerank_with_teleport(links, damping=0.85, max_iter=100):
    pages = set(links.keys())
    for linked in links.values():
        pages.update(linked)
    
    n = len(pages)
    pr = {p: 1.0/n for p in pages}
    
    for _ in range(max_iter):
        dangling_sum = sum(pr[p] for p in pages 
                         if not links.get(p))
        
        new_pr = {}
        for page in pages:
            base = (1 - damping) / n + damping * dangling_sum / n
            
            rank = base
            for linking in pages:
                if page in links.get(linking, []):
                    out = links[linking]
                    if out:
                        rank += damping * pr[linking] / len(out)
            
            new_pr[page] = rank
        
        pr = new_pr
    
    return pr

Numerical Stability

For very large graphs, use normalization:

def pagerank_normalized(links, damping=0.85, iterations=100):
    pages = list(set(links.keys()) | 
                set(l for links_ in links.values() for l in links_))
    n = len(pages)
    idx = {p: i for i, p in enumerate(pages)}
    
    # Build sparse matrix
    rows, cols, data = [], [], []
    for src, targets in links.items():
        if targets:
            w = 1.0 / len(targets)
            for t in targets:
                rows.append(idx[t])
                cols.append(idx[src])
                data.append(w)
    
    M = sp.csr_matrix((data, (rows, cols)), shape=(n, n))
    
    # Power iteration
    pr = np.ones(n) / n
    damping_matrix = damping * M
    
    for _ in range(iterations):
        new_pr = damping_matrix @ pr + (1 - damping) / n
        pr = new_pr / new_pr.sum()  # Renormalize
    
    return {pages[i]: pr[i] for i in range(n)}

Computing PageRank for Large Graphs

Distributed Computing with Spark

from pyspark import SparkContext

def distributed_pagerank(sc, links_rdd, num_pages, num_iterations=10):
    """
    links_rdd: RDD of (page, [linked_pages])
    """
    # Initialize ranks
    ranks = links_rdd.mapValues(lambda x: 1.0 / num_pages)
    
    for _ in range(num_iterations):
        # Map: (target, contribution)
        contribs = links_rdd.join(ranks).flatMap(
            lambda x: [(target, x[1][1] / len(x[1][0])) 
                      for target in x[1][0]]
        )
        
        # Reduce: sum contributions
        totals = contribs.reduceByKey(lambda x, y: x + y)
        
        # Update ranks
        ranks = totals.mapValues(lambda r: 0.15 + 0.85 * r)
    
    return ranks.collect()

Graph Processing Frameworks

  • Apache Giraph: Pregel-like bulk synchronous processing
  • GraphX: Spark’s graph processing library
  • NetworkX: Python’s network analysis library (for smaller graphs)

Time-Complex PageRank

For dynamic graphs that change over time:

def timpr(links, time_windows, decay=0.9):
    """
    time_windows: list of (start_time, end_time, links)
    """
    all_pages = set(links.keys())
    for tw in time_windows:
        for l in tw[2].values():
            all_pages.update(l)
    
    n = len(all_pages)
    pr = {p: 1.0/n for p in all_pages}
    
    for start, end, window_links in time_windows:
        window_weight = decay ** (start)
        
        # Compute PageRank for this window
        window_pr = compute_pagerank(window_links)
        
        # Blend with cumulative PageRank
        for page in all_pages:
            pr[page] = (1 - window_weight) * pr[page] + \
                       window_weight * window_pr.get(page, 0)
    
    return pr

Conclusion

PageRank remains a fundamental algorithm for analyzing networked data. Its elegance lies in the simple insight that importance is determined by the importance of those linking to you—creating a distributed, emergent measure of significance.

From Google’s founding to modern applications in social networks, biology, and neuroscience, PageRank provides a principled approach to ranking nodes in any directed graph. The algorithm’s versatility is demonstrated by its numerous extensions: personalized rankings, weighted edges, trust propagation, and temporal variations.

Understanding PageRank provides essential foundations for:

  • Link analysis and web search
  • Network science methodology
  • Recommendation systems
  • Influence measurement

The algorithm continues to inspire research in graph-based machine learning and network analysis, with connections to deep learning methods like graph neural networks.

Resources

Comments

Share this article

Scan to read on mobile