Plotting multigraphs with NetworkX/PyGraphviz: fine tuning node/edge position - python

I'd like to use NetworkX to more-or-less reproduce the following figure (from F. Crick, Nature 227, 561 (1970)):
I can reproduce the underlying graph using MultiDiGraph:
import networkx as nx
g = nx.MultiDiGraph()
weakEdges = [('RNA', 'DNA'), ('RNA', 'RNA'), ('RNA', 'protein')]
strongEdges = [('DNA', 'DNA'), ('DNA', 'RNA'), ('DNA', 'protein')]
g.add_edges_from(weakEdges)
g.add_edges_from(strongEdges)
but apparently the built-in matplotlib plotting doesn't support parallel edges, as is needed for proper multigraphs.
On the other hand, I can convert g to a PyGraphviz AGraph and plot that:
a = nx.nx_agraph.to_agraph(g)
for etup in weakEdges:
a.get_edge(*etup).attr['style'] = 'dashed'
a.draw('test2.png', prog='circo')
This is pretty close to what I want, but I'm having trouble figuring out some of the finer details:
Using the circo layout, is it possible to center the "DNA" node at the top of the figure? If not, how can I control the absolute position of each node?
How do I tweak the positioning of the self-edges (eg the "DNA" -> "DNA" edge) so that they more closely resemble those in the original figure?

A small setup in plain Graphviz / dot syntax (feed it to http://www.webgraphviz.com/ and one can see the result):
digraph X {
{rank=same; RNA Protein}
DNA -> RNA
DNA -> Protein
Protein -> RNA
DNA:n -> DNA:n
}
Basic parts are here:
:n place to get the array at (other possibilities are other wind directions including e.g. :nw).
rank=same; to align the mentioned nodes in one line

Related

Return to original OSMnx graph from simplified graph with edge geometries

Is it possible to generate the original OSMnx graph from the simplified graph (which has the edge geometries preserved)?
For instance:
import osmnx as ox
place = 'Piedmont, California, USA'
G = ox.graph_from_place(place, network_type='drive', simplify=False)
G_simple = ox.simplify_graph(G)
G_simple has the original edge geometries of G stored as "geometry" on the simplified edges:
simple_nodes, simple_edges = ox.graph_to_gdfs(G_simple)
print(simple_edges.iloc[10].geometry)
# LINESTRING (-122.2429303 37.8205234, -122.2426591 37.8207235, -122.2424827 37.820899, -122.2421775 37.8212363, -122.2420372 37.8214758, -122.2420254 37.8215051, -122.2419343 37.8217305, -122.2418551 37.8218894, -122.2415415 37.8222826)
Would it be possible to generate the original graph G from the simplified one? I have many simplified graphs stored on disk, but unfortunately cannot regenerate the unsimplified graphs, so I need to find a way to "unsimplify" them.
This is a one-way destruction of information built into OSMnx. You could try to write your own script, but it would be nontrivial. You'd have to identify each vertex in each geometry of each edge, create a new node there, and break the edge into two at that vertex.

Python Netgraph Interactive Labels

I am trying to create an interactive plot using netgraph and networkx.
I would like the plot to allow movement of the nodes and as the nodes move, the edges and edge_labels will also dynamically update.
Moving the nodes was addressed by the author of netgraph here . Now when I make a simpler plot and try to label an edge, the label stays static, and is sometimes not even on an edge.
It seems like handling edge_positions similar to node_positions on the last two lines should at least address the label not moving. Why the label isn't anchored to a particular edge is still puzzling. Does anyone know if the desired effect is achievable?
Here is a snip before moving anything:
Here is a snip after moving the bottom-right node to the top left:
Here is my current code:
import matplotlib.pyplot as plt
import networkx as nx
import netgraph # pip install netgraph
#Graph creation:
G=nx.Graph(type="")
for i in range(6):
G.add_node(i,shape="o")
#Changing shape for two nodes
G.nodes[1]['shape'] = "v"
G.nodes[5]['shape'] = "v"
#Add edges
G.add_edge(1,2)
G.add_edge(4,5)
G.add_edge(0,4)
G.add_edge(2,3)
G.add_edge(2,4)
labs={(1,2):"1 to 2"}
nx.draw_networkx_edge_labels(G, pos=nx.spring_layout(G),edge_labels=labs)
#Get node shapes
node_shapes = nx.get_node_attributes(G,"shape")
# Create an interactive plot.
# NOTE: you must retain a reference to the object instance!
# Otherwise the whole thing will be garbage collected after the initial draw
# and you won't be able to move the plot elements around.
pos = nx.layout.spring_layout(G)
######## drag nodes around #########
# To access the new node positions:
plot_instance = netgraph.InteractiveGraph(G, node_shape=node_shapes, node_positions=pos, edge_positions=pos)
node_positions = plot_instance.node_positions
edge_positions = plot_instance.edge_positions
To have it as formal answer, you need to add to the InteractiveGraph object the information that you want to draw (and move) the edge labels, i.e. the following
netgraph.InteractiveGraph(G,
node_shape=node_shapes,
node_positions=pos,
edge_positions=pos,
edge_labels=labs)
(With emphasis on the last parameter)
As you already, noticed then you don't need the call of nx.draw_networkx_edge_labels.

Is it possible to control the order that nodes are drawn using NetworkX in python?

I have a large graph object with many nodes that I am trying to graph. Due to the large number of nodes, many are being drawn one over another. This in itself is not a problem. However, a small percentage of nodes have node attributes which dictate their colour.
Ideally I would be able to draw the graph in such a way that nodes with this property are drawn last, on top of the other nodes, so that it is possible to see their distribution across the graph.
The code I have so far used to generate the graph is shown below:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import os
import pickle
from pathlib import Path
def openFileAtPath(filePath):
print('Opening file at: ' + filePath)
with open(filePath, 'rb') as input:
file = pickle.load(input)
return file
# Pre manipulation path
g = openFileAtPath('../initialGraphs/wordNetadj_dictionary1.11.pkl')
# Post manipulation path
# g = openFileAtPath('../manipulatedGraphs/wordNetadj_dictionary1.11.pkl')
print('Fetching SO scores')
scores = list()
for node in g.nodes:
scores.append(g.node[node]['weight'])
print('Drawing network')
nx.draw(g,
with_labels=False,
cmap=plt.get_cmap('RdBu'),
node_color=scores,
node_size=40,
font_size=8)
plt.show()
And currently the output is as shown:
This graph object itself has taken a relatively long time to generate and is computationally intensive, so ideally I wouldn't have to remake the graph from scratch.
However, I am fairly sure that the graph is drawn in the same order that the nodes were added to the graph object. I have searched for a way of changing the order that the nodes are stored within the graph object, but given directional graphs actually have an order, my searches always end up with answers showing me how to reverse the direction of a graph.
So, is there a way to dictate the order in which nodes are drawn, or alternatively, change the order that nodes are stored inside some graph object.
Potentially worthy of a second question, but the edges are also blocked out by the large number of nodes. Is there a way to draw the edges above the nodes behind them?
Piggybacking off Paul Brodersen's answer, if you want different nodes to be in the foreground and background, I think you should do the following:
For all nodes that belong in the same layer, draw the subgraph corresponding to the nodes, and set the , as follows:
pos = {...} # some dictionary of node positions, required for the function below
H = G.subgraph(nbunch)
collection = nx.draw_networkx_nodes(H, pos)
collection.set_zorder(zorder)
Do this for every group of nodes that belong in the same level. It's tedious, but it will do the trick. Here is a toy example that I created based on looking up this question as part of my own research
import matplotlib as mpl
mpl.use('agg')
import pylab
import networkx as nx
G = nx.Graph()
G.add_path([1, 2, 3, 4])
pos = {1 : (0, 0), 2 : (0.5, 0), 3 : (1, 0), 4 : (1.5, 0)}
for node in G.nodes():
H = G.subgraph([node])
collection = nx.draw_networkx_nodes(H, pos)
collection.set_zorder(node)
pylab.plot([0, 2], [0, 0], zorder=2.5)
pylab.savefig('nodes_zorder.pdf', format='pdf')
pylab.close()
This makes a graph, and then puts the each node at a successively higher level going from left to right, so the leftmost node is farthest in the background and the rightmost node is farthest in the foreground. It then draws a straight line whose zorder is 2. As a result, it comes in front of the two left nodes, and behind the two right nodes. Here is the result.
draw is a wrapper around draw_networkx_nodes and draw_networkx_edges.
Unlike draw, the two functions return their respective artists ( PathCollection and LineCollection, IIRC). These are your standard matplotlib artists, and as as such their relative draw order can be controlled via their zorder attribute.

Counting node attributes using succesors on a directed graph

I am currently working on a power distribution reliability index tool for radial networks for my engineering dissertation using NewtworkX and Python. I am struggling to write a command which will add to my accumulator all node attributes downstream of a particular edge which meets a certain condition. I've tried using the successors feature NetworkX offers however it will only count first successor that meets the edge condition instead of all downstream the directed path. I'm seeking guidance as this is confusing me and I can't seem to work my around this simple task.
import networkx as nx
import matplotlib.pyplot as plt
H=nx.DiGraph()
H.add_node(1, loads=2)
H.add_node(2, loads=2)
H.add_node(3, loads=5)
H.add_node(4, loads=5)
H.add_edge(1,2,fault=True, switch=True)
H.add_edge(2,3,fault=False, switch=True)
H.add_edge(3,4,fault=False, switch=True)
nx.draw(H)
plt.show()
a=0
for n1,n2 in H.edges():
if H[n1][n2]['fault']==True:
a=a+H.node[n2]['loads']
for n in H.successors(n2):
a=a+H.node[n]['loads']
My algorithm returns a=7 and the correct answer would be a=12 and so on for all edges that meet the criteria. Obviously is me that is writing the wrong instruction.
If I understand your question right you want to get all of the successors so you can use a breadth first search like this
import networkx as nx
H=nx.DiGraph()
H.add_node(1, loads=2)
H.add_node(2, loads=2)
H.add_node(3, loads=5)
H.add_node(4, loads=5)
H.add_edge(1,2,fault=True, switch=True)
H.add_edge(2,3,fault=False, switch=True)
H.add_edge(3,4,fault=False, switch=True)
source = 1
a = H.node[source]['loads']
nofault = [t for s,t in nx.bfs_edges(H,source=source) if not H.edge[s][t]['fault']]
a += sum(H.node[t]['loads'] for t in nofault)
print(a) #12

Improving Python NetworkX graph layout

I am having some problems in visualizing the graphs created with python-networkx, I want to able to reduce clutter and regulate the distance between the nodes (I have also tried spring_layout, it just lays out the nodes in an elliptical fashion). Please advise.
Parts of code:
nx.draw_networkx_edges(G, pos, edgelist=predges, edge_color='red', arrows=True)
nx.draw_networkx_edges(G, pos, edgelist=black_edges, arrows=False, style='dashed')
# label fonts
nx.draw_networkx_labels(G,pos,font_size=7,font_family='sans-serif')
nx.draw_networkx_edge_labels(G,pos,q_list,label_pos=0.3)
In networkx, it's worth checking out the graph drawing algorithms provided by graphviz via nx.graphviz_layout.
I've had good success with neato but the other possible inputs are
dot - "hierarchical" or layered drawings of directed graphs. This is the default tool to use if edges have directionality.
neato - "spring model'' layouts. This is the default tool to use if the graph is not too large (about 100 nodes) and you don't know anything else about it. Neato attempts to minimize a global energy function, which is equivalent to statistical multi-dimensional scaling.
fdp - "spring model'' layouts similar to those of neato, but does this by reducing forces rather than working with energy.
sfdp - multiscale version of fdp for the layout of large graphs.
twopi - radial layouts, after Graham Wills 97. Nodes are placed on concentric circles depending their distance from a given root node.
circo - circular layout, after Six and Tollis 99, Kauffman and Wiese 02. This is suitable for certain diagrams of multiple cyclic structures, such as certain telecommunications networks.
In general, graph drawing is a hard problem. If these algorithms are not sufficient, you'll have to write your own or have networkx draw parts individually.
I found this to be useful for quickly visualizing interaction data (here, genes) sourced as a CSV file.
Data file [a.csv]
APC,TP73
BARD1,BRCA1
BARD1,ESR1
BARD1,KRAS2
BARD1,SLC22A18
BARD1,TP53
BRCA1,BRCA2
BRCA1,CHEK2
BRCA1,MLH1
BRCA1,PHB
BRCA2,CHEK2
BRCA2,TP53
CASP8,ESR1
CASP8,KRAS2
CASP8,PIK3CA
CASP8,SLC22A18
CDK2,CDKN1A
CHEK2,CDK2
ESR1,BRCA1
ESR1,KRAS2
ESR1,PPM1D
ESR1,SLC22A18
KRAS2,BRCA1
MLH1,CHEK2
MLH1,PMS2
PIK3CA,BRCA1
PIK3CA,ESR1
PIK3CA,RB1CC1
PIK3CA,SLC22A18
PMS2,TP53
PTEN,BRCA1
PTEN,MLH3
RAD51,BRCA1
RB1CC1,SLC22A18
SLC22A18,BRCA1
TP53,PTEN
Python 3.7 venv
import networkx as nx
import matplotlib.pyplot as plt
G = nx.read_edgelist("a.csv", delimiter=",")
G.edges()
'''
[('CDKN1A', 'CDK2'), ('MLH3', 'PTEN'), ('TP73', 'APC'), ('CHEK2', 'MLH1'),
('CHEK2', 'BRCA2'), ('CHEK2', 'CDK2'), ('CHEK2', 'BRCA1'), ('BRCA2', 'TP53'),
('BRCA2', 'BRCA1'), ('KRAS2', 'CASP8'), ('KRAS2', 'ESR1'), ('KRAS2', 'BRCA1'),
('KRAS2', 'BARD1'), ('PPM1D', 'ESR1'), ('BRCA1', 'PHB'), ('BRCA1', 'ESR1'),
('BRCA1', 'PIK3CA'), ('BRCA1', 'PTEN'), ('BRCA1', 'MLH1'), ('BRCA1', 'SLC22A18'),
('BRCA1', 'BARD1'), ('BRCA1', 'RAD51'), ('CASP8', 'ESR1'), ('CASP8', 'SLC22A18'),
('CASP8', 'PIK3CA'), ('TP53', 'PMS2'), ('TP53', 'PTEN'), ('TP53', 'BARD1'),
('PMS2', 'MLH1'), ('PIK3CA', 'SLC22A18'), ('PIK3CA', 'ESR1'), ('PIK3CA', 'RB1CC1'),
('SLC22A18', 'ESR1'), ('SLC22A18', 'RB1CC1'), ('SLC22A18', 'BARD1'),
('BARD1', 'ESR1')]
'''
G.number_of_edges()
# 36
G.nodes()
'''
['CDKN1A', 'MLH3', 'TP73', 'CHEK2', 'BRCA2', 'KRAS2', 'CDK2', 'PPM1D', 'BRCA1',
'CASP8', 'TP53', 'PMS2', 'RAD51', 'PIK3CA', 'MLH1', 'SLC22A18', 'BARD1',
'PHB', 'APC', 'ESR1', 'RB1CC1', 'PTEN']
'''
G.number_of_nodes()
# 22
UPDATE
This used to work (2018-03), but now (2019-12) gives a pygraphviz import error:
from networkx.drawing.nx_agraph import graphviz_layout
nx.draw(G, pos = graphviz_layout(G), node_size=1200, node_color='lightblue', \
linewidths=0.25, font_size=10, font_weight='bold', with_labels=True)
Traceback (most recent call last):
...
ImportError: libpython3.7m.so.1.0: cannot open shared object file:
No such file or directory
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
ImportError: ('requires pygraphviz ', 'http://pygraphviz.github.io/')
SOLUTION
Outside Python (at the venv terminal prompt: $) install pydot.
pip install pydot
Back in Python run the following code.
import warnings
warnings.filterwarnings("ignore", category=UserWarning)
import networkx as nx
import matplotlib.pyplot as plt
G = nx.read_edgelist("a.csv", delimiter=",")
# For a DiGraph() [directed edges; not shown]:
# G = nx.read_edgelist("a.csv", delimiter=",", create_using=nx.DiGraph)
nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), node_size=1200, \
node_color='lightblue', linewidths=0.25, font_size=10, \
font_weight='bold', with_labels=True)
plt.show() ## plot1.png attached
The main change was to replace
nx.draw(G, pos = graphviz_layout(G), ...)
with
nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), ...)
References
Remove matplotlib depreciation warning from showing
What could cause NetworkX & PyGraphViz to work fine alone but not together?
Specifically: https://stackoverflow.com/a/40750101/1904943
Improved plot layout
It is difficult to decrease congestion in these static networkx / matplotlib plots; one workaround is to increase the figure size, per this StackOverflow Q/A: High Resolution Image of a Graph using NetworkX and Matplotlib :
plt.figure(figsize=(20,14))
# <matplotlib.figure.Figure object at 0x7f1b65ea5e80>
nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), \
node_size=1200, node_color='lightblue', linewidths=0.25, \
font_size=10, font_weight='bold', with_labels=True, dpi=1000)
plt.show() ## plot2.png attached
To reset the output figure size to the system default:
plt.figure()
# <matplotlib.figure.Figure object at 0x7f1b454f1588>
Bonus: shortest path
nx.dijkstra_path(G, 'CDKN1A', 'MLH3')
# ['CDKN1A', 'CDK2', 'CHEK2', 'BRCA1', 'PTEN', 'MLH3']
plot1.png
plot2.png
Although I did not do this here, if you want to add node borders and thicken the node border lines (node edge thickness: linewidths), do the following.
nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), \
node_size=1200, node_color='lightblue', linewidths=2.0, \
font_size=10, font_weight='bold', with_labels=True)
# Get current axis:
ax = plt.gca()
ax.collections[0].set_edgecolor('r')
# r : red (can also use #FF0000) | b : black (can also use #000000) | ...
plt.show()
You have a lot of data in your graph, so it is going to be hard to remove clutter.
I suggest you to use any standard layout. You said that you used spring_layout. I suggest you to try it again but this time using the weight attribute when adding the edges.
For example:
import networkx as nx
G = nx.Graph();
G.add_node('A')
G.add_node('B')
G.add_node('C')
G.add_node('D')
G.add_edge('A','B',weight=1)
G.add_edge('C','B',weight=1)
G.add_edge('B','D',weight=30)
pos = nx.spring_layout(G,scale=2)
nx.draw(G,pos,font_size=8)
plt.show()
Additionally you can use the parameter scale to increase the global distance between the nodes.
To answer your question how to regulate the distance between nodes, I expand on Hooked's answer:
If you draw the graph via the Graphviz backend and when you then use the fdp algorithm, you can adjust the distance between nodes by the edge attribute len.
Here a code example, how to draw a graph G and save in the Graphviz file gvfile with wider distance between nodes (default distance for fdp is 0.3):
A = nx.to_agraph(G)
A.edge_attr.update(len=3)
A.write(gv_file_name)
Two comments:
It is normally advisable to adjust len with the number of nodes in the graph.
The len attribute is only recognised by the fdp and neato algorithm, but not e.g. by the sfdp algorithm.
So, this may not be useful for original question, but maybe for future searchers :
A solution can be
pos = nx.nx_agraph.graphviz_layout(G)
nx.draw(G, pos=pos)
Think of installing agraph first with pip install agraph.
(The solution with nx.nx_pydot.graphviz_layout(G) is now to be depreciated according to networkx.)
Hope it helps :)

Categories