Getting subgraph of nodes between two nodes? - python

I have this graph:
%matplotlib inline
import networkx as nx
G = nx.Graph()
G.add_edge(1, 2)
G.add_edge(2, 3)
G.add_edge(3, 4)
G.add_edge(3, 5)
G.add_edge(4, 6)
G.add_edge(5, 6)
G.add_edge(3, 7)
G.add_edge(7, 6)
G.add_edge(6, 8)
G.add_edge(8, 9)
nx.draw(G, pos=nx.spring_layout(G), with_labels=True)
Is it possible to get the subgraph between the nodes 3 and 6 without using nx.subgraph(G, [3,4,5,6,7]). I mean, what if I know that there is this subgraph, but I don't know e.g. about the 5?

My answer is very similar to back2basics, but more directly finds the nodes between the two. If there is a path from source to target, that path will be found by nx.all_simple_paths(G, source=source, target=target) which returns a generator for the paths.
import networkx as nx
G = nx.Graph()
G.add_edges_from([(1, 2), (2, 3), (3, 4), (3, 5), (4, 6), (5, 6), (3, 7), (7, 6), (6, 8), (8, 9)])
paths_between_generator = nx.all_simple_paths(G,source=3,target=6)
nodes_between_set = {node for path in paths_between_generator for node in path}
SG = G.subgraph(nodes_between_set)
the nodes_between_set = ... uses a "set generator". It is equivalent to
nodes_between_set = set()
for path in paths_beween_generator:
for node in path:
nodes_between_set.add(node)

The first 3 lines help made the list that you need to make the subset.
import networkx as nx
c_score = nx.algorithms.betweenness_centrality_subset(G,(3,), (6,))
nodes_between = [x for x in c_score if c_score[x]!=0.0]
nodes_between.extend((3,6)) #add on the ends
SG = G.subgraph(nodes_between)
nx.draw(SG, pos=nx.spring_layout(SG), with_labels=True)
One caveat: The subgraph points are defined as being in a path from point 3 to point 6

This works on the principle that
any node in the new subgraph is reachable from source or destination, and
any node on the path by definition has at least one predecessor and one successor (for a directed graph) or two neighbors (for an undirected graph).
So, first we find the subgraph of nodes that we can reach, and then recursively remove nodes without at least one predecessor and one successor until there is only the existing subgraph.
import networkx as nx
def subgraph_from_connections(G, source, target, directed = None):
included_nodes = [x for x in G.node if nx.has_path(G, source, x) and nx.has_path(G, x, target)]
G2 = G.subgraph(included_nodes)
# If this is a undirected graph, we only need to know if it only has 1 neighbor
# If this is a directed graph, then it needs at least 1 predecessor and at least 1 successor
if directed == True or (directed is None and type(G) == nx.classes.digraph.DiGraph):
removals = [x for x in G2.node if len(G2.predecessors(x)) == 0 or len(G2.successors(x)) == 0]
while len(removals) > 0:
G2.remove_nodes_from(removals)
removals = [x for x in G.node if len(G2.predecessors(x)) == 0 or len(G2.successors(x)) == 0]
else:
removals = [x for x in G2.node if len(G2.neighbors(x)) < 2]
while len(removals) > 0:
G2.remove_nodes_from(removals)
removals = [x for x in G2.node if len(G2.neighbors(x)) < 2]
return G2
Not extensively tested, but it worked for the few cases outlined here, and it includes 10/11 when those were included from Joel's test. The algorithm is fast enough - 130 ms for my 1000/10 node random test from before (maybe I shouldn't have deleted that after all).

Related

Is there a way to find a bidirectional relationship between nodes in NetworkX?

I have a network of Twitter users and their followers which I am modelling using NetworkX. I am trying to find a bidirectional link between users, i.e. if one node follows one of its neighbors, does that neighbor also follow that node.
Is there a built in function within NetworkX that would accomplish this? I tried using nx.reciprocity() but it just returns a single value rather than a dictionary.
You can determine if there is an edge connection between two nodes with networkx.Graph.has_edge. This could then be used to test if there are opposite directed edges between nodes.
import networkx as nx
def have_bidirectional_relationship(G, node1, node2):
return G.has_edge(node1, node2) and G.has_edge(node2, node1)
G = nx.DiGraph()
G.add_edge(1, 2)
G.add_edge(2, 1)
G.add_edge(3, 4)
print(f"Nodes 1, 2 have opposite edges: {have_bidirectional_relationship(G, 1, 2)}")
print(f"Nodes 3, 4 have opposite edges: {have_bidirectional_relationship(G, 3, 4)}")
Output
Nodes 1, 2 have opposite edges: True
Nodes 3, 4 have opposite edges: False
Find all nodes with bidirectional relationships
biconnections = set()
for u, v in G.edges():
if u > v: # Avoid duplicates, such as (1, 2) and (2, 1)
v, u = u, v
if have_bidirectional_relationship(G, u, v):
biconnections.add((u, v))
print(biconnections)
Output
{(1, 2)}

Find the longest circle way in networkx

I am trying to found the longest way in networkx from node A to node A using only 10 other nodes in fully connected graph as shown below. Is it possible?
G = nx.Graph()
for i in range(len(hashlist)):
for j in range(len(hashlist)):
if i!=j:
connection=(i,j)
size=hashlist[i]-hashlist[j]
G.add_edge(i, j, weight=size)
This will find the longest cycle in a directed graph.
import networkx as nx
G = nx.Graph([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]).to_directed()
a = sorted(list(nx.simple_cycles(G)), key = lambda s: len(s))
print(a[-1])
The same approach will work for weighted graph.
import networkx as nx
G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5), (2,3, 5), (1,3, 2.4)])
G = G.to_directed()
a = sorted(list(nx.simple_cycles(G)), key = lambda s: len(s))
print(a[-1])

Counting edges between nodes in networkx

I have a graph where my nodes can have multiple edges between them in both directions and I want to set the width between the nodes based on the sum of all edges between them.
import networkx as nx
nodes = [0,1]
edges = [(0,1),(1,0)]
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
weights = [2,3]
nx.draw(G, width = weights)
I would like to have the width between 0 and 1 set to 5 as that is the summed weight.
First you need to create a MultiDiGraph and add all possible edges to it. This is because it supports multiple directed egdes between the same set of nodes including self-loops.
import networkx as nx
nodes = [0, 1, 2, 3, 4, 5]
edges = [(0,1), (1,0), (1, 0),(0, 1), (2, 3), (2, 3), (2, 3), (2, 3),
(4, 1), (4, 1), (4, 1), (4, 1), (4, 1), (4, 1), (4, 5), (5, 0)]
G = nx.MultiDiGraph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
Next, create a dictionary containing counts of each edges
from collections import Counter
width_dict = Counter(G.edges())
edge_width = [ (u, v, {'width': value})
for ((u, v), value) in width_dict.items()]
Now create a new DiGraph from the edge_width dictionary created above
G_new = nx.DiGraph()
G_new.add_edges_from(edge_width)
Plotting using thickened edges
This is an extension of answer mentioned here.
edges = G_new.edges()
weights = [G_new[u][v]['width'] for u,v in edges]
nx.draw(G_new, edges=edges, width=weights)
Add Edge labels
See this answer for more info.
pos = nx.spring_layout(G_new)
nx.draw(G_new, pos)
edge_labels=dict([((u,v,),d['width'])
for u,v,d in G_new.edges(data=True)])
nx.draw_networkx_edges(G_new, pos=pos)
nx.draw_networkx_edge_labels(G_new, pos, edge_labels=edge_labels,
label_pos=0.25, font_size=10)
You can also view this Google Colab Notebook with working code.
References
https://stackoverflow.com/a/25651827/8160718
https://stackoverflow.com/a/22862610/8160718
Drawing networkX edges
MultiDiGraph in NetworkX
Count occurrences of List items

Networkx: Finding the shortest path to one of multiple nodes in Graph

I have a graph of different locations:
import networkx as nx
G = nx.Graph()
for edge in Edge.objects.all():
G.add_edge(edge.from_location, edge.to_location, weight=edge.distance)
The locations (nodes) have different types (toilets, building entrances, etc.) I need to find the shortest way from some given location to any location of a specific type. (For example: Find the nearest entrance from a given node.)
Is there some method in the Networkx library to solve that without loops? Something like:
nx.shortest_path(
G,
source=start_location,
target=[first_location, second_location],
weight='weight'
)
The result will be the shortest path to either the first_location or the second_location, if both locations are of the same type.
And is there some method that also returns path length?
We will do it in three steps.
Step 1: Let's create a dummy graph to illustrate
Step 2: Plot the graph and color nodes to indicate edge lengths and special node types (toilets, entrances etc.)
Step 3: From any given node (source) calculate shortest path to all reachable nodes, then subset to the node types of interest and select path with the minimum length.
The code below can definitely be optimized, but this might be easier to follow.
Step 1: Create the graph
edge_objects = [(1,2, 0.4), (1, 3, 1.7), (2, 4, 1.2), (3, 4, 0.3), (4 , 5, 1.9),
(4 ,6, 0.6), (1,7, 0.4), (3,5, 1.7), (2, 6, 1.2), (6, 7, 0.3),
(6, 8, 1.9), (8,9, 0.6)]
toilets = [5,9] # Mark two nodes (5 & 9) to be toilets
entrances = [2,7] # Mark two nodes (2 & 7) to be Entrances
common_nodes = [1,3,4,6,8] #all the other nodes
node_types = [(9, 'toilet'), (5, 'toilet'),
(7, 'entrance'), (2, 'entrance')]
#create the networkx Graph with node types and specifying edge distances
G = nx.Graph()
for n,typ in node_types:
G.add_node(n, type=typ) #add each node to the graph
for from_loc, to_loc, dist in edge_objects:
G.add_edge(from_loc, to_loc, distance=dist) #add all the edges
Step 2: Draw the graph
#Draw the graph (optional step)
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True)
edge_labels = nx.get_edge_attributes(G,'distance')
nx.draw_networkx_edge_labels(G, pos, edge_labels = edge_labels)
nx.draw_networkx_nodes(G, pos, nodelist=toilets, node_color='b')
nx.draw_networkx_nodes(G, pos, nodelist=entrances, node_color='g')
nx.draw_networkx_nodes(G, pos, nodelist=common_nodes, node_color='r')
plt.show()
Step 3: create small functions to find the shortest path to node type
def subset_typeofnode(G, typestr):
'''return those nodes in graph G that match type = typestr.'''
return [name for name, d in G.nodes(data=True)
if 'type' in d and (d['type'] ==typestr)]
#All computations happen in this function
def find_nearest(typeofnode, fromnode):
#Calculate the length of paths from fromnode to all other nodes
lengths=nx.single_source_dijkstra_path_length(G, fromnode, weight='distance')
paths = nx.single_source_dijkstra_path(G, fromnode)
#We are only interested in a particular type of node
subnodes = subset_typeofnode(G, typeofnode)
subdict = {k: v for k, v in lengths.items() if k in subnodes}
#return the smallest of all lengths to get to typeofnode
if subdict: #dict of shortest paths to all entrances/toilets
nearest = min(subdict, key=subdict.get) #shortest value among all the keys
return(nearest, subdict[nearest], paths[nearest])
else: #not found, no path from source to typeofnode
return(None, None, None)
Test:
find_nearest('entrance', fromnode=5)
produces:
(7, 2.8, [5, 4, 6, 7])
Meaning: The nearest 'entrance' node from 5 is 7, the path length is 2.8 and the full path is: [5, 4, 6, 7]. Hope this helps you move forward. Please ask if anything is not clear.

Find edges of multiplicity larger than 2 in a NetworkX MultiGraph

I have a MultiGraph which can have multiple edges between any two nodes:
g = MultiGraph()
g.add_nodes_from([1,2,3,4,5,6])
g.add_edges_from([(1,2), (2,1), (1,2), (3,4), (5,6), (6,5), (3,5), (3, 5), (3, 5), (3, 5)])
How can I find all edges with multiplicity larger than 2 in this graph? I can do something like this:
for s in itertools.combinations(g.nodes(), 2):
e = g[s[0]].get(s[1], {})
if len(e) > 2:
print(s[0], s[1])
but it is too inefficient, so I'm looking for a better solution
for u in G.nodes():
for neighbor in G.neighbors(u):
if G.number_of_edges(u,neighbor)>2:
print (u,neighbor)
Note - each such edge will be printed twice.
[x for x in tmp.edges if tmp.number_of_edges(x[0], x[1]) > 1]

Categories

Resources