Counting node attributes using succesors on a directed graph - python

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

Related

Add node between existing edge in Networkx Graph generated by OSMnx

I have gotten sensor location data from Highway England. I want to add these sensor locations to OSM multidigraph. How to do that?
import numpy as np
import pandas as pd
import networkx as nx
from shapely.geometry import Point, Polygon, LineString
import geopandas as gpd
import osmnx as ox
Graph data is
graph = ox.graph.graph_from_bbox(52.2, 51.85, -.6, -0.9, network_type='drive', simplify=False)
I want to add sensor = Point(-0.6116768, 51.8508765) on the edge nearest to it. Nearest edges to this sensor is n_edge = osmnx.distance.nearest_edges(graph, -0.6116768, 51.8508765, return_dist=False). Now, I need to bend this n_edge such that it passes through the given sensor point.
I found a way to solve this issue by creating a new node in graph, graph.add_node('sensor25', y= 51.8508765, x= -0.6116768, street_count = 2) then graph.add_edges_from([(n_edge[0], 'sensor25'), ('sensor25', n_edge[1)]). However, the node created by me (sensor25) is not identical to other nodes. How to make this node similar to existing nodes?
I have went through following questions
add attribute to node
add new node to existing edge in networkx
add random nodes on edges manually.
I'm not 100% certain what you need, what I understand: You want to add new edges with attributes: speed_limit, length, street number one way, copied from the edge you delete?
I assume that some of these attributes can be copied 1:1, like one way, while others will have to be recalulated. For simplicity, let's assume we have a function d(a, b) that takes (graph) nodes a and b, extracts their position, and calculates the air distance between them. Define other functions as required.
Then you could e.g. define the new edge like this:
# Get from/to id of closest edge
f, t = osmnx.distance.nearest_edges(graph, -0.6116768, 51.8508765, return_dist=False)[0]
c = 'sensor25' # Id of new node, c as in 'center'
edge_attrs = g[f][t] # Copy edge attributes
g.remove_edge(f, t) # Remove edge from graph
graph.add_node(c, y= 51.8508765, x= -0.6116768, street_count = 2)
# Add new edges, recalculating atttributes as required
g.add_edge(f, c, **{**edge_attrs, 'length': d(f, c)})
g.add_edge(c, t, **{**edge_attrs, 'length': d(c, t)})
Hope the syntax is clear, otherwise ask. It copies edge_attrs 1:1, except for attributes you specify after, like lenght. Probably you will have to define multiple functions like d, that also calculate the geometry etc.
The code isn't tested.

Networkx - problem while working big data

I am trying to triangulate a large amount of massive data using Delaunay to scipy.spatial for triangulation and networkx to get the node adjacency relations. My code works very well on small data sets but when I try to introduce volumes of about 2 miollion points I always get the following error:
raise NetworkXError(f"The node {n} is not in the graph.") from e
NetworkXError: The node 1 is not in the graph.
It seems like my graph store the first node and nothing more. When I did my research I found that networkx is well adapted to massive data
Here is my code :
import numpy as np
import networkx as nx
import scipy.spatial
points = np.genfromtxt('las1.xyz', delimiter = ';')
xy= points[:,0:2]
z= points[:,2]
delTri = scipy.spatial.Delaunay(xy)
edges = set()
for n in range(delTri.nsimplex):
edge = sorted([delTri.vertices[n,0], delTri.vertices[n,1]])
edges.add((edge[0], edge[1]))
edge = sorted([delTri.vertices[n,0], delTri.vertices[n,2]])
edges.add((edge[0], edge[1]))
edge = sorted([delTri.vertices[n,1], delTri.vertices[n,2]])
edges.add((edge[0], edge[1]))
pts_neigh = {}
graph = nx.Graph(list(edges))
for i in range(len(xy)):
pts_neigh[i] = list(graph.neighbors(i))
I still get the edges list from my networkx graph but it seems like it fails at the level of constructing the nodes.
I will be so grateful for your help.
Although it's possible to instantiate graph with specific data, the syntax can be a bit complex. An easier option is to explicitly add edges from a list:
graph = nx.Graph()
graph.add_edges_from(list(edges))

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

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

How to find nodes with Python string matching functions in Networkx?

Given a dependency parse graph, if I want to find the shortest path length between two fixed nodes, this is how I've coded it:
nx.shortest_path_length (graph, source='cost', target='20.4')
My question here is: What if I want to match for all sentences in the graph or collection a target with any number formatted approximately as a currency? Would I have to first find every node in the graph that is a currency, and then iterate over the set of currency values?
It would be ideal to have:
nx.shortest_path_length (graph, source='cost', target=r'^[$€£]?(\d+([\.,]00)?)$')
Or from #bluepnume ^[$€£]?((([1-5],?)?\d{2,3}|[5-9])(\.\d{2})?)$
You could do it in two steps, without having to loop over.
Step 1: Calculate the shortest distance from your 'cost' node to all reachable nodes.
Step 2: Subset (using regex) just the currency nodes that you are interested in.
Here's an example to illustrate.
import networkx as nx
import matplotlib.pyplot as plt
import re
g = nx.DiGraph()
#create a dummy graph for illustration
g.add_edges_from([('cost','apples'),('cost', 'of'),
('$2', 'pears'),('lemon', '£1.414'),
('apples', '$2'),('lemon', '£1.414'),
('€3.5', 'lemon'),('pears', '€3.5'),
], distance=0.5) # using a list of edge tuples & specifying distance
g.add_edges_from([('€3.5', 'lemon'),('of', '€3.5')],
distance=0.7)
nx.draw(g, with_labels=True)
which produces:
Now, you can calculate the shortest paths to your nodes of interest, subsetting using regex like you wanted to.
paths = nx.single_source_dijkstra_path(g, 'cost')
lengths=nx.single_source_dijkstra_path_length(g,'cost', weight='distance')
currency_nodes = [ n for n in lengths.keys() if re.findall('(\$|€|£)',n)]
[(n,len) for (n,len) in lengths.items() if n in currency_nodes]
produces:
[('$2', 1.0), ('€3.5', 1.2), ('£1.414', 2.4)]
Hope that helps you move forward.

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.

Categories