Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArangoDB - how to implement a custom recommendation engine using graph?

Lets say we have a database of food items such as:

item1 = {name: 'item1', tags: ['mexican', 'spicy']};
item2 = {name: 'item2', tags: ['sweet', 'chocolate', 'nuts']};
item3 = {name: 'item3', tags: ['sweet', 'vanilla', 'cold']};

And we have a user looking for food recommendations, where they indicate their preference weight for some tags:

foodPref = {sweet: 4, chocolate: 11}

Now we need to calculate how well each item scores and recommend the best items:

item1 score = 0 (doesn't contain any of the tags user is looking for)
item2 score = 4 (contains the tag 'sweet')
item3 score = 15 (contains the tag 'sweet' and 'chocolate')

I have modeled the problem as a graph:sample graph

What's the correct way to get the recommendations -- a custom traversal object or just filter and count using AQL or just implement it in Foxx (javascript layer)?

Also, can you help out with a sample implementation for the methods you suggest?

Thanks in advance!

like image 201
Stan Lee Avatar asked Feb 28 '16 08:02

Stan Lee


1 Answers

First, lets create the collections and their contents the way you specified them. We will add a second user.

db._create("user")
db._create("tags")
db._create("dishes")

db.user.save({_key: 'user1'})
db.user.save({_key: 'user2'})

db.tags.save({_key: 'sweet'})
db.tags.save({_key: 'chocolate'})
db.tags.save({_key: 'vanilla'})
db.tags.save({_key: 'spicy'})

db.dishes.save({_key: 'item1'})
db.dishes.save({_key: 'item2'})
db.dishes.save({_key: 'item3'})

Now lets create the edge collections with their edges:

db._createEdgeCollection("userPreferences")
db._createEdgeCollection("dishTags")

db.userPreferences.save("user/user1", "tags/sweet", {score: 4})
db.userPreferences.save("user/user1", "tags/chocolate", {score: 11})
db.userPreferences.save("user/user2", "tags/sweet", {score: 27})
db.userPreferences.save("user/user2", "tags/vanilla", {score: 7})

db.dishTags.save("tags/sweet", "dishes/item2", {score: 4});
db.dishTags.save("tags/sweet", "dishes/item3", {score: 7})
db.dishTags.save("tags/chocolate", "dishes/item2", {score: 2})
db.dishTags.save("tags/vanilla", "dishes/item3", {score: 3})
db.dishTags.save("tags/spicy", "dishes/item1", {score: 666})

Our relations are like this:

user-[userPreferences]->tags-[dishTags]->dishes

finding out what user1 likes can be done with this query:

FOR v, e IN 1..2 OUTBOUND "user/user1" userPreferences, dishTags
  RETURN {item: v, connection: e}

if you now want to find all dishes that user1 likes best:

FOR v, e IN 2..2 OUTBOUND "user/user1" userPreferences, dishTags 
  FILTER e.score > 4 RETURN v

We filter for the score attribute.

Now we want to find another user that has the same preferences as user1 does:

FOR v, e IN 2..2 ANY "user/user1" userPreferences RETURN v

We go into ANY direction (forward and backward), but only are interested in the userPreferences edge collection, else 2..2 would also give use dishes. The way we do it now. we go back into the user collections to find users with similar preferences.

Whether or not creating a Foxx-service is a good option depends on personal preferences. Foxx is great if you want to combine & filter results on the server side, so client communication is less. You can also use it if you like to put your Application rather on top of microservices than on db-queries. Your application may then stay free of database specific code - it only operates with the microservice as its backend. There may be usecases where Foxx

In general, there is no "correct" way - there are different ways which you may prefer above others because of performance, code cleanness, scalability, etc.

like image 178
dothebart Avatar answered Sep 20 '22 08:09

dothebart