Skip to content
Developer Edition runs on one host and does not require a product license file. Need multi-host deployment or performance testing at scale? Explore Enterprise Edition

Personalized PageRank

Algorithm introduction

The PPR algorithm is a variant of the PageRank algorithm that computes the importance of pages based on personalized preferences. Unlike the traditional PageRank, which treats all pages equally, PPR allows users to specify a set of pages that they are interested in, and the algorithm prioritizes these pages in its ranking.

The PPR score of a page is determined by the likelihood that a random surfer, who starts from a personalized set of pages, arrives at that page by following links. The formula for calculating the PPR score of a page is as follows:

\[ PPR(i) = (1 - d) \cdot v(i) + d \cdot \sum_{j \in In(i)} \frac{PPR(j)}{Out(j)} \]

Where:

  • PPR(i): The PPR score of page i.
  • d: The damping factor that represents the probability that a user follows a link from the current page to another page. Value range: [0, 1).
  • v(i): The vector that represents the preference for page i. A higher value indicates a higher preference. It is typically set to 1 for pages in the personalized set and 0 for others.
  • In(i): The set of pages that link to page i.
  • Out(j): The number of outbound links from page j.

Data model

To implement the PPR algorithm in GQL, the following data model is used to create a graph type:

  • The web pages are abstracted as persons represented by nodes of type Person.
  • The links between pages are abstracted as relationships between persons, represented by edges of type Knows.

For the complete definition of the graph type, see Prepare graph data.

Procedure implementation

To compute the PPR scores of nodes in a graph, a procedure is created to implement the PPR algorithm.

For the complete definition of the procedure, see Create a procedure. The following section describes the key components of the procedure and how it works.

The procedure takes the following parameters:

  • source_pks: The list of IDs of nodes that are in the personalized set.
  • damping: The damping factor.
  • max_iter: The maximum number of iterations to perform.
  • eps: The convergence threshold. If the total change in PPR scores in an iteration is less than this threshold, the iteration stops.

Additionally, the following variables are defined within the procedure:

Variable name Variable type Description
start_set Active set variable The set of nodes that are being processed in each iteration.
total_set Active set variable The set of nodes that are processed in all iterations.
cur_iter Primitive value variable The current iteration count.
delta Global SumAgg aggregator value variable The total change in PPR scores in each iteration. This helps determine when to stop the iteration process.
topk_score Global TopKAgg aggregator value variable The top K nodes with the highest PPR scores.
pk_set Global SetAgg aggregator value variable The set of IDs of nodes in the personalized set.
current_score Node-attached SumAgg aggregator value variable The PPR score of a node in the current iteration.
next_score Node-attached SumAgg aggregator value variable The PPR score of a node in the next iteration.
out_edge Node-attached SumAgg aggregator value variable The number of outgoing edges from a node.
is_source Node-attached OrAgg aggregator value variable The flag indicating whether a node is in the personalized set.
result_file File variable The file that stores the PPR scores of the top K nodes.

The following steps outline the logic of the procedure:

  1. Initialize out_edge for each node by counting its outgoing edges.
  2. Initialize pk_set with the IDs of nodes in the personalized set provided by the source_pks parameter.
  3. Initialize start_set with nodes in the personalized set and set their current_score to 1.0 and is_source to true. Initialize total_set with these nodes as well.
  4. Start the iteration process to update PPR scores until the maximum number of iterations (specified by max_iter) is reached or the convergence threshold (specified by eps) is met:
    1. Reset delta to 0.0.
    2. For each edge from nodes in start_set, set next_score of the destination node to current_score of the source node divided by its out_edge.
    3. Add all destination nodes to start_set.
    4. For each node in start_set, perform the following operations:
      1. Update its next_score as follows:
        • If the node is in the personalized set, set its next_score to 1.0 - damping + damping * next_score.
        • Otherwise, set next_score to damping * next_score.
      2. Accumulate delta by adding the absolute difference between its next_score and current_score.
      3. Prepare for the next iteration by setting its current_score to next_score and resetting next_score to 0.0.
    5. Add the nodes in start_set to total_set.
    6. Increment the iteration count cur_iter.
    7. Output the value of delta for monitoring purposes.
  5. After the final iteration ends, initialize topk_score with the top 20 nodes having the highest current_score from total_set.
  6. Export the results in topk_score to the file specified by result_file.
  7. Return the following results as output parameters:
    • node_id: The ID of the node in topk_score.
    • score: The PPR score of the node rounded to five decimal places.

Steps

Run the following code blocks in order in the NebulaGraph Studio CONSOLE. For first-time setup, follow Prepare algorithm sample data to check where to run commands and how the directory is mounted.

1. Prepare graph data

  1. Create a graph type that matches your data model.

    '''
    CREATE GRAPH TYPE IF NOT EXISTS pagerank_graph_type AS {
        NODE Person (LABEL Person {id INT PRIMARY KEY, name STRING}),
        EDGE Knows (Person)-[LABEL Knows {MULTIEDGE KEY()}]->(Person)
    }
    '''
    
  2. Download these CSV files and save them with their original names in examples/algorithms/ppr/ under your deployment directory:

    Preview the CSV files
    pagerank_node.csv
    u_id,u_name
    1,Alice
    2,Bob
    3,Charlie
    4,David
    
    pagerank_edge.csv
    src_id,dst_id
    1,2
    2,3
    1,4
    3,4
    4,2
    
  3. Create a temporary graph based on the graph type and load the data files into the temporary graph.

    CREATE TEMPORARY GRAPH IF NOT EXISTS #ppr_graph TYPED pagerank_graph_type
    
    '''
    USE #ppr_graph IMPORT INTO GRAPH {
          NODE (v@Person {id: u_id, name: u_name}) FROM DATAFILE {
              PATH: "file:///examples/algorithms/ppr/pagerank_node.csv",
              FORMAT: "CSV",
              DELIMITER: ','
          },
          EDGE (id: src_id)-[e@Knows{}]->(id: dst_id) FROM DATAFILE {
              PATH: "file:///examples/algorithms/ppr/pagerank_edge.csv",
              FORMAT: "CSV",
              DELIMITER: ','
          }
        }
    '''
    

    After the import succeeds, run this query in the console to check the node count:

    '''
    USE #ppr_graph
    MATCH (n)
    RETURN count(n) AS node_count
    '''
    

    The expected node_count is 4. If it differs, check the files and import results before continuing.

2. Create a procedure

Create a procedure to implement the PPR algorithm. For more information about the procedure, see Procedure implementation.

'''
CREATE OR REPLACE PROCEDURE pers_pagerank(source_pks LIST<INT>, damping DOUBLE, max_iter INT, eps DOUBLE) RETURNS (node_id INT, score DOUBLE) AS {
    VALUE      start_set     ACTIVE_SET
    VALUE      total_set     ACTIVE_SET
    VALUE      cur_iter      = 0
    VALUE      delta         SumAgg<DOUBLE> = 1.0
    VALUE      topk_score    TopKAgg<20, score DOUBLE DESC, node_id INT DESC>
    VALUE      pk_set        SetAgg<INT>
    NODE VALUE current_score SumAgg<DOUBLE> = 0.0
    NODE VALUE next_score    SumAgg<DOUBLE> = 0.0
    NODE VALUE out_edge      SumAgg<INT64> = 0
    NODE VALUE is_source     OrAgg = false

    FILE result_file {vid INT64, score DOUBLE} = DATAFILE {PATH:"file:///examples/algorithms/ppr/output", FORMAT:"CSV", INCLUDE_HEADER: true}

    MATCH (s)-[e]->(t)
    PER PATH {
      SET s.@out_edge += 1
    }

    SET @pk_set = source_pks

    MATCH (s@Person)
    WHERE @pk_set.contains_key(s.id)
    FINALLY {
      SET start_set = s
    }

    MATCH (s)
    WHERE s IN start_set
    PER NODE (s) {
      SET s.@current_score = 1.0
      SET s.@is_source = true
    }
    FINALLY {
      SET total_set = s
    }

    WHILE cur_iter < max_iter AND @delta > eps THEN {
      SET @delta = 0.0
      MATCH (s)-[e]->(t)
      WHERE s IN start_set
      PER PATH {
        SET t.@next_score += s.@current_score / s.@out_edge
      }
      FINALLY {
        SET start_set |= t
      }

      MATCH (s)
      WHERE s IN start_set
      PER NODE (s) {
          IF s.@is_source = true THEN {
            SET s.@next_score = 1.0 - damping + damping * s.@next_score
            } ELSE {
              SET s.@next_score = damping * s.@next_score
            }
          SET @delta += abs(s.@next_score - s.@current_score)
          SET s.@current_score = s.@next_score
          SET s.@next_score = 0.0
      }
      FINALLY {
        SET total_set |= s
      }
      SET cur_iter = cur_iter + 1
      LOG_INFO("delta == ", @delta)
    }

    MATCH (a)
    WHERE a IN total_set
    PER NODE (a) {
      SET @topk_score += RECORD{score:a.@current_score, node_id:a.id}
    }

    FOR r IN @topk_score
      EXPORT r.node_id AS vid, round(r.score, 5) AS score INTO result_file

    FOR r IN @topk_score
      RETURN r.node_id, round(r.score, 5)
}
'''

3. Call your procedure

Call the procedure to compute the PPR scores. The following example specifies nodes with IDs 1 and 2 as the personalized set, sets the damping factor to 0.85, the maximum number of iterations to 10, and the convergence threshold to 0.001.

USE #ppr_graph CALL pers_pagerank([1,2],0.85, 10, 0.001) RETURN *

This procedure returns a table in the console and also exports the results as CSV files.

Return:

+---------+---------+
| node_id | score   |
+---------+---------+
| 2       | 0.66672 |
| 3       | 0.65038 |
| 4       | 0.5329  |
| 1       | 0.15    |
+---------+---------+
Example execution log
Log:
2025-10-11T09:17:37.900499 INFO  - delta == 2.55
2025-10-11T09:17:37.909048 INFO  - delta == 0.7225
2025-10-11T09:17:37.918101 INFO  - delta == 0.614125
2025-10-11T09:17:37.927305 INFO  - delta == 0.522006
2025-10-11T09:17:37.936807 INFO  - delta == 0.443705
2025-10-11T09:17:37.944857 INFO  - delta == 0.37715
2025-10-11T09:17:37.953506 INFO  - delta == 0.320577
2025-10-11T09:17:37.962312 INFO  - delta == 0.272491
2025-10-11T09:17:37.970650 INFO  - delta == 0.231617
2025-10-11T09:17:37.978231 INFO  - delta == 0.196874

On the Docker host, open the CSV files in examples/algorithms/ppr/output/ under your deployment directory. Example contents:

"vid","score"
2,0.66672
3,0.65038
4,0.5329
1,0.15
Write results to a graph

If you want to persist Personalized PageRank results in a graph, follow EXPORT:

  • Export PPR result fields to a table variable first (typically vid and score).
  • For a remote graph, batch rows with table_split(...) and execute batched USE + INSERT statements through nebula_exec.

Define the target graph type based on your PPR output model (for example, a node property such as ppr_score).