Related
I am completely new on making network graphs in Python. I came to learn that networkx library can be used to plot network graphs in Python.
I have 11 nodes. Node1, Node2, ..., Node11. I want to plot them in four different levels. Node1 is in level 1. Node2 and Node3 are in level 2. Node4 to Node7 are in level3, and Node8 to Node11 are in level 4.
I have created a very simple basic network graph between level 1 and 2 using
# Build a dataframe with 4 connections
df = pd.DataFrame({ 'from':['Node1',"Node1"], 'to':["Node2","Node3"]})
# Build your graph
G=nx.from_pandas_edgelist(df, 'from', 'to')
# Plot it
nx.draw(G, arrows = True, with_labels = True)
plt.show()
And it looks as follows:
I want to plot Node2 and Node3 in the same direction towards right of Node1 above and below it respectively. Is it also possible to get a rectangle instead of a circle in the node, and is it also possible to display the labels outside of the node?
To plot between level 2 and level3, I want to ask the user if there is edgelink between each of Node2 and Node3, with each of Node4, Node5, Node6 and Node7. Also, I want to ask the user if there is edge link between each node in level 3 and level 4 respectively?
It should look something as shown below and the connections between level 2 and level 3, and level 3 and level 4 should be based on user input. What would be the suitable way to obtain this network graph plot? Is there any other libraries better suited for this?
Based on other question on Stackoverflow: networkx add_node with specific position
You can use draw(..., pos=dictionary, ...) to set nodes in specific positions.
positions = {
'Node1': (0, 0),
'Node2': (1, 1),
'Node3': (1, -1),
'Node4': (2, 2),
'Node5': (2, 0),
}
draw(..., pos=positions, ...)
Minimal working code
import pandas as pd
import networkx as nx
from matplotlib import pyplot as plt
df = pd.DataFrame({
'from': ['Node1', 'Node1', 'Node2', 'Node2'],
'to': ['Node2', 'Node3', 'Node4', 'Node5']
})
G = nx.from_pandas_edgelist(df, 'from', 'to')
positions = {
'Node1': (0, 0),
'Node2': (1, 1),
'Node3': (1, -1),
'Node4': (2, 2),
'Node5': (2, 0),
}
nx.draw(G, pos=positions, arrows=True, with_labels=True)
plt.show()
Result:
Based on other answer on Stackoverflow - you can use node_shape to change shape like in matplotlib. I found only square - node_shape='s' - and using node_size=1000 I could change size for all squares or use list/tuple node_size=(1000, 1000, ...) to change size to every node separatelly
nx.draw(G, pos=positions, arrows=True, with_labels=True, node_shape='s', node_size=1000)
Result:
Based on documentation I found that you can draw separatelly nodes, labels, edges so you can use different positions for labes and set them above node.
nodes_positions = {
'Node1': (0, 0),
'Node2': (1, 1),
'Node3': (1, -1),
'Node4': (2, 2),
'Node5': (2, 0),
}
labels_positions = {
'Node1': (0, 0.3),
'Node2': (1, 1.3),
'Node3': (1, -0.7),
'Node4': (2, 2.3),
'Node5': (2, 0.3),
}
nx.draw_networkx_nodes(G, pos=nodes_positions, node_shape='s', node_size=1000)
nx.draw_networkx_labels(G, pos=labels_positions)
nx.draw_networkx_edges(G, pos=nodes_positions)
Result:
Doc:
draw_networkx_nodes
draw_networkx_labels
draw_networkx_edges
EDIT:
Full example code for last image
import pandas as pd
import networkx as nx
from matplotlib import pyplot as plt
#df = pd.DataFrame({
# 'from': ['Node1', 'Node1', 'Node2', 'Node2'],
# 'to': ['Node2', 'Node3', 'Node4', 'Node5']
#})
df = pd.DataFrame([
# pairs (from, to)
('Node1', 'Node2'),
('Node1', 'Node3'),
('Node2', 'Node4'),
('Node2', 'Node5'),
], columns=['from', 'to'])
G = nx.from_pandas_edgelist(df, 'from', 'to')
nodes_positions = {
'Node1': (0, 0),
'Node2': (1, 1),
'Node3': (1, -1),
'Node4': (2, 2),
'Node5': (2, 0),
}
labels_positions = {
'Node1': (0, 0.3),
'Node2': (1, 1.3),
'Node3': (1, -0.7),
'Node4': (2, 2.3),
'Node5': (2, 0.3),
}
#nx.draw(G, pos=positions, arrows=True, with_labels=True, node_shape='s', node_size=1000)
nx.draw_networkx_nodes(G, pos=nodes_positions, node_shape='s', node_size=1000)
nx.draw_networkx_labels(G, pos=labels_positions)
nx.draw_networkx_edges(G, pos=nodes_positions)
plt.show()
furas has answered first half of my question. For the second part, I created two lists using for-loop to determine if there is link between two specific nodes and updated the lists accordingly. Using the list, I created a dataframe containing From and To columns, that reflect the linkage from any parent to child node(s).
a = ["Node1","Node1"]
b = ["Node2","Node3"]
#Ask if you want link between nodes 2 and 3, with each of node 4 to 7
for i in range(2,4):
for j in range(4,8):
edge = input(f"Edge between {i} and {j}?")
if edge == "Yes":
a.append(f"Node{i}")
b.append(f"Node{j}")
#Ask if you want link between nodes 4 and 7, with each of node 5 to 8
for i in range(4,8):
for j in range(8,12):
edge = input(f"Edge between {i} and {j}?")
if edge == "Yes":
a.append(f"Node{i}")
b.append(f"Node{j}")
#Create a dataframe using the lists a and b
df = pd.DataFrame([])
df["From"] = a
df["To"] = b
df
Currently there is a function in networkx library for getting positions of all nodes: spring_layout. Quoting from the docs, it returns:
dict :
A dictionary of positions keyed by node
And can be used as:
G=nx.path_graph(4)
pos = nx.spring_layout(G)
I would like something similar to access the position of an edge-weight for a weighted graph. It should return the position of where the number for edge-weight would be placed, preferably at the center of the edge and just above the edge. (By above, I mean "outside" the graph, so for a horizontally-placed square graph's bottom-most edge, it would be just below the edge).
So the question is, is there anything in-built similar to spring_layout for achieving this? And if not, how to go about it yourself?
You can use nx.draw_edge_labels which returns a dictionary with edges as keys and (x, y, label) as values
import matplotlib.pyplot as plt
import networkx as nx
# Create a graph
G = nx.path_graph(10)
# Add 2 egdes with labels
G.add_edge(0, 8, name='n1')
G.add_edge(2, 7, name='n2')
# Get the layout
pos = nx.spring_layout(G)
# Draw the graph
nx.draw(G, pos=pos)
# Draw the edge labels
edge_labels = nx.draw_networkx_edge_labels(G, pos)
.
Now you can see the variables edge_labels
print(edge_labels)
# {(0, 1): Text(0.436919941201627, -0.2110471432994752, '{}'),
# (0, 8): Text(0.56941037628304, 0.08059107891826373, "{'name': 'n1'}"),
# (1, 2): Text(0.12712625526483384, -0.2901338796021985, '{}'),
# (2, 3): Text(-0.28017240645783603, -0.2947104829441387, '{}'),
# (2, 7): Text(0.007024254096114596, -0.029867791669433513, "{'name': 'n2'}"),
# (3, 4): Text(-0.6680363649371021, -0.26708812849092933, '{}'),
# (4, 5): Text(-0.8016944207643129, -0.0029986274715349814, '{}'),
# (5, 6): Text(-0.5673817462107436, 0.23808073918504968, '{}'),
# (6, 7): Text(-0.1465270298295821, 0.23883392944036055, '{}'),
# (7, 8): Text(0.33035539545007536, 0.2070939421162053, '{}'),
# (8, 9): Text(0.7914739158501038, 0.2699223242747882, '{}')}
Now to get the position of say, edge (2,7), you just need to do
print(edge_labels[(2,7)].get_position())
# Output: (0.007024254096114596, -0.029867791669433513)
You can read more about the documentation here.
If you want to extract the x,y coordinates of all the edges, you can try this:
edge_label_pos = { k: v.get_position()
for k, v in edge_labels.items()}
#{(0, 1): (0.436919941201627, -0.2110471432994752),
# (0, 8): (0.56941037628304, 0.08059107891826373),
# (1, 2): (0.12712625526483384, -0.2901338796021985),
# (2, 3): (-0.28017240645783603, -0.2947104829441387),
# (2, 7): (0.007024254096114596, -0.029867791669433513),
# (3, 4): (-0.6680363649371021, -0.26708812849092933),
# (4, 5): (-0.8016944207643129, -0.0029986274715349814),
# (5, 6): (-0.5673817462107436, 0.23808073918504968),
# (6, 7): (-0.1465270298295821, 0.23883392944036055),
# (7, 8): (0.33035539545007536, 0.2070939421162053),
# (8, 9): (0.7914739158501038, 0.2699223242747882)}
I am trying to found the longest way in networkx from node A to node A using only 10 other nodes in fully connected graph as shown below. Is it possible?
G = nx.Graph()
for i in range(len(hashlist)):
for j in range(len(hashlist)):
if i!=j:
connection=(i,j)
size=hashlist[i]-hashlist[j]
G.add_edge(i, j, weight=size)
This will find the longest cycle in a directed graph.
import networkx as nx
G = nx.Graph([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]).to_directed()
a = sorted(list(nx.simple_cycles(G)), key = lambda s: len(s))
print(a[-1])
The same approach will work for weighted graph.
import networkx as nx
G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5), (2,3, 5), (1,3, 2.4)])
G = G.to_directed()
a = sorted(list(nx.simple_cycles(G)), key = lambda s: len(s))
print(a[-1])
Below are two plotted network graph using two methods. The lines and circles in the first graph looks better and smoother than the second one. But I cannot really tell the reason why the second one does not look the same as the first one in terms of the image quality.
locations = {
0:(4,4),
1:(2,0),
2:(8,0),
3:(0,1),
4:(1,1),
5:(5,2),
6:(7,2),
7:(3,3),
8:(6,3),
}
edges = [
(0, 8, {'vehicle': '0'}),
(8, 6, {'vehicle': '0'}),
(6, 2, {'vehicle': '0'}),
(2, 5, {'vehicle': '0'}),
(5, 0, {'vehicle': '0'}),
(0, 7, {'vehicle': '1'}),
(7, 1, {'vehicle': '1'}),
(1, 4, {'vehicle': '1'}),
(4, 3, {'vehicle': '1'}),
(3, 0, {'vehicle': '1'}),
]
G=nx.DiGraph()
G.add_edges_from(edges)
plt.figure(figsize=(15,10))
plt.show()
#vehicle 0
temp = [e for e in edges if e[2]['vehicle'] == '0'] #temporary list that filters the path of vehicle 0
nx.draw_networkx_nodes(G, locations, nodelist=[x[0] for x in temp], node_color='b')
nx.draw_networkx_edges(G, locations, edgelist=temp,
width=2, edge_color='b', style='dashed')
#vehicle 1
temp = [e for e in edges if e[2]['vehicle'] == '1']
nx.draw_networkx_nodes(G, locations, nodelist=[x[0] for x in temp], node_color='r')
nx.draw_networkx_edges(G, locations, edgelist=temp,
width=2, edge_color='r', style='dashed')
#let's color the node 0 in black
nx.draw_networkx_nodes(G, locations, nodelist=[0], node_color='k')
# labels
nx.draw_networkx_labels(G, locations, font_color='w', font_size=12, font_family='sans-serif')
#print out the graph
plt.axis('on')
plt.show()
The second graph and codes:
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
locations = \
[(4, 4), # depot
(2, 0), (8, 0), # row 0
(0, 1), (1, 1),
(5, 2), (7, 2),
(3, 3), (6, 3),
(5, 5), (8, 5),
(1, 6), (2, 6),
(3, 7), (6, 7),
(0, 8), (7, 8)]
v0 = [0, 1, 4, 3, 15, 0]
v1 = [0, 14, 16, 10, 2, 0]
vehicles = [v0, v1]
cl = ["r", "b","green","yellow"]
x=0
for v in vehicles:
n=0
e=[]
node=[]
for i in v:
G.add_node(i, pos=(locations[i][0], locations[i][1]))
# a= [locations[i][0], locations[i][1]]
# print(a)
if n > 0:
# print(n)
# print(v[n])
# print (v[n-1])
u= (v[n-1], v[n])
e.append(u)
node.append(i)
print(e)
print(node)
G.add_edge(v[n-1], v[n])
nx.draw(G, nx.get_node_attributes(G, 'pos'), nodelist=node, edgelist=e, with_labels=True, node_color=cl[x], width=2, edge_color=cl[x], \
style='dashed', font_color='w', font_size=12, font_family='sans-serif')
# print(x)
n += 1
x+=1
#let's color the node 0 in black
nx.draw_networkx_nodes(G, locations, nodelist=[0], node_color='k')
plt.axis('on')
plt.show()
when zoomed out (might be not very clear to see here), the lines and circles in the second graph are not as smooth as the first graph. what's the reason of this problem?
You are drawing the same nodes and edges more than once. Call the draw function outside of the node loop:
for v in vehicles:
n=0
e=[]
node=[]
for i in v:
G.add_node(i, pos=(locations[i][0], locations[i][1]))
# a= [locations[i][0], locations[i][1]]
# print(a)
if n > 0:
# print(n)
# print(v[n])
# print (v[n-1])
u= (v[n-1], v[n])
e.append(u)
node.append(i)
print(e)
print(node)
G.add_edge(v[n-1], v[n])
# print(x)
n += 1
nx.draw(G, nx.get_node_attributes(G, 'pos'), nodelist=node, edgelist=e, with_labels=True, node_color=cl[x], width=2, edge_color=cl[x], \
style='dashed', font_color='w', font_size=12, font_family='sans-serif')
x+=1
Which results in this image:
Your code doesn't show how plt is initialized in the first code snipped. These differences tend to come from the backend used by matplotlib. You may want to take a look at this documentation: https://matplotlib.org/faq/usage_faq.html#what-is-a-backend
I have a graph of different locations:
import networkx as nx
G = nx.Graph()
for edge in Edge.objects.all():
G.add_edge(edge.from_location, edge.to_location, weight=edge.distance)
The locations (nodes) have different types (toilets, building entrances, etc.) I need to find the shortest way from some given location to any location of a specific type. (For example: Find the nearest entrance from a given node.)
Is there some method in the Networkx library to solve that without loops? Something like:
nx.shortest_path(
G,
source=start_location,
target=[first_location, second_location],
weight='weight'
)
The result will be the shortest path to either the first_location or the second_location, if both locations are of the same type.
And is there some method that also returns path length?
We will do it in three steps.
Step 1: Let's create a dummy graph to illustrate
Step 2: Plot the graph and color nodes to indicate edge lengths and special node types (toilets, entrances etc.)
Step 3: From any given node (source) calculate shortest path to all reachable nodes, then subset to the node types of interest and select path with the minimum length.
The code below can definitely be optimized, but this might be easier to follow.
Step 1: Create the graph
edge_objects = [(1,2, 0.4), (1, 3, 1.7), (2, 4, 1.2), (3, 4, 0.3), (4 , 5, 1.9),
(4 ,6, 0.6), (1,7, 0.4), (3,5, 1.7), (2, 6, 1.2), (6, 7, 0.3),
(6, 8, 1.9), (8,9, 0.6)]
toilets = [5,9] # Mark two nodes (5 & 9) to be toilets
entrances = [2,7] # Mark two nodes (2 & 7) to be Entrances
common_nodes = [1,3,4,6,8] #all the other nodes
node_types = [(9, 'toilet'), (5, 'toilet'),
(7, 'entrance'), (2, 'entrance')]
#create the networkx Graph with node types and specifying edge distances
G = nx.Graph()
for n,typ in node_types:
G.add_node(n, type=typ) #add each node to the graph
for from_loc, to_loc, dist in edge_objects:
G.add_edge(from_loc, to_loc, distance=dist) #add all the edges
Step 2: Draw the graph
#Draw the graph (optional step)
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True)
edge_labels = nx.get_edge_attributes(G,'distance')
nx.draw_networkx_edge_labels(G, pos, edge_labels = edge_labels)
nx.draw_networkx_nodes(G, pos, nodelist=toilets, node_color='b')
nx.draw_networkx_nodes(G, pos, nodelist=entrances, node_color='g')
nx.draw_networkx_nodes(G, pos, nodelist=common_nodes, node_color='r')
plt.show()
Step 3: create small functions to find the shortest path to node type
def subset_typeofnode(G, typestr):
'''return those nodes in graph G that match type = typestr.'''
return [name for name, d in G.nodes(data=True)
if 'type' in d and (d['type'] ==typestr)]
#All computations happen in this function
def find_nearest(typeofnode, fromnode):
#Calculate the length of paths from fromnode to all other nodes
lengths=nx.single_source_dijkstra_path_length(G, fromnode, weight='distance')
paths = nx.single_source_dijkstra_path(G, fromnode)
#We are only interested in a particular type of node
subnodes = subset_typeofnode(G, typeofnode)
subdict = {k: v for k, v in lengths.items() if k in subnodes}
#return the smallest of all lengths to get to typeofnode
if subdict: #dict of shortest paths to all entrances/toilets
nearest = min(subdict, key=subdict.get) #shortest value among all the keys
return(nearest, subdict[nearest], paths[nearest])
else: #not found, no path from source to typeofnode
return(None, None, None)
Test:
find_nearest('entrance', fromnode=5)
produces:
(7, 2.8, [5, 4, 6, 7])
Meaning: The nearest 'entrance' node from 5 is 7, the path length is 2.8 and the full path is: [5, 4, 6, 7]. Hope this helps you move forward. Please ask if anything is not clear.