├── .gitignore ├── README.adoc ├── empty.py ├── generate_notebook.py ├── generate_notebooks.sh ├── images └── installation.png ├── notebooks ├── AllPairsShortestPath.ipynb ├── BetweennessCentrality.ipynb ├── ClosenessCentrality.ipynb ├── DegreeCentrality.ipynb ├── LabelPropagation.ipynb ├── Louvain.ipynb ├── PageRank.ipynb ├── SingleSourceShortestPath.ipynb ├── StronglyConnectedComponents.ipynb ├── TriangleCounting.ipynb ├── UnweightedConnectedComponents.ipynb ├── WeightedConnectedComponents.ipynb ├── figure │ ├── neovis.js │ └── vis.min.css └── scripts │ ├── algo.py │ └── vis.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .ipynb_checkpoints/ 3 | a/ 4 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Neo4j Graph Algorithms Jupyter Notebooks 2 | 3 | This repository contains Jupyter Notebooks for each of the https://neo4j-contrib.github.io/neo4j-graph-algorithms/[Neo4j graph algorithms^]. 4 | 5 | 6 | == Path finding 7 | 8 | * link:notebooks/AllPairsShortestPath.ipynb[All Pairs Shortest Path^] 9 | * link:notebooks/SingleSourceShortestPath.ipynb[Single Source Shortest Path^] 10 | 11 | == Centrality 12 | 13 | * link:notebooks/DegreeCentrality.ipynb[Degree Centrality^] 14 | * link:notebooks/ClosenessCentrality.ipynb[Closeness Centrality^] 15 | * link:notebooks/BetweennessCentrality.ipynb[Betweenness Centrality^] 16 | 17 | == Community Detection 18 | 19 | * link:notebooks/StronglyConnectedComponents.ipynb[Strongly Connected Components^] 20 | * link:notebooks/WeightedConnectedComponents.ipynb[Weighted Union Find^] 21 | * link:notebooks/UnweightedConnectedComponents.ipynb[Unweighted Union Find^] 22 | * link:notebooks/LabelPropagation.ipynb[Label Propagation^] 23 | * link:notebooks/Louvain.ipynb[Louvain^] 24 | * link:notebooks/TriangleCounting.ipynb[Triangle Counting^] 25 | 26 | == Run the notebooks locally 27 | 28 | If we want to run the notebooks locally we need to setup Python and Neo4j environments. 29 | 30 | === Python 31 | 32 | I use https://virtualenv.pypa.io/en/stable/[virtualenv^] but that's just one option. 33 | We can run the following set of commands to create a Python environment with the libraries installed: 34 | 35 | ``` 36 | virtualenv a 37 | . a/bin/activate 38 | pip install -r requirements.txt 39 | ``` 40 | 41 | === Neo4j 42 | 43 | We'll also need to have a Neo4j server, with the Graph Algorithms library installed, running locally. 44 | The easiest way to do this is to download the Neo4j Desktop from http://neo4j.com/download[neo4j.com/download^]. 45 | 46 | Once we've done that we can create a project and then install Graph Algorithms from the Plugins section. 47 | 48 | image::images/installation.png[] 49 | 50 | 51 | === Launching Jupyter 52 | 53 | We're now ready to launch the Jupyter server. 54 | 55 | The notebooks assume that there's a Neo4j server running at `localhost:7687` with username `neo4j` and password `neo`. 56 | You can override these values with environment variables. 57 | 58 | ``` 59 | NEO4J_HOST="bolt://localhost:7687" NEO4J_USER="neo4j" NEO4J_PASSWORD="neo" jupyter notebook 60 | ``` 61 | 62 | Navigate to http://localhost:8888/notebooks - the notebooks will be waiting! -------------------------------------------------------------------------------- /empty.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from neo4j.v1 import GraphDatabase, basic_auth 4 | 5 | host = os.environ.get("NEO4J_HOST", "bolt://localhost") 6 | user = os.environ.get("NEO4J_USER", "neo4j") 7 | password = os.environ.get("NEO4J_PASSWORD", "neo") 8 | driver = GraphDatabase.driver(host, auth=basic_auth(user, password)) 9 | 10 | 11 | def clear_db(): 12 | with driver.session() as session: 13 | session.run("MATCH (n) DETACH DELETE n") 14 | 15 | 16 | clear_db() 17 | -------------------------------------------------------------------------------- /generate_notebook.py: -------------------------------------------------------------------------------- 1 | import re 2 | import nbformat as nbf 3 | import sys 4 | from urllib.request import urlopen 5 | 6 | 7 | def find_tag(file, tag): 8 | inner_text = "" 9 | found_tag = False 10 | with urlopen(file) as pagerank_file: 11 | for line in pagerank_file.readlines(): 12 | line = line.decode("utf-8") 13 | 14 | if line.startswith("// tag::"): 15 | groups = re.match("// tag::(.*)\[\]", line) 16 | tag_name = groups[1] 17 | 18 | if tag_name == tag: 19 | found_tag = True 20 | continue 21 | 22 | if line.startswith("// end::"): 23 | groups = re.match("// end::(.*)\[\]", line) 24 | tag_name = groups[1] 25 | if tag_name == tag: 26 | found_tag = False 27 | continue 28 | 29 | if found_tag: 30 | inner_text += "{0}".format(line) 31 | return inner_text.strip() 32 | 33 | 34 | if len(sys.argv) < 4: 35 | print("Usage: python generate_notebook.py ") 36 | sys.exit(1) 37 | 38 | algorithm_name = sys.argv[1] 39 | algorithm_file = sys.argv[2] 40 | cypher_file = sys.argv[3] 41 | 42 | algorithm_description = find_tag(algorithm_file, "introduction") 43 | 44 | stream_graph_tag = "stream-sample-graph" 45 | if len(sys.argv) >= 5: 46 | stream_graph_tag = sys.argv[4] 47 | 48 | explanation_tag = "stream-sample-graph-explanation" 49 | if len(sys.argv) >= 6: 50 | explanation_tag = sys.argv[5] 51 | 52 | write_graph_tag = "write-sample-graph" 53 | if len(sys.argv) >= 7: 54 | write_graph_tag = sys.argv[6] 55 | 56 | heading_text = """\ 57 | # {0} 58 | {1} 59 | 60 | First we'll import the Neo4j driver and Pandas libraries: 61 | """.format(algorithm_name, algorithm_description) 62 | 63 | imports = """\ 64 | from neo4j.v1 import GraphDatabase, basic_auth 65 | import pandas as pd 66 | import os""" 67 | 68 | driver_setup_text = """\ 69 | Next let's create an instance of the Neo4j driver which we'll use to execute our queries. 70 | """ 71 | 72 | driver_setup = """\ 73 | host = os.environ.get("NEO4J_HOST", "bolt://localhost") 74 | user = os.environ.get("NEO4J_USER", "neo4j") 75 | password = os.environ.get("NEO4J_PASSWORD", "neo") 76 | driver = GraphDatabase.driver(host, auth=basic_auth(user, password))""" 77 | 78 | create_graph_text = """\ 79 | Now let's create a sample graph that we'll run the algorithm against. 80 | """ 81 | 82 | create_graph_content = find_tag(cypher_file, "create-sample-graph") 83 | create_graph = """\ 84 | create_graph_query = '''\ 85 | 86 | %s 87 | ''' 88 | 89 | with driver.session() as session: 90 | result = session.write_transaction(lambda tx: tx.run(create_graph_query)) 91 | print("Stats: " + str(result.consume().metadata.get("stats", {})))""" % create_graph_content 92 | 93 | streaming_graph_text = """\ 94 | Finally we can run the algorithm by executing the following query: 95 | """ 96 | 97 | streaming_query_content = find_tag(cypher_file, stream_graph_tag) 98 | 99 | run_algorithm = '''\ 100 | streaming_query = """\ 101 | 102 | %s 103 | """ 104 | 105 | with driver.session() as session: 106 | result = session.read_transaction(lambda tx: tx.run(streaming_query)) 107 | df = pd.DataFrame([r.values() for r in result], columns=result.keys()) 108 | 109 | df''' % streaming_query_content 110 | 111 | streaming_graph_explanation_text = find_tag(algorithm_file, explanation_tag) 112 | 113 | write_graph_text = '''We can also call a version of the algorithm that will store the result as a property on a 114 | node. This is useful if we want to run future queries that use the result.''' 115 | 116 | write_query_content = find_tag(cypher_file, write_graph_tag) 117 | 118 | write_graph = '''\ 119 | write_query = """\ 120 | 121 | %s 122 | """ 123 | 124 | with driver.session() as session: 125 | session.write_transaction(lambda tx: tx.run(write_query))''' % write_query_content 126 | 127 | viz_intro_text = '''\ 128 | ## Graph Visualisation 129 | 130 | Sometimes a picture can tell more than a table of results and this is often the case with graph algorithms. 131 | Let's see how to create a graph visualization using neovis.js. 132 | 133 | First we'll create a div into which we will generate the visualisation.''' 134 | 135 | python_to_js_text = '''Next we need to define the query that the visualization will be generated from, along with config 136 | that describes which properties will be used for node size, node colour, and relationship width. 137 | 138 | We'll then define a JavaScript variable that contains all our parameters.''' 139 | 140 | neo_vis_js_text = '''Now we're ready to call neovis.js and generate our graph visualisation. 141 | The following code will create an interactive graph into the div defined above. 142 | It will also extract an image representation of the graph and display that in the cell below.''' 143 | 144 | query = "MATCH (p1:Page)-[r:LINKS]->(p2:Page) RETURN *" 145 | 146 | labels_json = { 147 | "Page": { 148 | "caption": "name", 149 | "size": "pagerank" 150 | } 151 | } 152 | 153 | relationships_json = { 154 | "LINKS": { 155 | "thickness": "weight", 156 | "caption": False 157 | } 158 | } 159 | 160 | setup_js_graph_cell = '''\ 161 | from IPython.core.display import Javascript 162 | import json 163 | from scripts.algo import viz_config, render_image 164 | 165 | config = viz_config("%s") 166 | query = config["query"] 167 | labels_json = config["labels_json"] 168 | relationships_json = config["relationships_json"] 169 | 170 | json_graph = { 171 | "query": query, 172 | "labels": labels_json, 173 | "relationships": relationships_json, 174 | "host": host, 175 | "user": user, 176 | "password": password 177 | } 178 | 179 | Javascript("""window.jsonGraph={};""".format(json.dumps(json_graph)))''' % algorithm_name 180 | 181 | neo_vis_div_cell = '''\ 182 | %%html 183 | 199 |
''' 200 | 201 | neo_vis_js_cell = '''\ 202 | %%javascript 203 | var output_area = this; 204 | requirejs(['neovis.js'], function(NeoVis){ 205 | var config = { 206 | container_id: "viz", 207 | server_url: window.jsonGraph.host, 208 | server_user: window.jsonGraph.user, 209 | server_password: window.jsonGraph.password, 210 | labels: window.jsonGraph.labels, 211 | relationships: window.jsonGraph.relationships, 212 | initial_cypher: window.jsonGraph.query 213 | }; 214 | 215 | let viz = new NeoVis.default(config); 216 | viz.render(); 217 | 218 | viz.onVisualizationRendered(function(ctx) { 219 | let imageSrc = ctx.canvas.toDataURL(); 220 | let kernel = IPython.notebook.kernel; 221 | let command = "image_src = '" + imageSrc + "'"; 222 | kernel.execute(command); 223 | 224 | var cell_element = output_area.element.parents('.cell'); 225 | var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element); 226 | var cell = Jupyter.notebook.get_cell(cell_idx+1); 227 | cell.set_text("render_image(image_src)") 228 | cell.execute(); 229 | }); 230 | });''' 231 | 232 | display_neo_vis_cell = '''''' 233 | 234 | nb = nbf.v4.new_notebook() 235 | nb['cells'] = [nbf.v4.new_markdown_cell(heading_text), 236 | nbf.v4.new_code_cell(imports), 237 | nbf.v4.new_markdown_cell(driver_setup_text), 238 | nbf.v4.new_code_cell(driver_setup), 239 | nbf.v4.new_markdown_cell(create_graph_text), 240 | nbf.v4.new_code_cell(create_graph), 241 | nbf.v4.new_markdown_cell(streaming_graph_text), 242 | nbf.v4.new_code_cell(run_algorithm), 243 | nbf.v4.new_markdown_cell(streaming_graph_explanation_text), 244 | nbf.v4.new_markdown_cell(write_graph_text), 245 | nbf.v4.new_code_cell(write_graph), 246 | nbf.v4.new_markdown_cell(viz_intro_text), 247 | nbf.v4.new_code_cell(neo_vis_div_cell), 248 | nbf.v4.new_markdown_cell(python_to_js_text), 249 | nbf.v4.new_code_cell(setup_js_graph_cell), 250 | nbf.v4.new_markdown_cell(neo_vis_js_text), 251 | nbf.v4.new_code_cell(neo_vis_js_cell), 252 | nbf.v4.new_code_cell(display_neo_vis_cell) 253 | ] 254 | 255 | output_file = 'notebooks/{0}.ipynb'.format(algorithm_name.replace(" ", "")) 256 | 257 | with open(output_file, 'w') as f: 258 | nbf.write(nb, f) 259 | -------------------------------------------------------------------------------- /generate_notebooks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | python generate_notebook.py \ 5 | "Page Rank" \ 6 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/pagerank.adoc" \ 7 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/pagerank.cypher" 8 | 9 | python generate_notebook.py \ 10 | "Betweenness Centrality" \ 11 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/betweenness-centrality.adoc" \ 12 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/betweenness-centrality.cypher" 13 | 14 | python generate_notebook.py \ 15 | "Louvain" \ 16 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/louvain.adoc" \ 17 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/louvain.cypher" 18 | 19 | python generate_notebook.py \ 20 | "Closeness Centrality" \ 21 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/closeness-centrality.adoc" \ 22 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/closeness-centrality.cypher" 23 | 24 | python generate_notebook.py \ 25 | "Degree Centrality" \ 26 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/degree-centrality.adoc" \ 27 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/degree-centrality.cypher" 28 | 29 | python generate_notebook.py \ 30 | "Unweighted Connected Components" \ 31 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/connected-components.adoc" \ 32 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/connected-components.cypher" \ 33 | "unweighted-stream-sample-graph" \ 34 | "unweighted-stream-sample-graph-explanation" \ 35 | "unweighted-write-sample-graph" 36 | 37 | python generate_notebook.py \ 38 | "Weighted Connected Components" \ 39 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/connected-components.adoc" \ 40 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/connected-components.cypher" \ 41 | "weighted-stream-sample-graph" \ 42 | "weighted-stream-sample-graph-explanation" \ 43 | "weighted-write-sample-graph" 44 | 45 | python generate_notebook.py \ 46 | "Label Propagation" \ 47 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/label-propagation.adoc" \ 48 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/label-propagation.cypher" 49 | 50 | python generate_notebook.py \ 51 | "Strongly Connected Components" \ 52 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/strongly-connected-components.adoc" \ 53 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/strongly-connected-components.cypher" 54 | 55 | python generate_notebook.py \ 56 | "Louvain" \ 57 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/louvain.adoc" \ 58 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/louvain.cypher" 59 | 60 | python generate_notebook.py \ 61 | "Single Source Shortest Path" \ 62 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/single-shortest-path.adoc" \ 63 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/single-shortest-path.cypher" \ 64 | "single-pair-stream-sample-graph" \ 65 | "single-pair-stream-sample-graph-explanation" 66 | 67 | python generate_notebook.py \ 68 | "All Pairs Shortest Path" \ 69 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/all-pairs-shortest-path.adoc" \ 70 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/single-shortest-path.cypher" \ 71 | "all-pairs-sample-graph" \ 72 | "all-pairs-stream-sample-graph-explanation" 73 | 74 | python generate_notebook.py \ 75 | "Triangle Counting" \ 76 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/triangleCount.adoc" \ 77 | "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/3.2/doc/asciidoc/scripts/triangle-count.cypher" \ 78 | "stream-triples" 79 | 80 | for file in `find notebooks -name "*.ipynb" -maxdepth 1`; do 81 | echo $file 82 | python empty.py 83 | jupyter nbconvert --execute --inplace $file 84 | done 85 | -------------------------------------------------------------------------------- /images/installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-graph-analytics/graph-algorithms-notebooks/e1dadd7ae946ff9160d055da1e7776c39e6cd26e/images/installation.png -------------------------------------------------------------------------------- /notebooks/AllPairsShortestPath.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# All Pairs Shortest Path\n", 8 | "_All Pairs Shortest Path_ (APSP) calculates the shortest (weighted) path between all pairs of nodes.\n", 9 | "This algorithm has optimisations that make it quicker than calling the SSSP algorithm for every pair of nodes in the graph.\n", 10 | "\n", 11 | "First we'll import the Neo4j driver and Pandas libraries:\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from neo4j.v1 import GraphDatabase, basic_auth\n", 21 | "import pandas as pd\n", 22 | "import os" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "Next let's create an instance of the Neo4j driver which we'll use to execute our queries.\n" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 2, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "host = os.environ.get(\"NEO4J_HOST\", \"bolt://localhost\") \n", 39 | "user = os.environ.get(\"NEO4J_USER\", \"neo4j\")\n", 40 | "password = os.environ.get(\"NEO4J_PASSWORD\", \"neo\")\n", 41 | "driver = GraphDatabase.driver(host, auth=basic_auth(user, password))" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "Now let's create a sample graph that we'll run the algorithm against.\n" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 3, 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "Stats: {'labels-added': 6, 'relationships-created': 11, 'nodes-created': 6, 'properties-set': 17}\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "create_graph_query = '''\n", 66 | "CREATE (a:Loc{name:'A'}), (b:Loc{name:'B'}), (c:Loc{name:'C'}), \n", 67 | " (d:Loc{name:'D'}), (e:Loc{name:'E'}), (f:Loc{name:'F'}),\n", 68 | " (a)-[:ROAD {cost:50}]->(b),\n", 69 | " (a)-[:ROAD {cost:50}]->(c),\n", 70 | " (a)-[:ROAD {cost:100}]->(d),\n", 71 | " (a)-[:RAIL {cost:50}]->(d),\n", 72 | " (b)-[:ROAD {cost:40}]->(d),\n", 73 | " (c)-[:ROAD {cost:40}]->(d),\n", 74 | " (c)-[:ROAD {cost:80}]->(e),\n", 75 | " (d)-[:ROAD {cost:30}]->(e),\n", 76 | " (d)-[:ROAD {cost:80}]->(f),\n", 77 | " (e)-[:ROAD {cost:40}]->(f),\n", 78 | " (e)-[:RAIL {cost:20}]->(f);\n", 79 | "'''\n", 80 | "\n", 81 | "with driver.session() as session:\n", 82 | " result = session.write_transaction(lambda tx: tx.run(create_graph_query))\n", 83 | " print(\"Stats: \" + str(result.consume().metadata.get(\"stats\", {})))" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "Finally we can run the algorithm by executing the following query:\n" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 4, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "data": { 100 | "text/html": [ 101 | "
\n", 102 | "\n", 115 | "\n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | "
sourcetargetdistance
0AF160.0
1AE120.0
2BF110.0
3CF110.0
4AD90.0
5BE70.0
6DF70.0
7CE70.0
8AB50.0
9AC50.0
\n", 187 | "
" 188 | ], 189 | "text/plain": [ 190 | " source target distance\n", 191 | "0 A F 160.0\n", 192 | "1 A E 120.0\n", 193 | "2 B F 110.0\n", 194 | "3 C F 110.0\n", 195 | "4 A D 90.0\n", 196 | "5 B E 70.0\n", 197 | "6 D F 70.0\n", 198 | "7 C E 70.0\n", 199 | "8 A B 50.0\n", 200 | "9 A C 50.0" 201 | ] 202 | }, 203 | "execution_count": 4, 204 | "metadata": {}, 205 | "output_type": "execute_result" 206 | } 207 | ], 208 | "source": [ 209 | "streaming_query = \"\"\"\n", 210 | "CALL algo.allShortestPaths.stream('cost',{nodeQuery:'Loc',defaultValue:1.0})\n", 211 | "YIELD sourceNodeId, targetNodeId, distance\n", 212 | "WITH sourceNodeId, targetNodeId, distance \n", 213 | "WHERE algo.isFinite(distance) = true\n", 214 | "\n", 215 | "MATCH (source:Loc) WHERE id(source) = sourceNodeId\n", 216 | "MATCH (target:Loc) WHERE id(target) = targetNodeId\n", 217 | "WITH source, target, distance WHERE source <> target\n", 218 | "\n", 219 | "RETURN source.name AS source, target.name AS target, distance\n", 220 | "ORDER BY distance DESC\n", 221 | "LIMIT 10\n", 222 | "\"\"\"\n", 223 | "\n", 224 | "with driver.session() as session:\n", 225 | " result = session.read_transaction(lambda tx: tx.run(streaming_query)) \n", 226 | " df = pd.DataFrame([r.values() for r in result], columns=result.keys())\n", 227 | "\n", 228 | "df" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": {}, 234 | "source": [ 235 | "This query returned the top 10 pairs of nodes that are the furthest away from each other.\n", 236 | "\"F\" and \"E\" seem to be quite distant from the others." 237 | ] 238 | } 239 | ], 240 | "metadata": {}, 241 | "nbformat": 4, 242 | "nbformat_minor": 2 243 | } 244 | -------------------------------------------------------------------------------- /notebooks/BetweennessCentrality.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Betweenness Centrality\n", 8 | "_Betweenness Centrality_ is a way of detecting the amount of influence a node has over the flow of information in a graph.\n", 9 | "\n", 10 | "image::../images/betweenness_centrality.png[]\n", 11 | "\n", 12 | "It is often used to find nodes that serve as a bridge from one part of a graph to another.\n", 13 | "In the above example Alice is the main connection in the graph.\n", 14 | "If Alice is removed all connections in the graph would be cut off.\n", 15 | "This makes Alice \"important\" because she ensures that no nodes are isolated.\n", 16 | "\n", 17 | "First we'll import the Neo4j driver and Pandas libraries:\n" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 128, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "from neo4j.v1 import GraphDatabase, basic_auth\n", 27 | "import pandas as pd\n", 28 | "import os" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "Next let's create an instance of the Neo4j driver which we'll use to execute our queries.\n" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 130, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "host = os.environ.get(\"NEO4J_HOST\", \"bolt://localhost\") \n", 45 | "user = os.environ.get(\"NEO4J_USER\", \"neo4j\")\n", 46 | "password = os.environ.get(\"NEO4J_PASSWORD\", \"neo\")\n", 47 | "driver = GraphDatabase.driver(host, auth=basic_auth(user, password))" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "Now let's create a sample graph that we'll run the algorithm against.\n" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 131, 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "name": "stdout", 64 | "output_type": "stream", 65 | "text": [ 66 | "Stats: {}\n" 67 | ] 68 | } 69 | ], 70 | "source": [ 71 | "create_graph_query = '''\n", 72 | "MERGE (nAlice:User {id:'Alice'})\n", 73 | "MERGE (nBridget:User {id:'Bridget'})\n", 74 | "MERGE (nCharles:User {id:'Charles'})\n", 75 | "MERGE (nDoug:User {id:'Doug'})\n", 76 | "MERGE (nMark:User {id:'Mark'})\n", 77 | "MERGE (nMichael:User {id:'Michael'})\n", 78 | "\n", 79 | "MERGE (nAlice)-[:MANAGE]->(nBridget)\n", 80 | "MERGE (nAlice)-[:MANAGE]->(nCharles)\n", 81 | "MERGE (nAlice)-[:MANAGE]->(nDoug)\n", 82 | "MERGE (nMark)-[:MANAGE]->(nAlice)\n", 83 | "MERGE (nCharles)-[:MANAGE]->(nMichael);\n", 84 | "'''\n", 85 | "\n", 86 | "with driver.session() as session:\n", 87 | " result = session.write_transaction(lambda tx: tx.run(create_graph_query))\n", 88 | " print(\"Stats: \" + str(result.consume().metadata.get(\"stats\", {})))" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "Finally we can run the algorithm by executing the following query:\n" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 133, 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "data": { 105 | "text/html": [ 106 | "
\n", 107 | "\n", 120 | "\n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | "
usercentrality
0Alice4.0
1Charles2.0
2Bridget0.0
3Doug0.0
4Mark0.0
5Michael0.0
\n", 161 | "
" 162 | ], 163 | "text/plain": [ 164 | " user centrality\n", 165 | "0 Alice 4.0\n", 166 | "1 Charles 2.0\n", 167 | "2 Bridget 0.0\n", 168 | "3 Doug 0.0\n", 169 | "4 Mark 0.0\n", 170 | "5 Michael 0.0" 171 | ] 172 | }, 173 | "execution_count": 133, 174 | "metadata": {}, 175 | "output_type": "execute_result" 176 | } 177 | ], 178 | "source": [ 179 | "streaming_query = \"\"\"\n", 180 | "CALL algo.betweenness.stream('User','MANAGE',{direction:'out'}) \n", 181 | "YIELD nodeId, centrality\n", 182 | "\n", 183 | "MATCH (user:User) WHERE id(user) = nodeId\n", 184 | "\n", 185 | "RETURN user.id AS user,centrality\n", 186 | "ORDER BY centrality DESC\n", 187 | "LIMIT 20;\n", 188 | "\"\"\"\n", 189 | "\n", 190 | "with driver.session() as session:\n", 191 | " result = session.read_transaction(lambda tx: tx.run(streaming_query)) \n", 192 | " df = pd.DataFrame([r.values() for r in result], columns=result.keys())\n", 193 | "\n", 194 | "df" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "We can see that Alice is the main broker in this network and Charles is a minor broker.\n", 202 | "The others don't have any influence because all the shortest paths between pairs of people go via Alice or Charles." 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": {}, 208 | "source": [ 209 | "We can also call a version of the algorithm that will store the result as a property on a\n", 210 | "node. This is useful if we want to run future queries that use the result." 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 134, 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [ 219 | "write_query = \"\"\"\n", 220 | "CALL algo.betweenness('User','MANAGE', {direction:'out',write:true, writeProperty:'centrality'}) \n", 221 | "YIELD nodes, minCentrality, maxCentrality, sumCentrality, loadMillis, computeMillis, writeMillis;\n", 222 | "\"\"\"\n", 223 | "\n", 224 | "with driver.session() as session:\n", 225 | " session.write_transaction(lambda tx: tx.run(write_query))" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "## Graph Visualisation\n", 233 | "\n", 234 | "Sometimes a picture can tell more than a table of results and this is often the case with graph algorithms. \n", 235 | "Let's see how to create a graph visualization using neovis.js.\n", 236 | "\n", 237 | "First we'll create a div into which we will generate the visualisation." 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 136, 243 | "metadata": {}, 244 | "outputs": [ 245 | { 246 | "data": { 247 | "text/html": [ 248 | " \n", 264 | "
" 265 | ], 266 | "text/plain": [ 267 | "" 268 | ] 269 | }, 270 | "metadata": {}, 271 | "output_type": "display_data" 272 | } 273 | ], 274 | "source": [ 275 | "%%html\n", 276 | " \n", 292 | "
" 293 | ] 294 | }, 295 | { 296 | "cell_type": "markdown", 297 | "metadata": {}, 298 | "source": [ 299 | "Next we need to define the query that the visualization will be generated from, along with config \n", 300 | "that describes which properties will be used for node size, node colour, and relationship width. \n", 301 | "\n", 302 | "We'll then define a JavaScript variable that contains all our parameters." 303 | ] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "execution_count": 137, 308 | "metadata": {}, 309 | "outputs": [ 310 | { 311 | "data": { 312 | "application/javascript": [ 313 | "window.jsonGraph={\"query\": \"MATCH (p1:User)-[r:MANAGE]->(p2:User) RETURN *\", \"labels\": {\"User\": {\"caption\": \"id\", \"size\": \"centrality\"}}, \"relationships\": {\"MANAGE\": {\"thickness\": \"weight\", \"caption\": false}}, \"host\": \"bolt://localhost\", \"user\": \"neo4j\", \"password\": \"neo\"};" 314 | ], 315 | "text/plain": [ 316 | "" 317 | ] 318 | }, 319 | "execution_count": 137, 320 | "metadata": {}, 321 | "output_type": "execute_result" 322 | } 323 | ], 324 | "source": [ 325 | "from IPython.core.display import Javascript\n", 326 | "import json\n", 327 | "from scripts.algo import viz_config, render_image\n", 328 | "\n", 329 | "config = viz_config(\"Betweenness Centrality\")\n", 330 | "query = config[\"query\"]\n", 331 | "labels_json = config[\"labels_json\"]\n", 332 | "relationships_json = config[\"relationships_json\"]\n", 333 | "\n", 334 | "json_graph = {\n", 335 | " \"query\": query,\n", 336 | " \"labels\": labels_json,\n", 337 | " \"relationships\": relationships_json,\n", 338 | " \"host\": host,\n", 339 | " \"user\": user,\n", 340 | " \"password\": password\n", 341 | "}\n", 342 | "\n", 343 | "Javascript(\"\"\"window.jsonGraph={};\"\"\".format(json.dumps(json_graph)))" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "Now we're ready to call neovis.js and generate our graph visualisation. \n", 351 | "The following code will create an interactive graph into the div defined above.\n", 352 | "It will also extract an image representation of the graph and display that in the cell below." 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 244, 358 | "metadata": {}, 359 | "outputs": [ 360 | { 361 | "data": { 362 | "application/javascript": [ 363 | "var output_area = this;\n", 364 | "requirejs(['neovis.js'], function(NeoVis){ \n", 365 | " var config = {\n", 366 | " container_id: \"viz\",\n", 367 | " server_url: window.jsonGraph.host,\n", 368 | " server_user: window.jsonGraph.user,\n", 369 | " server_password: window.jsonGraph.password,\n", 370 | " labels: window.jsonGraph.labels,\n", 371 | " relationships: window.jsonGraph.relationships,\n", 372 | " initial_cypher: window.jsonGraph.query\n", 373 | " };\n", 374 | " \n", 375 | " let viz = new NeoVis.default(config);\n", 376 | " viz.render();\n", 377 | " \n", 378 | " viz.onVisualizationRendered(function(ctx) {\n", 379 | " let imageSrc = ctx.canvas.toDataURL();\n", 380 | " let kernel = IPython.notebook.kernel;\n", 381 | " let command = \"image_src = '\" + imageSrc + \"'\";\n", 382 | " kernel.execute(command);\n", 383 | " \n", 384 | " var cell_element = output_area.element.parents('.cell');\n", 385 | " var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element);\n", 386 | " var cell = Jupyter.notebook.get_cell(cell_idx+1);\n", 387 | " cell.set_text(\"render_image(image_src)\")\n", 388 | " cell.execute();\n", 389 | " });\n", 390 | "});" 391 | ], 392 | "text/plain": [ 393 | "" 394 | ] 395 | }, 396 | "metadata": {}, 397 | "output_type": "display_data" 398 | } 399 | ], 400 | "source": [ 401 | "%%javascript\n", 402 | "var output_area = this;\n", 403 | "requirejs(['neovis.js'], function(NeoVis){ \n", 404 | " var config = {\n", 405 | " container_id: \"viz\",\n", 406 | " server_url: window.jsonGraph.host,\n", 407 | " server_user: window.jsonGraph.user,\n", 408 | " server_password: window.jsonGraph.password,\n", 409 | " labels: window.jsonGraph.labels,\n", 410 | " relationships: window.jsonGraph.relationships,\n", 411 | " initial_cypher: window.jsonGraph.query\n", 412 | " };\n", 413 | " \n", 414 | " let viz = new NeoVis.default(config);\n", 415 | " viz.render();\n", 416 | " \n", 417 | " viz.onVisualizationRendered(function(ctx) {\n", 418 | " let imageSrc = ctx.canvas.toDataURL();\n", 419 | " let kernel = IPython.notebook.kernel;\n", 420 | " let command = \"image_src = '\" + imageSrc + \"'\";\n", 421 | " kernel.execute(command);\n", 422 | " \n", 423 | " var cell_element = output_area.element.parents('.cell');\n", 424 | " var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element);\n", 425 | " var cell = Jupyter.notebook.get_cell(cell_idx+1);\n", 426 | " cell.set_text(\"render_image(image_src)\")\n", 427 | " cell.execute();\n", 428 | " });\n", 429 | "});" 430 | ] 431 | }, 432 | { 433 | "cell_type": "code", 434 | "execution_count": 247, 435 | "metadata": { 436 | "scrolled": true 437 | }, 438 | "outputs": [ 439 | { 440 | "data": { 441 | "text/html": [ 442 | "" 443 | ], 444 | "text/plain": [ 445 | "" 446 | ] 447 | }, 448 | "execution_count": 247, 449 | "metadata": {}, 450 | "output_type": "execute_result" 451 | } 452 | ], 453 | "source": [ 454 | "render_image(image_src)" 455 | ] 456 | } 457 | ], 458 | "metadata": { 459 | "kernelspec": { 460 | "display_name": "Python 3", 461 | "language": "python", 462 | "name": "python3" 463 | }, 464 | "language_info": { 465 | "codemirror_mode": { 466 | "name": "ipython", 467 | "version": 3 468 | }, 469 | "file_extension": ".py", 470 | "mimetype": "text/x-python", 471 | "name": "python", 472 | "nbconvert_exporter": "python", 473 | "pygments_lexer": "ipython3", 474 | "version": "3.6.0" 475 | } 476 | }, 477 | "nbformat": 4, 478 | "nbformat_minor": 2 479 | } 480 | -------------------------------------------------------------------------------- /notebooks/ClosenessCentrality.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Closeness Centrality\n", 8 | "_Closeness Centrality_ is a way of detecting nodes that are able to spread information very efficiently through a graph.\n", 9 | "\n", 10 | "The _Closeness Centrality_ of a node measures its average distance to all other nodes.\n", 11 | "Nodes with a high closeness score have the shortest distances to all other nodes.\n", 12 | "\n", 13 | "First we'll import the Neo4j driver and Pandas libraries:\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "from neo4j.v1 import GraphDatabase, basic_auth\n", 23 | "import pandas as pd\n", 24 | "import os" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "Next let's create an instance of the Neo4j driver which we'll use to execute our queries.\n" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 2, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "host = os.environ.get(\"NEO4J_HOST\", \"bolt://localhost\") \n", 41 | "user = os.environ.get(\"NEO4J_USER\", \"neo4j\")\n", 42 | "password = os.environ.get(\"NEO4J_PASSWORD\", \"neo\")\n", 43 | "driver = GraphDatabase.driver(host, auth=basic_auth(user, password))" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "Now let's create a sample graph that we'll run the algorithm against.\n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 3, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "Stats: {'labels-added': 5, 'relationships-created': 8, 'nodes-created': 5, 'properties-set': 5}\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "create_graph_query = '''\n", 68 | "MERGE (a:Node{id:\"A\"})\n", 69 | "MERGE (b:Node{id:\"B\"})\n", 70 | "MERGE (c:Node{id:\"C\"})\n", 71 | "MERGE (d:Node{id:\"D\"})\n", 72 | "MERGE (e:Node{id:\"E\"})\n", 73 | "\n", 74 | "MERGE (a)-[:LINK]->(b)\n", 75 | "MERGE (b)-[:LINK]->(a)\n", 76 | "MERGE (b)-[:LINK]->(c)\n", 77 | "MERGE (c)-[:LINK]->(b)\n", 78 | "MERGE (c)-[:LINK]->(d)\n", 79 | "MERGE (d)-[:LINK]->(c)\n", 80 | "MERGE (d)-[:LINK]->(e)\n", 81 | "MERGE (e)-[:LINK]->(d);\n", 82 | "'''\n", 83 | "\n", 84 | "with driver.session() as session:\n", 85 | " result = session.write_transaction(lambda tx: tx.run(create_graph_query))\n", 86 | " print(\"Stats: \" + str(result.consume().metadata.get(\"stats\", {})))" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "Finally we can run the algorithm by executing the following query:\n" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 4, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "data": { 103 | "text/html": [ 104 | "
\n", 105 | "\n", 118 | "\n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | "
nodecentrality
0C0.666667
1B0.571429
2D0.571429
3A0.400000
4E0.400000
\n", 154 | "
" 155 | ], 156 | "text/plain": [ 157 | " node centrality\n", 158 | "0 C 0.666667\n", 159 | "1 B 0.571429\n", 160 | "2 D 0.571429\n", 161 | "3 A 0.400000\n", 162 | "4 E 0.400000" 163 | ] 164 | }, 165 | "execution_count": 4, 166 | "metadata": {}, 167 | "output_type": "execute_result" 168 | } 169 | ], 170 | "source": [ 171 | "streaming_query = \"\"\"\n", 172 | "CALL algo.closeness.stream('Node', 'LINK')\n", 173 | "YIELD nodeId, centrality\n", 174 | "\n", 175 | "MATCH (n:Node) WHERE id(n) = nodeId\n", 176 | "\n", 177 | "RETURN n.id AS node, centrality\n", 178 | "ORDER BY centrality DESC\n", 179 | "limit 20;\n", 180 | "\"\"\"\n", 181 | "\n", 182 | "with driver.session() as session:\n", 183 | " result = session.read_transaction(lambda tx: tx.run(streaming_query)) \n", 184 | " df = pd.DataFrame([r.values() for r in result], columns=result.keys())\n", 185 | "\n", 186 | "df" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": {}, 192 | "source": [ 193 | "\"C\" is the best connected node in this graph although \"B\" and \"D\" aren't far behind.\n", 194 | "\"A\" and \"E\" don't have close ties to many other nodes so their scores are lower.\n", 195 | "A score of 1 would indicate that a node has a direct connection to all other nodes." 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "We can also call a version of the algorithm that will store the result as a property on a\n", 203 | "node. This is useful if we want to run future queries that use the result." 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": 5, 209 | "metadata": {}, 210 | "outputs": [], 211 | "source": [ 212 | "write_query = \"\"\"\n", 213 | "CALL algo.closeness('Node', 'LINK', {write:true, writeProperty:'centrality'}) \n", 214 | "YIELD nodes,loadMillis, computeMillis, writeMillis;\n", 215 | "\"\"\"\n", 216 | "\n", 217 | "with driver.session() as session:\n", 218 | " session.write_transaction(lambda tx: tx.run(write_query))" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "## Graph Visualisation\n", 226 | "\n", 227 | "Sometimes a picture can tell more than a table of results and this is often the case with graph algorithms. \n", 228 | "Let's see how to create a graph visualization using neovis.js.\n", 229 | "\n", 230 | "First we'll create a div into which we will generate the visualisation." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 6, 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "data": { 240 | "text/html": [ 241 | " \n", 257 | "
" 258 | ], 259 | "text/plain": [ 260 | "" 261 | ] 262 | }, 263 | "metadata": {}, 264 | "output_type": "display_data" 265 | } 266 | ], 267 | "source": [ 268 | "%%html\n", 269 | " \n", 285 | "
" 286 | ] 287 | }, 288 | { 289 | "cell_type": "markdown", 290 | "metadata": {}, 291 | "source": [ 292 | "Next we need to define the query that the visualization will be generated from, along with config \n", 293 | "that describes which properties will be used for node size, node colour, and relationship width. \n", 294 | "\n", 295 | "We'll then define a JavaScript variable that contains all our parameters." 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 7, 301 | "metadata": {}, 302 | "outputs": [ 303 | { 304 | "data": { 305 | "application/javascript": [ 306 | "window.jsonGraph={\"query\": \"MATCH (p1:Node)-[r:LINK]->(p2:Node) RETURN *\", \"labels\": {\"Node\": {\"caption\": \"id\", \"size\": \"centrality\"}}, \"relationships\": {\"LINK\": {\"thickness\": \"weight\", \"caption\": false}}, \"host\": \"bolt://localhost\", \"user\": \"neo4j\", \"password\": \"neo\"};" 307 | ], 308 | "text/plain": [ 309 | "" 310 | ] 311 | }, 312 | "execution_count": 7, 313 | "metadata": {}, 314 | "output_type": "execute_result" 315 | } 316 | ], 317 | "source": [ 318 | "from IPython.core.display import Javascript\n", 319 | "import json\n", 320 | "from scripts.algo import viz_config, render_image\n", 321 | "\n", 322 | "config = viz_config(\"Closeness Centrality\")\n", 323 | "query = config[\"query\"]\n", 324 | "labels_json = config[\"labels_json\"]\n", 325 | "relationships_json = config[\"relationships_json\"]\n", 326 | "\n", 327 | "json_graph = {\n", 328 | " \"query\": query,\n", 329 | " \"labels\": labels_json,\n", 330 | " \"relationships\": relationships_json,\n", 331 | " \"host\": host,\n", 332 | " \"user\": user,\n", 333 | " \"password\": password\n", 334 | "}\n", 335 | "\n", 336 | "Javascript(\"\"\"window.jsonGraph={};\"\"\".format(json.dumps(json_graph)))" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "Now we're ready to call neovis.js and generate our graph visualisation. \n", 344 | "The following code will create an interactive graph into the div defined above.\n", 345 | "It will also extract an image representation of the graph and display that in the cell below." 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": 8, 351 | "metadata": {}, 352 | "outputs": [ 353 | { 354 | "data": { 355 | "application/javascript": [ 356 | "var output_area = this;\n", 357 | "requirejs(['neovis.js'], function(NeoVis){ \n", 358 | " var config = {\n", 359 | " container_id: \"viz\",\n", 360 | " server_url: window.jsonGraph.host,\n", 361 | " server_user: window.jsonGraph.user,\n", 362 | " server_password: window.jsonGraph.password,\n", 363 | " labels: window.jsonGraph.labels,\n", 364 | " relationships: window.jsonGraph.relationships,\n", 365 | " initial_cypher: window.jsonGraph.query\n", 366 | " };\n", 367 | " \n", 368 | " let viz = new NeoVis.default(config);\n", 369 | " viz.render();\n", 370 | " \n", 371 | " viz.onVisualizationRendered(function(ctx) {\n", 372 | " let imageSrc = ctx.canvas.toDataURL();\n", 373 | " let kernel = IPython.notebook.kernel;\n", 374 | " let command = \"image_src = '\" + imageSrc + \"'\";\n", 375 | " kernel.execute(command);\n", 376 | " \n", 377 | " var cell_element = output_area.element.parents('.cell');\n", 378 | " var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element);\n", 379 | " var cell = Jupyter.notebook.get_cell(cell_idx+1);\n", 380 | " cell.set_text(\"render_image(image_src)\")\n", 381 | " cell.execute();\n", 382 | " });\n", 383 | "});" 384 | ], 385 | "text/plain": [ 386 | "" 387 | ] 388 | }, 389 | "metadata": {}, 390 | "output_type": "display_data" 391 | } 392 | ], 393 | "source": [ 394 | "%%javascript\n", 395 | "var output_area = this;\n", 396 | "requirejs(['neovis.js'], function(NeoVis){ \n", 397 | " var config = {\n", 398 | " container_id: \"viz\",\n", 399 | " server_url: window.jsonGraph.host,\n", 400 | " server_user: window.jsonGraph.user,\n", 401 | " server_password: window.jsonGraph.password,\n", 402 | " labels: window.jsonGraph.labels,\n", 403 | " relationships: window.jsonGraph.relationships,\n", 404 | " initial_cypher: window.jsonGraph.query\n", 405 | " };\n", 406 | " \n", 407 | " let viz = new NeoVis.default(config);\n", 408 | " viz.render();\n", 409 | " \n", 410 | " viz.onVisualizationRendered(function(ctx) {\n", 411 | " let imageSrc = ctx.canvas.toDataURL();\n", 412 | " let kernel = IPython.notebook.kernel;\n", 413 | " let command = \"image_src = '\" + imageSrc + \"'\";\n", 414 | " kernel.execute(command);\n", 415 | " \n", 416 | " var cell_element = output_area.element.parents('.cell');\n", 417 | " var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element);\n", 418 | " var cell = Jupyter.notebook.get_cell(cell_idx+1);\n", 419 | " cell.set_text(\"render_image(image_src)\")\n", 420 | " cell.execute();\n", 421 | " });\n", 422 | "});" 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": 10, 428 | "metadata": {}, 429 | "outputs": [ 430 | { 431 | "data": { 432 | "text/html": [ 433 | "" 434 | ], 435 | "text/plain": [ 436 | "" 437 | ] 438 | }, 439 | "execution_count": 10, 440 | "metadata": {}, 441 | "output_type": "execute_result" 442 | } 443 | ], 444 | "source": [ 445 | "render_image(image_src)" 446 | ] 447 | } 448 | ], 449 | "metadata": { 450 | "kernelspec": { 451 | "display_name": "Python 3", 452 | "language": "python", 453 | "name": "python3" 454 | }, 455 | "language_info": { 456 | "codemirror_mode": { 457 | "name": "ipython", 458 | "version": 3 459 | }, 460 | "file_extension": ".py", 461 | "mimetype": "text/x-python", 462 | "name": "python", 463 | "nbconvert_exporter": "python", 464 | "pygments_lexer": "ipython3", 465 | "version": "3.6.0" 466 | } 467 | }, 468 | "nbformat": 4, 469 | "nbformat_minor": 2 470 | } 471 | -------------------------------------------------------------------------------- /notebooks/DegreeCentrality.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Degree Centrality\n", 8 | "Degree Centrality is the simplest of all the centrality algorithms.\n", 9 | "It measures the number of incoming and outgoing relationships from a node.\n", 10 | "\n", 11 | "The algorithm can help us find popular nodes in a graph.\n", 12 | "\n", 13 | "First we'll import the Neo4j driver and Pandas libraries:\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "from neo4j.v1 import GraphDatabase, basic_auth\n", 23 | "import pandas as pd\n", 24 | "import os" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "Next let's create an instance of the Neo4j driver which we'll use to execute our queries.\n" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 3, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "host = os.environ.get(\"NEO4J_HOST\", \"bolt://localhost\") \n", 41 | "user = os.environ.get(\"NEO4J_USER\", \"neo4j\")\n", 42 | "password = os.environ.get(\"NEO4J_PASSWORD\", \"neo\")\n", 43 | "driver = GraphDatabase.driver(host, auth=basic_auth(user, password))" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "Now let's create a sample graph that we'll run the algorithm against.\n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 4, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "Stats: {}\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "create_graph_query = '''\n", 68 | "MERGE (nAlice:User {id:'Alice'})\n", 69 | "MERGE (nBridget:User {id:'Bridget'})\n", 70 | "MERGE (nCharles:User {id:'Charles'})\n", 71 | "MERGE (nDoug:User {id:'Doug'})\n", 72 | "MERGE (nMark:User {id:'Mark'})\n", 73 | "MERGE (nMichael:User {id:'Michael'})\n", 74 | "\n", 75 | "MERGE (nAlice)-[:FOLLOWS]->(nDoug)\n", 76 | "MERGE (nAlice)-[:FOLLOWS]->(nBridget)\n", 77 | "MERGE (nAlice)-[:FOLLOWS]->(nCharles)\n", 78 | "MERGE (nMark)-[:FOLLOWS]->(nDoug)\n", 79 | "MERGE (nMark)-[:FOLLOWS]->(nMichael)\n", 80 | "MERGE (nBridget)-[:FOLLOWS]->(nDoug)\n", 81 | "MERGE (nCharles)-[:FOLLOWS]->(nDoug)\n", 82 | "MERGE (nMichael)-[:FOLLOWS]->(nDoug)\n", 83 | "'''\n", 84 | "\n", 85 | "with driver.session() as session:\n", 86 | " result = session.write_transaction(lambda tx: tx.run(create_graph_query))\n", 87 | " print(\"Stats: \" + str(result.consume().metadata.get(\"stats\", {})))" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "Finally we can run the algorithm by executing the following query:\n" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 5, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/html": [ 105 | "
\n", 106 | "\n", 119 | "\n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | "
namefollowsfollowers
0Alice30
1Bridget11
2Charles11
3Doug05
4Mark20
5Michael11
\n", 167 | "
" 168 | ], 169 | "text/plain": [ 170 | " name follows followers\n", 171 | "0 Alice 3 0\n", 172 | "1 Bridget 1 1\n", 173 | "2 Charles 1 1\n", 174 | "3 Doug 0 5\n", 175 | "4 Mark 2 0\n", 176 | "5 Michael 1 1" 177 | ] 178 | }, 179 | "execution_count": 5, 180 | "metadata": {}, 181 | "output_type": "execute_result" 182 | } 183 | ], 184 | "source": [ 185 | "streaming_query = \"\"\"\n", 186 | "MATCH (u:User)\n", 187 | "RETURN u.id AS name,\n", 188 | " size((u)-[:FOLLOWS]->()) AS follows,\n", 189 | " size((u)<-[:FOLLOWS]-()) AS followers\n", 190 | "\"\"\"\n", 191 | "\n", 192 | "with driver.session() as session:\n", 193 | " result = session.read_transaction(lambda tx: tx.run(streaming_query)) \n", 194 | " df = pd.DataFrame([r.values() for r in result], columns=result.keys())\n", 195 | "\n", 196 | "df" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "We can see that Doug is the most popular user in our imaginary Twitter graph with 5 followers - all other users follow him but he doesn't follow anybody back.\n", 204 | "In the real Twitter network celebrities have very high follower counts but tend to follow very few back people.\n", 205 | "We could therefore consider Doug a celebrity!" 206 | ] 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "metadata": {}, 211 | "source": [ 212 | "We can also call a version of the algorithm that will store the result as a property on a\n", 213 | "node. This is useful if we want to run future queries that use the result." 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 6, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "write_query = \"\"\"\n", 223 | "MATCH (u:User)\n", 224 | "set u.followers = size((u)<-[:FOLLOWS]-())\n", 225 | "\"\"\"\n", 226 | "\n", 227 | "with driver.session() as session:\n", 228 | " session.write_transaction(lambda tx: tx.run(write_query))" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": {}, 234 | "source": [ 235 | "## Graph Visualisation\n", 236 | "\n", 237 | "Sometimes a picture can tell more than a table of results and this is often the case with graph algorithms. \n", 238 | "Let's see how to create a graph visualization using neovis.js.\n", 239 | "\n", 240 | "First we'll create a div into which we will generate the visualisation." 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 7, 246 | "metadata": {}, 247 | "outputs": [ 248 | { 249 | "data": { 250 | "text/html": [ 251 | " \n", 267 | "
" 268 | ], 269 | "text/plain": [ 270 | "" 271 | ] 272 | }, 273 | "metadata": {}, 274 | "output_type": "display_data" 275 | } 276 | ], 277 | "source": [ 278 | "%%html\n", 279 | " \n", 295 | "
" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "Next we need to define the query that the visualization will be generated from, along with config \n", 303 | "that describes which properties will be used for node size, node colour, and relationship width. \n", 304 | "\n", 305 | "We'll then define a JavaScript variable that contains all our parameters." 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 8, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "data": { 315 | "application/javascript": [ 316 | "window.jsonGraph={\"query\": \"MATCH (p1:User)-[r:FOLLOWS]->(p2:User) RETURN *\", \"labels\": {\"User\": {\"caption\": \"id\", \"size\": \"followers\"}}, \"relationships\": {\"FOLLOWS\": {\"thickness\": \"weight\", \"caption\": false}}, \"host\": \"bolt://localhost\", \"user\": \"neo4j\", \"password\": \"neo\"};" 317 | ], 318 | "text/plain": [ 319 | "" 320 | ] 321 | }, 322 | "execution_count": 8, 323 | "metadata": {}, 324 | "output_type": "execute_result" 325 | } 326 | ], 327 | "source": [ 328 | "from IPython.core.display import Javascript\n", 329 | "import json\n", 330 | "from scripts.algo import viz_config, render_image\n", 331 | "\n", 332 | "config = viz_config(\"Degree Centrality\")\n", 333 | "query = config[\"query\"]\n", 334 | "labels_json = config[\"labels_json\"]\n", 335 | "relationships_json = config[\"relationships_json\"]\n", 336 | "\n", 337 | "json_graph = {\n", 338 | " \"query\": query,\n", 339 | " \"labels\": labels_json,\n", 340 | " \"relationships\": relationships_json,\n", 341 | " \"host\": host,\n", 342 | " \"user\": user,\n", 343 | " \"password\": password\n", 344 | "}\n", 345 | "\n", 346 | "Javascript(\"\"\"window.jsonGraph={};\"\"\".format(json.dumps(json_graph)))" 347 | ] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "metadata": {}, 352 | "source": [ 353 | "Now we're ready to call neovis.js and generate our graph visualisation. \n", 354 | "The following code will create an interactive graph into the div defined above.\n", 355 | "It will also extract an image representation of the graph and display that in the cell below." 356 | ] 357 | }, 358 | { 359 | "cell_type": "code", 360 | "execution_count": 9, 361 | "metadata": {}, 362 | "outputs": [ 363 | { 364 | "data": { 365 | "application/javascript": [ 366 | "var output_area = this;\n", 367 | "requirejs(['neovis.js'], function(NeoVis){ \n", 368 | " var config = {\n", 369 | " container_id: \"viz\",\n", 370 | " server_url: window.jsonGraph.host,\n", 371 | " server_user: window.jsonGraph.user,\n", 372 | " server_password: window.jsonGraph.password,\n", 373 | " labels: window.jsonGraph.labels,\n", 374 | " relationships: window.jsonGraph.relationships,\n", 375 | " initial_cypher: window.jsonGraph.query\n", 376 | " };\n", 377 | " \n", 378 | " let viz = new NeoVis.default(config);\n", 379 | " viz.render();\n", 380 | " \n", 381 | " viz.onVisualizationRendered(function(ctx) {\n", 382 | " let imageSrc = ctx.canvas.toDataURL();\n", 383 | " let kernel = IPython.notebook.kernel;\n", 384 | " let command = \"image_src = '\" + imageSrc + \"'\";\n", 385 | " kernel.execute(command);\n", 386 | " \n", 387 | " var cell_element = output_area.element.parents('.cell');\n", 388 | " var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element);\n", 389 | " var cell = Jupyter.notebook.get_cell(cell_idx+1);\n", 390 | " cell.set_text(\"render_image(image_src)\")\n", 391 | " cell.execute();\n", 392 | " });\n", 393 | "});" 394 | ], 395 | "text/plain": [ 396 | "" 397 | ] 398 | }, 399 | "metadata": {}, 400 | "output_type": "display_data" 401 | } 402 | ], 403 | "source": [ 404 | "%%javascript\n", 405 | "var output_area = this;\n", 406 | "requirejs(['neovis.js'], function(NeoVis){ \n", 407 | " var config = {\n", 408 | " container_id: \"viz\",\n", 409 | " server_url: window.jsonGraph.host,\n", 410 | " server_user: window.jsonGraph.user,\n", 411 | " server_password: window.jsonGraph.password,\n", 412 | " labels: window.jsonGraph.labels,\n", 413 | " relationships: window.jsonGraph.relationships,\n", 414 | " initial_cypher: window.jsonGraph.query\n", 415 | " };\n", 416 | " \n", 417 | " let viz = new NeoVis.default(config);\n", 418 | " viz.render();\n", 419 | " \n", 420 | " viz.onVisualizationRendered(function(ctx) {\n", 421 | " let imageSrc = ctx.canvas.toDataURL();\n", 422 | " let kernel = IPython.notebook.kernel;\n", 423 | " let command = \"image_src = '\" + imageSrc + \"'\";\n", 424 | " kernel.execute(command);\n", 425 | " \n", 426 | " var cell_element = output_area.element.parents('.cell');\n", 427 | " var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element);\n", 428 | " var cell = Jupyter.notebook.get_cell(cell_idx+1);\n", 429 | " cell.set_text(\"render_image(image_src)\")\n", 430 | " cell.execute();\n", 431 | " });\n", 432 | "});" 433 | ] 434 | }, 435 | { 436 | "cell_type": "code", 437 | "execution_count": 11, 438 | "metadata": {}, 439 | "outputs": [ 440 | { 441 | "data": { 442 | "text/html": [ 443 | "" 444 | ], 445 | "text/plain": [ 446 | "" 447 | ] 448 | }, 449 | "execution_count": 11, 450 | "metadata": {}, 451 | "output_type": "execute_result" 452 | } 453 | ], 454 | "source": [ 455 | "render_image(image_src)" 456 | ] 457 | } 458 | ], 459 | "metadata": { 460 | "kernelspec": { 461 | "display_name": "Python 3", 462 | "language": "python", 463 | "name": "python3" 464 | }, 465 | "language_info": { 466 | "codemirror_mode": { 467 | "name": "ipython", 468 | "version": 3 469 | }, 470 | "file_extension": ".py", 471 | "mimetype": "text/x-python", 472 | "name": "python", 473 | "nbconvert_exporter": "python", 474 | "pygments_lexer": "ipython3", 475 | "version": "3.6.0" 476 | } 477 | }, 478 | "nbformat": 4, 479 | "nbformat_minor": 2 480 | } 481 | -------------------------------------------------------------------------------- /notebooks/SingleSourceShortestPath.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Single Source Shortest Path\n", 8 | "The _Single Source Shortest Path_ (SSSP) algorithm calculates the shortest (weighted) path between a pair of nodes.\n", 9 | "Dijkstra's algorithm is the most well known one in this category.\n", 10 | "SSSP is a real time graph algorithm - it can be used as part of the normal user flow in a web or mobile application.\n", 11 | "\n", 12 | "First we'll import the Neo4j driver and Pandas libraries:\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "from neo4j.v1 import GraphDatabase, basic_auth\n", 22 | "import pandas as pd\n", 23 | "import os" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "Next let's create an instance of the Neo4j driver which we'll use to execute our queries.\n" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "host = os.environ.get(\"NEO4J_HOST\", \"bolt://localhost\") \n", 40 | "user = os.environ.get(\"NEO4J_USER\", \"neo4j\")\n", 41 | "password = os.environ.get(\"NEO4J_PASSWORD\", \"neo\")\n", 42 | "driver = GraphDatabase.driver(host, auth=basic_auth(user, password))" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "Now let's create a sample graph that we'll run the algorithm against.\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 3, 55 | "metadata": {}, 56 | "outputs": [ 57 | { 58 | "name": "stdout", 59 | "output_type": "stream", 60 | "text": [ 61 | "Stats: {'labels-added': 6, 'relationships-created': 11, 'nodes-created': 6, 'properties-set': 17}\n" 62 | ] 63 | } 64 | ], 65 | "source": [ 66 | "create_graph_query = '''\n", 67 | "CREATE (a:Loc{name:'A'}), (b:Loc{name:'B'}), (c:Loc{name:'C'}), \n", 68 | " (d:Loc{name:'D'}), (e:Loc{name:'E'}), (f:Loc{name:'F'}),\n", 69 | " (a)-[:ROAD {cost:50}]->(b),\n", 70 | " (a)-[:ROAD {cost:50}]->(c),\n", 71 | " (a)-[:ROAD {cost:100}]->(d),\n", 72 | " (a)-[:RAIL {cost:50}]->(d),\n", 73 | " (b)-[:ROAD {cost:40}]->(d),\n", 74 | " (c)-[:ROAD {cost:40}]->(d),\n", 75 | " (c)-[:ROAD {cost:80}]->(e),\n", 76 | " (d)-[:ROAD {cost:30}]->(e),\n", 77 | " (d)-[:ROAD {cost:80}]->(f),\n", 78 | " (e)-[:ROAD {cost:40}]->(f),\n", 79 | " (e)-[:RAIL {cost:20}]->(f);\n", 80 | "'''\n", 81 | "\n", 82 | "with driver.session() as session:\n", 83 | " result = session.write_transaction(lambda tx: tx.run(create_graph_query))\n", 84 | " print(\"Stats: \" + str(result.consume().metadata.get(\"stats\", {})))" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "Finally we can run the algorithm by executing the following query:\n" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 4, 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "data": { 101 | "text/html": [ 102 | "
\n", 103 | "\n", 116 | "\n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | "
namecost
0A0.0
1D50.0
2E80.0
3F100.0
\n", 147 | "
" 148 | ], 149 | "text/plain": [ 150 | " name cost\n", 151 | "0 A 0.0\n", 152 | "1 D 50.0\n", 153 | "2 E 80.0\n", 154 | "3 F 100.0" 155 | ] 156 | }, 157 | "execution_count": 4, 158 | "metadata": {}, 159 | "output_type": "execute_result" 160 | } 161 | ], 162 | "source": [ 163 | "streaming_query = \"\"\"\n", 164 | "MATCH (start:Loc{name:'A'}), (end:Loc{name:'F'})\n", 165 | "CALL algo.shortestPath.stream(start, end, 'cost') \n", 166 | "YIELD nodeId, cost\n", 167 | "MATCH (other:Loc) WHERE id(other) = nodeId\n", 168 | "RETURN other.name AS name, cost\n", 169 | "\"\"\"\n", 170 | "\n", 171 | "with driver.session() as session:\n", 172 | " result = session.read_transaction(lambda tx: tx.run(streaming_query)) \n", 173 | " df = pd.DataFrame([r.values() for r in result], columns=result.keys())\n", 174 | "\n", 175 | "df" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "The quickest route takes us from \"A\" to \"F\" via \"D\" and \"E\" at a total cost of 100.\n", 183 | "We first go by rail from \"A\" to \"D\" at a cost of 50, from \"D\" to \"E\" by road for an additional 30, and finally from \"E\" to \"F\" by rail for an additional 20." 184 | ] 185 | } 186 | ], 187 | "metadata": {}, 188 | "nbformat": 4, 189 | "nbformat_minor": 2 190 | } 191 | -------------------------------------------------------------------------------- /notebooks/TriangleCounting.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Triangle Counting\n", 8 | "Triangle counting is a community detection graph algorithm that is used to determine the number of triangles passing through each node in the graph.\n", 9 | "A triangle is a set of three nodes where each node has a relationship to all other nodes.\n", 10 | "\n", 11 | "First we'll import the Neo4j driver and Pandas libraries:\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from neo4j.v1 import GraphDatabase, basic_auth\n", 21 | "import pandas as pd\n", 22 | "import os" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "Next let's create an instance of the Neo4j driver which we'll use to execute our queries.\n" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 2, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "host = os.environ.get(\"NEO4J_HOST\", \"bolt://localhost\") \n", 39 | "user = os.environ.get(\"NEO4J_USER\", \"neo4j\")\n", 40 | "password = os.environ.get(\"NEO4J_PASSWORD\", \"neo\")\n", 41 | "driver = GraphDatabase.driver(host, auth=basic_auth(user, password))" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "Now let's create a sample graph that we'll run the algorithm against.\n" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 3, 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "Stats: {'labels-added': 6, 'relationships-created': 8, 'nodes-created': 6, 'properties-set': 6}\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "create_graph_query = '''\n", 66 | "CREATE (alice:Person{id:\"Alice\"}),\n", 67 | " (michael:Person{id:\"Michael\"}),\n", 68 | " (karin:Person{id:\"Karin\"}),\n", 69 | " (chris:Person{id:\"Chris\"}),\n", 70 | " (will:Person{id:\"Will\"}),\n", 71 | " (mark:Person{id:\"Mark\"})\n", 72 | "CREATE (michael)-[:KNOWS]->(karin),\n", 73 | " (michael)-[:KNOWS]->(chris),\n", 74 | " (will)-[:KNOWS]->(michael),\n", 75 | " (mark)-[:KNOWS]->(michael),\n", 76 | " (mark)-[:KNOWS]->(will),\n", 77 | " (alice)-[:KNOWS]->(michael),\n", 78 | " (will)-[:KNOWS]->(chris),\n", 79 | " (chris)-[:KNOWS]->(karin);\n", 80 | "'''\n", 81 | "\n", 82 | "with driver.session() as session:\n", 83 | " result = session.write_transaction(lambda tx: tx.run(create_graph_query))\n", 84 | " print(\"Stats: \" + str(result.consume().metadata.get(\"stats\", {})))" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "Finally we can run the algorithm by executing the following query:\n" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 4, 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "data": { 101 | "text/html": [ 102 | "
\n", 103 | "\n", 116 | "\n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | "
nodeAnodeBnodeC
0MichaelChrisWill
1MichaelKarinChris
2MichaelWillMark
\n", 146 | "
" 147 | ], 148 | "text/plain": [ 149 | " nodeA nodeB nodeC\n", 150 | "0 Michael Chris Will\n", 151 | "1 Michael Karin Chris\n", 152 | "2 Michael Will Mark" 153 | ] 154 | }, 155 | "execution_count": 4, 156 | "metadata": {}, 157 | "output_type": "execute_result" 158 | } 159 | ], 160 | "source": [ 161 | "streaming_query = \"\"\"\n", 162 | "CALL algo.triangle.stream('Person','KNOWS') \n", 163 | "yield nodeA,nodeB,nodeC\n", 164 | "\n", 165 | "MATCH (a:Person) WHERE id(a) = nodeA\n", 166 | "MATCH (b:Person) WHERE id(b) = nodeB\n", 167 | "MATCH (c:Person) WHERE id(c) = nodeC\n", 168 | "\n", 169 | "RETURN a.id AS nodeA, b.id AS nodeB, c.id AS nodeC\n", 170 | "\"\"\"\n", 171 | "\n", 172 | "with driver.session() as session:\n", 173 | " result = session.read_transaction(lambda tx: tx.run(streaming_query)) \n", 174 | " df = pd.DataFrame([r.values() for r in result], columns=result.keys())\n", 175 | "\n", 176 | "df" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [] 183 | } 184 | ], 185 | "metadata": {}, 186 | "nbformat": 4, 187 | "nbformat_minor": 2 188 | } 189 | -------------------------------------------------------------------------------- /notebooks/figure/vis.min.css: -------------------------------------------------------------------------------- 1 | .vis-background,.vis-labelset,.vis-timeline{overflow:hidden}.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper::after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}.vis-panel,.vis-timeline{padding:0;box-sizing:border-box}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#3876c2', endColorstr='#385380', GradientType=0 );box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#9d9d9d', endColorstr='#c8c8c8', GradientType=0 )}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:#fff solid 1px;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}.vis-timeline{position:relative;border:1px solid #bfbfbf;margin:0}.vis-panel{position:absolute;margin:0}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-itemset,.vis-labelset,.vis-labelset .vis-label{position:relative;box-sizing:border-box}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-labelset .vis-label{left:0;top:0;width:100%;color:#4d4d4d;border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}.vis-itemset{padding:0;margin:0}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-item{position:absolute;color:#1A1A1A;border-color:#97B0F8;border-width:1px;background-color:#D5DDF6;display:inline-block}.vis-item.vis-point.vis-selected,.vis-item.vis-selected{background-color:#FFF785}.vis-item.vis-selected{border-color:#FFC200;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:0 0}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-delete{background:url(img/timeline/delete.png) center no-repeat;position:absolute;width:24px;height:24px;top:-4px;right:-24px;cursor:pointer}.vis-item.vis-range .vis-drag-left{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis-current-time{background-color:#FF7F6E;width:2px;z-index:1}.vis-custom-time{background-color:#6E94FF;width:2px;cursor:move;z-index:1}div.vis-network div.vis-close,div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-touch-callout:none;-khtml-user-select:none}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px rgba(154,154,154,.55)}.vis-legend-text{white-space:nowrap;display:inline-block}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}div.vis-network div.vis-manipulation{border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc', GradientType=0 );padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;padding:0 8px;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313', GradientType=0 )}div.vis-color-picker div.vis-initial-color,div.vis-color-picker div.vis-new-color{width:140px;height:20px;top:380px;font-size:10px;color:rgba(0,0,0,.4);line-height:20px;position:absolute;vertical-align:middle}div.vis-color-picker div.vis-new-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:159px;text-align:right;padding-right:2px}div.vis-color-picker div.vis-initial-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:10px;text-align:left;padding-left:2px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px} -------------------------------------------------------------------------------- /notebooks/scripts/algo.py: -------------------------------------------------------------------------------- 1 | from IPython.core.display import HTML 2 | 3 | 4 | def viz_config(algorithm_name): 5 | return { 6 | "Page Rank": { 7 | "query": "MATCH (p1:Page)-[r:LINKS]->(p2:Page) RETURN *", 8 | "labels_json": { 9 | 'Page': { 10 | 'caption': 'name', 11 | 'size': 'pagerank' 12 | } 13 | }, 14 | "relationships_json": { 15 | 'LINKS': { 16 | 'thickness': 'weight', 17 | 'caption': False 18 | } 19 | } 20 | }, 21 | "Betweenness Centrality": { 22 | "query": "MATCH (p1:User)-[r:MANAGE]->(p2:User) RETURN *", 23 | "labels_json": { 24 | 'User': { 25 | 'caption': 'id', 26 | 'size': 'centrality' 27 | } 28 | }, 29 | "relationships_json": { 30 | 'MANAGE': { 31 | 'thickness': 'weight', 32 | 'caption': False 33 | } 34 | } 35 | }, 36 | "Closeness Centrality": { 37 | "query": "MATCH (p1:Node)-[r:LINK]->(p2:Node) RETURN *", 38 | "labels_json": { 39 | 'Node': { 40 | 'caption': 'id', 41 | 'size': 'centrality' 42 | } 43 | }, 44 | "relationships_json": { 45 | 'LINK': { 46 | 'thickness': 'weight', 47 | 'caption': False 48 | } 49 | } 50 | }, 51 | "Degree Centrality": { 52 | "query": "MATCH (p1:User)-[r:FOLLOWS]->(p2:User) RETURN *", 53 | "labels_json": { 54 | 'User': { 55 | 'caption': 'id', 56 | 'size': 'followers' 57 | } 58 | }, 59 | "relationships_json": { 60 | 'FOLLOWS': { 61 | 'thickness': 'weight', 62 | 'caption': False 63 | } 64 | } 65 | }, 66 | "Louvain": { 67 | "query": "MATCH (p1:User)-[r:FRIEND]->(p2:User) RETURN *", 68 | "labels_json": { 69 | 'User': { 70 | 'caption': 'id', 71 | 'size': 'centrality', 72 | 'community': 'community' 73 | } 74 | }, 75 | "relationships_json": { 76 | 'FRIEND': { 77 | 'thickness': 'weight', 78 | 'caption': False 79 | } 80 | } 81 | }, 82 | "Strongly Connected Components": { 83 | "query": "MATCH (p1:User)-[r:FOLLOW]->(p2:User) RETURN *", 84 | "labels_json": { 85 | 'User': { 86 | 'caption': 'id', 87 | 'community': 'partition' 88 | } 89 | }, 90 | "relationships_json": { 91 | 'FOLLOW': { 92 | 'thickness': 'weight', 93 | 'caption': False 94 | } 95 | } 96 | }, 97 | "Unweighted Connected Components": { 98 | "query": "MATCH (p1:User)-[r:FRIEND]->(p2:User) RETURN *", 99 | "labels_json": { 100 | 'User': { 101 | 'caption': 'id', 102 | 'community': 'partition' 103 | } 104 | }, 105 | "relationships_json": { 106 | 'FRIEND': { 107 | 'thickness': 'weight', 108 | 'caption': False 109 | } 110 | } 111 | }, 112 | "Weighted Connected Components": { 113 | "query": "MATCH (p1:User)-[r:FRIEND]->(p2:User) RETURN *", 114 | "labels_json": { 115 | 'User': { 116 | 'caption': 'id', 117 | 'community': 'partition' 118 | } 119 | }, 120 | "relationships_json": { 121 | 'FRIEND': { 122 | 'thickness': 'weight', 123 | 'caption': False 124 | } 125 | } 126 | }, 127 | "Label Propagation": { 128 | "query": "MATCH (p1:User)-[r:FOLLOW]->(p2:User) RETURN *", 129 | "labels_json": { 130 | 'User': { 131 | 'caption': 'id', 132 | 'community': 'partition' 133 | } 134 | }, 135 | "relationships_json": { 136 | 'FOLLOW': { 137 | 'thickness': 'weight', 138 | 'caption': False 139 | } 140 | } 141 | } 142 | }[algorithm_name] 143 | 144 | 145 | def render_image(image_src): 146 | return HTML('' % image_src) 147 | -------------------------------------------------------------------------------- /notebooks/scripts/vis.py: -------------------------------------------------------------------------------- 1 | from IPython.display import IFrame, HTML 2 | import json 3 | import uuid 4 | 5 | 6 | def generate_vis(host, user, password, cypher, labels_json, relationships_json): 7 | html = """\ 8 | 9 | 10 | Neovis.js Simple Example 11 | 22 | 23 | 24 | 25 | 29 | 30 | 61 | 62 | 63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | """ 72 | 73 | html = html.format( 74 | host=host, 75 | user=user, 76 | password=password, 77 | cypher=cypher, 78 | labels = json.dumps(labels_json), 79 | relationships=json.dumps(relationships_json) 80 | # relationships=json.dumps(relationships).replace("{", "{{").replace("}", "}}") 81 | ) 82 | 83 | unique_id = str(uuid.uuid4()) 84 | filename = "figure/graph-{}.html".format(unique_id) 85 | with open(filename, "w") as f: 86 | f.write(html) 87 | 88 | # print(filename) 89 | 90 | # return HTML(html) 91 | 92 | # return HTML(html) 93 | 94 | return IFrame(filename, width="100%", height="320px") 95 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | bleach==2.1.1 3 | decorator==4.1.2 4 | entrypoints==0.2.3 5 | html5lib==1.0b10 6 | ipykernel==4.6.1 7 | ipython==6.2.1 8 | ipython-genutils==0.2.0 9 | ipywidgets==7.0.5 10 | jedi==0.11.0 11 | Jinja2==2.10 12 | jsonschema==2.6.0 13 | jupyter==1.0.0 14 | jupyter-client==5.1.0 15 | jupyter-console==5.2.0 16 | jupyter-core==4.4.0 17 | MarkupSafe==1.0 18 | mistune==0.8.3 19 | nbconvert==5.3.1 20 | nbformat==4.4.0 21 | neo4j-driver==1.5.2 22 | notebook==5.2.2 23 | pandocfilters==1.4.2 24 | parso==0.1.0 25 | pexpect==4.3.0 26 | pickleshare==0.7.4 27 | prompt-toolkit==1.0.15 28 | ptyprocess==0.5.2 29 | Pygments==2.2.0 30 | python-dateutil==2.6.1 31 | pyzmq==16.0.3 32 | qtconsole==4.3.1 33 | simplegeneric==0.8.1 34 | six==1.11.0 35 | terminado==0.8.1 36 | testpath==0.3.1 37 | tornado==4.5.2 38 | traitlets==4.3.2 39 | wcwidth==0.1.7 40 | webencodings==0.5.1 41 | widgetsnbextension==3.0.8 42 | --------------------------------------------------------------------------------