Remove weights from networkx graph - python

I have a weighted Networkx graph G. I first want to make some operation on G with weights (which is why I just don't read the input and set weights=None) and then remove them from G afterwards. What is the most straightforward way to make it unweighted?
I could just do:
G = nx.from_scipy_sparse_array(nx.to_scipy_sparse_array(G,weight=None))
Or loop through the G.adj dictionary and set weights=0, but both of these options feels too complicated. Something like:
G = G.drop_weights()

It is possible to access the data structure of the networkx graphs directly and remove any unwanted attributes.
At the end, what you can do is define a function that loops over the dictionaries and remove the "weight" attribute.
def drop_weights(G):
'''Drop the weights from a networkx weighted graph.'''
for node, edges in nx.to_dict_of_dicts(G).items():
for edge, attrs in edges.items():
attrs.pop('weight', None)
and an example of usage:
import networkx as nx
def drop_weights(G):
'''Drop the weights from a networkx weighted graph.'''
for node, edges in nx.to_dict_of_dicts(G).items():
for edge, attrs in edges.items():
attrs.pop('weight', None)
G = nx.Graph()
G.add_weighted_edges_from([(1,2,0.125), (1,3,0.75), (2,4,1.2), (3,4,0.375)])
print(nx.is_weighted(G)) # True
F = nx.Graph(G)
print(nx.is_weighted(F)) # True
# OP's suggestion
F = nx.from_scipy_sparse_array(nx.to_scipy_sparse_array(G,weight=None))
print(nx.is_weighted(F)) # True
# Correct solution
drop_weights(F)
print(nx.is_weighted(F)) # False
Note that even reconstructing the graph without the weights through nx.to_scipy_sparse_array is not enough because the graph is constructed with weights, only these are set to 1.

Related

Adding specific weights to edges through dictionary

I have two dictionaries of nodes with edges like below:
all_nodes_edges = {20101: (20102, 20201), 20102: (20101, 20103), 20103: (20102, 20104)}
nodes_with_weights = {20101: 20201, 20119: 20219, 20201: (20301, 20101)}
I've created the graph and default weights for all the edges in the graph with the below code:
g = nx.Graph(all_nodes_edges)
nx.set_edge_attributes(g, 1, 'weight')
I'm trying to use the nodes_with_weights dict to create weights of 2 on specific edges. How do I achieve this?. Do I have to loop through the dictionary or can I just use a specific nx function?
Sorry kinda new to graphs.
I finally figured it out!
for u,v,a in g.edges(data=True):
if u and v in nodes_with_weights:
a = 2

How do I calculate the Graph Edit Distance with networkx(Python)?

I am working with the graph edit distance; According to the definition it is the minimum sum of costs to transform the original graph G1 into a graph that is isomorphic to G2;
The graph edit operations typically include:
vertex insertion to introduce a single new labeled vertex to a graph.
vertex deletion to remove a single (often disconnected) vertex from a graph.
vertex substitution to change the label (or color) of a given vertex.
edge insertion to introduce a new colored edge between a pair of vertices.
edge deletion to remove a single edge between a pair of vertices.
edge substitution to change the label (or color) of a given edge.
Now I want to use the implementation networkx has - I do not have any edge labels, the node set of G1 and G2 is the same and I do not want a graph isomorphic to G2 but I want G2 itself;
This is mainly because G1: 1->2->3 and G2: 3->2->1 are isomorphic to each other but if the nodes represent some events, from a perspective of causality, they are very very different;
So in this context, I've been running a test like the following:
import networkx as nx
G=nx.DiGraph()
G.add_node(1)
G.add_node(2)
G.add_node(3)
G.add_edges_from([(1, 2),(2,3)])
G2=nx.DiGraph()
G2.add_node(1)
G2.add_node(2)
G2.add_node(3)
G2.add_edges_from([(3, 2),(2, 1)])
nx.graph_edit_distance(G,G2)
But it returns that the distance is zero which makes sense because the graphs are isomorphic to each other;
So I tried to set up node_match but still no luck
import networkx as nx
def nmatch(n1, n2):
return n1==n2
G=nx.DiGraph()
G.add_node(1)
G.add_node(2)
G.add_node(3)
G.add_edges_from([(1, 2),(2,3)])
G2=nx.DiGraph()
G2.add_node(1)
G2.add_node(2)
G2.add_node(3)
G2.add_edges_from([(3, 2),(2, 1)])
nx.graph_edit_distance(G,G2, node_match=nmatch)
If we assume a cost of 1 to delete or add an edge/ vertex, then the edit distance should be 4, because we can:
delete both edges in G, add the 2 edges from G2
How would it be suitable to calculate the edit distance not considering isomorphy but really equivalence?
It doesn't seem that you are comparing what you want to. n1 and n2 in nmatch are always {}. From documentation
(...) That is, the function will receive the node attribute dictionaries for n1 and n2 as inputs.
you are not comparing the nodes object, but dictionaries associated with them (as any data you need)
You can add your custom data to that dictionary when adding nodes, for example:
import networkx as nx
def nmatch(n1, n2):
return n1==n2
G=nx.DiGraph()
G.add_node(1, id=1)
G.add_node(2, id=2)
G.add_node(3, id=3)
G.add_edges_from([(1, 2),(2,3)])
G2=nx.DiGraph()
G2.add_node(1, id=1)
G2.add_node(2, id=2)
G2.add_node(3, id=3)
G2.add_edges_from([(3, 2),(2,1)])
nx.graph_edit_distance(G,G2, node_match=nmatch)
returns 2, as you can do 2 edge substitutions. You could probably increase substitution cost if you wanted result to be 4 (2 insertions, 2 deletions)
This is another solution, which produce 2
import networkx as nx
G=nx.DiGraph()
G.add_node(1, id=1)
G.add_node(2, id=2)
G.add_node(3, id=3)
G.add_edges_from([(1, 2),(2,3)])
G2=nx.DiGraph()
G2.add_node(1, id=1)
G2.add_node(2, id=2)
G2.add_node(3, id=3)
G2.add_edges_from([(3, 2),(2,1)])
# arguments
# arguments for nodes
def node_subst_cost(node1, node2):
# check if the nodes are equal, if yes then apply no cost, else apply 1
if node1 == node2:
return 0
return 1
def node_del_cost(node):
return 1 # here you apply the cost for node deletion
def node_ins_cost(node):
return 1 # here you apply the cost for node insertion
# arguments for edges
def edge_subst_cost(edge1, edge2):
# check if the edges are equal, if yes then apply no cost, else apply 3
if edge1==edge2:
return 0
return 1
def edge_del_cost(node):
return 1 # here you apply the cost for edge deletion
def edge_ins_cost(node):
return 1 # here you apply the cost for edge insertion
paths, cost = nx.optimal_edit_paths(
G,
G2,
node_subst_cost=node_subst_cost,
node_del_cost=node_del_cost,
node_ins_cost=node_ins_cost,
edge_subst_cost=edge_subst_cost,
edge_del_cost=edge_del_cost,
edge_ins_cost=edge_ins_cost
)
print(cost)
If you run it on Python 2.7, add the following lines to the header
# This Python file uses the following encoding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import absolute_import, division

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)

Is there a way to run pagerank algorithm on NetworkX's MultiGraph?

I'm working on a graph with multiple edges between the same nodes (edges are having different values). In order to model this graph I need to use MultiGraph instead of normal Graph. Unfortunately, it's not possible to run PageRank algo on it.
Any workarounds known ?
NetworkXNotImplemented: not implemented for multigraph type
You could create make a graph without parallel edges and then run pagerank.
Here is an example of summing edge weights of parallel edges to make a simple graph:
import networkx as nx
G = nx.MultiGraph()
G.add_edge(1,2,weight=7)
G.add_edge(1,2,weight=10)
G.add_edge(2,3,weight=9)
# make new graph with sum of weights on each edge
H = nx.Graph()
for u,v,d in G.edges(data=True):
w = d['weight']
if H.has_edge(u,v):
H[u][v]['weight'] += w
else:
H.add_edge(u,v,weight=w)
print H.edges(data=True)
#[(1, 2, {'weight': 17}), (2, 3, {'weight': 9})]
print nx.pagerank(H)
#{1: 0.32037465332634, 2: 0.4864858243244209, 3: 0.1931395223492388}
You can still compose a Digraph by combining the edges
while adding their weights.
# combining edges using defaultdict
# input-- combined list of all edges
# ouput-- list of edges with summed weights for duplicate edges
from collections import defaultdict
def combine_edges(combined_edge_list):
ddict = defaultdict(list)
for edge in combined_edge_list:
n1,n2,w = edge
ddict[(n1,n2)].append(w)
for k in ddict.keys():
ddict[k] = sum(ddict[k])
edges = list(zip( ddict.keys(), ddict.values() ) )
return [(n1,n2,w) for (n1,n2),w in edges]

Graph traversal with Networkx (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.

Categories