Introduction
Now that you understand bipartite and multipartite graphs, it’s time to practice creating projections that preserve multiple node types.
In this lesson, you’ll work with the Movies dataset to create various bipartite projections and run node similarity on them—the same algorithm you used in the previous lesson.
By the end of this lesson, you will be able to:
-
Create bipartite projections with preserved labels
-
Run node similarity on different bipartite structures
-
Understand how projection patterns affect algorithm results
The movies dataset
Your database contains:
-
Actornodes with properties likenameandborn -
Movienodes with properties liketitleandreleased -
Usernodes with properties likename -
Genrenodes with properties likename -
ACTED_INrelationships (Actor → Movie) -
RATEDrelationships (User → Movie) -
IN_GENRErelationships (Movie → Genre)
Projection 1: User-movie bipartite
Remember this projection you created in the previous lesson? Run this command again to create a bipartite user-movie network:
MATCH (source:User)-[r:RATED]->(target:Movie) // (1)
WITH gds.graph.project( // (2)
'user-movie', // (3)
source, // (4)
target, // (5)
{
sourceNodeLabels: labels(source), // (6)
targetNodeLabels: labels(target), // (7)
relationshipType: type(r) // (8)
},
{}
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (9)-
Match User nodes connected to Movie nodes via RATED relationships
-
Call the GDS projection function
-
Name the projection 'user-movie'
-
Include source (User) nodes
-
Include target (Movie) nodes
-
Preserve source node labels
-
Preserve target node labels
-
Preserve relationship types
-
Return projection statistics
This projection creates:
-
A bipartite network with User and Movie labels preserved
-
Users connected to movies through RATED relationships
-
A structure perfect for node similarity
.write() mode to persist algorithm results to your database. Module 3 will teach all execution modes in detail—for now, follow the patterns shown in each example.Now run node similarity on this projection:
CALL gds.nodeSimilarity.write( // (1)
'user-movie', // (2)
{
writeRelationshipType: 'SIMILAR', // (3)
writeProperty: 'score' // (4)
})
YIELD nodesCompared, relationshipsWritten // (5)-
Call node similarity algorithm in write mode
-
Run on 'user-movie' projection
-
Write new relationships with type 'SIMILAR'
-
Write similarity scores as 'score' property
-
Yield the number of nodes compared and relationships written
Remember from the previous lesson: node similarity connects nodes on the same side of the bipartite graph.
Verify the results:
MATCH path = (:User)-[:SIMILAR]->(:User)-[:SIMILAR]-(:User) // (1)
RETURN path // (2)
LIMIT 10 // (3)-
Match a path of users connected by SIMILAR relationships
-
Return the complete path
-
Limit to 10 results
What this reveals: Users with similar movie rating patterns.
Actor-Movie Bipartite
Now, let’s create a different bipartite projection: actors and movies with preserved labels.
Copy the code below into the query window and replace ???? with the correct values.
MATCH (source:????)-[r:????]->(target:????) // (1)
WITH ????( // (2)
'actor-movie', // (3)
source, // (4)
target, // (5)
{
????, // (6)
????, // (7)
???? // (8)
},
{}
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (9)-
Match Actor nodes connected to Movie nodes (fill in the labels and relationship)
-
Call the GDS projection function (fill in function name)
-
Name the projection 'actor-movie'
-
Include source nodes
-
Include target nodes
-
Preserve source node labels (fill in configuration)
-
Preserve target node labels (fill in configuration)
-
Preserve relationship types (fill in configuration)
-
Return projection statistics
If you need help finding the solution, you’ll find the full projection command in the dropdown below:
Details
MATCH (source:Actor)-[r:ACTED_IN]->(target:Movie) // (1)
WITH gds.graph.project( // (2)
'actor-movie', // (3)
source, // (4)
target, // (5)
{
sourceNodeLabels: labels(source), // (6)
targetNodeLabels: labels(target), // (7)
relationshipType: type(r) // (8)
},
{}
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (9)-
Match Actor nodes connected to Movie nodes via ACTED_IN relationships
-
Call the GDS projection function
-
Name the projection 'actor-movie'
-
Include source (Actor) nodes
-
Include target (Movie) nodes
-
Preserve source node labels
-
Preserve target node labels
-
Preserve relationship types
-
Return projection statistics
This projection creates:
-
A bipartite network with Actor and Movie labels preserved
-
Actors connected to movies through ACTED_IN relationships
Now run node similarity on this projection.
CALL gds.nodeSimilarity.write( // (1)
'actor-movie', // (2)
{
writeRelationshipType: 'SIMILAR', // (3)
writeProperty: 'score' // (4)
})
YIELD nodesCompared, relationshipsWritten // (5)-
Call node similarity algorithm in write mode
-
Run on 'actor-movie' projection
-
Write new relationships with type 'SIMILAR'
-
Write similarity scores as 'score' property
-
Yield the number of nodes compared and relationships written
Verify the results:
MATCH path = (:Actor)-[:SIMILAR]->(:Actor)-[:SIMILAR]->(:Actor) // (1)
RETURN path // (2)
LIMIT 10 // (3)-
Match a path of actors connected by SIMILAR relationships
-
Return the complete path
-
Limit to 10 results
What this reveals: Actors who collaborate.
Movie-genre bipartite
Now create a bipartite projection of movies and genres, then run node similarity on it—this time, complete both steps yourself.
Step 1: Create the projection by replacing the ????? placeholders:
MATCH (source:?????)-[r:?????]->(target:?????) // (1)
WITH ????( // (2)
'?????', // (3)
source, // (4)
target, // (5)
{
????, // (6)
????, // (7)
???? // (8)
},
{}
) ???? // (9)
RETURN ????, ????, ???? // (10)-
Match Movie nodes connected to Genre nodes (fill in the labels and relationship)
-
Call the GDS projection function (fill in function name)
-
Name the projection (choose a descriptive name)
-
Include source nodes
-
Include target nodes
-
Preserve source node labels (fill in configuration)
-
Preserve target node labels (fill in configuration)
-
Preserve relationship types (fill in configuration)
-
Alias the projection result (fill in alias)
-
Return projection statistics (fill in return fields)
Step 2: Run node similarity on your projection. Replace the graph name and choose a relationship type:
CALL gds.nodeSimilarity.write( // (1)
'movie-genre', // (2)
{
writeRelationshipType: 'SIMILAR', // (3)
writeProperty: 'score' // (4)
})
YIELD nodesCompared, relationshipsWritten // (5)-
Call node similarity algorithm in write mode
-
Run on 'movie-genre' projection
-
Write new relationships with type 'SIMILAR'
-
Write similarity scores as 'score' property
-
Yield the number of nodes compared and relationships written
If you need to see the full command, feel free to open the dropdown below.
Details
Step 1: Create the projection
MATCH (source:Movie)-[r:IN_GENRE]->(target:Genre) // (1)
WITH gds.graph.project( // (2)
'movie-genre', // (3)
source, // (4)
target, // (5)
{
sourceNodeLabels: labels(source), // (6)
targetNodeLabels: labels(target), // (7)
relationshipType: type(r) // (8)
},
{}
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (9)-
Match Movie nodes connected to Genre nodes via IN_GENRE relationships
-
Call the GDS projection function
-
Name the projection 'movie-genre'
-
Include source (Movie) nodes
-
Include target (Genre) nodes
-
Preserve source node labels
-
Preserve target node labels
-
Preserve relationship types
-
Return projection statistics
Key points:
-
Match
Movienodes toGenrenodes viaIN_GENRErelationships -
Name the projection
'movie-genre' -
Preserve labels with
labels(source)andlabels(target) -
Run
nodeSimilarity.writewith'SIMILAR'as the relationship type
Check which movies are similar based on shared genres:
MATCH (m1:Movie)-[s:SIMILAR]->(m2:Movie) // (1)
RETURN m1.title, m2.title, s.score // (2)
ORDER BY s.score DESC // (3)
LIMIT 10 // (4)-
Match pairs of Movie nodes connected by SIMILAR relationships
-
Return the titles of both movies and their similarity score
-
Sort by score in descending order
-
Limit to top 10 most similar pairs
What this reveals: Movies that belong to similar genre combinations.
Comparing algorithm results
Each bipartite projection you created produces different node similarity results because the structure determines what "similarity" means.
User-Movie projection: Similarity is based on shared movie ratings (collaborative filtering)
Actor-Movie projection: Similarity is based on shared cast members (collaboration patterns)
Movie-Genre projection: Similarity is based on shared genre classifications (content-based similarity)
The same algorithm—node similarity—reveals completely different insights depending on your projection structure.
Understanding bipartite and multipartite graphs helps you design projections that answer specific analytical questions.
What’s next
You’ve now practiced creating multiple bipartite projections from the Movies dataset and running node similarity on each one—consolidating your understanding of how label preservation enables different types of analysis.
Each projection transformed the same data into different analytical contexts:
-
User-Movie (collaborative filtering)
-
Actor-Movie (collaboration patterns)
-
Movie-Genre (content-based similarity)
In the next lesson, you’ll put this knowledge to the test with a challenge that requires you to create your own bipartite projection and run node similarity independently.
Check your understanding
How Projections Affect Similarity
You ran node similarity on three different bipartite projections: User-Movie, Actor-Movie, and Movie-Genre.
Why did each projection produce different similarity results?
-
✓ The projection structure determines what "similarity" means—shared ratings vs. shared cast vs. shared genres
-
❏ Node similarity uses different algorithms depending on the node types in the projection
-
❏ The three projections had different numbers of nodes, changing the algorithm’s behavior
-
❏ GDS automatically adjusts similarity calculations based on the relationship types
Hint
Think about what node similarity does: it connects nodes that have shared neighbors. What are the neighbors in each projection?
Solution
The projection structure determines what "similarity" means—shared ratings vs. shared cast vs. shared genres.
Node similarity always does the same thing: it connects nodes on the same side of a bipartite graph based on shared neighbors on the other side.
However, what those neighbors represent changes the meaning of similarity:
-
User-Movie: Users are similar if they rated the same movies (collaborative filtering)
-
Actor-Movie: Actors are similar if they appeared in the same movies (collaboration patterns)
-
Movie-Genre: Movies are similar if they belong to the same genres (content-based similarity)
The algorithm doesn’t change—the analytical context does. This is why thoughtful projection design is fundamental to GDS work.
Summary
Bipartite projections with preserved labels enable node similarity to connect nodes on the same side of the graph based on shared neighbors.
You practiced creating three bipartite projections:
-
User-Movie: Found similar users based on rating patterns
-
Actor-Movie: Found similar actors based on collaboration patterns
-
Movie-Genre: Found similar movies based on genre classifications
The same algorithm—node similarity—reveals completely different insights depending on your projection structure. Understanding how to design meaningful bipartite projections is a fundamental GDS skill for relationship inference and recommendation systems.