I have the following code which works for a random graph as can be shown. However when I try to use other graph types I get an error in the edge drawing function. In particular the edge positions.
If you comment
G = nw.random_geometric_graph(200, 0.125)
and un-comment
G = nw.barabasi_albert_graph(200, 2)
Error messages appear. I am new to python and NetworkX in particular so any help is appreciated!
import matplotlib.pyplot as plt
import networkx as nw
G = nw.random_geometric_graph(200, 0.125)
#G = nw.watts_strogatz_graph(200, 3, 0.125, seed=None)
#G = nw.barabasi_albert_graph(200, 2)
# position is stored as node attribute data for random_geometric_graph
pos = nw.get_node_attributes(G, 'pos')
# find node near center (0.5, 0.5)
dmin = 1
ncenter = 0
for n in pos:
x, y = pos[n]
d = (x - 0.5) ** 2 + (y - 0.5) ** 2
if d < dmin:
ncenter = n
dmin = d
# color by path length from node near center
p = nw.single_source_shortest_path_length(G, ncenter)
plt.figure(figsize=(8, 8))
nw.draw_networkx_edges(G, pos, nodelist=[ncenter], alpha=0.4)
nw.draw_networkx_nodes(G, pos, nodelist=list(p.keys()), node_size=80, node_color=list(p.values()), cmap=plt.cm.Reds_r)
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
plt.axis('off')
plt.savefig('random_geometric_graph.png')
plt.show()
The error message given is;
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-11-> in <module>()
22 plt.figure(figsize=(8,8))
23
---> 24 nw.draw_networkx_edges(G, pos, nodelist=[ncenter], alpha=0.4)
25 nw.draw_networkx_nodes(G, pos, nodelist=list(p.keys()), node_size=80, node_color=list(p.values()), cmap=plt.cm.Reds_r)
26
/Users//anaconda/lib/python3.6/site-packages/networkx/drawing/nx_pylab.py in draw_networkx_edges(G, pos, edgelist, width, edge_color, style, alpha, edge_cmap, edge_vmin, edge_vmax, ax, arrows, label, **kwds)
513
514 # set edge positions
--> 515 edge_pos = numpy.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist])
516
517 if not cb.iterable(width):
/Users//anaconda/lib/python3.6/site-packages/networkx/drawing/nx_pylab.py in <listcomp>(.0)
513
514 # set edge positions
--> 515 edge_pos = numpy.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist])
516
517 if not cb.iterable(width):
KeyError: 0
I don't think any other graph initialisation methods apart from random_geometric_graph sets the node position automatically (as the connectivity in this graph depends on the node positions, it makes sense to set one by default). If you check your example with the watts_strogatz_graph, the dictionary that is returned is actually empty (although it arguably should throw a KeyError).
You need to determine the layout explicitly, e.g. using
pos = nw.spring_layout(G)
or any of the other layout algorithms.
You can see that the problem (from stack trace) is in this line:
nw.draw_networkx_edges(G, pos, nodelist=[ncenter], alpha=0.4)
And the error is KeyError, so something cannot be found. Probably, you need to draw edges here, but you do provide the nodelist. According official docs, method to drawing the edges should accept edgelist, not a nodelist.
So you need to do this:
nw.draw_networkx_edges(G, pos, edgelist=[SOME_EDGES_HERE], alpha=0.4)
Note that this should be edges, not nodes, so you need to find them from the center node.
Related
I have a community list as the following list_community.
How do I edit the code below to make the community visible?
from igraph import *
list_community = [['A', 'B', 'C', 'D'],['E','F','G'],['G', 'H','I','J']]
list_nodes = ['A', 'B', 'C', 'D','E','F','G','H','I','J']
tuple_edges = [('A','B'),('A','C'),('A','D'),('B','C'),('B','D'), ('C','D'),('C','E'),
('E','F'),('E','G'),('F','G'),('G','H'),
('G','I'), ('G','J'),('H','I'),('H','J'),('I','J'),]
# Make a graph
g_test = Graph()
g_test.add_vertices(list_nodes)
g_test.add_edges(tuple_edges)
# Plot
layout = g_test.layout("kk")
g.vs["name"] = list_nodes
visual_style = {}
visual_style["vertex_label"] = g.vs["name"]
visual_style["layout"] = layout
ig.plot(g_test, **visual_style)
I would like a plot that visualizes the community as shown below.
I can also do this by using a module other than igraph.
Thank you.
In igraph you can use the VertexCover to draw polygons around clusters (as also suggested by Szabolcs in his comment). You have to supply the option mark_groups when plotting the cover, possibly with some additional palette if you want. See some more detail in the documentation here.
In order to construct the VertexCover, you first have to make sure you get integer indices for each node in the graph you created. You can do that using g_test.vs.find.
clusters = [[g_test.vs.find(name=v).index for v in cl] for cl in list_community]
cover = ig.VertexCover(g_test, clusters)
After that, you can simply draw the cover like
ig.plot(cover,
mark_groups=True,
palette=ig.RainbowPalette(3))
resulting in the following picture
Here is a script that somewhat achieves what you're looking for. I had to handle the cases of single-, and two-nodes communities separately, but for greater than two nodes this draws a polygon within the nodes.
I had some trouble with matplotlib not accounting for overlapping edges and faces of polygons which meant the choice was between (1) not having the polygon surround the nodes or (2) having an extra outline just inside the edge of the polygon due to matplotlib overlapping the widened edge with the fill of the polygon. I left a comment on how to change the code from option (2) to option (1).
I also blatantly borrowed a convenience function from this post to handle correctly sorting the nodes in the polygon for appropriate filling by matplotlib's plt.fill().
Option 1:
Option 2:
Full code:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
def sort_xy(x, y):
x0 = np.mean(x)
y0 = np.mean(y)
r = np.sqrt((x-x0)**2 + (y-y0)**2)
angles = np.where((y-y0) > 0, np.arccos((x-x0)/r), 2*np.pi-np.arccos((x-x0)/r))
mask = np.argsort(angles)
x_sorted = x[mask]
y_sorted = y[mask]
return x_sorted, y_sorted
G = nx.karate_club_graph()
pos = nx.spring_layout(G, seed=42)
fig, ax = plt.subplots(figsize=(8, 10))
nx.draw(G, pos=pos, with_labels=True)
communities = nx.community.louvain_communities(G)
alpha = 0.5
edge_padding = 10
colors = cm.get_cmap('viridis', len(communities))
for i, comm in enumerate(communities):
if len(comm) == 1:
cir = plt.Circle((pos[comm.pop()]), edge_padding / 100, alpha=alpha, color=colors(i))
ax.add_patch(cir)
elif len(comm) == 2:
comm_pos = {k: pos[k] for k in comm}
coords = [a for a in zip(*comm_pos.values())]
x, y = coords[0], coords[1]
plt.plot(x, y, linewidth=edge_padding, linestyle="-", alpha=alpha, color=colors(i))
else:
comm_pos = {k: pos[k] for k in comm}
coords = [a for a in zip(*comm_pos.values())]
x, y = sort_xy(np.array(coords[0]), np.array(coords[1]))
plt.fill(x, y, alpha=alpha, facecolor=colors(i),
edgecolor=colors(i), # set to None to remove edge padding
linewidth=edge_padding)
I'm using osmnx map plots to find the shortest path and the 3 shortest paths between 2 points as in the below code:
location = (18.5204,73.8567)
mode = "drive"
ox.config(log_console=True, use_cache=True)
G = ox.graph_from_point(location, dist=2000, simplify=True, network_type=mode)
nodes_proj, edges_proj = ox.graph_to_gdfs(G, nodes=True, edges=True)
ox.plot_graph(G,node_color='r')
origin_point = (18.515802, 73.846754)
destination_point =(18.519423, 73.852966)
origin_node = ox.distance.nearest_nodes(G, origin_point[1], origin_point[0])
print('origin_node',origin_node)
destination_node = ox.distance.nearest_nodes(G, destination_point[1], destination_point[0])
print('destination_node',destination_node)
bgcolor = "#061529"
route = ox.distance.shortest_path(G, origin_node,destination_node)
route
bbox = ox.utils_geo.bbox_from_point(point=(18.515802, 73.846754), dist=700)
fig, ax = ox.plot_graph_route(G, route, bbox = bbox, route_linewidth=6, node_size=0, bgcolor=bgcolor,dpi = 300)
routes = ox.k_shortest_paths(G, origin_node, destination_node, k=3, weight='length')
bbox = ox.utils_geo.bbox_from_point(point=(18.515802, 73.846754), dist=700)
fig, ax = ox.plot_graph_routes(G, list(routes), bbox = bbox, route_colors=['r','b','g'], route_linewidth=2, node_size=0, bgcolor=bgcolor,dpi = 300)
The above code runs fine. However the background is black and very basic. I want the background to be interactive colored map, something like folium:
I tried this for the shortest path using ox.plot_route_folium, and it worked fine
ox.plot_route_folium(G, route, popup_attribute='length')
For the shortest 3 paths "routes", I tried the below and it gave me the below error:
ox.plot_route_folium(G, routes, popup_attribute='length')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-135-2d9737bbef6b> in <module>
----> 1 ox.plot_route_folium(G, routes, popup_attribute='length')
2
3
/opt/anaconda3/lib/python3.8/site-packages/osmnx/folium.py in plot_route_folium(G, route, route_map, popup_attribute, tiles, zoom, fit_bounds, **kwargs)
92 """
93 # create gdf of the route edges in order
---> 94 node_pairs = zip(route[:-1], route[1:])
95 uvk = ((u, v, min(G[u][v], key=lambda k: G[u][v][k]["length"])) for u, v in node_pairs)
96 gdf_edges = utils_graph.graph_to_gdfs(G.subgraph(route), nodes=False).loc[uvk]
TypeError: 'generator' object is not subscriptable
Edit: I've already tried this converting generator to list: routes=list(routes). However, it gives empty list: [] after conversion.
Any advice, please?
Thanks,
You are trying to pass it routes (plural), as a generator of lists. You must pass it a single list representing a single route, as stated in its documentation.
I'm new to networkx (version 2.4) and a bit puzzled by the error that I get for stochastic_block_model when I try to add a nodelist. I'm trying to have a different color attribute for each block in the network using this code:
import networkx as nx
N_p = 10
N_n = 10
N_0 = 30
sizes = [N_p, N_n, N_0]
probs = [[0.25, 0.05, 0.02],
[0.05, 0.35, 0.07],
[0.02, 0.07, 0.40]]
nodelist = ['blue' for i in range(N_p)]
nodelist.extend(['red' for i in range(N_n)])
nodelist.extend(['green' for i in range(N_0)])
G = nx.stochastic_block_model(sizes, probs,nodelist=nodelist, seed=0,directed=1)
But I get the following error message:
...
/opt/anaconda3/lib/python3.7/site-packages/networkx/generators/community.py in stochastic_block_model(sizes, p, nodelist, seed, directed, selfloops, sparse)
576 raise nx.NetworkXException("'nodelist' and 'sizes' do not match.")
577 if len(nodelist) != len(set(nodelist)):
--> 578 raise nx.NetworkXException("nodelist contains duplicate.")
579 else:
580 nodelist = range(0, sum(sizes))
NetworkXException: nodelist contains duplicate.
What am I doing wrong?
The error is just that - the nodelist contains duplicates:
>>> nodelist
['blue'*10, 'red'*10, 'green'*30]
As in your documentation link:
Raises NetworkXError –
If probabilities are not in [0,1]. If the
probability matrix is not square (directed case). If the probability
matrix is not symmetric (undirected case). If the sizes list does not
match nodelist or the probability matrix. If nodelist contains
duplicate.
To fix this, either don't use a nodelist, or do something like the following:
nodelist = [f'blue_{i}' for i in range(N_p)]
nodelist.extend([f'red_{i}' for i in range(N_n)])
nodelist.extend([f'green_{i}' for i in range(N_0)])
I'm using the DiGraph class from networkx, which, by the docs, should allow self loops. However, when plotting with Matplotlib, I just cannot see any self loop, no matter if
print(G.nodes_with_selfloops())
returns a list of nodes with self loops.
I'm wondering how to display these self loops.
I'm using these functions to draw:
nx.draw_networkx_edge_labels(G,pos,edge_labels=edge_labels)
nx.draw_networkx(G,pos,font_color='k',node_size=500, edge_color='b', alpha=0.5)
I have faced the same issue when trying to draw a chord diagram using networkx. On an older version of networkx (2.5) self-loops were drawn with one dot behind the node (which means you don't see them at all). On the newer version (2.6.2), self-loops are drawn in the same direction as on the image below
Self-loops in the networkx 2.6.2
If this is enough for you, try to update networkx. Looks like this problem is solved. At least, the documentation has some info about it
However, if this is not enough for you (as it was for me), you can write a custom code to draw self-loops nicer. I created a repository for that task. It allows to draw self-loop with different directions, looking away from the center:
Self-loops with my code
Here is briefly the idea behind it:
You know the start and the end coordinates of the loop. These are just the coordinates of your node
You need 2 more points to draw a self-loop with Bézier curve. You want them to be further away from the center, than the node of the graph. You also want them to be further from the node in the orthogonal direction to the vector from the center to the node
If this sounds too complicated, I hope the image makes it clear:
Visualization of anchors
When we obtained the anchors, we can draw a Bézier curve through them. This is how it looks:
Self-loops
Here is code:
from typing import Optional
import matplotlib.pyplot as plt
from matplotlib.path import Path as MplPath # To avoid collisions with pathlib.Path
import matplotlib.patches as patches
import networkx as nx
import numpy as np
# Some useful functions
def normalize_vector(vector: np.array, normalize_to: float) -> np.array:
"""Make `vector` norm equal to `normalize_to`
vector: np.array
Vector with 2 coordinates
normalize_to: float
A norm of the new vector
Returns
-------
Vector with the same direction, but length normalized to `normalize_to`
"""
vector_norm = np.linalg.norm(vector)
return vector * normalize_to / vector_norm
def orthogonal_vector(point: np.array, width: float, normalize_to: Optional[float] = None) -> np.array:
"""Get orthogonal vector to a `point`
point: np.array
Vector with x and y coordinates of a point
width: float
Distance of the x-coordinate of the new vector from the `point` (in orthogonal direction)
normalize_to: Optional[float] = None
If a number is provided, normalize a new vector length to this number
Returns
-------
Array with x and y coordinates of the vector, which is orthogonal to the vector from (0, 0) to `point`
"""
EPSILON = 0.000001
x = width
y = -x * point[0] / (point[1] + EPSILON)
ort_vector = np.array([x, y])
if normalize_to is not None:
ort_vector = normalize_vector(ort_vector, normalize_to)
return ort_vector
def draw_self_loop(
point: np.array,
ax: Optional[plt.Axes] = None,
padding: float = 1.5,
width: float = 0.3,
plot_size: int = 10,
linewidth = 0.2,
color: str = "pink",
alpha: float = 0.5
) -> plt.Axes:
"""Draw a loop from `point` to itself
!Important! By "center" we assume a (0, 0) point. If your data is centered around a different points,
it is strongly recommended to center it around zero. Otherwise, you will probably get ugly plots
Parameters
----------
point: np.array
1D array with 2 coordinates of the point. Loop will be drawn from and to these coordinates.
padding: float = 1.5
Controls how the distance of the loop from the center. If `padding` > 1, the loop will be
from the outside of the `point`. If `padding` < 1, the loop will be closer to the center
width: float = 0.3
Controls the width of the loop
linewidth: float = 0.2
Width of the line of the loop
ax: Optional[matplotlib.pyplot.Axes]:
Axis on which to draw a plot. If None, a new Axis is generated
plot_size: int = 7
Size of the plot sides in inches. Ignored if `ax` is provided
color: str = "pink"
Color of the arrow
alpha: float = 0.5
Opacity of the edge
Returns
-------
Matplotlib axes with the self-loop drawn
"""
if ax is None:
fig, ax = plt.subplots(figsize=(plot_size, plot_size))
point_with_padding = padding * point
ort_vector = orthogonal_vector(point, width, normalize_to=width)
first_anchor = ort_vector + point_with_padding
second_anchor = -ort_vector + point_with_padding
verts = [point, first_anchor, second_anchor, point]
codes = [MplPath.MOVETO, MplPath.CURVE4, MplPath.CURVE4, MplPath.CURVE4]
path = MplPath(verts, codes)
patch = patches.FancyArrowPatch(
path=path,
facecolor='none',
lw=linewidth,
arrowstyle="-|>",
color=color,
alpha=alpha,
mutation_scale=30 # arrowsize in draw_networkx_edges()
)
ax.add_patch(patch)
return ax
Code example with drawing a plot:
fig, ax = plt.subplots(figsize=(6, 6))
graph = nx.DiGraph(
np.array([
[1, 1, 1, 1, 1],
[1, 0, 1, 0, 0],
[1, 1, 1, 0, 1],
[0, 0, 1, 0, 1],
[1, 1, 1, 1, 1]
])
)
pos = nx.circular_layout(graph, center=(0, 0))
nx.draw_networkx_nodes(graph, pos, ax=ax)
nx.draw_networkx_edges(graph, pos, ax=ax)
for node in graph.nodes:
if (node, node) in graph.edges:
draw_self_loop(point=pos[node], ax=ax, color="k", alpha=1, linewidth=1)
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
Result: chord diagram
You can find more examples and functions to draw a beautiful chord diagram in my repository
https://networkx.github.io/documentation/networkx-1.10/reference/drawing.html
In the future, graph visualization functionality may be removed from
NetworkX or only available as an add-on package.
We highly recommend that people visualize their graphs with tools
dedicated to that task.
Link above provides many alternatives to built-in visualization. Do consider alternatives they provide to save yourself A LOT of time down the road.
Personally I use cytoscape, which accepts files in .graphml format. Exporting your graph to .graphml is very easy:
nx.write_graphml(graph, path_to_file)
I'm trying to draw graph by networkx. However, the below code suffers the error that is
NetworkXError: Node 8 has no position
I believe only less than seven nodes are allowed, but I can not understand why.
Please suggest why the error happens and how to fix to draw more than 8 nodes.
import networkx as nx
# new_words = ["1","2","3","4","5","6","7"] => It does not occur the error
new_words = ["1","2","3","4","5","6","7","8"]
target_word = ["0"]
G=nx.cubical_graph()
pos=nx.spring_layout(G)
nx.draw_networkx_nodes(G,pos,
nodelist=[0],
node_color='r',
node_size=50, alpha=0.8)
nx.draw_networkx_nodes(G,pos,
nodelist=range(1, len(new_words)+1),
node_color='w',
node_size=50, alpha=0.8)
nx.draw_networkx_edges(G,pos,
edgelist=[(0,i) for i in range(1,len(new_words)+1)])
# some math labels
labels={}
labels[0]=target_word
for idx in range(0,len(new_words)):
labels[idx+1] = new_words[idx]
nx.draw_networkx_labels(G,pos,labels)
The error message is:
---------------------------------------------------------------------------
NetworkXError Traceback (most recent call last)
<ipython-input-16-5ddab3dbe0d6> in <module>()
15 nodelist=range(1, len(new_words)+1),
16 node_color='w',
---> 17 node_size=50, alpha=0.8)
18
19 nx.draw_networkx_edges(G,pos,
/Users/sudou/.pyenv/versions/anaconda-2.0.1/lib/python2.7/site-packages/networkx/drawing/nx_pylab.pyc in draw_networkx_nodes(G, pos, nodelist, node_size, node_color, node_shape, alpha, cmap, vmin, vmax, ax, linewidths, label, **kwds)
384 xy = numpy.asarray([pos[v] for v in nodelist])
385 except KeyError as e:
--> 386 raise nx.NetworkXError('Node %s has no position.'%e)
387 except ValueError:
388 raise nx.NetworkXError('Bad value in node positions.')
NetworkXError: Node 8 has no position.
nx.cubical_graph() has 8 nodes. Namely [0,1,2,3,4,5,6,7]. You have sent a nodelist that includes node 8 which is not part of your graph. The graph you are drawing is the graph of a cube - 8 nodes (the vertices) and 12 edges (the edges of the cube).
You're asking it to plot 9 nodes of an 8 node graph. Thus it cannot find where that node goes.
Responding to your question on how to plot more than 8 nodes in a graph, you have to start with a graph with more than 8 nodes.