{"id":2123,"date":"2018-04-29T17:39:49","date_gmt":"2018-04-30T00:39:49","guid":{"rendered":"http:\/\/www.hhhh.org\/joeboy\/blog\/?p=2123"},"modified":"2018-04-29T22:22:14","modified_gmt":"2018-04-30T05:22:14","slug":"networkx-graph-library-for-python","status":"publish","type":"post","link":"https:\/\/www.hhhh.org\/joeboy\/blog\/?p=2123","title":{"rendered":"NetworkX Graph library for Python"},"content":{"rendered":"<p>I&#8217;ll admit &#8211; I still try and write Python like it was C or C++. To fix that for a while I am going to switch to writing things in Python for a while, while working on actually writing good Python.<\/p>\n<p>Graph algorithms are the kidneys of computer science &#8211; there in the background filtering out all the bad things for us. In my switch over to writing in Python I needed to find a solid python graph library, and decided to try NetworkX. Here is a simple example program using it. These are mostly just notes for myself &#8211; but I am posting it here in case it is useful to anyone else. <\/p>\n<p>This code is basically the NetworkX <a href=\"https:\/\/networkx.github.io\/documentation\/stable\/auto_examples\/drawing\/plot_weighted_graph.html\" title=\"NetworkX - Weighted Graph Example\">weighted graph example<\/a>, with some annotations. Here is some code to create and plot a weighted graph. <\/p>\n<pre>\r\n<CODE>\r\nimport matplotlib.pyplot as plt\r\nimport networkx as nx\r\n\r\n# Populate the graph...\r\nmap_graph = nx.Graph()\r\n\r\nmap_graph.add_edge('Issaquah','Bellevue', weight=0.11 )\r\nmap_graph.add_edge('Bellevue','Kirkland', weight=0.05 )\r\nmap_graph.add_edge('Bellevue','Redmond', weight=0.06 )\r\nmap_graph.add_edge('Kirkland','Redmond', weight=0.24 )\r\nmap_graph.add_edge('Bellevue','Seattle', weight=0.01 )\r\nmap_graph.add_edge('Renton','Seattle', weight=0.03 )\r\nmap_graph.add_edge('Renton','Bellevue', weight=0.48 )\r\n\r\n# Determine the layout of points in the graph. Some other layouts that \r\n# would work here are spring_layout, shell_layout, and random_layout                                                                                                 \r\npos = nx.circular_layout( map_graph ) # Sets positions for nodes\r\n\r\n# Put \"dots\" down for each node                                                     \r\nnx.draw_networkx_nodes( map_graph, pos, node_size=700)\r\n\r\n# Attaches Labesl to each node                                                               \r\nnx.draw_networkx_labels( map_graph, pos, font_size=14, font_family='sans-serif')\r\n\r\nmap_edges = [(u,v) for (u, v, d) in map_graph.edges(data=True) ]\r\n\r\nnx.draw_networkx_edges( map_graph,\r\n                        pos,             # Spring\r\n                        map_edges,       # List of edges                                     \r\n                        width=5, alpha=0.5, edge_color='b', style='dashed' )\r\n\r\nplt.axis('off')\r\nplt.show()\r\n<\/CODE>\r\n<\/pre>\n<p>The plotted graph looks something like this. <\/p>\n<p><a href=\"https:\/\/www.hhhh.org\/joeboy\/blog\/wp-content\/uploads\/2018\/04\/Screen-Shot-2018-04-29-at-5.01.30-PM.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.hhhh.org\/joeboy\/blog\/wp-content\/uploads\/2018\/04\/Screen-Shot-2018-04-29-at-5.01.30-PM.png\" alt=\"Screen Shot 2018-04-29 at 5.01.30 PM\" width=\"1566\" height=\"728\" class=\"aligncenter size-full wp-image-2124\" \/><\/a><\/p>\n<p>One of the things I want from a graph library is the standard graph algorithms &#8211; like finding me the shortest path between nodes, or testing for the presence of a path at all. So for example to find the shortest path from Issaquah to Renton we could add the following code to the example above?:<\/p>\n<pre>\r\n<CODE>\r\n# Generates the shortest path - but since there could be more than                                                   \r\n# one of them what you get back is a \"generator\" from which you                                                      \r\n# need to pluck the shortest path.                                                                                   \r\na_shortest_path = nx.all_shortest_paths(map_graph, source='Issaquah', target='Renton')                              \r\n\r\nprint( [p for p in a_shortest_path] )    \r\n<\/CODE>\r\n<\/pre>\n<p>The added code will give the path <CODE>[[&#8216;Issaquah&#8217;, &#8216;Bellevue&#8217;, &#8216;Renton&#8217;]]<\/CODE>, which is the shortest path by number of edges traversed. To obtain the shortest path by edge weight we can use dijkstras like so:<\/p>\n<p><CODE><br \/>\na_shortest_path = nx.dijkstra_path(map_graph, &#8216;Issaquah&#8217;, &#8216;Renton&#8217;)<br \/>\nprint( a_shortest_path )<br \/>\n<\/CODE><\/p>\n<p>Which gives the path: <CODE>[&#8216;Issaquah&#8217;, &#8216;Bellevue&#8217;, &#8216;Seattle&#8217;, &#8216;Renton&#8217;]<\/CODE><\/p>\n<p>The problem is that this implementation of dijkstra&#8217;s fails when a path is not present between the origin and destination nodes. So for example if we add a disconnected vertex with no edges like below, searches on a path to that vertex will cause the call to fail. <\/p>\n<p><CODE><br \/>\nmap_graph.add_node(&#8216;Mercer Island&#8217;)<br \/>\n<\/CODE><\/p>\n<p><a href=\"https:\/\/www.hhhh.org\/joeboy\/blog\/wp-content\/uploads\/2018\/04\/Screen-Shot-2018-04-29-at-8.15.15-PM.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.hhhh.org\/joeboy\/blog\/wp-content\/uploads\/2018\/04\/Screen-Shot-2018-04-29-at-8.15.15-PM.png\" alt=\"Screen Shot 2018-04-29 at 8.15.15 PM\" width=\"1722\" height=\"948\" class=\"aligncenter size-full wp-image-2136\" \/><\/a><\/p>\n<p>I think you need to use the has_path method to test for the existence of any path first, then if there is a path use dijkstras to find the minimum cost path. Something like this:<\/p>\n<pre>\r\n<CODE>\r\n# Create a helper function for testing for a path\r\ndef shortest_weighted(from_node, to_node):\r\n    if nx.has_path( map_graph, from_node, to_node ) == True :\r\n\ta_shortest_path = nx.dijkstra_path(map_graph, from_node, to_node)\r\n        return( a_shortest_path )\r\n    else:\r\n        print(\"No path from\",from_node,\"to\", to_node, \"\\r\\n\")\r\n        return( False )\r\n<\/CODE>\r\n<\/pre>\n<p>Then checking for the minimum path can look something like this:<\/p>\n<pre>\r\n<CODE>\r\n# Test where path does not exist\r\nshortest_weighted_path = shortest_weighted( 'Issaquah', 'Mercer Island' )\r\nif shortest_weighted_path != False:\r\n    print( shortest_weighted_path )\r\n\r\n# Test where path does exist\r\nshortest_weighted_path = shortest_weighted( 'Issaquah', 'Renton' )\r\nif shortest_weighted_path != False:\r\n    print( shortest_weighted_path )\r\n<CODE>\r\n<\/pre>\n<p>Neither call will fail and checking for the existing path from Issaquah to Renton will return: <CODE>[&#8216;Issaquah&#8217;, &#8216;Bellevue&#8217;, &#8216;Seattle&#8217;, &#8216;Renton&#8217;]<\/CODE><\/p>\n<p>Since the graphs are weighted &#8211; list comprehensions can be used to conditionally act based on edge weights. For example:<\/p>\n<pre>\r\n<CODE>\r\n# Segment the nodes by weight                                                                                                  \r\nmap_red_edges  = [(u,v) for (u, v, d) in map_graph.edges(data=True) if d['weight'] >  0.1  ]\r\nmap_blue_edges = [(u,v) for (u, v, d) in map_graph.edges(data=True) if d['weight'] <= 0.1  ]\r\n<\/CODE>\r\n<\/pre>\n<p>Then use the segmentation to perform multiple colorings.<\/p>\n<pre>\r\n<CODE>\r\nnx.draw_networkx_edges( map_graph,\r\n                        pos,             # Spring                                                                    \r\n                        map_red_edges,   # List of edges                                                             \r\n                        width=5,\r\n                        alpha=0.5,\r\n                        edge_color='r',\r\n                        style='dashed' )\r\n\r\nnx.draw_networkx_edges( map_graph,\r\n                        pos,             # Spring                                                                    \r\n                        map_blue_edges,  # List of edges                                                             \r\n                        width=5,\r\n                        alpha=0.5,\r\n                        edge_color='b',\r\n                        style='solid' )\r\n<\/CODE>\r\n<\/pre>\n<p>Which will produce the following graph:<\/p>\n<p><a href=\"https:\/\/www.hhhh.org\/joeboy\/blog\/wp-content\/uploads\/2018\/04\/Screen-Shot-2018-04-29-at-9.49.43-PM.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.hhhh.org\/joeboy\/blog\/wp-content\/uploads\/2018\/04\/Screen-Shot-2018-04-29-at-9.49.43-PM.png\" alt=\"Screen Shot 2018-04-29 at 9.49.43 PM\" width=\"1538\" height=\"830\" class=\"aligncenter size-full wp-image-2145\" \/><\/a><\/p>\n<p>Also useful is shortest_path, which returns all the paths between all nodes, but sorted by vertex traversal path length. So the following code: <\/p>\n<pre>\r\n<CODE>\r\npaths = nx.shortest_path(map_graph)\r\nprint( paths )\r\n<\/CODE>\r\n<\/pre>\n<p>Generates the following paths. <\/p>\n<pre>\r\n<CODE>\r\n{'Issaquah': {'Issaquah': ['Issaquah'], 'Bellevue': ['Issaquah', 'Bellevue'], 'Kirkland': ['Issaquah', 'Bellevue', 'Kirkland'], 'Redmond': ['Issaquah', 'Bellevue', 'Redmond'], 'Seattle': ['Issaquah', 'Bellevue', 'Seattle'], 'Renton': ['Issaquah', 'Bellevue', 'Renton']}, 'Bellevue': {'Bellevue': ['Bellevue'], 'Issaquah': ['Bellevue', 'Issaquah'], 'Kirkland': ['Bellevue', 'Kirkland'], 'Redmond': ['Bellevue', 'Redmond'], 'Seattle': ['Bellevue', 'Seattle'], 'Renton': ['Bellevue', 'Renton']}, 'Kirkland': {'Kirkland': ['Kirkland'], 'Bellevue': ['Kirkland', 'Bellevue'], 'Redmond': ['Kirkland', 'Redmond'], 'Issaquah': ['Kirkland', 'Bellevue', 'Issaquah'], 'Seattle': ['Kirkland', 'Bellevue', 'Seattle'], 'Renton': ['Kirkland', 'Bellevue', 'Renton']}, 'Redmond': {'Redmond': ['Redmond'], 'Bellevue': ['Redmond', 'Bellevue'], 'Kirkland': ['Redmond', 'Kirkland'], 'Issaquah': ['Redmond', 'Bellevue', 'Issaquah'], 'Seattle': ['Redmond', 'Bellevue', 'Seattle'], 'Renton': ['Redmond', 'Bellevue', 'Renton']}, 'Seattle': {'Seattle': ['Seattle'], 'Bellevue': ['Seattle', 'Bellevue'], 'Renton': ['Seattle', 'Renton'], 'Issaquah': ['Seattle', 'Bellevue', 'Issaquah'], 'Kirkland': ['Seattle', 'Bellevue', 'Kirkland'], 'Redmond': ['Seattle', 'Bellevue', 'Redmond']}, 'Renton': {'Renton': ['Renton'], 'Seattle': ['Renton', 'Seattle'], 'Bellevue': ['Renton', 'Bellevue'], 'Issaquah': ['Renton', 'Bellevue', 'Issaquah'], 'Kirkland': ['Renton', 'Bellevue', 'Kirkland'], 'Redmond': ['Renton', 'Bellevue', 'Redmond']}, 'Mercer Island': {'Mercer Island': ['Mercer Island']}}\r\n<\/CODE>\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ll admit &#8211; I still try and write Python like it was C or C++. To fix that for a while I am going to switch to writing things in Python for a while, while working on actually writing good Python. Graph algorithms are the kidneys of computer science &#8211; there in the background filtering [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[39],"tags":[],"class_list":["post-2123","post","type-post","status-publish","format-standard","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2123","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2123"}],"version-history":[{"count":18,"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2123\/revisions"}],"predecessor-version":[{"id":2149,"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2123\/revisions\/2149"}],"wp:attachment":[{"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2123"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2123"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.hhhh.org\/joeboy\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2123"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}