반응형
Visualization: e(연결선 시각화의 모든 것):editing
Python에서 plot을 하다 보면 가끔 연결선을 시각화해야하는 경우가 있다.
바로 다음과 같다.
- Network를 그릴 때
- Hierarchy를 표현할 때
- diagram 또는 관계도를 그릴 때
- 그외에 기타 등등
이런 경우에 시각화를 하다보면 맘에 드는 경우도 있고, 마음에 들지 않는 경우도 있다.
나는 첫 번째와 두 번째 경우가 모두 해당되는 경우였다. Louvain Method
를 통해 군집에 대한 Hierarchy structure
를 얻었고, 이를 시각화하기 위해 graphviz_layout
의 prog='dot'
옵션을 사용해서 다음과 같은 이미지를 얻었는데, graphviz_layout
에는 우선 두 가지 문제점이 있었다.
position
에 node size가 고려되지 않아 겹치는 부분이 발생했다.- 상위 노드들이 너무 가운데 몰려있어서 군집 구조가 한눈에 들어오지 않는다.
이를 해결하기 위해 두 가지를 고려해야 한다.
graphviz_layout(prog='dot')
에서 밀집된 node를 펼쳐야 한다.- 이 때 군집별로 domain_wall 같은 것이 있으면 더욱 좋을 것 같다.
먼저 graphviz_layout
으로 얻었던 pos
를 기반으로 가장 node가 많은 층을 중심으로, 위로 올라가면서 상위노드의 위치를 하위노드의 평균 위치로 설정해주는 코드를 작성했다.
최종 결과물은 다음과 같다.
Ref
Plot
fig = plt.figure(figsize=(24,8), dpi=200)
plt.title(f'Food clustering with Louvain Algorithm, THRESHOLD_N={THRESHOLD_N}')
nodes = G.nodes()
sizes = dict(G.nodes('size'))
colors = dict(G.nodes('dm_rate'))
# ec = nx.draw_networkx_edges(G, pos, alpha=0.4, arrows=False,
# width=.5, edge_color='k', style='solid', arrowsize=5,
# connectionstyle="angle3,angleA=90,angleB=0")
for edge in edges:
_from = pos2[edge[0]]
_to = pos2[edge[1]]
plt.annotate("",
xy=_from, xycoords='data',
xytext=_to, textcoords='data',
arrowprops=dict(arrowstyle="<-", color="0.5",
shrinkA=5, shrinkB=5,
patchA=None, patchB=None,
connectionstyle="arc,angleA=90,angleB=-90,armA=40,armB=40,rad=10",
# connectionstyle="arc,angleA=0,angleB=0,armA=20,armB=0,rad=0",
),
zorder=-10
)
nc = nx.draw_networkx_nodes(G, pos2, nodelist=nodes,
# node_color = [v/100. for v in range(len(list(G.nodes)))],
node_color = [v for v in colors.values()],
# with_labels=False,
# node_size=[v*100 for v in range(len(list(G.nodes)))]
node_size=[v for v in sizes.values()],
# alpha = [min(v/100., 1.) for v in sizes.values()],
label=list(G.nodes),
cmap=plt.cm.turbo, vmin = 0, vmax =.2,)
# nx.draw_networkx_labels(G, pos, font_size = [12 - 2*int(x[2]) for x in list(G.nodes)])
for node, (x, y) in pos2.items():
# plt.text(x, y, f'{node.split(":")[-1]}', fontsize=12 - 2*int(node[2]), ha='center', va='center')
plt.text(x, y, f'{node}', fontsize=12 - 2*int(node[2]), ha='center', va='center')
# plt.edgecolor('none')
nc.set_edgecolor('none')
plt.colorbar(nc, label='DM prevalence')
# plt.clim(0,1)
# plt.savefig('nx_test.png')
# plt.xlim(left=-20,right=7100)
plt.show()
Get position
d_depth = 55
min_gap = 70
gap_ratio = .8
max_depth = 6
THRESHOLD_N = 30
SIZE_CONST = .7
WEIGHT_CONST = 1
THRESHOLD_N = 30
new_pos = {}
G = nx.DiGraph()
G.add_node("lv0:0", size=6127*SIZE_CONST, dm_rate = 0.127795)
edges = []
for stt_lv in range(6):
# stt_lv = 0
col_from = f'lv{stt_lv}'
col_to = f'lv{stt_lv+1}'
_from = df2[col_from]
_to = df2[col_to]
cnt = Counter([(f'{col_from}:{_from[i]}', f'{col_to}:{_to[i]}') for i in df.index])
edge_list = cnt.most_common(1000000)
dm_rate = df2.groupby(col_to)['DM'].mean()
i = 0
others = 0
for (f, t), n in edge_list:
if n > THRESHOLD_N:
G.add_node(t, size=n*SIZE_CONST, dm_rate = dm_rate[i])
G.add_edge(f, t, weight=n*WEIGHT_CONST)
edges.append([f, t])
i+=1
pos=graphviz_layout(G, prog='dot')
# Set Max_lv (main lavel)
_edges, _x, _df = get_mainlv_pos(edges, max_lv, min_gap, gap_ratio)
for i, edge in enumerate(_edges):
new_pos[edge[1]] = (_x[i], d_depth*(max_depth - max_lv))
# Upper nodes
for lv in np.arange(max_lv)[::-1]:
_edges, _x, _df = get_upperlv_pos(_df, edges, lv, min_gap, gap_ratio)
for i, edge in enumerate(_edges):
new_pos[edge[1]] = (_x[i], d_depth*(max_depth - lv))
# Mother node
lv = -1
_temp = _df.groupby('head').mean()['x_shift']
new_pos[_temp.index[0]] = (_temp.values[0], d_depth*(max_depth - lv))
# bottom nodes
# Get new pos
pos2 = pos.copy()
for node in new_pos.keys():
pos2[node] = new_pos[node]
Related Function
def get_edges(edges, _from, option = 'all'):
if option == 'from':
return [e1 for e1, e2 in edges if (e1[2] == f'{_from}') & (e2[2] == f'{_from+1}')]
elif option == 'to':
return [e2 for e1, e2 in edges if (e1[2] == f'{_from}') & (e2[2] == f'{_from+1}')]
elif option == 'all':
return [(e1, e2) for e1, e2 in edges if (e1[2] == f'{_from}') & (e2[2] == f'{_from+1}')]
##### Def max_lv pos ####
def get_mainlv_pos(edges, lv, min_gap = 60 , gap_ratio = .2):
'''
min_gap = 80 #min_size +20
gap_gr = min_gap*gap_ratio
'''
# Set Params
gap_gr = min_gap*gap_ratio
_edges = get_edges(edges, lv)
_edges = np.array(_edges)
xs = []
for edge in _edges:
x = pos[edge[1]]
xs.append(x[0])
df = pd.DataFrame()
df['edge'] = get_edges(edges, lv)
df['head'] = _edges[:,0]
df['x'] = xs
df = df.sort_values('x')
_edges = df['edge'].values
# Get new pos with domain-wall
_xs = []
_head_bf = 0
_x = min_gap
for i in range(len(df)):
_head = int(df['head'].values[i][-1])
if _head_bf != _head:
_head_bf = _head
_x+=gap_gr
_xs.append(_x)
_x+=min_gap
# df['x_shift'] = np.arange(len(df))*min_gap + min_gap
df['x_shift'] = _xs
return _edges, _xs, df
def get_upperlv_pos(df, edges, lv, min_gap = 80 , gap_ratio = .4):
# Set Params
gap_gr = min_gap*gap_ratio
df_gr = df.groupby('head').mean().sort_values('x_shift')
_edges = get_edges(edges, lv)
_edges = np.array(_edges)
xs = []
for edge in _edges:
x = pos[edge[1]]
xs.append(x[0])
df = pd.DataFrame()
df['edge'] = get_edges(edges, lv)
df['head'] = _edges[:,0]
df['offs'] = _edges[:,1]
df['x'] = xs
df = df.sort_values('x')
_edges = df['edge'].values
_xs = []
for offs in df['offs'].values:
try:
_xs.append(df_gr.loc[offs]['x_shift'])
except:
_xs.append(-1) #do not have any offspring
# Fix pos which do not have any offspring
x_new = -1
for i, x in enumerate(_xs):
if x < 0:
_xs[i] = x_new
else:
x_new = x
x_new += min_gap
df['x_shift'] = _xs
return _edges, _xs, df
반응형
'Programing Language > Python' 카테고리의 다른 글
jupyter lab initial setting: password setting issue (0) | 2021.09.14 |
---|---|
[pandas] pd.DataFrame.to_csv()를 excel에서 열었을 때 한글 깨짐 (0) | 2021.08.25 |
[Archiving] 파이썬 문법 꿀팁 100선 (0) | 2021.05.11 |
[JupyterLab] 필수 업데이트!!! 3.0.12 관련사항 (0) | 2021.03.24 |
[python] PDF, CDF, CCDF 그리기 with log binning (0) | 2021.03.05 |