Node cluster NetworkX dalam formasi melingkar berdasarkan warna node

Nov 30 2020

Saya memiliki pertanyaan yang sama dengan pertanyaan ini . Solusinya berfungsi, namun, saya tidak bisa memberi jarak pada node dan membuatnya muncul dalam format melingkar dengan dataset saya. Saya memiliki total sekitar 30 node yang diberi kode warna.

Node dengan warna yang sama saling tumpang tindih alih-alih dikelompokkan dalam format melingkar / lebih konsentris.

Saya menggunakan kode dalam pertanyaan di atas, dan mencoba semua nilai jari-jari mungkin tetapi tampaknya tidak dapat membuat simpul dari kelompok warna yang sama dalam lingkaran .

Kode:

import networkx
import numpy as np
import matplotlib.pyplot as plt

nodesWithGroup = {'A':'#7a8eff', 'B': '#7a8eff', 'C': '#eb2c30', 'D':'#eb2c30', 'E': '#eb2c30', 'F':'#730a15', 'G': '#730a15'}
# Set up graph, adding nodes and edges
G = nx.Graph()
G.add_nodes_from(nodesWithGroup.keys())

# Create a dictionary mapping color to a list of nodes
nodes_by_color = {}
for k, v in nodesWithGroup.items():
    if v not in nodes_by_color:
        nodes_by_color[v] = [k]
    else:
        nodes_by_color[v].append(k)

# Create initial circular layout
pos = nx.circular_layout(RRR)

# Get list of colors
colors2 = list(nodes_by_color.keys())
# clustering
angs = np.linspace(0, 2*np.pi, 1+len(colors))
repos = []
rad = 13
for ea in angs:
    if ea > 0:
        repos.append(np.array([rad*np.cos(ea), rad*np.sin(ea)]))

for color, nodes in nodes_by_color.items():
    posx = colors.index(color)
    for node in nodes:
        pos[node] += repos[posx]


# Plot graph
fig,ax = plt.subplots(figsize=(5, 5))


# node colors
teamX = ['A', 'B']
teamY = ['C', 'D', 'E']
teamZ = ['F', 'G']

for n in G.nodes():
    if n in teamX:
        G.nodes[n]['color'] = '#7a8eff'
    elif n in teamY:
        G.nodes[n]['color'] = '#eb2c30'
    else:
        G.nodes[n]['color'] = '#730a15'


colors = [node[1]['color'] for node in G.nodes(data=True)]

# edges
zorder_edges = 3
zorder_nodes = 4
zorder_node_labels = 5


for edge in G.edges():
    source, target = edge
    rad = 0.15
    node_color_dict = dict(G.nodes(data='color'))
    if node_color_dict[source] == node_color_dict[target]:
        arrowprops=dict(lw=G.edges[(source,target)]['weight'],
                        arrowstyle="-",
                        color='blue',
                        connectionstyle=f"arc3,rad={rad}",
                        linestyle= '-',
                        alpha=0.65, zorder=zorder_edges)
        ax.annotate("",
                    xy=pos[source],
                    xytext=pos[target],
                    arrowprops=arrowprops
                   )
    else:
        arrowprops=dict(lw=G.edges[(source,target)]['weight'],
                        arrowstyle="-",
                        color='purple',
                        connectionstyle=f"arc3,rad={rad}",
                        linestyle= '-',
                        alpha=0.65,  zorder=zorder_edges)
        ax.annotate("",
                    xy=pos[source],
                    xytext=pos[target],
                    arrowprops=arrowprops
                   )

# drawing 
node_labels_dict = nx.draw_networkx_labels(G, pos, font_size=5, font_family="monospace", font_color='white', font_weight='bold')


for color, nodes in nodes_by_color.items():
    nodes_draw = nx.draw_networkx_nodes(G, pos=pos, nodelist=nodes, node_color=color, edgecolors=[(0,0,0,1)])
    nodes_draw.set_zorder(zorder_nodes)
    for node_labels_draw in node_labels_dict.values():
        node_labels_draw.set_zorder(zorder_node_labels)




plt.show()

Saya mendapatkan hasil berikut:

Output yang diinginkan (seperti dalam solusi):

Jawaban

2 PaulBrodersen Dec 07 2020 at 18:03

Seperti yang disarankan @willcrack, sedikit mengadaptasi jawaban ini bekerja dengan baik.

Anda dapat menyesuaikan node yang tumpang tindih dengan mengubah ratioparameter di partition_layout.

#!/usr/bin/env python

import numpy as np
import matplotlib.pyplot as plt
import networkx as nx


NODE_LAYOUT = nx.circular_layout
COMMUNITY_LAYOUT = nx.circular_layout


def partition_layout(g, partition, ratio=0.3):
    """
    Compute the layout for a modular graph.

    Arguments:
    ----------
    g -- networkx.Graph or networkx.DiGraph instance
        network to plot

    partition -- dict mapping node -> community or None
        Network partition, i.e. a mapping from node ID to a group ID.

    ratio: 0 < float < 1.
        Controls how tightly the nodes are clustered around their partition centroid.
        If 0, all nodes of a partition are at the centroid position.
        if 1, nodes are positioned independently of their partition centroid.

    Returns:
    --------
    pos -- dict mapping int node -> (float x, float y)
        node positions

    """

    pos_communities = _position_communities(g, partition)

    pos_nodes = _position_nodes(g, partition)
    pos_nodes = {k : ratio * v for k, v in pos_nodes.items()}

    # combine positions
    pos = dict()
    for node in g.nodes():
        pos[node] = pos_communities[node] + pos_nodes[node]

    return pos


def _position_communities(g, partition, **kwargs):

    # create a weighted graph, in which each node corresponds to a community,
    # and each edge weight to the number of edges between communities
    between_community_edges = _find_between_community_edges(g, partition)

    communities = set(partition.values())
    hypergraph = nx.DiGraph()
    hypergraph.add_nodes_from(communities)
    for (ci, cj), edges in between_community_edges.items():
        hypergraph.add_edge(ci, cj, weight=len(edges))

    # find layout for communities
    pos_communities = COMMUNITY_LAYOUT(hypergraph, **kwargs)

    # set node positions to position of community
    pos = dict()
    for node, community in partition.items():
        pos[node] = pos_communities[community]

    return pos


def _find_between_community_edges(g, partition):

    edges = dict()

    for (ni, nj) in g.edges():
        ci = partition[ni]
        cj = partition[nj]

        if ci != cj:
            try:
                edges[(ci, cj)] += [(ni, nj)]
            except KeyError:
                edges[(ci, cj)] = [(ni, nj)]

    return edges


def _position_nodes(g, partition, **kwargs):
    """
    Positions nodes within communities.
    """
    communities = dict()
    for node, community in partition.items():
        if community in communities:
            communities[community] += [node]
        else:
            communities[community] = [node]

    pos = dict()
    for community, nodes in communities.items():
        subgraph = g.subgraph(nodes)
        pos_subgraph = NODE_LAYOUT(subgraph, **kwargs)
        pos.update(pos_subgraph)

    return pos


def _layout(networkx_graph):
    edge_list = [edge for edge in networkx_graph.edges]
    node_list = [node for node in networkx_graph.nodes]

    pos = circular_layout(edge_list)

    # NB: some nodes might not be connected and hence will not be in the edge list.
    # Assuming a [0, 0, 1, 1] canvas, we assign random positions on the periphery
    # of the existing node positions.
    # We define the periphery as the region outside the circle that covers all
    # existing node positions.
    xy = list(pos.values())
    centroid = np.mean(xy, axis=0)
    delta = xy - centroid[np.newaxis, :]
    distance = np.sqrt(np.sum(delta**2, axis=1))
    radius = np.max(distance)

    connected_nodes = set(_flatten(edge_list))
    for node in node_list:
        if not (node in connected_nodes):
            pos[node] = _get_random_point_on_a_circle(centroid, radius)

    return pos


def _flatten(nested_list):
    return [item for sublist in nested_list for item in sublist]


def _get_random_point_on_a_circle(origin, radius):
    x0, y0 = origin
    random_angle = 2 * np.pi * np.random.random()
    x = x0 + radius * np.cos(random_angle)
    y = y0 + radius * np.sin(random_angle)
    return np.array([x, y])


def test():

    # create test data
    cliques = 8
    clique_size = 7
    g = nx.connected_caveman_graph(cliques, clique_size)
    partition = {ii : np.int(ii/clique_size) for ii in range(cliques * clique_size)}

    pos = partition_layout(g, partition, ratio=0.2)
    nx.draw(g, pos, node_color=list(partition.values()))
    plt.show()

def test2():

    # create test data
    cliques = 8
    clique_size = 7
    g = nx.connected_caveman_graph(cliques, clique_size)
    partition = {ii : np.int(ii/clique_size) for ii in range(cliques * clique_size)}

    # add additional between-clique edges
    total_nodes = cliques*clique_size
    for ii in range(cliques):
        start = ii*clique_size + int(clique_size/2)
        stop = (ii+cliques/2)*clique_size % total_nodes + int(clique_size/2)
        g.add_edge(start, stop)

    pos = partition_layout(g, partition, ratio=0.2)
    nx.draw(g, pos, node_color=list(partition.values()))
    plt.show()


if __name__ == '__main__':
    test()
    test2()

Tambahan

Contoh dengan tepi antar-cluster tambahan seperti yang diminta dalam komentar: