Source: https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/ppr/ Documentation version: v5.3.2 # Personalized PageRank ## Algorithm introduction The PPR algorithm is a variant of the [PageRank](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/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](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/ppr/#1_prepare_graph_data). ## Procedure implementation To compute the PPR scores of nodes in a graph, a [procedure](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/procedures/create/) is created to implement the PPR algorithm. For the complete definition of the procedure, see [Create a procedure](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/ppr/#2_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](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/active-set-variable/) | The set of nodes that are being processed in each iteration. | `total_set` | [Active set variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/active-set-variable/) | The set of nodes that are processed in all iterations. | `cur_iter` | [Primitive value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/value-variable/) | The current iteration count. | `delta` | Global [SumAgg aggregator value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/sum_agg/) | 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](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/topk_agg/) | The top K nodes with the highest PPR scores. | `pk_set` | Global [SetAgg aggregator value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/set_agg/) | The set of IDs of nodes in the personalized set. | `current_score` | Node-attached [SumAgg aggregator value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/sum_agg/) | The PPR score of a node in the current iteration. | `next_score` | Node-attached [SumAgg aggregator value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/sum_agg/) | The PPR score of a node in the next iteration. | `out_edge` | Node-attached [SumAgg aggregator value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/sum_agg/) | The number of outgoing edges from a node. | `is_source` | Node-attached [OrAgg aggregator value variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/or_agg/) | The flag indicating whether a node is in the personalized set. | `result_file` | [File variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/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](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/prepare-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: - [pagerank_node.csv](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/files/ppr/pagerank_node.csv): Node data. - [pagerank_edge.csv](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/files/ppr/pagerank_edge.csv): Edge data. 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](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/prepare-data/#troubleshooting) before continuing. ### 2. Create a procedure Create a procedure to implement the PPR algorithm. For more information about the procedure, see [Procedure implementation](https://dev-docs.nebula-graph.io/v5.3.2/analytic/algorithms/ppr/#procedure_implementation). ```` ''' CREATE OR REPLACE PROCEDURE pers_pagerank(source_pks LIST, 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 = 1.0 VALUE topk_score TopKAgg<20, score DOUBLE DESC, node_id INT DESC> VALUE pk_set SetAgg NODE VALUE current_score SumAgg = 0.0 NODE VALUE next_score SumAgg = 0.0 NODE VALUE out_edge SumAgg = 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](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/in-out/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`).