In typical facility location problems, I have three facilities (Fi, i=1,2,3) and six nodes (Dj, j=1,2,3,4,5,6). I want to plot all Fi and Dj and then connect nodes Dj to facilities Fi, based on an assignment matrix Xij.
The matrix Xij is given as:
Xij = np.array([[1,0,0,1,1,1],
[0,1,1,0,0,0],
[0,0,0,0,0,0]])
The first row of Xij shows that nodes Dj (j=0,3,4,5) are allocated to the facility Fi (i=0). Second row shows Nodes Dj (j=1,2) are allocated to second facility Fi (i=2). Third row shows that no node is allocated to Facility Fi(i=2).
I tried to do it in matplotlib, to plot the nodes at specified locations, but don't know how to connect them.
fx = np.array([30, 30, 30])
fy = np.array([10, 20, 30])
f = np.vstack((fx, fy))
px = np.array([50, 50, 50, 50, 50])
py = np.array([10, 15, 20, 25, 30])
p = np.vstack((px, py))
plt.scatter(fx,fy, marker='D', s=100)
plt.scatter(px,py, marker='o', s=100)
Then I read about the Networkx library and tried to plot them as:
G1 = nx.Graph()
G2 = nx.Graph()
Fi = {0: (10,10),
1: (10,20),
2: (10,30)}
Dj ={0: (20,5),
1: (20,10),
2: (20,15),
3: (20,20),
4: (20,25),
5: (20,30)}
nx.draw_networkx(G1, Fi, node_color= 'gray', node_size=500)
nx.draw_networkx(G2, Dj, node_color= 'gray', node_size=300)
However, can't figure out how to connect these nodes easily in any tool?
The given problem is just a simple version of a bigger network.
One way of doing this is to convert your bipartite assignment matrix into a full adjacency matrix, then using that to populate your nx graph.
Xij = np.array([[1,0,0,1,1,1],
[0,1,1,0,0,0],
[0,0,0,0,0,0]])
A = Xij
At = A.T
Z_top_left = np.zeros((A.shape[0], At.shape[1]))
Z_bottom_right = np.zeros((At.shape[0], A.shape[1]))
G = nx.from_numpy_matrix(np.vstack([np.hstack([Z_top_left,A]) , np.hstack([At, Z_bottom_right])]))
Then you can draw your G graph (having set out the positions using methods outlined elsewhere here) and it will contain the edges you're looking for.
To get from an assignment matrix X, you need to compose an array consisting of X and the transpose of X in the top right and bottom left corners, filling in the rest with zeros, since there are no edges from Facility to Facility or Node to Node (to use your terms). It's a bipartite graph. That's what the hstack and vstack calls are doing in the above.
Alternately, you could loop through your assignment array, with i and j as row/column iterators and do:
G.add_edge(i,j)
This will create the nodes, and connect them with edges. One of the nx.draw family of commands will then lay them out graphically. I also notice there's an upcoming bipartite_layout option coming to networkx at sometime in the future.
You need to use pos for drawing in the right location, and for the edges, you should iterate over the matrix:
import numpy as np
import networkx as nx
from matplotlib import pyplot as plt
Xij = np.array([[1,0,0,1,1,1],
[0,1,1,0,0,0],
[0,0,0,0,0,0]])
Fi = {'F0': [10,10],
'F1': [10,20],
'F2': [10,30]}
Dj ={'D0': [20,5],
'D1': [20,10],
'D2': [20,15],
'D3': [20,20],
'D4': [20,25],
'D5': [20,30]}
newD = dict(Dj.items()) #Build a dictionary with all the items, for position
newD.update(Fi.items())
G1 = nx.Graph()
G1.add_nodes_from(newD)
for i in range(Xij.shape[0]): # Add an edge according to the matrix
for j in range(Xij.shape[1]):
if Xij[i,j] == 1:
G1.add_edge('F'+str(i), 'D'+str(j))
nx.draw(G1, with_labels=True, pos = newD) #Draw, with locations and edges
With result:
Added explanation inline with the code.
Edit:
For colors you need to define a color for each node:
colors = ['r' if x[0] == 'D' else 'b' for x in list(G1.nodes)]
nx.draw(G1, with_labels=True,node_color=colors, pos = newD) #Draw, with locations and edges, and colors
Related
I have a set of nodes with an adjacency matrix. I want to color these nodes based on the array P such that node 1 = P[0], node 2 = P[1], node 3 = P[2] and so on with a colorbar showing the range of values. The current and expected outputs are presented.
import numpy as np
import networkx as nx
G = nx.grid_2d_graph(3,3)
new_nodes = {e: n for n, e in enumerate(G.nodes, start=1)}
new_edges = [(new_nodes[e1], new_nodes[e2]) for e1, e2 in G.edges]
G = nx.Graph()
G.add_edges_from(new_edges)
nx.draw(G, with_labels=True)
A1 = nx.adjacency_matrix(G)
A=A1.toarray()
print([A])
P=np.array([10.5,20.7,30.7,40.1,50.6,60.3,70.6,80.9,90.8])
The current output is
enter image description here
The expected output is
enter image description here
If you want to color your nodes, you can pass to the draw function a list of color to bind to each nodes.
Those color can be computed as hexa decimal values from any range you decided to target. In my proposition below : your P vector holds values between 0 and 100, while color values can be beteween 0 and 255, coded in hexadecimal.
Proposition:
P=np.array([10,20,30,40,50,60,70,80,90])
color_hex_values = [ hex(int(e*255/100))[2:] for e in P ]
print(color_hex_values)
nx.draw(G, with_labels=True,node_color=[f"#00{hv}00" for hv in color_hex_values] )
I have a graph with lots of nodes. I am going to segregate them based on their timesteps. Let's say I have 3 timesteps and I have 5 nodes for each time step. So I want to create a 3-columned graph where each column has 5 nodes. So if we imagine, it needs to like a bipartite layout.
But this is not a bipartite graph and hence I am not sure how to proceed
# Greate your Bipartite graph object
G = nx.Graph()
y_out = [6, 6, 12 ,8, 23, 23]
# Add nodes to set 0 and 1
G.add_nodes_from(y_out, bipartite=0)
G.add_nodes_from(['a6', 'b6', 'c12', 'd8', 'd23', 'e23'], bipartite=1)
# # Add edges between exclusive sets
# G.add_edges_from([dets_tuple])
top_nodes = {n for n, d in G.nodes(data=True) if d['bipartite'] == 0}
bottom_nodes = set(G) - top_nodes
print("top:", top_nodes)
print("bottom: " ,bottom_nodes)
# Visualize the graph
plt.subplot(121)
nx.draw_networkx(
G, pos=nx.drawing.layout.bipartite_layout(G, top_nodes))
plt.show()
UPDATED CODE:
unique_frames = set(y_out[:, 0])
print("Generate dynamic graph")
# Visualize the graph
plt.subplot(121)
for i in unique_frames:
# find indices that belong to the same frame
idx = np.where(y_out[:, 0] == i)
print("Comparing frame no: ", i)
print("Idx : ", idx)
# embed()
curr_nodes = np.asarray(idx)[0]
# Add nodes to set 0 and 1
G.add_nodes_from(curr_nodes, bipartite=i)
# compute partition nodes
top_nodes = {n for n, d in G.nodes(data=True) if d['bipartite'] == i}
# Draw Graph
nx.draw_networkx(G, pos=nx.drawing.layout.bipartite_layout(G, top_nodes))
plt.show(block=False)
plt.pause(0.5)
This literally shows me a bipartite graph. I have plugged in the nodes temporarily so that it respects the bipartite rule. But ideally I can not have one. How can I construct a normal multiple connected graph with a bipartite layout
Update: The new updated code is creating graphs, but on the same column. I not sure how to space them horizontally
Does NetworkX have a built-in way of scaling the nodes and edges proportional to the adjacency matrix frequency / node-node frequency? I am trying to scale the size of the nodes and text based on the adjacency matrix frequency and the weight of the edge based on the node-node frequency. I have created a frequency attribute for the graph, but that doesn't solve my problem of passing information to the graph about the node-node frequency.
So two part question:
1) What are best practices transferring an adjacency matrix into a networkX graph?
2) How do I use that information to scale the size of the nodes and the weight of the edges?
## Compute Graph (G)
G = nx.Graph(A)
## Add frequency of word as attribute of graph
def Freq_Attribute(G, A):
frequency = {} # Dictionary Declaration
for node in G.nodes():
frequency[str(node)] = A[str(node)][str(node)]
return nx.set_node_attributes(G, 'frequency', frequency)
Freq_Attribute(g,A) # Adds attribute frequency to graph, for font scale
## Plot Graph with Labels
plt.figure(1, figsize=(10,10))
# Set location of nodes as the default
pos = nx.spring_layout(G, k=0.50, iterations=30)
# Nodes
node_size = 10000
nodes1 = nx.draw_networkx_nodes(G,pos,
node_color='None',
node_size=node_size,
alpha=1.0) # nodelist=[0,1,2,3],
nodes1.set_edgecolor('#A9C1CD') # Set edge color to black
# Edges
edges = nx.draw_networkx_edges(G,pos,width=1,alpha=0.05,edge_color='black')
edges.set_zorder(3)
# Labels
nx.draw_networkx_labels(G,pos,labels=nx.get_node_attributes(G,'label'),
font_size=16,
font_color='#062D40',
font_family='arial') # sans-serif, Font=16
# node_labels = nx.get_node_attributes(g, 'name')
# Use 'g.graph' to find attribute(s): {'name': 'words'}
plt.axis('off')
#plt.show()
I have tried setting label font_size, but this didn't work.:
font_size=nx.get_node_attributes(G,'frequency')) + 8)
I tried the following to match your need:
import networkx as nx
import matplotlib.pyplot as plt
## create nx graph from adjacency matrix
def create_graph_from_adj(A):
# A=[(n1, n2, freq),....]
G = nx.Graph()
for a in A:
G.add_edge(a[0], a[1], freq=a[2])
return G
A = [(0, 1, 0.5), (1, 2, 1.0), (2, 3, 0.8), (0, 2, 0.2), (3, 4, 0.1), (2, 4, 0.6)]
## Compute Graph (G)
G = create_graph_from_adj(A)
plt.subplot(121)
# Set location of nodes as the default
spring_pose = nx.spring_layout(G, k=0.50, iterations=30)
nx.draw_networkx(G,pos=spring_pose)
plt.subplot(122)
# Nodes
default_node_size = 300
default_label_size = 12
node_size_by_freq = []
label_size_by_freq = []
for n in G.nodes():
sum_freq_in = sum([G.edge[n][t]['freq'] for t in G.neighbors(n)])
node_size_by_freq.append(sum_freq_in*default_node_size)
label_size_by_freq.append(int(sum_freq_in*default_label_size))
nx.draw_networkx_nodes(G,pos=spring_pose,
node_color='red',
node_size=node_size_by_freq,
alpha=1.0)
nx.draw_networkx_labels(G,pos=spring_pose,
font_size=12, #label_size_by_freq is not allowed
font_color='#062D40',
font_family='arial')
# Edges
default_width = 5.0
edge_width_by_freq = []
for e in G.edges():
edge_width_by_freq.append(G.edge[e[0]][e[1]]['freq']*default_width)
nx.draw_networkx_edges(G,pos=spring_pose,
width=edge_width_by_freq,
alpha=1.0,
edge_color='black')
plt.show()
First of all, the adjacency reaction is not given in Matrix format, but IMHO that's too tedious.
Secondly, nx.draw_networkx_labels does not allow different font size for the labels. Can't help there.
Last, the edge width and node size however allows that. So they are scaled based on its frequency and summation of incoming frequency, respectively.
Hope it helps.
I am working with a regular network of 100x100=10000 nodes. The network is created just like this:
import networkx as nx
import matplotlib.pyplot as plt
N=100
G=nx.grid_2d_graph(N,N) #2D regular graph of 10000 nodes
pos = dict( (n, n) for n in G.nodes() ) #Dict of positions
labels = dict( ((i, j), i + (N-1-j) * N ) for i, j in G.nodes() )
nx.relabel_nodes(G,labels,False)
pos = {y:x for x,y in labels.iteritems()} #An attempt to change node indexing
I want to have node 0 in the upper left corner, and node 9999 in the lower right. This is why you see a second call to pos: it is an attempt to change node indexing according to my will.
However, I have noticed that after I run the script:
pos[0]=(0,99), pos[99]=(99,99), pos[9900]=(0,0), pos[9999]=(99,0).
This means that networkx sees the origin of the grid in the bottom left corner and that the farthest position from the origin, (99,99), belongs to the 99th node.
Now, I want to change that so to have my origin in the upper left corner. This means that I want to have:
pos[0]=(0,0), pos[99]=(0,99), pos[9900]=(99,0), pos[9999]=(99,99).
What should I change in pos?
I'm assuming you are following the example here: Remove rotation effect when drawing a square grid of MxM nodes in networkx using grid_2d_graph
With that being said, your picture will look like theirs if you do it just like they did. If you just want 'pos' to look different you can use:
inds = labels.keys()
vals = labels.values()
inds.sort()
vals.sort()
pos2 = dict(zip(vals,inds))
In [42]: pos2[0]
Out[42]: (0, 0)
In [43]: pos2[99]
Out[43]: (0, 99)
In [44]: pos2[9900]
Out[44]: (99, 0)
In [45]: pos2[9999]
Out[45]: (99, 99)
I'm trying to visualize a few graphs whose nodes represent different objects. I want to create an image that looks like the one here:
Basically, I need a 3D plot and the ability to draw edges between nodes on the same level or nodes on different levels.
This answer below may not be a complete solution, but is a working demo for rendering 3D graphs using networkx.
networkx as such cannot render 3D graphs. We will have to install mayavi for that to happen.
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from mayavi import mlab
import random
def draw_graph3d(graph, graph_colormap='winter', bgcolor = (1, 1, 1),
node_size=0.03,
edge_color=(0.8, 0.8, 0.8), edge_size=0.002,
text_size=0.008, text_color=(0, 0, 0)):
H=nx.Graph()
# add edges
for node, edges in graph.items():
for edge, val in edges.items():
if val == 1:
H.add_edge(node, edge)
G=nx.convert_node_labels_to_integers(H)
graph_pos=nx.spring_layout(G, dim=3)
# numpy array of x,y,z positions in sorted node order
xyz=np.array([graph_pos[v] for v in sorted(G)])
# scalar colors
scalars=np.array(G.nodes())+5
mlab.figure(1, bgcolor=bgcolor)
mlab.clf()
#----------------------------------------------------------------------------
# the x,y, and z co-ordinates are here
# manipulate them to obtain the desired projection perspective
pts = mlab.points3d(xyz[:,0], xyz[:,1], xyz[:,2],
scalars,
scale_factor=node_size,
scale_mode='none',
colormap=graph_colormap,
resolution=20)
#----------------------------------------------------------------------------
for i, (x, y, z) in enumerate(xyz):
label = mlab.text(x, y, str(i), z=z,
width=text_size, name=str(i), color=text_color)
label.property.shadow = True
pts.mlab_source.dataset.lines = np.array(G.edges())
tube = mlab.pipeline.tube(pts, tube_radius=edge_size)
mlab.pipeline.surface(tube, color=edge_color)
mlab.show() # interactive window
# create tangled hypercube
def make_graph(nodes):
def make_link(graph, i1, i2):
graph[i1][i2] = 1
graph[i2][i1] = 1
n = len(nodes)
if n == 1: return {nodes[0]:{}}
nodes1 = nodes[0:n/2]
nodes2 = nodes[n/2:]
G1 = make_graph(nodes1)
G2 = make_graph(nodes2)
# merge G1 and G2 into a single graph
G = dict(G1.items() + G2.items())
# link G1 and G2
random.shuffle(nodes1)
random.shuffle(nodes2)
for i in range(len(nodes1)):
make_link(G, nodes1[i], nodes2[i])
return G
# graph example
nodes = range(10)
graph = make_graph(nodes)
draw_graph3d(graph)
This code was modified from one of the examples here.
Please post the code in this case, when you succeed in reaching the objective.