Recommendation Engine¶
Build a product recommendation system using collaborative filtering on a graph.
What You'll Learn¶
- Modeling user-product interactions
- Collaborative filtering with graph patterns
- Scoring and ranking recommendations
The Approach¶
We'll use the principle: "Users who bought X also bought Y" implemented as graph traversals.
Setup¶
Create the Data Model¶
with db.session() as session:
# Products
session.execute("""
INSERT (:Product {id: 'P1', name: 'Laptop', category: 'Electronics', price: 999})
INSERT (:Product {id: 'P2', name: 'Headphones', category: 'Electronics', price: 149})
INSERT (:Product {id: 'P3', name: 'Mouse', category: 'Electronics', price: 49})
INSERT (:Product {id: 'P4', name: 'Keyboard', category: 'Electronics', price: 79})
INSERT (:Product {id: 'P5', name: 'Monitor', category: 'Electronics', price: 299})
INSERT (:Product {id: 'P6', name: 'USB Hub', category: 'Electronics', price: 29})
""")
# Users
session.execute("""
INSERT (:User {id: 'U1', name: 'Alice'})
INSERT (:User {id: 'U2', name: 'Bob'})
INSERT (:User {id: 'U3', name: 'Carol'})
INSERT (:User {id: 'U4', name: 'Dave'})
INSERT (:User {id: 'U5', name: 'Eve'})
""")
Create Purchase History¶
with db.session() as session:
# Alice bought Laptop, Headphones, Mouse
session.execute("""
MATCH (u:User {id: 'U1'}), (p:Product {id: 'P1'})
INSERT (u)-[:PURCHASED {date: '2024-01-10'}]->(p)
""")
session.execute("""
MATCH (u:User {id: 'U1'}), (p:Product {id: 'P2'})
INSERT (u)-[:PURCHASED {date: '2024-01-11'}]->(p)
""")
session.execute("""
MATCH (u:User {id: 'U1'}), (p:Product {id: 'P3'})
INSERT (u)-[:PURCHASED {date: '2024-01-12'}]->(p)
""")
# Bob bought Laptop, Keyboard, Mouse
session.execute("""
MATCH (u:User {id: 'U2'}), (p:Product {id: 'P1'})
INSERT (u)-[:PURCHASED]->(p)
""")
session.execute("""
MATCH (u:User {id: 'U2'}), (p:Product {id: 'P4'})
INSERT (u)-[:PURCHASED]->(p)
""")
session.execute("""
MATCH (u:User {id: 'U2'}), (p:Product {id: 'P3'})
INSERT (u)-[:PURCHASED]->(p)
""")
# Carol bought Laptop, Monitor, USB Hub
session.execute("""
MATCH (u:User {id: 'U3'}), (p:Product {id: 'P1'})
INSERT (u)-[:PURCHASED]->(p)
""")
session.execute("""
MATCH (u:User {id: 'U3'}), (p:Product {id: 'P5'})
INSERT (u)-[:PURCHASED]->(p)
""")
session.execute("""
MATCH (u:User {id: 'U3'}), (p:Product {id: 'P6'})
INSERT (u)-[:PURCHASED]->(p)
""")
Generate Recommendations¶
Products Frequently Bought Together¶
with db.session() as session:
result = session.execute("""
MATCH (p1:Product {id: 'P1'})<-[:PURCHASED]-(u:User)-[:PURCHASED]->(p2:Product)
WHERE p1 <> p2
RETURN p2.name AS recommended, count(u) AS buyers
ORDER BY buyers DESC
LIMIT 5
""")
print("Frequently bought with 'Laptop':")
for row in result:
print(f" {row['recommended']} ({row['buyers']} buyers)")
Personalized Recommendations for a User¶
def get_recommendations(db, user_id: str, limit: int = 5):
with db.session() as session:
result = session.execute(f"""
// Find products similar users bought but this user hasn't
MATCH (u:User {{id: '{user_id}'}})-[:PURCHASED]->(p:Product)
<-[:PURCHASED]-(other:User)-[:PURCHASED]->(rec:Product)
WHERE NOT (u)-[:PURCHASED]->(rec)
RETURN rec.name AS product,
rec.price AS price,
count(DISTINCT other) AS score
ORDER BY score DESC
LIMIT {limit}
""")
return list(result)
recs = get_recommendations(db, 'U1')
print("Recommendations for Alice:")
for r in recs:
print(f" {r['product']} (${r['price']}) - score: {r['score']}")
Category-Based Recommendations¶
with db.session() as session:
result = session.execute("""
MATCH (u:User {id: 'U1'})-[:PURCHASED]->(bought:Product)
WITH u, collect(bought.id) AS purchased
MATCH (rec:Product)
WHERE rec.category = 'Electronics'
AND NOT rec.id IN purchased
RETURN rec.name, rec.price
""")
print("Other products in categories you've purchased from:")
for row in result:
print(f" {row['rec.name']} (${row['rec.price']})")