paulbrodersen / netgraph

Publication-quality network visualisations in python
GNU General Public License v3.0
660 stars 39 forks source link

Unable to use Community layout for Multi-Component graph? #75

Closed a-arbabian closed 11 months ago

a-arbabian commented 1 year ago

Hello,

I have a multi component graph where connections between nodes, even within the same community, are not guaranteed. I have color coded the nodes based on community, but cannot get the community layout to run with my graph, getting the following error:

netgraph/_node_layout.py:1549: UserWarning: Graph contains a single community. Unable to compute a community layout. Computing spring layout instead.

After some quick debugging I saw that get_community_layout() infers the nodes from the edges. I also saw some past issues about the community layout needing edges within communities (and also between communities if I'm not mistaken?). I tried a workaround where I added invisible edges between nodes within the same community, but that did not solve the error.

Maybe my use-case is not meant for the community layout, but I'm basically trying to visually cluster the nodes which are in the same community (and thus same color) regardless of connectivity within the communities. For example, even if a community has two clusters between which there are no edges, I want the two clusters to plot next to each other.

I am using version 4.12.11 and plotting a networkx.DiGraph for reference.

paulbrodersen commented 12 months ago

That is just a warning, not an error. Presumably, you have some unconnected nodes in your example, and for those nodes (and those nodes only), the position is not determined using the community layout but the spring layout. Does the visualisation look correct?

a-arbabian commented 11 months ago

I see, I didn't realize the warning was per node, not for the entire layout. The visualization is fine except for multi-component communities. As you can see from the image, if one or more nodes are disconnected from the rest of the community, they are not plotted together.

I can think of a workaround where I check for disconnected nodes within a community, and add invisible edges to them. Do you think it's worth covering this use-case and/or the implementation would be doable? I'm not super familiar with the algorithm for the community layout so I can't tell.

Screenshot 2023-08-08 at 1 14 00 PM
paulbrodersen commented 11 months ago

I am back from my vacation, so I should be more responsive now.

The community layout was designed for graphs with a modular structure, i.e. graphs with densely connected subgraphs (communities), but sparse connectivity between nodes from different communities. In your example visualisation, you just seem to have a lot of components in your graph but not a lot of structure within each component, so I am not sure this is the right layout for the job.

Most node layout routines in Netgraph use a two-step process:

  1. Partition the canvas into as many parts as there are components.
  2. Compute the layout for each component separately, and then arrange the nodes on the pre-assigned partition of the canvas.

If you just want to visually group certain collections of nodes, you can essentially use the same process. However, in step 1, you use your custom node collections instead of the component structure of the graph.

import networkx as nx
import matplotlib.pyplot as plt

from netgraph import Graph
from netgraph._node_layout import _get_packed_component_bboxes

# Create a graph with several subgraphs. Subgraph 1 consists of multiple components.
subgraph_1 = nx.complete_graph(5)
subgraph_1.add_nodes_from(range(5, 10))
subgraph_2 = nx.complete_graph(3)
subgraph_3 = nx.complete_graph(3)
subgraph_4 = nx.complete_graph(3)
subgraph_5 = nx.complete_graph(3)
g = nx.disjoint_union_all([subgraph_1, subgraph_2, subgraph_3, subgraph_4, subgraph_5])

# Plot default layout, with nodes arranged into components.
fig, (ax1, ax2) = plt.subplots(1, 2)
origin = (0, 0)
scale = (2, 1)
Graph(g, node_layout='circular', origin=origin, scale=scale, ax=ax1)
ax1.set_title("Default arrangement")

# Define custom node partitions.
partitions = [
    list(range(10)),     # subgraph 1
    list(range(10, 13)), # subgraph 2
    list(range(13, 16)), # subgraph 3
    list(range(16, 19)), # subgraph 4
    list(range(19, 22)), # subgraph 5
]

# Arrange custom node partitions w.r.t. each other.
bboxes = _get_packed_component_bboxes(partitions, origin=origin, scale=scale)

# Plot custom node arrangement.
for nodes, bbox in zip(partitions, bboxes):
    subgraph = nx.subgraph(g, nodes)
    Graph(subgraph, node_layout='circular', origin=bbox[:2], scale=bbox[2:], ax=ax2)
ax2.set_title("Custom arrangement")

plt.show()

Figure_1

paulbrodersen commented 11 months ago

Closing the issue for now but feel free to re-open if you have further questions w.r.t. this issue or my suggestions.