Graph traversal with Networkx (Python) - python

I'm playing a bit with Networkx to manage a graph of dependencies.
Let's say I have this Graph which each letter represent a server
>>> G = nx.Graph()
>>> G.add_edge("A","B")
>>> G.add_edge("A","H")
>>> G.add_edge("H","C")
>>> G.add_edge("B","C")
>>> G.add_edge("B","D")
A
/ \
H B
/ / \
C C D
So here we can see that before starting A we need to start H and B and to start H we need to start C and then to start B wee need to start C and D
By fiddling a bit with Networkx I found that I can get that by doing a dfs traversal
print nx.dfs_successors(G,"A")
{A:[H,B], H:[C], B:[D] }
But I have a problem with that method. As you can see when there is two same letter in the tree, Networkx only chose to put one of them in the final structure (which is correct) But I need to have the complete structure
How can I force Networkx to add in the structure B:[D,C] ??
I want to precise that by doing
>>> nx.dfs_successors(G,"B")
{'B': ['C', 'D']}
So everything is "Internally" correct, it's just the dfs_successors that displays it not in the way I wish.
Thank you

Taking your code, your graph doesn't come out as you'd expect. If you do:
import pylab as p
import networkx as nx
G = nx.Graph()
G.add_edge("A","B")
G.add_edge("A","H")
G.add_edge("H","C")
G.add_edge("B","C")
G.add_edge("B","D")
nx.draw(G)
p.show()
you will see your graph as:
This is due to the logic of G.add_edge("A", "B"):
If G has no node of id "A", add it.
If G has no node of id "B", add it.
Connect "A" to "B" with a new edge.
Thus, you only create five nodes, not six as in your picture.
Edit
Networkx can take any hashable as value for a node, and in the graph it uses str(node) to label each circle. So we can simply define our own Node class (which you maybe want to call Server?) and give it the desired behavior.
import pylab as p
import networkx as nx
class Node(object):
nodes = []
def __init__(self, label):
self._label = label
def __str__(self):
return self._label
nodes = [Node(l) for l in ["A","B","C","C","D","H"]]
edges = [(0,1),(0,5),(5,2),(1,3),(1,4)]
G = nx.Graph()
for i,j in edges:
G.add_edge(nodes[i], nodes[j])
nx.draw(G)
p.show()
gives us
and so what you wanted.

I think what you are looking for is a topological sort https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.dag.topological_sort.html
This only works if you have a DAG (directed acyclic graph).
If so you can draw the tree you want too - like this:
import uuid
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
G.add_edge("A","B")
G.add_edge("A","H")
G.add_edge("H","C")
G.add_edge("B","C")
G.add_edge("B","D")
order = nx.topological_sort(G)
print "topological sort"
print order
# build tree
start = order[0]
nodes = [order[0]] # start with first node in topological order
labels = {}
print "edges"
tree = nx.Graph()
while nodes:
source = nodes.pop()
labels[source] = source
for target in G.neighbors(source):
if target in tree:
t = uuid.uuid1() # new unique id
else:
t = target
labels[t] = target
tree.add_edge(source,t)
print source,target,source,t
nodes.append(target)
nx.draw(tree,labels=labels)
plt.show()
The drawing uses a label mapping to map the ids of the node to the original labels.

Related

How to combine two egdes and nodes in to one that has common starting nodes in Networkx?

I am quite new for networkx and I am asking help from the Stackeroverflow community.
I am trying to combine nodes and edges that have a common starting node as shown below in the figure. The arrow shows the expected result.
nodes_to_combine = [n for n in graph.nodes if len(list(graph.neighbors(n))) == 2]
for node in nodes_to_combine:
graph.add_edge(*graph.neighbors(node))
nx.draw(graph, with_labels=True)
Can anyone help me to figure out this?
NetworkX has no functions to merge nodes in the graph so it should be implemented manually. Here is the example without attributes merging (it can has its own logic):
def merge(G, n1, n2):
# Get all predecessors and successors of two nodes
pre = set(G.predecessors(n1)) | set(G.predecessors(n2))
suc = set(G.successors(n1)) | set(G.successors(n2))
# Create the new node with combined name
name = str(n1) + '/' + str(n2)
# Add predecessors and successors edges
# We have DiGraph so there should be one edge per nodes pair
G.add_edges_from([(p, name) for p in pre])
G.add_edges_from([(name, s) for s in suc])
# Remove old nodes
G.remove_nodes_from([n1, n2])
Here is how it works:
import networkx as nx
G = nx.DiGraph()
G.add_edges_from([
('0','20'),
('10','20'),
('10','30'),
('20','40'),
('30','50'),
])
nx.draw(
G,
pos=nx.nx_agraph.graphviz_layout(G, prog='dot'),
node_color='#FF0000',
with_labels=True
)
merge(G, '20', '30')
nx.draw(
G,
pos=nx.nx_agraph.graphviz_layout(G, prog='dot'),
node_color='#FF0000',
with_labels=True
)

Connect nodes in a graph when one attribute is the same (NetworkX)

I want to create a graph which automatically adds edges between nodes if one particular attribute is the same. Nodes in my graph represent students. I am adding two attributes to my nodes: university_id and full_name. I want to add an edge between two people only if they go to the same university.
I've been looking at this solution: NetworkX: add edges to graph from node attributes
From testing it, it seems that this solution connects all edges of a graph, whether or not any of the attributes are the same. Is there a simple solution I can use to connect students only based on their university_id?
Here is my code:
import matplotlib.pyplot as plt
import networkx as nx
import MySQLdb
# #############################################################################
# Retrieve the data from remote server.
myDB = MySQLdb.connect(host="*,port=3306,user="mysql",passwd="***")
cHandler = myDB.cursor()
cHandler.execute("USE research_project")
cHandler.execute("SELECT * FROM students")
results = cHandler.fetchall()
G = nx.Graph()
for items in results:
# items[0] is a unique ID, items[1] = university_id, items[2] = full name
G.add_node(items[0], attr_dict={'university_id': items[1], 'full_name': items[2]})
for node_r, attributes in G.nodes(data=True):
key_set = set(attributes.keys())
G.add_edges_from([(node_r, node) for node, attributes in G.nodes(data=True)
if key_set.intersection(set(attributes.keys()))
and node != node_r])
nx.draw(G)
plt.show()
from __future__ import print_function
import matplotlib.pyplot as plt
import networkx as nx
import MySQLdb
# #############################################################################
# Retrieve the data from remote server.
myDB = MySQLdb.connect(host="*,port=3306,user="mysql",passwd="***")
cHandler = myDB.cursor()
cHandler.execute("USE research_project")
cHandler.execute("SELECT * FROM students")
results = cHandler.fetchall()
G = nx.Graph()
for items in results:
G.add_node(items[0], attr_dict={'university_id': items[1], 'full_name': items[2]})
for node_r in G.nodes(data=True):
for node in G.nodes(data=True):
if node != node_r and node[1]['attr_dict']['university_id'] == node_r[1]['attr_dict']['university_id']:
G.add_edge(node[0], node_r[0], attr_dict=None)
nx.draw(G, with_labels=True)
plt.show()
I tested the above on small sets of data and it appears to work.
I have a hunch that what was happening had to do with the way I was adding attributes to the nodes.
The caveat with the above solution is that it's TREMENDOUSLY slow at runtime. I will update my answer whenever I can come up with a faster solution.

How to build directed graph from nested dictionary? (Python 3 | NetworkX)

I am trying to build a hierarchical directed network where some nodes can branch into others, while others do not. The values in the inner dictionary (i.e. the integers) are to keep track of leaves in the tree-like structure. I've created a naive way to turn this particular nested dictionary graph_data into a directed graph but it is only specific to 3 layers. Below shows the hierarchy:
How can I create a nested function that adds edges to the directed graph for any number of levels? For example, if there was a level-3 or a level-4 this would not work and I would have to expand it out each time. Do I need to use a while loop?
import numpy as np
from collections import *
import networkx as nx
%matplotlib inline
# Hierarchical data
graph_data = {"root": {"level-0.A":0,
"level-0.B":{"level-1.B.1":2,
"level-1.B.2": {"level-2.B.2.1":3, "level-2.B.2.2":1}}}}
# Empty directed graph
G = nx.DiGraph()
# Helper functions
is_dict = lambda x: type(x) in {dict, OrderedDict, defaultdict}
# Iterate through the layers
for root, level_0 in graph_data.items():
if len(level_0) > 0:
for level_0_node, level_1 in level_0.items():
G.add_edge(root, level_0_node)
if is_dict(level_1):
for level_1_node, level_2 in level_1.items():
G.add_edge(level_0_node, level_1_node)
if is_dict(level_2):
for level_2_node, level_3 in level_2.items():
G.add_edge(level_1_node, level_2_node)
np.random.seed(8)
nx.draw(G, with_labels=True)
Use a queue to hold the details, e.g.:
from collections import Mapping
graph_data = {"root": {"level-0.A":0,
"level-0.B":{"level-1.B.1":2,
"level-1.B.2": {"level-2.B.2.1":3, "level-2.B.2.2":1}}}}
# Empty directed graph
G = nx.DiGraph()
# Iterate through the layers
q = list(graph_data.items())
while q:
v, d = q.pop()
for nv, nd in d.items():
G.add_edge(v, nv)
if isinstance(nd, Mapping):
q.append((nv, nd))
np.random.seed(8)
nx.draw(G, with_labels=True)

Creating key:attribute pairs in networkx for Python

I am working on creating a graph method for analyzing images using pixels as nodes in Python. Using networkx as graph support(documentation here: https://networkx.github.io/documentation/latest/index.html ) Take this as an example:
new=np.arange(256)
g=nx.Graph()
for x in new:
g.add_node(x)
h=g.order()
print h
As expected, 256 nodes will be created.
Now, I would like to create node:attribute pairs based on another array, namely:
newarray=np.arange(256)
for x in new:
g.add_node(x)
nx.set_node_attributes(g, 'value' newarray[x])
With the addition of this line, I was hoping that the first node of newarray would be assigned to the first node of g. However, rather, all values of g will be assigned the last value of newarray. Namely, 256. How can I add attribute pairs for each node, element by element?
You need to pass in a dictionary as the third parameter for set_node_attribute, one that's aligned with the graph. See if this code does what you need:
import numpy as np
import networkx as nx
array1 = np.arange(256)
array2 = np.arange(256) * 10
g = nx.Graph()
valdict = {}
for x in array1:
g.add_node(x)
valdict[x] = array2[x]
nx.set_node_attributes(g, 'value', valdict)
for i in array1:
print g.nodes()[i], g.node[i]['value']

How do I get nodes from the specific edge in the networkx?

I want to compare nodes of different edges in the graph. How can I get the nodes(n1 and n2) from the edge(n1,n2)?
An edge in NetworkX is defined by its nodes, so I'm not really sure what you're asking here. A specific edge in the graph is just a tuple of nodes, with an optional weighting.
import networkx as nx
g = nx.Graph()
g.add_edge(1,2)
g.add_edge(2,3)
g.edges()
gives
[(1, 2), (2, 3)]
As you can see, the list of edges explicitly provides the nodes of each edge.
Update: Does this do what you want?
#!/usr/bin/python
import networkx as nx
import random
g = nx.Graph()
g.add_edges_from([(1,2),(2,3),(1,4),(2,5)])
random_edge = random.choice(g.edges())
print 'Randomly selected edge is:', random_edge
print 'Nodes are', random_edge[0], 'and', random_edge[1]
The answer to what I think was the intended question is:
graph = networkx.read_graphml('some_fully_loaded_graph.graphml')
edge0 = list(graph.edges(data=True))[0]
subgraph = graph.edge_subgraph([edge0[:2]])
nodes0 = list(subgraph.nodes(data=True))

Categories