{ "cells": [ { "attachments": { "image.png": { "image/png": "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" } }, "cell_type": "markdown", "metadata": {}, "source": [ "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n", "SPDX-License-Identifier: Apache-2.0\n", "\n", "![image.png](attachment:image.png)\n", "\n", "# Dining By Friends in Amazon Neptune\n", "\n", "> **Foreword:** While writing Graph Databases in Action, one of our driving principals was to build a framework that leveraged open source frameworks and languages as much as possible. Amazon Neptune's support for both Apache TinkerPop Gremlin and openCypher makes it a great choice to demonstrate the transferability of the framework of our book across query languages - *Dave Bechberger, co-author of Graph Databases in Action*\n", "\n", "## Background\n", "\n", "*[Graph Databases in Action](https://www.manning.com/books/graph-databases-in-action?a_aid=bechberger)* by Manning Publications is a book that provides readers with a framework for how to approach building applications using common graph development patterns. To teach this framework, the book uses these patterns to build a fictitious application, DiningByFriends, that uses friends and ratings to provide personalized restaurant recommendations. To accomplish this, DiningByFriends follows the development of three common graph use cases, a social network of users, a recommendation engine, and a personalization engine. Each of these use cases builds on top of one another in such a way as to leverage existing skills to answer ever more complex questions.\n", "\n", "In the following notebook, we demonstrate how to use Amazon Neptune to build the queries for the DiningByFriends app. This notebook will not focus on the processing and framework for constructing the queries, which is described in detail in the book. Instead, this notebook will highlight how to accomplish these use cases within Amazon Neptune. \n", "\n", "One of the unique aspects of Amazon Neptune is that it allows you to run both Apache TinkerPop Gremlin and openCypher queries on top of the same data. In the notebook below, we have presented a selection of the most important queries from the book in both languages. This allows you to look at the differences between the two query languages. In each example, we will first show the Gremlin traversal from the book and discuss any modifications required. We will then show the comparable openCypher query and discuss any highlight and differences you may want to be aware of between the two languages. You are welcome to run whichever you prefer, or both, to see the results.\n", "\n", "## Getting Started\n", "\n", "To get started with this notebook, we have added the data used in the book to the %seed command, so you can load your cluster using the command below.\n", "*Note* - This notebook assumes the cluster was empty before this command being run.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%seed --model Property_Graph --dataset dining_by_friends --run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have our data loaded let's take a look at how DiningByFriends works when using Amazon Neptune.\n", "\n", "# Social Network\n", "\n", "In Chapters 3 and 4 of the book, we went through the steps required to build a simple graph-based application by creating a social network for DiningByFriends. Social networks are often thought of as some of the most canonical use cases, where people are represented as the nodes in the graph and the connections between the nodes are represented by edges.\n", "\n", "Let's look at our entire social network data for DiningByFriends. Typically, this is not recommended as networks with more than hundreds of results tend to be unintelligible. This social network graph is a realistic yet much smaller representation than what you would normally be working against.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin -p v,oute,inv -d first_name\n", "\n", "g.V().hasLabel('person').outE('friends').inV().path().by(elementMap())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher -d first_name\n", "\n", "MATCH p=(:person)-[:friends]->(:person) \n", "RETURN p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Get Friends - Who are Ted's Friends?\n", "\n", "In *Graph Databases in Action*, a lot of time was spent learning an underlying framework for thinking about graph problems and how to approach solving them. In the book, we went through the steps to develop a Gremlin traversal to answer \"Who are Ted's friends?\", shown below. When working with Neptune, we can copy that traversal to get that answer." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "\n", "g.V().has('person', 'first_name', 'Ted').\n", "out('friends').valueMap('first_name')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher \n", "\n", "MATCH (:person {first_name:'Ted'})-[:friends]->(p) \n", "RETURN p.first_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While we are not going to go into much detail about the differences between Gremlin and openCypher, one important difference to notice is that in Gremlin returns the `first_name` as a List while openCypher returns it as a single value. \n", "\n", "## Get Friends of Friends - Who are the Friends of Friends of Ted?\n", "\n", "As with the previous query, we can copy and paste the query developed in the book for use with Amazon Neptune. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "g.V().has('person', 'first_name', 'Ted').\n", " repeat(\n", " out('friends')\n", " ).times(2).\n", " values('first_name')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher \n", "\n", "MATCH (:person {first_name:'Ted'})-[:friends]->()-[:friends]->(p:person) \n", "RETURN DISTINCT p.first_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this query, you may notice that both the Gremlin and openCypher return a single value. For Gremlin, this is due to the use of the values() step instead of the valueMap() step. However, you may also notice that the openCypher version still remains a key/value pair while the Gremlin version is just a string." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get the Path between Two Friends - How are Ted and Denise connected?\n", "\n", "One of the most powerful things in graph databases is the ability to perform recursive traversals over your data to find unknown connections. This functionality is available in both Gremlin, through the use of repeat(), and in openCypher, through the use of variable length paths `[:1..4]`. In the book, we went through why constructing paths is important and the sorts of problems you may potentially encounter when using them. \n", "\n", "While we could copy and paste the Gremlin traversal from the book, the query below has a few changes to it to make it display better. Specifically:\n", "\n", "* `both(\"friends\").simplePath()` changed to `bothE(\"friends\").otherV().simplePath()` to include the edge direction in the visualization of the results\n", "* `by(elementMap())` was added to the `path()` to return the properties for display" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin -d first_name\n", "\n", "g.V().has(\"person\", \"first_name\", 'Ted').\n", " until(has(\"person\", \"first_name\", 'Denise')).\n", " repeat(\n", " bothE(\"friends\").otherV().simplePath()\n", " ).path().by(elementMap())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher -d first_name\n", "MATCH p=(src:person {first_name:'Ted'})-[:friends*1..]-(dst:person {first_name:'Denise'})\n", "RETURN p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When looking at the openCypher query above, it appears very intuitive, yet there is a potential complexity hiding regarding cycles. In Gremlin, we add a `simplePath()` step to filter out any vertices we have already visited in a recursive traversal which allows you to avoid cycles in a graph. openCypher takes a different approach and only prevents you from traversing the same edge multiple times in the same path. This means that if you had two edges between vertices (e.g. two `friends` edges between `Dave` and `Kelly`), then each one would be treated as a unique path which can lead to repeatedly cycling through the same set of vertices. In general, the best way to avoid this is to add a maximum number to the variable length path expression (e.g. `[:friends*1..4]`)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Recommendation\n", "\n", "In Chapter 8, we used known walks in graphs to build out a recommendation engine for DiningByFriends. A walk is a specific type of path in a graph which consists of a set of vertices and incident edges, and a known walk is a path where you specify certain vertices and incident edges. Recommendation engines are a common graph use case, and known walks are one way in which we can use the connections within our data to provide answers to complex real-world questions.\n", "\n", "Let's take a look at the restaurant and review data within our DiningByFriends data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin -d name\n", "\n", "g.V().hasLabel('restaurant').bothE().otherV().path().by(elementMap())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher -d name\n", "MATCH p=(:restaurant)-[]-()\n", "RETURN p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What are the newest reviews for a restaurant?\n", "\n", "One of the simplest patterns of known walks in a graph are to traverse from a vertex to an incident edge, then to the adjacent vertex. To see this function in Amazon Neptune, copy and paste the Gremlin traversal we developed in the book." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "\n", "g.V().has(\"restaurant\", \"restaurant_id\", 8).\n", " in(\"about\").\n", " order().\n", " by(\"created_date\").\n", " limit(3).\n", " elementMap(\"rating\", \"body\", \"created_date\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher \n", "MATCH (:restaurant {restaurant_id:8})<-[:about]-(r:review)\n", "RETURN r.rating, r.body, r.created_date\n", "ORDER BY r.created_date DESC\n", "LIMIT 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Comparing the openCypher query to the equivalent Gremlin traversal, we see that openCypher's ASCII art-based pattern-matching syntax (e.g. `()-[]->()`) excels at making the query easy to read and understand when dealing with these sorts of known walks in queries." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What restaurants does a person rate highly?\n", "\n", "Below is an example of a more complicated known walk that uses grouping and projecting of results to build a more complex result set." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "\n", "g.V().has(\"person\", \"person_id\", 8).\n", " out(\"lives\").\n", " in(\"within\").\n", " where(inE(\"about\")).\n", " group().\n", " by(identity()).\n", " by(in(\"about\").values(\"rating\").mean()).\n", " unfold().\n", " order().\n", " by(values, Order.desc).\n", " limit(10).\n", " project(\"name\", \"address\", \"rating_avg\").\n", " by(select(keys).values(\"name\")).\n", " by(select(keys).values(\"address\")).\n", " by(select(values))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher \n", "\n", "MATCH (:person {person_id:8})-[:lives]->(:city)<-[:within]-(r:restaurant)<-[:about]-(v:review)\n", "WITH r, avg(v.rating) AS rating_average\n", "RETURN r.name AS name, \n", "r.address AS address, rating_average\n", "ORDER BY rating_average DESC \n", "LIMIT 10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looking at the two different queries, one difference you notice is that the openCypher version of the query does not explicitly require grouping the results together to get the average rating. In openCypher, grouping is implicitly performed when returning a key and an associated aggregate, as is done within this portion of the query `WITH r, avg(v.rating) AS rating_average`. In our Gremlin traversal, we must explicitly perform this grouping to get the average rating for a specific restaurant." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What restaurants near the person with a specific cuisine is the highest rated?\n", "\n", "The final example of a known walk from the book is an example of a known walk that traverses several branches to filter and answer the question. This was another example query where we could copy and paste our Gremlin traversal directly from the book." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "\n", "g.V().has('person','person_id',1).\n", " out('lives').\n", " in('within').\n", " where(out('serves').has('name', within('diner', 'bar'))).\n", " where(inE('about')).\n", " group().\n", " by(identity()).\n", " by(__.in('about').values('rating').mean()).\n", " unfold().\n", " order().\n", " by(values, desc).\n", " limit(1).\n", " project('name', 'address', 'rating_average', 'cuisine').\n", " by(select(keys).values('name')).\n", " by(select(keys).values('address')).\n", " by(select(values)).\n", " by(select(keys).out('serves').values('name'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher\n", "\n", "MATCH (p:person)-[:lives]->(:city)<-[:within]-(r:restaurant)-[:serves]->(c:cuisine) \n", "WHERE p.person_id = 1 AND c.name IN ['diner', 'bar'] \n", "WITH r\n", "MATCH (r)<-[:about]-(v:review) \n", "WITH r, avg(v.rating) AS rating_average \n", "RETURN r.name AS name, \n", " r.address AS address, rating_average\n", "ORDER BY rating_average DESC \n", "LIMIT 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One of the differences you will notice between the Gremlin and openCypher traversals is that in Gremlin we traverse our graph, filter on branches using `where()`, and then group the results at the end. With openCypher's pattern matching syntax, we have to approach this slightly differently. openCypher does not provide the same branching capabilities that Gremlin has, so we must first match our base pattern (Finding restaurants that serve a cuisine in the person's city), filter on this pattern to find the types of cuisine we want, then do an additional match on those filtered results to get the review and calculate the average." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Personalization\n", "\n", "Personalization of restaurant recommendations is a type of [collaborative filtering](https://en.wikipedia.org/wiki/Collaborative_filtering) approach to recommendation engines that uses information about a user's likes/dislikes (in this case, we use your friends likes/dislikes) to provide recommendations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What restaurants do my friends recommend?\n", "\n", "To answer this question, we must take a different approach from the one described in the book. In the book, we leveraged sessions within Gremlin to pull a subgraph into the memory of Gremlin server for computing recommendations. While this approach can provide some benefits, as discussed in the book, using sessions is not supported in Amazon Neptune. Luckily, this is not the only way to achieve this end goal; another way is create the subgraph and filter on the recommendations within the same traversal as we have done with the traversal below.\n", "\n", "We recommend you also try this traversal with some different `person_id` values to see how the results are personalized for a specific user." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "\n", "g.V().\n", " has('person', 'person_id', 8).\n", " both('friends').\n", " both('wrote').as('v').\n", " both('about').\n", " where(both('within').has('city', 'name', 'Houston')).\n", " group().by().by(select('v').values('rating').mean()).unfold().\n", " order().by(values, desc).limit(3).\n", " project(\"rating_average\",\"name\",\"address\",\"restaurant_id\").\n", " by(__.select(values)).\n", " by(__.select(keys).values(\"name\")).\n", " by(__.select(keys).values(\"address\")).\n", " by(__.select(keys).values(\"restaurant_id\"))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%opencypher \n", "\n", "MATCH (p:person {person_id:8})-[:friends]-()-[:wrote]-(v)-[:about]-\n", "(r:restaurant)-[:within]-(c:city {name:'Houston'}) \n", "RETURN avg(v.rating) as rating_average, r.name AS name, r.address AS address, r.restaurant_id AS restaurant_id\n", "ORDER BY rating_average DESC\n", "LIMIT 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Summary\n", "\n", "As we can see throughout this notebook, both Apache TinkerPop Gremlin and openCypher are powerful graph query languages that have their strengths and weaknesses. The more important thing to understand is that no matter which language you choose, the framework and approach to working with graph data to build applications remains the same. " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.13" } }, "nbformat": 4, "nbformat_minor": 4 }