Assume we have these sequences:
A->X->Y->Z
B->Y->Z
C->Y->Z
D->X->Z
I would like to create a graph like:
C
|
A-X-Y-Z
| |
D B
In the sequence D-X-Z there is a short cut. My goal is to create a directed acyclic graph by eliminating these short-cuts and vice versa, expand existing edges when encountering expanded paths (e.g.: X-Z with X-Y-Z).
My approach so far was to create a directed graph with Networkx but this does not solve the problem because I could not find a way to eliminate short circuits (it is a big graph with hundreds of thousands of nodes).
Any hints would be appreciated.
You can set up the graph:
import networkx as nx
text = '''
A-X-Y-Z
B-Y-Z
C-Y-Z
D-X-Z
'''
G = nx.Graph()
for s in text.strip().split('\n'):
l = s.split('-')
G.add_edges_from(zip(l,l[1:]))
Then use find_cycles and remove_edge repeatedly to identify and remove edges that form cycles:
while True:
try:
c = nx.find_cycle(G)
print(f'found cycle: {c}')
G.remove_edge(*c[0])
except nx.NetworkXNoCycle:
break
I'm developing a python application, and i want to list all possible connected subgraph of any size and starting from every node using NetworkX.
I just tried using combinations() from itertools library to find all possible combination of nodes but it is very too slow because it searchs also for not connected nodes:
for r in range(0,NumberOfNodes)
for SG in (G.subgraph(s) for s in combinations(G,r):
if (nx.is_connected(SG)):
nx.draw(SG,with_labels=True)
plt.show()
The actual output is correct. But i need another way faster to do this, because all combinations of nodes with a graph of 50 nodes and 8 as LenghtTupleToFind are up to 1 billion (n! / r! / (n-r)!) but only a minimal part of them are connected subgraph so are what i am interested in. So, it's possible to have a function for do this?
Sorry for my english, thank you in advance
EDIT:
As an example:
so, the results i would like to have:
[0]
[0,1]
[0,2]
[0,3]
[0,1,4]
[0,2,5]
[0,2,5,4]
[0,1,4,5]
[0,1,2,4,5]
[0,1,2,3]
[0,1,2,3,5]
[0,1,2,3,4]
[0,1,2,3,4,5]
[0,3,2]
[0,3,1]
[0,3,2]
[0,1,4,2]
and all combination that generates a connected graph
I had the same requirements and ended up using this code, super close to what you were doing. This code yields exactly the input you asked for.
import networkx as nx
import itertools
G = you_graph
all_connected_subgraphs = []
# here we ask for all connected subgraphs that have at least 2 nodes AND have less nodes than the input graph
for nb_nodes in range(2, G.number_of_nodes()):
for SG in (G.subgraph(selected_nodes) for selected_nodes in itertools.combinations(G, nb_nodes)):
if nx.is_connected(SG):
print(SG.nodes)
all_connected_subgraphs.append(SG)
I have modified Charly Empereur-mot's answer by using ego graph to make it faster:
import networkx as nx
import itertools
G = you_graph.copy()
all_connected_subgraphs = []
# here we ask for all connected subgraphs that have nb_nodes
for n in you_graph.nodes():
egoG = nx.generators.ego_graph(G,n,radius=nb_nodes-1)
for SG in (G.subgraph(sn+(n,) for sn in itertools.combinations(egoG, nb_nodes-1)):
if nx.is_connected(SG):
all_connected_subgraphs.append(SG)
G.remove_node(n)
You might want to look into connected_components function. It will return you all connected nodes, which you can then filter by size and node.
You can find all the connected components in O(n) time and memory complexity. Keep a seen boolean array, and run Depth First Search (DFS) or Bread First Search (BFS), to find the connected components.
In my code, I used DFS to find the connected components.
seen = [False] * num_nodes
def search(node):
component.append(node)
seen[node] = True
for neigh in G.neighbors(node):
if not seen[neigh]:
dfs(neigh)
all_subgraphs = []
# Assuming nodes are numbered 0, 1, ..., num_nodes - 1
for node in range(num_nodes):
component = []
dfs(node)
# Here `component` contains nodes in a connected component of G
plot_graph(component) # or do anything
all_subgraphs.append(component)
I generate a pydot graph with the following code
graph1 = pydot.Dot(graph_type='digraph')
A = pydot.Node("A", style="filled", fillcolor="green")
B = pydot.Node("B", style="filled", fillcolor="blue")
graph1.add_node(A)
graph1.add_node(B)
graph1.add_edge(pydot.Edge(A,B))
graph1.write_png('graph1.png')
and my output is
and I generate a another pydot graph with the following code
graph2 = pydot.Dot(graph_type='digraph')
C = pydot.Node("C", style="filled", fillcolor="green")
D = pydot.Node("D", style="filled", fillcolor="blue")
graph2.add_node(C)
graph2.add_node(D)
graph2.add_edge(pydot.Edge(C,D))
graph2.write_png('graph2.png')
and my output is as follows.
My request is how to merge these 2 graphs(graph1 and graph2)?
My expected output after merging as
I tried with the following code, but, its not working..
graph3 = pydot.Dot(graph_type='digraph')
graph1_leaf = pydot.Node(graph1.get_node(B), style="filled",
fillcolor="green")
graph2_root = pydot.Node(graph2.get_node(C), style="filled",
fillcolor="green")
graph3.add_node(graph1_leaf)
graph3.add_node(graph2_root)
graph3.add_edge(pydot.Edge(graph1_leaf,graph2_root))
graph3.write_png('graph3.png')
Please guide me to merge these 2 graphs using pydot in python.. Thanks in advance..
I couldn't find documentation describing joining 2 graphs. the common practice seems to be joining 2 sub graphs (clusters).
Here is answer that shows how it's done:
Edges between two subgraphs in pydot
another helpful answer:
Merge two dot graphs at a common node in python
Update, answer for edited question:
a few issues with your code:
graph1.get_node(B) returns a list of nodes
you are adding only the nodes and edge for connecting the graphs while you want all other nodes and edges
This code should return your wanted result:
graph3 = pydot.Dot(graph_type='digraph')
for node in graph1.get_nodes():
graph3.add_node(node)
for node in graph2.get_nodes():
graph3.add_node(node)
for edge in graph1.get_edges():
graph3.add_edge(edge)
for edge in graph2.get_edges():
graph3.add_edge(edge)
node_graph1 = graph1.get_node('B')[0]
node_graph2 = graph2.get_node('C')[0]
graph3.add_edge(pydot.Edge(node_graph1,node_graph2))
graph3.write_png('/tmp/graph3.png')
hope it helps.
is there such a function premade in python-igraph (or also networkx, so I could adapt it to work in igraph), or do i have to implement it?
If it doesnt exist yet, I would outlay it like that:
Get the incident edges of the source node,
only keep edges that meet the attribute criteria (for example, only edges with attribute "major")
For the edges from 2., find the target nodes using the target property of the edge as described here
Any improvements welcome!
There's no pre-made function for this in igraph, but you can try something like:
def filtered_neighbors(graph, node, condition):
return [ \
edge.source if edge.target == node else edge.source \
for edge in graph.es[graph.incident(node)] \
if condition(edge)
]
condition must be a Python callable that takes an edge and returns whether the edge can be accepted.
Try a list comprehension.
import networkx as nx
G = nx.Graph()
G.add_edge(1,2,weight=3)
G.add_edge(1,3,weight = 5)
node = 1
weight3_neighbors = [neighbor for neighbor in G.neighbors_iter(node) if G.edge[node][neighbor]['weight']==3]
weight3_neighbors
> [2]
I have been working with pygraph on some project. I completed this example, it works fine.
Now, the problem is the following: the graph is drawn in a picture format (gif). What I need is to get the actual coordinates for each node for the graph layout shown on the gif image. How do I do this? I've been trying and trying, but couldn't find solution to this problem. I thought the the problem's solution would be somehow with manipulating one of the two following lines:
gv.layout(gvv,'dot')
gv.render(gvv,'png','europe.png')
Thanks in advance!
You can add the layout information into the graph with :
gv.render(gvv)
and then find out the position of a node getting its attribute pos :
n_france = gv.findnode(gvv, "France")
pos = gv.getv(n_france, "pos")
Then depending on what you want to do, you might need to convert dot coordinates into png image coordinates. You can get useful information from here :
http://www.graphviz.org/faq/#FaqCoordTransformation
It explains in great details the computation from the graph-units to image pixels.
Hope that this is what you are looking for.
I just found a similar solution that works perfectly for my needs
pos = nx.drawing.nx_agraph.graphviz_layout(G, prog='dot', args='-Grankdir=LR')
cheers!
Using pydotplus you can load in and parse a dot/gv file and interrogate the data structure pydotplus produces, but this internal representation seems not to initially possess all the node attributes, like pos, unless they were already in the file.
But you can also call .write_dot() to produce a much more verbose dot file version. If you parse this then the resulting data structure seems to have pos of all the nodes (and even pos for the splines)
Note: maybe best to index the nodes by name not by index because any text with square brackets after it in the verbose file will be parsed as a node, so the node list may have spurious extra elements.
In the following (slightly edited) experiment at the Spyder prompt I have a terse dot file interior.gv (that does not have pos for nodes) which I .graph_from_dot_file(), then .write_dot(). Then .graph_from_dot_file() again on the verbose generated file, and so find the pos as required.
import pydotplus as pdp
interior = pdp.graphviz.graph_from_dot_file('interior.gv')
interior.write_dot('interior2.dot')
Out[210]: True
interior2 = pdp.graphviz.graph_from_dot_file('interior2.dot')
interior2.get_nodes()[3].get_pos()
Out[214]: '"213.74,130"'
Networkx can do this:
import networkx as nx
def setup_europe():
G = nx.Graph()
G.add_edge("Portugal", "Spain")
G.add_edge("Spain","France")
G.add_edge("France","Belgium")
G.add_edge("France","Germany")
G.add_edge("France","Italy")
G.add_edge("Belgium","Netherlands")
G.add_edge("Germany","Belgium")
G.add_edge("Germany","Netherlands")
G.add_edge("England","Wales")
G.add_edge("England","Scotland")
G.add_edge("Scotland","Wales")
G.add_edge("Switzerland","Austria")
G.add_edge("Switzerland","Germany")
G.add_edge("Switzerland","France")
G.add_edge("Switzerland","Italy")
G.add_edge("Austria","Germany")
G.add_edge("Austria","Italy")
G.add_edge("Austria","Czech Republic")
G.add_edge("Austria","Slovakia")
G.add_edge("Austria","Hungary")
G.add_edge("Denmark","Germany")
G.add_edge("Poland","Czech Republic")
G.add_edge("Poland","Slovakia")
G.add_edge("Poland","Germany")
G.add_edge("Czech Republic","Slovakia")
G.add_edge("Czech Republic","Germany")
G.add_edge("Slovakia","Hungary")
return G
G = setup_europe()
Write a dot file:
nx.write_dot(G, '/tmp/out.dot')
Compute the position of the nodes:
pos = nx.pygraphviz_layout(G, prog = 'dot')
print(pos)
# {'Netherlands': (713.86, 167.0), 'Italy': (473.86, 389.0), 'Czech Republic': (100.86, 241.0), 'Portugal': (879.86, 315.0), 'England': (1024.9, 241.0), 'Denmark': (568.86, 167.0), 'Poland': (100.86, 167.0), 'Scotland': (1024.9, 389.0), 'France': (571.86, 315.0), 'Belgium': (713.86, 19.0), 'Austria': (320.86, 167.0), 'Slovakia': (156.86, 315.0), 'Wales': (990.86, 315.0), 'Switzerland': (473.86, 241.0), 'Hungary': (294.86, 241.0), 'Germany': (465.86, 93.0), 'Spain': (879.86, 241.0)}
Render an png:
agraph = nx.to_agraph(G)
agraph.draw("/tmp/europe.png", format = 'png', prog = 'dot')
Using just pydot/dot you can do it by generating the SVG and then reading the position of the nodes from the SVG. It is a bit hacky, but works reliably enough
from xml.dom import minidom
import pydot
from io import BytesIO
def extract_node_positions(
in_dot: pydot.Dot
) -> Dict[str, Tuple[str, float, float]]:
"""
Extract the x,y positions from a pydot graph by rendering
Args:
in_dot: the graph to render
Returns:
a list of all the nodes
Examples:
>>> g = pydot.Dot()
>>> g.add_node(pydot.Node("A"))
>>> g.add_node(pydot.Node("B"))
>>> g.add_node(pydot.Node("C"))
>>> g.add_edge(pydot.Edge("A", "B"))
>>> g.add_edge(pydot.Edge("B", "C"))
>>> extract_node_positions(g)
{'A': ('A', 27.0, -157.8), 'B': ('B', 27.0, -85.8), 'C': ('C', 27.0, -13.8)}
"""
node_mapping = {f'node{i}': k.get_name()
for i, k in enumerate(in_dot.get_nodes(), 1)}
svg_io = BytesIO(in_dot.create_svg())
doc = minidom.parse(svg_io) # parseString also exists
node_dict = {node_mapping[p.getAttribute('id')]: (c_text.firstChild.data,
float(c_text.getAttribute('x')),
float(c_text.getAttribute('y')))
for p in doc.getElementsByTagName("g") if "node" == p.getAttribute('class').lower()
for c_text in p.getElementsByTagName('text')
}
doc.unlink()
return node_dict
To directly access the results from the Graphviz rendering command (e.g. dot) as binary data string from within Python instead of writing to a file, use the pipe()-method of your Graph or Digraph object:
h = Graph('hello', format='svg')
h.edge('Hello', 'World')
print(h.pipe().decode('utf-8'))
I struggled with this recently, and the answers here were some help but not quite there. The suggestions about networkx are on the right track. Networkx uses pygraphviz to interface with graphviz, and so you can instead use pygraphviz directly if you wish:
import pygraphviz as pgv
G = pgv.AGraph(strict=False,directed=True)
# add nodes and edges
G.add_edge(1,2)
G.add_edge(2,1)
# generate a layout--this creates `pos` attributes for both nodes and edges
G.layout(prog="dot")
#change something about the graph that would change the layout, e.g.,
edge = G.get_edge("1", "2")
edge.attr["label"] = "test label"
# create the graph using the layout already generated; note, do not provide `prog`
G.draw("test.pdf")
# compare it to a newly generated layout
G.draw("test2.pdf", prog="dot")
The important part is to not provide prog to the draw command if you want to use the node and edge positions already generated by the layout command. I see now this is stated in the pygraphviz docstring for draw. BTW, it is does the same as prog="neato" with the -n2 argument. Looking at the sources, pygraphviz calls graphviz to generate the layout.