Implementing cocktail recommendation engine using neo4j
A recommendation engine can give site visitors an opportunity to discover the most relevant content on the site depending on the page user is browsing. A content publisher or a store can benefit from extra views/exposure or upselling.
Basic recommendation using similarity
In this post, I am sharing how I implemented a very simple cocktail recommendation system. It uses similarity and recommends the user with cocktails that have the most common ingredients with the cocktail the user is viewing. A benefit for users to see these recommendations is that if they are mixing the drink they are currently browsing, they will be able to make the recommended drinks with very little change in ingredients.
Cypher query
Let’s begin by composing our Cypher query.
First of all, let’s take an example of Mojito, and we want to recommend the user the drinks that are most similar to Mojito. Now, select Mojito using Cypher query.
MATCH (mojito:Drink {slug: 'Mojito'}) return mojito
MATCH
is like SELECT
in SQL
, it is followed by a pattern of a graph that Cypher will match and returns the respective, nodes and relations.
Nodes are represented by parenthesis and have the following sub-components (NODE_VARIABLE:NODE_LABEL {NODE_PROPERTY: PROPERTY_VALUE_TO_MATCH})
and RETURN
will allow you to select what to return in the result.
Noe4j browser can visualize the returned subgraph in a visual form. It even shows the primary relations of the returned node so, we can see all the ingredients of Mojito along with its node.
In cypher, you can use -[:RELATION_TYPE]-> in both directions to select relations of a node. The nodes can be named with variables as we did before for mojito. This time we will call it cocktail
and the related ingredient, ingredient
.
MATCH (cocktail:Drink {slug: 'Mojito'})-[:HAS_INGREDIENT]->(ingredient),
Next, we want to get all the drinks, other than the initial one, that are related to the ingredient.
(ingredient)<-[:HAS_INGREDIENT]-(otherCocktail:Drink)
WHERE cocktail <> otherCocktail
Now we can return the cocktail
, otherCocktail
and commonIngredients
and order it by the count of commonIngredients
RETURN cocktail.name, otherCocktail.name, collect(distinct ingredient.name) AS commonIngredients, size(collect(distinct ingredient)) as commonCount
ORDER BY commonCount DESC Limit 5
Where collect()
will aggregate all the ingredients otherwise separate rows into one, and size()
gives the length of the list.
Finally, our query will look something like follows.
MATCH (cocktail:Drink {slug: 'Mojito'})-[:HAS_INGREDIENT]->(ingredient),
(ingredient)<-[:HAS_INGREDIENT]-(otherCocktail:Drink)
WHERE cocktail <> otherCocktail
RETURN cocktail.name, otherCocktail.name, collect(distinct ingredient.name) AS commonIngredients, size(collect(distinct ingredient)) as commonCount
ORDER BY commonCount DESC Limit 5
Result
This query will return the following data as a result. For the sake of brevity, I chose to only return names of the drinks and components, but you can return any properties of the returned nodes.
╒═══════════════╤══════════════════════════╤══════════════════════════╤═════════════╕ │"cocktail.name"│"otherCocktail.name" │"commonIngredients" │"commonCount"│ ╞═══════════════╪══════════════════════════╪══════════════════════════╪═════════════╡ │"Mojito" │"Acapulco" │["rum","carbonated water",│5 │ │ │ │"mint","sugar","lemon"] │ │ ├───────────────┼──────────────────────────┼──────────────────────────┼─────────────┤ │"Mojito" │"Sudret Iced Tea" │["rum","carbonated water",│5 │ │ │ │"mint","sugar","lemon"] │ │ ├───────────────┼──────────────────────────┼──────────────────────────┼─────────────┤ │"Mojito" │"Mojito #2" │["rum","carbonated water",│5 │ │ │ │"mint","sugar","lemon"] │ │ ├───────────────┼──────────────────────────┼──────────────────────────┼─────────────┤ │"Mojito" │"Rum Rickey" │["rum","carbonated water",│4 │ │ │ │"sugar","lemon"] │ │ ├───────────────┼──────────────────────────┼──────────────────────────┼─────────────┤ │"Mojito" │"Frozen Mango And Mint Spi│["rum","mint","sugar","lem│4 │ │ │ced Daiquiri" │on"] │ │ └───────────────┴──────────────────────────┴──────────────────────────┴─────────────┘
Conclusion
The recommendation engine can enrich your site with feature of a great value to both the visitor and you as a publisher/business/store. If done right, a very simple implementation can add value to your site. The given example is as simple as it can get, but with Neo4j you can use numerous built-in graph algorithms to help you with even an extremely sophisticated recommendation engine.