networkx
networkx copied to clipboard
Why are draw() results so different from spring_layout + draw_networkx results?
Everything I've seen says that nx.draw() uses spring_layout with its default parameter values. Yet I'm getting wildly different results when I try nx.draw() versus nx.draw_networkx using pos=spring_layout, where the draw() graphs have a FAR superior overall layout and organization of related nodes.
My two approaches:
nx.draw(graph, with_labels=True) creates this result, which perfectly displays the node and sub-node links in a readable tree-like structure:
pos=nx.spring_layout(graph) nx.draw_networkx(graph, pos, with_labels=True) These create pretty horrific and unintelligle graph of the same data in an elliptical layout:
If this is not truly an issue, can someone help me understand why draw() only is so superior when it claims to use spring_layout for its positions? If draw() really is using default spring_layout, shouldn't these graphs' basic shapes be very similar?
(The reason I'm asking this is because I need to use the explicit pos=nx.spring_layout(graph) with nx.draw_networkx(graph, pos, with_labels=True) approach because I will eventually need to add edge labels using the same pos.)
The default pos value is provided by spring_layout. It is highly unlikely that the layout you are seeing is created by spring_layout. Are you sure you don't have pos created in some other way (maybe for something else?) that is messing up the drawing code you are using?
Also, both nx.draw and nx.draw_networkx treat pos the same. In fact nx.draw calls nx.draw_networkx.
Thanks for the feedback! I've been running nx.draw without assigning any value to pos, not passing any pos into nx.draw, so any positioning that's happening in my nx.draw is happening automatically withing nx.draw.
Tonight, I attempted fixing the first node (my root node) at 0,0 and then running spring layout this way: initial_pos = {1: (0, 0)} pos = netx.spring_layout(graph, pos=initial_pos, fixed=[1]) and then using that pos in nx.draw_networkx. That GREATLY improved the graph it created, making it much more like the good nx.draw graph I've been getting. So maybe nx.draw is automatically fixing the first node in the center before running spring_layout?
No -- nx.draw just calls pos = nx.spring_layout(G).
But there is a random part of that function -- you should get a different layout each time you call spring_layout unless you add a seed keyword arg. So, by using the default pos values in nx.draw you should also get a different layout each time you run it.
Can you include a small example code that recreates your problem so I can see what might be going on?
I agree with @dschult and suspect that whatever code generated the second image in the OP was not actually using a spring layout (perhaps circular_layout or shell_layout was called accidentally, or some other arbitrary dict was named pos).
As noted above, draw just calls spring_layout under the hood, which is unseeded by default. This means that every time you call draw (without explicitly passing in pos) you will get a different layout. Whether or not it "looks good" will be arbitrary[^1].
Try the following:
>>> G = nx.Graph([
... (1, 4), (1, 13), (1, 8), (1, 12), (1, 27), (1, 34),
... (4, 10), (4, 31), (13, 14), (13, 37), (13, 15), (8, 5),
... (8, 26), (12, 21), (12, 3), (12, 16), (27, 28), (27, 29),
... (34, 35), (34, 36)
... ])
>>> pos = nx.spring_layout(G)
>>> pos_seeded = nx.spring_layout(G, seed=0xdeadc0de)
>>> fig, ax = plt.subplots(2, 3)
>>> nx.draw(G, ax=ax[0, 0], with_labels=True) # unseeded spring layout computed internally
>>> nx.draw(G, pos=pos, ax=ax[0, 1], with_labels=True) # `draw` with pre-computed layout
>>> nx.draw_networkx(G, pos=pos, ax=ax[0, 2], with_labels=True) # same layout as previous figure
>>> nx.draw(G, pos=pos_seeded, ax=ax[1, 0], with_labels=True) # Seeded layout, same on your computer and mine
>>> nx.draw_networkx(G, pos=pos_seeded, ax=ax[1, 1], with_labels=True) # No difference between `draw` and `draw_networkx`
The upper-left layout will be arbitrary and different than all the others (unseeded spring_layout computed internally). The upper-center and upper-right figures will also have arbitrary layouts, but they (the layouts) will be identical because the same pre-computed pos dict has been passed in to each -- there is no difference between draw and draw_networkx (in terms of layout) when this is the case. The two figures on the bottom row will not only have the same layout as each other, they will have the same layout on your computer because they have been computed with an explicit seed for reproducibility.
[^1]: Generally if you want to improve spring_layout, the first thing to try is to increase the number of iterations, e.g. iterations=200. Of course, you can't do this through draw - it requires calling spring_layout explicitly.
This one's been stale for a bit and I'm quite confident the behavior in the OP is not due to a bug. Hopefully the above explanation is sufficient to address any uncertainty about default layouts & seeding, but if not - please feel free to reopen!