How to get position of edge weights in a networkx graph? - python

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)}

Related

Simulate a liquid flow through a randomly created square matrix that contains a set of integers from 0-9 using Python [duplicate]

This question already has an answer here:
Stimulating Liquid Flow on Matrix
(1 answer)
Closed 2 years ago.
Simulate a liquid flow through a randomly created square matrix that contains a set of integers from 0-9 using Python. The liquid should start from the top left corner of the matrix. It could only move towards right or below adjacent matrix. The lower value of the adjacent matrix, the higher potential for it to flow. In the case of the right and below adjacent matrix have the same value, the program should be able to simulate both conditions (Refer attached example image). The movement of the liquid is considered stop at the right or below edge of the matrix. The program should be able to show the sum of all numbers that the liquid has pass through, in all possibilities and visualize the path. Visualization can be done by replacing the numbers lying in the path with asterisk (*) , vertical line/pipe symbol (|), hyphen (-) or other relevant symbols such as (~,>= etc). Other methods to visualize can be accepted. Example output should look like this:
Example output visualization
This is what i have coded so far, but it does not output all the possible outcomes and the sum of integers in which the liquid flows through;
import random
import numpy as np
import copy
a=[]
n=int(input("Enter matrix size between 8 to 20 only= "))
while True:
if n<8:
n=int(input("Input size less than 8! Enter at least 8 or more = "))
elif n>20:
n=int(input("Input size more than 20! Enter at most 20 or lesser = "))
else:
break
print()
print(f'{n} × {n} matrix generated from random numbers of 0-9 :\n')
def create_matrix(n, a):
for i in range (n):
v=[]
for j in range (n):
v.append(random.randint(0,9))
a.append(v)
my.create_matrix(n, a)
b=copy.deepcopy(a)
#c=copy.deepcopy(a)
def print_array(n, array):
for i in range(n):
for j in range(n):
print(array[i][j], end=" ")
print()
print()
my.print_array(n, a)
def move_right(b, i, j, n):
b[0][0]="."
while i+1 < n and j+1<n :
if b[i+1][j] < b[i][j+1]:
b[i+1][j]="."
i+=1
elif b[i][j+1] < b[i+1][j]:
b[i][j+1]="."
j+=1
elif b[i+1][j] == b[i][j+1]:
b[i][j]="*"
#c=copy.deepcopy(b)
#move_bottom(c, i, j, n)
b[i][j+1]="."
j+=1
else:
break
def move_bottom(array,i ,j, n):
array[i][j]="."
alt=0
while i+1 < n and j+1<n :
if array[i+1][j] < array[i][j+1]:
array[i+1][j]="."
i+=1
elif array[i][j+1] < array[i+1][j]:
array[i][j+1]="."
j+=1
elif array[i+1][j] == array[i][j+1]:
array[i][j]="*"
bb=copy.deepcopy(array)
move_right(bb,i,j,n)
array[i+1][j]="."
i+=1
alt+=1
else:
break
print_array(n, array)
my.move_bottom(b, 0, 0, n)
I really need help with my coding so that i can output all the possible outcomes that the liquid can flow and the sum of integers in which the liquid flows through. If there's any other way to easily code this program using python given the conditions, please let me know!
Here is how you could do this for all possible ways.
A map of
456
867
978
would be represented as dictionary of dictionaries:
{ (0,0): {(1,0): {(2,0): {},
(1,1): { (2,1): {},
(1,2): {}}}}
and can be used to generate all paths from it.
Then you need a copy of the original numbers and can add the ascii art instead of the numbers at the correct positions. The total map has to check if you add another way to a formerly already set position, and if so replace it with a "both-ways" marker.
Some utility-methods:
import random
import copy
random.seed(42) # fixed for repeatability
class Consts:
"""Some constants to use throughout"""
VALUES = range(10)
SIZE = 8
START = (0,0)
OUT_OF_BOUNDS = 99
def generate():
"""Generates an Consts.SIZE * Consts.SIZE list of lists of numbers 0-9"""
n = Consts.SIZE
data = random.choices(Consts.VALUES, k = n*n)
return [data[i * n : i * n + n] for i in range(n)]
def v(data, x, y):
"""Returns the value in data at position x,y or
Consts.OUT_OF_BOUNDS if out of bounds."""
try:
return data[y][x]
except:
return Consts.OUT_OF_BOUNDS
def peek_east(data, pos):
"""Returs the value, position tuple of the position one to the east"""
new_pos = pos[0] + 1, pos[1]
return v(data, *new_pos), new_pos
def peek_south(data, pos):
"""Returs the value, position tuple of the position one to the south"""
new_pos = pos[0], pos[1] + 1
return v(data, *new_pos), new_pos
def done(pos):
"""Returns True if a position is at the border of the map"""
return Consts.SIZE-1 in pos
def pp(arr):
"""Pretty print a map / list of lists"""
print('\n'.join(''.join(map(str, n)) for n in arr))
the exploration part:
def explore(data, start=None, ways=None):
"""Creates/Explores all the ways. The exploration position are stored
as dictionary that contains all explored tiles as a dict of dict of ...
of possible ways to go through the map."""
size = Consts.SIZE
OUT = Consts.OUT_OF_BOUNDS
start = start or Consts.START
ways = ways or {}
pos = start
if done(pos):
ways[pos] = "DONE"
return
routes = []
# get east and south data to see where we can go from here
east, east_pos = peek_east(data, pos)
south, south_pos = peek_south(data, pos)
# where to move to
if east <= south:
routes.append(east_pos)
if east >= south:
routes.append(south_pos)
# add the visited tiles and the empty dicts for them to ways
for way in routes:
if pos not in ways:
ways[pos] = {}
if way not in ways:
ways[way] = {}
ways[pos][way] = ways[way]
# explore further
for way in routes:
explore(data, way, ways)
# simplify dict, only return the (0,0) element
return {Consts.START: ways[Consts.START]}
How to use it & ascii art:
array = generate()
pp(array)
exp = explore(array)
# used to create all possible paths from the dict we generated
def get_paths(tree, cur=()):
""" Source: https://stackoverflow.com/a/11570745/7505395 """
if not tree or tree == "DONE":
yield cur
else:
for n, s in tree.items():
for path in get_paths(s, cur+(n,)):
yield path
p = list(get_paths(exp))
# copy the original map for a "all in one" map
d_all = copy.deepcopy(array)
for path in p:
# copy the original map for this paths map
d = copy.deepcopy(array)
# get from,to pairs from this runway
for (_from, _to) in zip(path, path[1:]):
_, pe = peek_east(array, _from)
_, ps = peek_south(array, _from)
# ASCII Art the map
if _to == pe:
d[_from[1]][_from[0]] = "-"
d_all[_from[1]][_from[0]] = "-" if isinstance(d_all[_from[1]][_from[0]], int) else ("-" if d_all[_from[1]][_from[0]] == "-" else "+")
else:
d[_from[1]][_from[0]] = "|"
d_all[_from[1]][_from[0]] = "|" if isinstance(d_all[_from[1]][_from[0]], int) else ("|" if d_all[_from[1]][_from[0]] == "|" else "+")
# ASCII Art the last one
d[_to[1]][_to[0]] = "°"
d_all[_to[1]][_to[0]] = "°"
# print this map
print("\nPath: ", path)
pp(d)
# and total map
print("\nTotal mapping")
pp(d_all)
Output:
60227680
40250165
25808631
93008687
59358685
70220212
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1),
(6, 2), (7, 2))
-|227680
4-----|5
258086-°
93008687
59358685
70220212
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (5, 2),
(6, 2), (7, 2))
-|227680
4----|65
25808--°
93008687
59358685
70220212
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (5, 5), (6, 5), (7, 5))
-|227680
4--|0165
258|8631
930|8687
593|8685
702----°
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (4, 6), (5, 6), (6, 6), (6, 7))
-|227680
4--|0165
258|8631
930|8687
593|8685
702-|212
6332--|6
171396°6
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (4, 6), (5, 6), (5, 7))
-|227680
4--|0165
258|8631
930|8687
593|8685
702-|212
6332-|66
17139°56
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (4, 6), (4, 7))
-|227680
4--|0165
258|8631
930|8687
593|8685
702-|212
6332|966
1713°656
Total mapping
-|227680
4--+-+|5
258|8--°
930|8687
593|8685
702-+--°
6332++|6
1713°°°6

Generate Complete DFS Paths using networkx

I am trying to generate complete path list instead of the optimized one. Better explained using the below example.
import networkx as nx
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
G.add_edges_from([(0, 1), (1, 2), (2, 4)])
G.add_edges_from([(0, 5), (5, 6)])
The above code create a Graph with edges 0=>1=>2=>3 and 0=>1=>2=>4 and 0=>5=>6
All I want is to extract all paths from 0.
I tried:
>> list(nx.dfs_edges(G, 0))
[(0, 1), (1, 2), (2, 3), (2, 4), (0, 5), (5, 6)]
All I want is:
[(0, 1, 2, 3), (0, 1, 2, 4), (0, 5, 6)]
Is there any pre-existing method from networkx which can be used? If not, any way to write an optimal method that can do the job?
Note: My problem is limited to the given example. No more corner cases possible.
Note2: For simplification the data is generated. In my case, the edges list is coming from data set. Assumption is given a graph and a node (Say 0), Can we generate all paths?
Give this a try:
import networkx as nx
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
G.add_edges_from([(0, 1), (1, 2), (2, 4)])
G.add_edges_from([(0, 5), (5, 6)])
pathes = []
path = [0]
for edge in nx.dfs_edges(G, 0):
if edge[0] == path[-1]:
# node of path
path.append(edge[1])
else:
# new path
pathes.append(path)
search_index = 2
while search_index <= len(path):
if edge[0] == path[-search_index]:
path = path[:-search_index + 1] + [edge[1]]
break
search_index += 1
else:
raise Exception("Wrong path structure?", path, edge)
# append last path
pathes.append(path)
print(pathes)
# [[0, 1, 2, 3], [0, 1, 2, 4], [0, 5, 6]]

Counting edges between nodes in networkx

I have a graph where my nodes can have multiple edges between them in both directions and I want to set the width between the nodes based on the sum of all edges between them.
import networkx as nx
nodes = [0,1]
edges = [(0,1),(1,0)]
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
weights = [2,3]
nx.draw(G, width = weights)
I would like to have the width between 0 and 1 set to 5 as that is the summed weight.
First you need to create a MultiDiGraph and add all possible edges to it. This is because it supports multiple directed egdes between the same set of nodes including self-loops.
import networkx as nx
nodes = [0, 1, 2, 3, 4, 5]
edges = [(0,1), (1,0), (1, 0),(0, 1), (2, 3), (2, 3), (2, 3), (2, 3),
(4, 1), (4, 1), (4, 1), (4, 1), (4, 1), (4, 1), (4, 5), (5, 0)]
G = nx.MultiDiGraph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
Next, create a dictionary containing counts of each edges
from collections import Counter
width_dict = Counter(G.edges())
edge_width = [ (u, v, {'width': value})
for ((u, v), value) in width_dict.items()]
Now create a new DiGraph from the edge_width dictionary created above
G_new = nx.DiGraph()
G_new.add_edges_from(edge_width)
Plotting using thickened edges
This is an extension of answer mentioned here.
edges = G_new.edges()
weights = [G_new[u][v]['width'] for u,v in edges]
nx.draw(G_new, edges=edges, width=weights)
Add Edge labels
See this answer for more info.
pos = nx.spring_layout(G_new)
nx.draw(G_new, pos)
edge_labels=dict([((u,v,),d['width'])
for u,v,d in G_new.edges(data=True)])
nx.draw_networkx_edges(G_new, pos=pos)
nx.draw_networkx_edge_labels(G_new, pos, edge_labels=edge_labels,
label_pos=0.25, font_size=10)
You can also view this Google Colab Notebook with working code.
References
https://stackoverflow.com/a/25651827/8160718
https://stackoverflow.com/a/22862610/8160718
Drawing networkX edges
MultiDiGraph in NetworkX
Count occurrences of List items

How to stop Networkx from changing the order of head and tail nodes(u,v) to (v,u) in an edge?

I've got a simple graph created using networkx.
import networkx as nx
import matplotlib.pyplot as plt
from pprint import pprint
G = nx.Graph()
head_nodes = range(0, 9)
tail_nodes = range(1, 10)
edge_ls = list(zip(head_nodes, tail_nodes))
G.add_nodes_from(range(0, 10))
G.add_edges_from(edge_ls)
pprint(G.nodes())
nx.draw(G)
plt.show()
I want to remove the edge between node 0 and 1 and add three new nodes (say node 10,11,12). Then, edges have
to be created between node 0 and 10, 10 and 11, 11 and 2.
I'm using G.remove_edge(0,1) to remove the edge between node 0 and 1.
Could someone suggest which function can be used to add n new nodes?
Also, if n new nodes are added, will these nodes be numbered automatically?
I intend to do this in a loop, delete an edge that already exists between two nodes and add n new nodes and edges connecting these nodes.
EDIT:
I tried the following to add n new edges
G = nx.Graph()
head_nodes = range(0, 9)
tail_nodes = range(1, 10)
edge_ls = list(zip(head_nodes, tail_nodes))
G.add_nodes_from(range(0, 10))
G.add_edges_from(edge_ls)
head = 0
tail = 1
G.remove_edge(head, tail)
Nnodes = G.number_of_nodes()
newnodes = [head, Nnodes+1, Nnodes+2, Nnodes+3, tail] # head and tail already exists
newedges = [(x, y) for x, y in zip(newnodes[0:len(newnodes)-1], newnodes[1:len(newnodes)])]
G.add_edges_from(newedges)
pprint(G.edges())
Output:
EdgeView([(0, 11), (1, 2), (1, 13), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (11, 12), (12, 13)])
Expected Output:
EdgeView([(0, 11), (1, 2), (13, 1), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (11, 12), (12, 13)])
I'm not sure why the edge that was added in the order (13,1)(head, tail) is stored as (1,13). Any suggestion on how to preserve the order of head and tail node while adding a new edge?
EDIT2:
replacing nx.Graph() with nx.OrderedGraph() also doesn't help.
A Graph is an undirected graph, where (1, 13) and (13, 1) mean the same thing, the edges have no 'arrows'.
What you want is a DiGraph, meaning a directed graph. See https://networkx.github.io/documentation/stable/reference/classes/index.html
An OrderedGraph is something else - it just means that when you iterate over nodes and edges, they come out in a particular order (similar to lists vs sets).

Networkx: plot quality issue

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

Categories

Resources