runtime error: dictionary changed size during iteration - python

I have a social network graph 'G'.
I'm trying to check that they keys of my graph are in the characteristics dataset as well.
I ran this command:
for node in G.nodes():
if node in caste1.keys():
pass
else:
G = G.remove_node(node)
It shows an error RuntimeError: dictionary changed size during iteration

The RuntimeError is self-explanatory. You're changing the size of an object you're iterating within the iteration. It messes up the looping.
What you can do is to first iterate across the nodes to get the ones you would like to remove, and store it in a separate variable. Then you can iterate with this variable to remove the nodes:
# Identify nodes for removal
nodes2remove = [node for node in G.nodes() if node not in caste1.keys()]
# Remove target-nodes
for node in nodes2remove:
G = G.remove_node(node)

Related

Remove all nodes that has a particular attribute in networkx

I`m trying to remove all nodes within a Graph that has an specific attribute value.
I have seen this: python networkx remove nodes and edges with some condition
But in that case the degree is a property and not an attribute.
My graph has an attribute call "Line" which could have different values like: A, B, C.
So for example I want to remove all nodes with attribute "Line" equal to A
You can filter nodes in your graph with a subgraph view:
filter_node = lambda node: G.nodes[node]['Line'] == 'A'
filtered_nodes = list(nx.subgraph_view(G, filter_node=filter_node).nodes)
You can then remove these nodes from the graph:
G.remove_nodes_from(e)

Get filtered Networkx MultiDiGraph to behave like a DiGraph

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)

Why are all my edges being assigned the same value in a networkx DiGraph?

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)

Recursively add Subgraphs to all Nodes in a Graph

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

Error when Searching trees iterively

I need to search a tree by checking if the sum of the branches from a node is greater than zero. However, I'm running into a problem with the sum - I get a type error (int object is not callable) on the
branch_sum = [t[0] for t in current]
line. I thought it was because eventually I'll get a single node
current = [[1,'b']]
(for example), and so I added the if/else statement. I.e. I thought that I was trying to sum something that looked like this:
first = [1]
However, the problem still persists. I'm unsure of what could be causing this.
For reference, current is a list of lists, with the first slot is the node data the second slot is a node id (in the inner list). The group() function groups the data on a node based on the id of the sub-nodes (left subnodes have ids beginning with 1, right have ids beginning with 0).
The tree I'm searching is stored as a list of lists like:
tree = [[0, '1'], [1,'01'], [0,'001']]
i.e. it's a set of Huffman Codes.
from collections import deque
def group(items):
right = [[item[0],item[1][1:]] for item in items if item[1].startswith('1')]
left = [[item[0],item[1][1:]] for item in items if item[1].startswith('0')]
return left, right
def search(node):
loops = 0
to_crawl = deque(group(node))
while to_crawl:
current = to_crawl.popleft() # this is the left branch of the tree
branch_sum = 0
if len(current)==1:
branch_sum = sum([t for t in current])
else:
branch_sum = sum([t[0] for t in current])
if branch_sum !=0 :
l,r = group(current)
to_crawl.extendleft(r)
to_crawl.extendleft(l)
loops += 1
return loops
Here's what I'm trying to do:
GIven a tree, with a lot of the data being 0, find the 1. To do this, split the tree into two branches (via the group() function) and push onto deque. Pop a branch off the deque, then sum the data in the branch. If the sum is not zero split the branch into two sub branches, push the sub branches onto the deque. Keep on doing this until I've found the non-zero datum. I should end up with a single item of the form [1,'101'] in the deque when I exit.
I strongly assume that the error says
TypeError: 'int' object is not iterable
because you end up passing a 2-tuple as node to
to_crawl = deque(group(node))
which gives you a 2-element deque. Then
current = to_crawl.popleft()
gives you a single element (an integer) as current. This is clearly not iterable, which leads to the given error.
Side note: For brevity, you can use sum like this
sum(current)
instead of
sum([x for x in current])

Categories

Resources