Here is a MWE to create the "issue"
>>> A = nx.complete_bipartite_graph(2, 3)
>>> same_neighbors = lambda u, v: (u not in A[v] and v not in A[u] and A[u] == A[v])
>>> B = nx.quotient_graph(A, same_neighbors)
>>> B.nodes()
NodeView((frozenset({0, 1}), frozenset({2, 3, 4})))
>>> B[frozenset({0, 1})]
AtlasView({frozenset({2, 3, 4}): {'weight': 6}})
>>> B.nodes[frozenset({0, 1})]
{'graph': <networkx.classes.graph.Graph object at 0x12b066e80>, 'nnodes': 2, 'nedges': 0, 'density': 0}
I believe this graph attribute on the node is specifying the subgraph which this node comes from in the original graph, but I'm not sure. If someone could verify that would be nice.
Either way though, this graph attribute prevents me from using the nx.write_graphml function because subgraphs cannot be used as data formats. In particular it raises
networkx.exception.NetworkXError: GraphML writer does not support <class 'networkx.classes.graph.Graph'> as data values.
Now I don't actually need the subgraph in the graphml file, so just dropping that data is probably the best way for me to get the graph written to a file. What is the best way to do this?
I believe this graph attribute on the node is specifying the subgraph which this node comes from in the original graph, but I'm not sure. If someone could verify that would be nice.
Yes, you are right in assuming that the graph attribute is actually specifying the subgraph based on the original graph. It is mentioned in the documentation for quotient graph . Check out the description of node_data, you will find that:
node_data (function) – This function takes one argument, B, a set of
nodes in G, and must return a dictionary representing the node data
attributes to set on the node representing B in the quotient graph. If
None, the following node attributes will be set:
- graph, the subgraph of the graph G that this block represents,
- nnodes, the number of nodes in this block,
- nedges, the number of edges within this block,
- density, the density of the subgraph of G that this block represents.
You can also check out the subgraph yourself, by typing
for node in B.nodes():
print("Information for subgraph ", node)
print(B.nodes[node]['graph'].nodes())
# Output:
# Information for subgraph frozenset({0, 1})
# Nodes are [0, 1]
# Information for subgraph frozenset({2, 3, 4})
# Nodes are [2, 3, 4]
Now I don't actually need the subgraph in the graphml file, so just
dropping that data is probably the best way for me to get the graph
written to a file. What is the best way to do this?
You can simply create a new graph and add only the nodes and edges, and discard any other data. (Again, you are right in assuming that this information is irrelevant for writing data in GraphML format as you only need the nodes and edges).
Let's check the EdgeView of the Quotient graph, before going further:
B.edges(data=True)
# EdgeDataView([(frozenset({0, 1}), frozenset({2, 3, 4}), {'weight': 6})])
Notice, how the data for the edge is dictionary with weight as key. This info. will be useful in the subsequent code.
Now, create a new graph, and add the edges directly from the quotient graph.
H = nx.Graph()
# Here B is the quotient graph
for u,v,d in B.edges(data=True):
# Notice how the weight is assigned to the new graph
H.add_edge(u, v, weight=d['weight'])
If you want to verify that the new graph has the same structure, you can check it out using nx.is_isomorphic
nx.is_isomorphic(H, B)
# True
Now you can simply write your graph into GraphML format
nx.readwrite.graphml.write_graphml(H, "my_graph.graphml")
For more information, you can check out this Google Colab Notebook, with the above working code.
References:
NetworkX - Quotient Graph
write_graphml
NetworkX - is_isomorphic
Related
I have a MultiDiGraph with all my data in it, now I want to do some math on a filtered view of it that has only single directed edges between nodes.
>>> filtered_view[0][1]
Out[23]: AtlasView(FilterAtlas({0: {'d': 0.038, 'l': 2, 'showfl': True, 'type': 'pipe', 'q': 0.0001}}, <function FilterMultiInner.__getitem__.<locals>.new_node_ok at 0x7fa0987b55a0>))
I already have a lot of code that was working on a DiGraph, so a lot of it would not work anymore because of the differences in accessing and storing information. So thus my question:
Is there a way to have the view behave like a DiGraph?
Alternatively, I can do: ndg = nx.DiGraph(filtered_view)to get a DiGraph, but is there a smart (simple, clear, error free) way of merging it back into the main graph?
This is the implementation I came up with, it allows to either merge only data on existing nodes and edges (allnodes=False) or to merge the entire results_graph which is a DiGraph (allnodes=True). Condition is that the MultiDiGraph has not changed since the filtered view was created.
def merge_results_back(results_graph, multidigraph, allnodes=False):
for n in results_graph.nodes:
if n not in multidigraph.nodes and allnodes:
multidigraph.add_node(n)
if n in multidigraph.nodes:
nx.set_node_attributes(multidigraph, {n : results_graph.nodes[n]})
for e in results_graph.edges:
if e in multidigraph.edges:
for ed1, ed2, key, data in multidigraph.edges(e[0], keys=True, data=True):
if data['type'] == results_graph.edges[e]['type']:
nx.set_edge_attributes(multidigraph, {(e[0], e[1], key) : results_graph.edges[e]})
else:
nx.set_edge_attributes(multidigraph, {(e[0], e[1], 0): results_graph.edges[e]})
Offering a couple of suggestions for improvement here based on the code that you posted. It's unclear under what circumstances a node would be added (if the DiGraph is based on the MultiDiGraph, how is a new node possible?), so I'll leave that part alone.
In the loop for modifying edges, you end up looping through multidigraph every time a common edge is found. As an improvement, I'd suggest the following (assuming the type attribute differs based on the edge index, which wasn't clear in your question):
for u, v, data in results_graph.edges(data = True):
#only loops through each edge in the multidigraph one time
for i in range(multidigraph.number_of_edges(u, v)):
if multidigraph.edges[u, v, i]['type'] == data['type']:
multidigraph.edges[u, v, i].update(data)
If the type doesn't change based on the index, just eliminate that if statement line.
I think you can also get rid of the else block:
else:
nx.set_edge_attributes(multidigraph, {(e[0], e[1], 0): results_graph.edges[e]})
If edge e from results_graph isn't in multidigraph, then setting the edge attributes won't create edge e and it will be silently ignored. If you have a new edge and attributes though (again, unclear how this is possible if results_graph was created from multidigraph), you can add the following directly under the for u, v, data... line:
if (u, v) not in multidigraph.edges:
multidigraph.add_edge(u, v, **data)
I have been stuck on this simple problem for awhile and cant quite figure out the solution. I have a dictionary that is structured like {(node1, node2): weight} called EdgeDictFull. I wanted to create a DiGraph that has the weight stored as an attribute in the graph. I have tried a whole bunch of different ideas but no seem to work. When I run this code....
(weights is just a list of all the weights I want to add to the edges as attributes)
TG = nx.DiGraph()
for x in weights:
TG.add_edges_from(EdgeDictFull.keys(), weight = x)
TG.edges(data = True)
What this does is it will create all the correct edges, but all edges will have the attribute value of the last integer in my weights list. I think I understand why it does that, however, I cant seem to figure out how to fix it. I know it's something really simple. Any advice would be great!
# the problem with your code is that in every iteration of your loop you add
# *all* edges, and all of them get the same weight.
# you can do either of the following:
# zip:
TG = nx.DiGraph()
for edge, weight in zip(EdgeDictFull.keys(), weights):
TG.add_edge(*edge, weight=weight)
# or directly work with the dictionary:
## dummy dictionary:
EdgeDictFull = {(np.random.randint(5),np.random.randint(5)):np.random.rand() for i in range(3)}
TG = nx.DiGraph()
TG.add_weighted_edges_from((a,b,c) for (a,b), c in EdgeDictFull.items())
TG.edges(data = True)
I wanted to know how I can change a single node name of a node of a digraph. I am new to networkx and could only find answers on how to change all node names.
In my case I am iterating over a graph A to create graph B. p and c are nodes of graph A. The edge (p,c) of graph A contains data I want to add to the node p of B. However, when I am adding the edge data from graph A to the already existing node p of graph B, I would like to update the name of p to be equal to the name of c so I am able to reference it again for the next edge of graph A because it then is the edge (c,x) and I can use the c to reference it again...
The relevant part of my code looks like this
new_stages = A.in_edge(c, data='stages')
stages = B.node[p]['stages']
stages.append(new_stages)
<<Update node p to have name of c??>>
B.add_node(p, stages=new_stage_set)
Any help is appreciated, thanks!
You have nx.relabel_nodes for this. Here's a simple use case:
G = nx.from_edgelist([('a','b'), ('f','g')])
mapping = {'b':'c'}
G = nx.relabel_nodes(G, mapping)
G.edges()
# EdgeView([('a', 'c'), ('f', 'g')])
So, I've a data structure of graph as follows:
graph = {'graphData': {'nodes': List of Nodes,
'edges': List of Edges,}
}
In addition to that, I've a list of subgraphs as follows:
In first step, I add those subgraphs having one node as SOI [3 under Annotation 3] together to form something like this:
The following code take care of it:
for coreferanceGraph in unattached_graphs:
coreferanceGraph = self.merge_entity_in_graph(soi_node, coreferanceGraph, "value-entityName")
if coreferanceGraph:
edges_in_coreferenceGraph = coreferanceGraph['graphData']['edges']
nodes_in_coreferenceGraph = coreferanceGraph['graphData']['nodes']
for node in nodes_in_coreferenceGraph:
if node not in graph['graphData']['nodes']:
graph['graphData']['nodes'].append(node)
for edge in edges_in_coreferenceGraph:
if edge not in graph['graphData']['edges']:
graph['graphData']['edges'].append(edge)
attached_graphs.append(coreferanceGraph)
else:
continue
What happens in above code is that:
For every unattached subgraph in 1st image, I find if any subgraph has SOI node using function merge_entity_in_graph(). If SOI is present in subgraph, the subgraph is returned, and added to original Graph variables of structure defined defined by variables graph = dict().
In next step, I want to recursively add all those sugraphs in Image 1, to be added to open nodes present. In my case, according to Image 2, thats Ec, Ep, E1.
What I expect is:
Had there been more subgraphs with one of their nodes as Ef, Eq, Er, they also would have been added recursively.
My question is, how do I achieve this? I've written a method as follows, but not sure weather approach is right or not:
def add_other_subgraphs(self, subgraph, node, all_nodes, graph, coreference_graphs):
"""
Args:
subgraph (dict):
node (dict):
all_nodes (list):
graph (dict):
Returns:
"""
subgraph_edges = subgraph['graphData']['edges']
subgraph_nodes = subgraph['graphData']['nodes']
for node in subgraph_nodes:
if node not in graph['graphData']['nodes']:
graph['graphData']['nodes'].append(node)
for edge in subgraph_edges:
if edge not in graph['graphData']['edges']:
graph['graphData']['edges'].append(edge)
for node in graph['graphData']['nodes']:
coreference_graph = self.fetch_coreference_graph(node, coreference_graphs)
if coreference_graph:
graph = self.add_other_subgraphs(coreference_graph, node, all_nodes, graph, coreference_graphs)
else:
return graph
return graph
And the function add_other_subgraphs() will be called for each of Nodes i.e. Ec, Ep, E1
I am working with networkx and cant find a list of available attributes for edges or nodes anywhere. I am not interested in what attributes are assigned already, but what I can set/change when i create or edit the node or edge.
Can someone point me to where this is documented?
Thanks!
If you want to query a graph for all the possible attributes that might have been applied across the various nodes (and this, for communally created graphs, or ones that have been edited over time, is more common than you might imagine), then the following does the trick for me:
set(np.array([list(self.graph.node[n].keys()) for n in self.graph.nodes()]).flatten())
This returns all possible attribute names for which there are values attributed to graph-nodes. I've imported numpy as np here in order to use np.flatten for (relative) performance, but I'm sure there are various vanilla python alternatives (e.g. try the following itertools.chain method, if you need to avoid numpy )
from itertools import chain
set(chain(*[(ubrg.graph.node[n].keys()) for n in ubrg.graph.nodes()]))
You can assign a lot of edge or node attributes when you create them. It's up to you to decide what their names will be.
import networkx as nx
G=nx.Graph()
G.add_edge(1,2,weight=5) #G now has nodes 1 and 2 with an edge
G.edges()
#[(1, 2)]
G.get_edge_data(2,1) #note standard graphs don't care about order
#{'weight': 5}
G.get_edge_data(2,1)['weight']
#5
G.add_node('extranode',color='yellow', age = 17, qwerty='dvorak', asdfasdf='lkjhlkjh') #nodes are now 1, 2, and 'extranode'
G.node['extranode']
{'age': 17, 'color': 'yellow', 'qwerty': 'dvorak', 'asdfasdf': 'lkjhlkjh'}
G.node['extranode']['qwerty']
#'dvorak'
Or you can use a dict to define some of the attributes with nx.set_node_attributes and create a dict for all nodes for which a particular attribute is defined with nx.get_node_attributes
tmpdict = {1:'green', 2:'blue'}
nx.set_node_attributes(G,'color', tmpdict)
colorDict = nx.get_node_attributes(G,'color')
colorDict
#{1: 'green', 2: 'blue', 'extranode': 'yellow'}
colorDict[2]
#'blue'
Similarly there is a nx.get_edge_attributes and nx.set_edge_attributes.
More information is here in the networkx tutorial. About halfway down this page under the headings "Node Attributes" and "Edge Attributes". Specific documentation for the set...attributes and get...attributes can be found here under "Attributes".