Closed lcastri closed 1 year ago
Agreed, that looks terrible.
Is there a way to set the edge style in order to get the graph as in the first image?
There isn't but your example convinced me that there probably should be. The parallel edges really don't work very well if the edges are labelled. I don't have much time today or Friday but I will look into it next week.
Also, that kink in the middle of both edges is really unexpected. Could you make a MWE that reproduces the figure?
I just want to check that you are not setting some parameter to a weird value (k
?).
My attempt at reproducing your plot yields no kink:
#!/usr/bin/env python
"""
Issue 52
"""
import matplotlib.pyplot as plt
from netgraph import Graph
edges = [
(0, 1),
(1, 0),
(0, 2),
(2, 0)
]
Graph(edges, node_labels=True, edge_labels=True, edge_label_position=0.33, edge_layout='curved', arrows=True)
plt.show()
I am using this fnc to generate the graph. Attached you can find the result.pkl to load and input to the fnc
def dag(result,
alpha = None,
min_width = 1,
max_width = 5,
node_color = 'orange',
edge_color = 'grey',
font_size = 12,
show_edge_labels = True,
save_name = None):
"""
build a dag
Args:
result (dict): result from pcmci
alpha (float): significance level. Defaults to None
min_width (int, optional): minimum linewidth. Defaults to 1.
max_width (int, optional): maximum linewidth. Defaults to 5.
node_color (str, optional): node color. Defaults to 'orange'.
edge_color (str, optional): edge color. Defaults to 'grey'.
font_size (int, optional): font size. Defaults to 12.
show_edge_labels (bool, optional): bit to show the time-lag label of the dependency on the edge. Defaults to True.
save_name (str, optional): Filename path. If None, plot is shown and not saved. Defaults to None.
"""
if alpha is not None: result['graph'] = __apply_alpha(result, alpha)
res = __PCMCIres_converter(result)
G = nx.DiGraph()
# add nodes
G.add_nodes_from(res.keys())
border = {t: __scale(s[SCORE], min_width, max_width) for t in res.keys() for s in res[t] if t == s[SOURCE]}
if show_edge_labels:
# node_label = {t: round(s[SCORE], 3) for t in res.keys() for s in res[t] if t == s[SOURCE]}
node_label = {t: s[LAG] for t in res.keys() for s in res[t] if t == s[SOURCE]}
else:
node_label = None
# edges definition
edges = [(s[SOURCE], t) for t in res.keys() for s in res[t] if t != s[SOURCE]]
G.add_edges_from(edges)
edge_width = {(s[SOURCE], t): __scale(s[SCORE], min_width, max_width) for t in res.keys() for s in res[t] if t != s[SOURCE]}
if show_edge_labels:
# edge_label = {(s[SOURCE], t): round(s[SCORE], 3) for t in res.keys() for s in res[t] if t != s[SOURCE]}
edge_label = {(s[SOURCE], t): s[LAG] for t in res.keys() for s in res[t] if t != s[SOURCE]}
else:
edge_label = None
fig, ax = plt.subplots(figsize=(8,6))
a = Graph(G,
node_layout = 'circular',
node_size = 10,
node_color = node_color,
node_labels = node_label,
node_edge_width = border,
node_label_fontdict = dict(size=font_size),
node_edge_color = edge_color,
node_label_offset = 0.15,
node_alpha = 1,
arrows = True,
edge_layout = 'curved',
edge_label = show_edge_labels,
edge_labels = edge_label,
edge_label_fontdict = dict(size=font_size),
edge_color = edge_color,
edge_width = edge_width,
edge_alpha = 1,
edge_zorder = 1,
edge_label_position = 0.35)
nx.draw_networkx_labels(G,
pos = a.node_positions,
labels = {n: n for n in G},
font_size = font_size)
if save_name is not None:
plt.savefig(save_name, dpi = 300)
else:
plt.show()
if __name__ == '__main__':
file_path = "result.pkl"
with open(file_path, 'rb') as f:
result = pickle.load(f)
dag(result, alpha = result['alpha'], save_name = 'dag.png', font_size=18)
Sorry I didn't notice there were other fnc to attach
def __scale(score, min_width, max_width, min_score = 0, max_score = 1):
"""
Scales the score of the cause-effect relationship strength to a linewitdth
Args:
score (float): score to scale
min_width (float): minimum linewidth
max_width (float): maximum linewidth
min_score (int, optional): minimum score range. Defaults to 0.
max_score (int, optional): maximum score range. Defaults to 1.
Returns:
float: scaled score
"""
return ((score-min_score)/(max_score-min_score))*(max_width-min_width)+min_width
def __convert_to_string_graph(graph_bool):
"""Converts the 0,1-based graph returned by PCMCI to a string array
with links '-->'.
Parameters
----------
graph_bool : array
0,1-based graph array output by PCMCI.
Returns
-------
graph : array
graph as string array with links '-->'.
"""
graph = np.zeros(graph_bool.shape, dtype='<U3')
graph[:] = ""
# Lagged links
graph[:,:,1:][graph_bool[:,:,1:]==1] = "-->"
# Unoriented contemporaneous links
graph[:,:,0][np.logical_and(graph_bool[:,:,0]==1,
graph_bool[:,:,0].T==1)] = "o-o"
# Conflicting contemporaneous links
graph[:,:,0][np.logical_and(graph_bool[:,:,0]==2,
graph_bool[:,:,0].T==2)] = "x-x"
# Directed contemporaneous links
for (i,j) in zip(*np.where(
np.logical_and(graph_bool[:,:,0]==1, graph_bool[:,:,0].T==0))):
graph[i,j,0] = "-->"
graph[j,i,0] = "<--"
return graph
def __apply_alpha(result, alpha):
"""
Applies alpha threshold to the pcmci result
Args:
result (dict): pcmci result
alpha (float): significance level
Returns:
ndarray: graph filtered by alpha
"""
mask = np.ones(result['p_matrix'].shape, dtype='bool')
# Set all p-values of absent links to 1.
result['p_matrix'][mask==False] == 1.
# Threshold p_matrix to get graph
graph_bool = result['p_matrix'] <= alpha
# Convert to string graph representation
graph = __convert_to_string_graph(graph_bool)
return graph
def __PCMCIres_converter(result):
"""
Re-elaborates the PCMCI result in a new dictionary
Args:
result (dict): pcmci result
Returns:
dict: pcmci result re-elaborated
"""
res_dict = {f:list() for f in result['var_names']}
N, lags = result['graph'][0].shape
for s in range(len(result['graph'])):
for t in range(N):
for lag in range(lags):
if result['graph'][s][t,lag] == '-->':
res_dict[result['var_names'][t]].append({SOURCE : result['var_names'][s],
SCORE : result['val_matrix'][s][t,lag],
PVAL : result['p_matrix'][s][t,lag],
LAG : lag})
return res_dict
with all of them it should run
What version of netgraph are you using?
Some progress. For whatever reason, using very large nodes induces the kinks. What the heck?
I am using the 4.9.3 version
Alright, I have added a flag that allows toggling off the bundling of bi-directional edges. Still working on the issue with the kinks but there may be no easy fix for that as my first experiments suggest that those are simply the correct result given the physics simulation in the Fruchterman-Reingold simulation for optimizing edge control point positions. In the meantime, I suggest you use smaller nodes.
import matplotlib.pyplot as plt
from netgraph import Graph
edges = [
(0, 1),
(1, 0),
(0, 2),
(2, 0)
]
fig, axes = plt.subplots(1, 2)
Graph(edges,
node_size=5,
node_labels=True,
edge_label_position=0.33,
node_layout='circular',
edge_layout='curved',
edge_layout_kwargs=dict(bundle_parallel_edges=False, k=0.05),
arrows=True,
ax=axes[0])
Graph(edges,
node_size=10,
node_labels=True,
edge_label_position=0.33,
node_layout='circular',
edge_layout='curved',
edge_layout_kwargs=dict(bundle_parallel_edges=False, k=0.05),
arrows=True,
ax=axes[1])
plt.show()
Thanks a lot! So the flag is the following:
edge_layout_kwargs=dict(bundle_parallel_edges=False, k=0.05),
Correct, specifically the bundle_parallel_edges=False
bit. I set k = 0.05
as it straightens the edges a little bit which helps with the kinks.
Closing the issue for now as the kinks (1) only seem to appear under fairly special circumstances (giant nodes), and (2) seem to be valid solutions of the edge layout algorithm.
Hi Paul,
thanks for the fix. I think this fix creates (again) another issue that was already solved here #48
Nodes = ['$X_0$', '$X_1$', '$X_2$', '$X_3$', '$X_4$', '$X_5$']
Edges = [('$X_1$', '$X_0$'), ('$X_1$', '$X_2$'), ('$X_2$', '$X_0$'), ('$X_2$', '$X_1$'), ('$X_5$', '$X_4$')])
Graph(G,
node_layout = 'dot',
node_size = 8,
node_alpha = 1,
arrows = True,
edge_layout = 'curved',
edge_layout_kwargs = dict(bundle_parallel_edges = False, k = 0.05))
Output
Some given node positions are not within the data range specified by `origin` and `scale`!
Origin : 0.0, 0.0
Scale : 0.6763301515504611, 1.0
The following nodes do not fall within this range:
60f9710e-a859-4ee3-8223-f842172fad6b : [0.7994626 0.47216302]
18737562-f6e3-44e6-a77c-6e4aa6553518 : [0.7994626 0.35412226]
f4d83eff-1845-4f0b-bb19-411250848aac : [0.7994626 0.23608151]
2817e2c1-af1d-4f6c-8285-bb2792f0b16a : [0.7994626 0.11804075]
$X_5$ : [0.79946319 0.59020377]
$X_4$ : [ 0.79946319 -0. ]
$X_3$ : [0.71410453 0.83050847]
This error can occur if the graph contains multiple components but some or all node positions are initialised explicitly (i.e. node_positions != None).
If instead, I set bundle_parallel_edges = True the graph is plotted without errors.
Luca
Thanks for the heads up! I will look into it later this week.
I re-applied the previous fix. No idea why I thought it no longer was necessary. Setting parallel_edges = False
now also works in combination with a dot
node layout and multiple components.
Hi,
using an old version of the software I generated this graph:![atc_hg](https://user-images.githubusercontent.com/87081572/196777837-fdea1cb6-3ceb-4a6d-9296-ea997a677cbb.png)
now I updated the software and I am trying to generate the same graph, but now the edge style is different:![dag](https://user-images.githubusercontent.com/87081572/196778434-d861efa6-0d83-4c80-81a3-14598113ad17.png)
Is there a way to set the edge style in order to get the graph as in the first image?
Thanks