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:
Where:
PPR(i): The PPR score of pagei.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 pagei. A higher value indicates a higher preference. It is typically set to1for pages in the personalized set and0for others.In(i): The set of pages that link to pagei.Out(j): The number of outbound links from pagej.
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:
- Initialize
out_edgefor each node by counting its outgoing edges. - Initialize
pk_setwith the IDs of nodes in the personalized set provided by thesource_pksparameter. - Initialize
start_setwith nodes in the personalized set and set theircurrent_scoreto1.0andis_sourcetotrue. Initializetotal_setwith these nodes as well. - 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 byeps) is met:- Reset
deltato0.0. - For each edge from nodes in
start_set, setnext_scoreof the destination node tocurrent_scoreof the source node divided by itsout_edge. - Add all destination nodes to
start_set. - For each node in
start_set, perform the following operations:- Update its
next_scoreas follows:- If the node is in the personalized set, set its
next_scoreto1.0 - damping + damping * next_score. - Otherwise, set
next_scoretodamping * next_score.
- If the node is in the personalized set, set its
- Accumulate
deltaby adding the absolute difference between itsnext_scoreandcurrent_score. - Prepare for the next iteration by setting its
current_scoretonext_scoreand resettingnext_scoreto0.0.
- Update its
- Add the nodes in
start_settototal_set. - Increment the iteration count
cur_iter. - Output the value of
deltafor monitoring purposes.
- Reset
- After the final iteration ends, initialize
topk_scorewith the top 20 nodes having the highestcurrent_scorefromtotal_set. - Export the results in
topk_scoreto the file specified byresult_file. - Return the following results as output parameters:
node_id: The ID of the node intopk_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¶
-
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) } ''' -
Download these CSV files and save them with their original names in
examples/algorithms/ppr/under your deployment directory:- pagerank_node.csv: Node data.
- pagerank_edge.csv: Edge data.
Preview the CSV files
pagerank_node.csvu_id,u_name 1,Alice 2,Bob 3,Charlie 4,Davidpagerank_edge.csvsrc_id,dst_id 1,2 2,3 1,4 3,4 4,2 -
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_countis4. 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
vidandscore). - For a remote graph, batch rows with
table_split(...)and execute batchedUSE + INSERTstatements throughnebula_exec.
Define the target graph type based on your PPR output model (for example, a node property such as ppr_score).