networkx의 layout 정리하기

네트워크를 다양한 레이아웃에 따라서 2차원에 그리기.

  • matplotlib에서 scatter, plot등으로 그림을 그릴 때는 이미 axis에서 어떤 좌표에 그림을 그리면 될지가 명확하게 나와 있습니다만, network에서는 개별 node에 좌표값이 없기 때문에 어디에 그려야 하는지 약간 어렵습니다.
  • 뭐 한 두개라면 대충 그리겠는데, 사실 node의 수도 엄청 많음은 물론, 해당 네트워크의 특징이 잘 보이도록 보여져야 하니까요.
  • 그래서 복잡한 네트워크에서 각 노드의 좌표를 잘 배치해서 네트워크를 예쁘게 보이게 하는 방법 또한 다양한 알고리즘으로 나오고 있습니다.

임의로 node위치 배정하기

  • node의 x, y 좌표만 알려주면 edge는 알아서 잘 배치됩니다. 사실 당연한 이야기죠.
  • 이를 증명하려고 이상한 짓을 했는데요. 영문 알파벳 QnA를 네트워크로 표현해보려고 했습니다.
  • 똑같이 노드를 추가하고 엣지를 추가한 다음 pos = {'Q0':[0, 0]}의 식으로 node의 이름을 key로, node의 x, y 좌표를 tuple이나 리스트로 value로 넘겨줍니다.
  • 이렇게 넘겨주고, 그림을 그릴 때, nx.draw_networkx_nodes(QnA, pos) 로 position을 넘겨주면 해당 포지션에 맞춰서 2차원에 잘 그려지게 됩니다.

  • 아래는 제가 임의로 노드 포지션을 만들어서 넘겨준 것입니다.
""" QnA를 네트워크로 그려줌 """
import networkx as nx

QnA = nx.Graph()
## Q
QnA.add_nodes_from(["Q{}".format(i) for i in range(0, 8)])
QnA.add_edges_from([("Q{}".format(i), "Q{}".format(i+1) ) for i in range(0, 7)]), QnA.add_edge('Q0', 'Q7')
QnA.add_nodes_from(['Q8', 'Q9']), QnA.add_edge('Q8', 'Q9') 

## A
QnA.add_nodes_from(["A{}".format(i) for i in range(0, 5)])
QnA.add_edges_from([('A0', 'A1'), ('A0', 'A2'), ('A2', 'A1'), ('A3', 'A1'), ('A2', 'A4')])

## n
QnA.add_nodes_from(['n{}'.format(i) for i in range(0, 6)])
QnA.add_edges_from([('n0', 'n1'), ('n1', 'n2'), ('n1', 'n3'), ('n3', 'n4'), ('n4', 'n5')])

## network의 좌표는 다음처럼 key=> node name, value=> (x, y) 로 넘겨주면 됨 
## 각 node의 좌표를 아래처럼 다 지정해주고 
pos = {'Q0':[0, 0], 'Q1':[1, 0], 'Q2':[1.5, -0.5], 'Q3':[1.5, -2.5], 
       'Q4':[1, -3], 'Q5':[0, -3], 'Q6':[-0.5, -2.5], 'Q7':[-0.5, -0.5],
       'Q8':[0.75, -2], 'Q9':[1.7, -3.3],
       'n0':[3.5, -1.5], 'n1':[3.5, -2.0], 'n2':[3.5, -3], 'n3':[4.0, -1.5], 'n4':[4.5, -2.0], 'n5':[4.5, -3], 
       'A0':[7.5, 0], 'A1': [7.0, -1.5], 'A2': [8.0, -1.5], 'A3':[6.5, -3], 'A4':[8.5, -3], 
      }

plt.figure(figsize=(10, 6))
plt.ylim(-4.0, 1.0)
## 그림을 그릴 때, pos로 넘겨주면 끝남. 
nx.draw_networkx_nodes(QnA, pos, node_size=400, alpha=1.0, 
                       node_shape='o', node_color='red')
nx.draw_networkx_edges(QnA, pos, width=10, alpha=0.8, edge_color='crimson')
plt.axis('off')
plt.tight_layout()
plt.savefig('../../assets/images/markdown_img/180807_qna.svg')
plt.show()

networkx의 layout 함수 이용하기

  • 여기서는 networkx에 있는 레이아웃만 보여주도록 하겠습니다.
  • 일단 적절한 네트워크를 하나 만듭니다. 임의의 complete_graph를 만든 다음에, edge를 임의로 몇 개 지워줍니다.
  • 그냥 complete_graph를 그대로 그릴 경우에는 layout별 차이가 잘 보이지 않아요. 약하게 연결된 부분, 밀집된 부분 등 그리려는 네트워크가 다양한 특성을 가지고 있어야 이 부분을 좀 더 명확하게 그릴 수 있습니다.
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np 

G = nx.complete_graph(15)
for i in range(0, 150):
    ## 지울 node 쌍을 고르고 
    n1 = list(G.nodes())[np.random.randint(0, len(G.nodes()))]
    n2 = list(G.nodes())[np.random.randint(0, len(G.nodes()))]
    try:## 문제가 없다면 edge를 지움 
        G.remove_edge(n1, n2)
    except:
        continue
  • 자 이제 그림을 그려줍니다. 일단 해당 네트워크로 부터 노드의 좌표를 도출해주는 layout을 만들어야 하는데요 networkx에서는 다음과 같은 총 6가지의 레이아웃을 지원합니다. 간단하게, 만든 네트워크를 그대로 넘겨주면 끝나죠.
layouts = {'spring': nx.spring_layout(G), 
           'spectral':nx.spectral_layout(G), 
           'shell':nx.shell_layout(G), 
           'circular':nx.circular_layout(G),
           'kamada_kawai':nx.kamada_kawai_layout(G), 
           'random':nx.random_layout(G)
          }
  • 그리고 아래처럼 그림을 그려줍니다.
f, axes = plt.subplots(3, 2)
f.set_size_inches((12, 18)) 
## layout 설정 
layouts = {'spring': nx.spring_layout(G), 
           'spectral':nx.spectral_layout(G), 
           'shell':nx.shell_layout(G), 
           'circular':nx.circular_layout(G),
           'kamada_kawai':nx.kamada_kawai_layout(G), 
           'random':nx.random_layout(G)
          }
## 각 axis마다 그림을 따로 그
려줌
for i, kv in enumerate(layouts.items()):
    title, pos, ax = kv[0], kv[1], axes[i//2][i%2]
    nx.draw_networkx(G, kv[1], ax=ax)
    ax.set_title("{} layout".format(title), fontsize=20)
    ax.axis('off')
plt.tight_layout()
plt.savefig('../../assets/images/markdown_img/180807_nx_layout_comp.svg')
plt.show()
  • 결과는 다음과 같습니다. 그냥 앞으로는 spring_layout이나, kamada_kawai_layout만 쓰면 될 것 같습니다.

wrap-up

  • circular layout과 shell layout은 같아 보이는데, 왜 두 개가 따로 있을까요?

raw code

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

## 네트워크 생성 
G = nx.complete_graph(15)
for i in range(0, 150):
    n1 = list(G.nodes())[np.random.randint(0, len(G.nodes()))]
    n2 = list(G.nodes())[np.random.randint(0, len(G.nodes()))]
    try:
        G.remove_edge(n1, n2)
    except:
        continue
## 그림 그리기 시작 
f, axes = plt.subplots(3, 2)
f.set_size_inches((12, 18)) 
## layout 설정 
layouts = {'spring': nx.spring_layout(G), 
           'spectral':nx.spectral_layout(G), 
           'shell':nx.shell_layout(G), 
           'fruchterman_reingold':nx.layout.fruchterman_reingold_layout(G), 
           'kamada_kawai':nx.kamada_kawai_layout(G), 
           'random':nx.random_layout(G)
          }
## 각 axis마다 그림을 따로 그려줌
for i, kv in enumerate(layouts.items()):
    title, pos, ax = kv[0], kv[1], axes[i//2][i%2]
    nx.draw_networkx(G, kv[1], ax=ax)
    ax.set_title("{} layout".format(title), fontsize=20)
    ax.axis('off')
plt.tight_layout()
plt.savefig('../../assets/images/markdown_img/180807_nx_layout_comp.svg')
plt.show()
  • 찾아보니 몇 개 layout이 더있네요.

댓글남기기