Deleting nodes while plotting 3D network using mayavi - python-3.x

I've a graph network created using Networkx and plotted using Mayavi.
After the graph is created, I 'm deleting nodes with degree < 2, using G.remove_nodes_from(). Once the nodes are deleted, the edges connected to these nodes are deleted but the nodes still appear in the final output (image below).
import matplotlib.pyplot as plt
from mayavi import mlab
import numpy as np
import pandas as pd
pos = [[0.1, 2, 0.3], [40, 0.5, -10],
[0.1, -40, 0.3], [-49, 0.1, 2],
[10.3, 0.3, 0.4], [-109, 0.3, 0.4]]
pos = pd.DataFrame(pos, columns=['x', 'y', 'z'])
ed_ls = [(x, y) for x, y in zip(range(0, 5), range(1, 6))]
G = nx.Graph()
G.add_edges_from(ed_ls)
remove = [node for node, degree in dict(G.degree()).items() if degree < 2]
G.remove_nodes_from(remove)
pos.drop(pos.index[remove], inplace=True)
print(G.edges)
nx.draw(G)
plt.show()
mlab.figure(1, bgcolor=bgcolor)
mlab.clf()
for i, e in enumerate(G.edges()):
# ----------------------------------------------------------------------------
# the x,y, and z co-ordinates are here
pts = mlab.points3d(pos['x'], pos['y'], pos['z'],
scale_mode='none',
scale_factor=1)
# ----------------------------------------------------------------------------
pts.mlab_source.dataset.lines = np.array(G.edges())
tube = mlab.pipeline.tube(pts, tube_radius=edge_size)
mlab.pipeline.surface(tube, color=edge_color)
mlab.show() # interactive window
I'd like to ask for suggestions on how to remove the deleted nodes and the corresponding positions and display the rest in the output.
Secondly, I would like to know how to delete the nodes and the edges connected to these nodes interactively. For instance, if I want to delete nodes and edges connected to nodes of degree < 2, first I would like to display an interactive graph with all nodes with degree < 2 highlighted. The user can select the nodes that have to be deleted in an interactive manner. By clicking on a highlighted node, the node and connect edge can be deleted.
EDIT:
I tried to remove the positions of the deleted nodes from the dataframe pos by including pos.drop(pos.index[remove], inplace=True) updated in the complete code posted above.
But I still don't get the correct output.

Here is a solution for interactive removal of network nodes and edges in Mayavi
(I think matplotlib might be sufficient and easier but anyways...).
The solution is inspired by this Mayavi example.
However, the example is not directly transferable because a glyph (used to visualize the nodes) consists of many points and when plotting
each glyph/node by itself, the point_id cannot be used to identify the glyph/node. Moreover, it does not include the option to
hide/delete objects. To avoid these problems, I used four ideas:
Each node/edge is plotted as a separate object, so it is easier to adjust it's (visibility) properties.
Instead of deleting nodes/edges, they are just hidden when clicked upon.
Moreover, clicking twice makes the node visible again
(this does not work for the edges with the code below but you might be able to implement that if required,
just needs keeping track of visible nodes).
The visible nodes can be collected at the end (see code below).
As in the example, the mouse position is captured using a picker callback.
But instead of using the point_id of the closest point, it's coordinates are used directly.
The node to be deleted/hidden is found by computing the minimum Euclidean distance between the mouse position and all nodes.
PS: In your original code, the for-loop is quite redundant because it plots all nodes and edges many times on top of each other.
Hope that helps!
# import modules
from mayavi import mlab
import numpy as np
import pandas as pd
import networkx as nx
# set number of nodes
number = 6
# create random node positions
np.random.seed(5)
pos = 100*np.random.rand(6, 3)
pos = pd.DataFrame(pos, columns=['x', 'y', 'z'])
# create chain graph links
links = [(x, y) for x, y in zip(range(0, number-1), range(1, number))]
# create graph (not strictly needed, link list above would be enough)
graph = nx.Graph()
graph.add_edges_from(links)
# setup mayavi figure
figure = mlab.gcf()
mlab.clf()
# add nodes as individual glyphs
# store glyphs in dictionary to allow interactive adjustments of visibility
color = (0.5, 0.0, 0.5)
nodes = dict()
texts = dict()
for ni, n in enumerate(graph.nodes()):
xyz = pos.loc[n]
n = mlab.points3d(xyz['x'], xyz['y'], xyz['z'], scale_factor=5, color=color)
label = 'node %s' % ni
t = mlab.text3d(xyz['x'], xyz['y'], xyz['z']+5, label, scale=(5, 5, 5))
# each glyph consists of many points
# arr = n.glyph.glyph_source.glyph_source.output.points.to_array()
nodes[ni] = n
texts[ni] = t
# add edges as individual tubes
edges = dict()
for ei, e in enumerate(graph.edges()):
xyz = pos.loc[np.array(e)]
edges[ei] = mlab.plot3d(xyz['x'], xyz['y'], xyz['z'], tube_radius=1, color=color)
# define picker callback for figure interaction
def picker_callback(picker):
# get coordinates of mouse click position
cen = picker.pick_position
# compute Euclidean distance btween mouse position and all nodes
dist = np.linalg.norm(pos-cen, axis=1)
# get closest node
ni = np.argmin(dist)
# hide/show node and text
n = nodes[ni]
n.visible = not n.visible
t = texts[ni]
t.visible = not t.visible
# hide/show edges
# must be adjusted if double-clicking should hide/show both nodes and edges in a reasonable way
for ei, edge in enumerate(graph.edges()):
if ni in edge:
e = edges[ei]
e.visible = not e.visible
# add picker callback
picker = figure.on_mouse_pick(picker_callback)
picker.tolerance = 0.01
# show interactive window
# mlab.show()
# collect visibility/deletion status of nodes, e.g.
# [(0, True), (1, False), (2, True), (3, True), (4, True), (5, True)]
[(key, node.visible) for key, node in nodes.items()]

Related

Retaining specific nodes while using k-core in Networkx

I've a random graph created using Netwrokx and I want to delete nodes with degree less than 2 except for 2 user-defined nodes that have degree = 1. To remove all nodes with degree < 2, I could use Networkx's k-core. But I am not sure how to retain the 2 user-defined nodes. For example, the following code generates,
import networkx as nx
import matplotlib.pyplot as plt
# fig 1
G = nx.gnm_random_graph(n=20, m=30, seed=1)
nx.draw(G, with_labels=True, pos=nx.spring_layout(G))
plt.show()
G = nx.k_core(G, k=2)
nx.draw(G, with_labels=True, pos=nx.spring_layout(G))
plt.show()
Figure 1:
Figure 2:
I would like to ask for suggestions on how to retain 2 user-defined nodes:
e.g
retain_node_ids = [1,2]
EDIT:
I could use remove_nodes_from as suggested below. But if we delete nodes with degree < 2 we may end up with new nodes, which originally had degree >=2, with degree <2. To repeat the process until no nodes with degree < 2 is found, k-core has been used.
Here is how you can do it:
degrees = nx.classes.degree(G)
G.remove_nodes_from([node
for node in G.nodes
if node not in retain_node_ids and degrees[node] <= 2])
Of course this piece of code does not find a maximal subgraph (as k_core function does): it simply remove all nodes with degree less than or equal to 2, and which are not in the retain_node_ids list.
EDIT:
You can add two fake nodes, connect nodes to retain to them, compute the k-core and then get rid of them:
G.add_edges_from([(u, v) for u in retain_node_ids for v in (n, n+1)])
G = nx.k_core(G, k=2)
G.remove_nodes_from([n, n+1])

Control marker properties in seaborn pairwise boxplot

I'm trying to plot a boxplot for two different datasets on the same plot. The x axis are the hours in a day, while the y axis goes from 0 to 1 (let's call it Efficiency). I would like to have different markers for the means of each dataset' boxes. I use the 'meanprops' for seaborn but that changes the marker style for both datasets at the same time. I've added 2000 lines of data in the excel that can be downloaded here. The values might not coincide with the ones in the picture but should be enough.
Basically I want the red squares to be blue on the orange boxplot, and red on the blue boxplot. Here is what I managed to do so far:
I tried changing the meanprops by using a dictionary with the labels as keys , but it seems to be entering a loop (in PyCharm is says Evaluating...)
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
#make sure you have your path sorted out
group1 = pd.read_excel('group1.xls')
ax,fig = plt.subplots(figsize = (20,10))
#does not work
#ax = sns.boxplot(data=group1, x='hour', y='M1_eff', hue='labels',showfliers=False, showmeans=True,\
# meanprops={"marker":{'7':"s",'8':'s'},"markerfacecolor":{'7':"white",'8':'white'},
#"markeredgecolor":{'7':"blue",'8':'red'})
#works but produces similar markers
ax = sns.boxplot(data=group1, x='hour', y='M1_eff', hue='labels',showfliers=False, showmeans=True,\
meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"blue"})
plt.legend(title='Groups', loc=2, bbox_to_anchor=(1, 1),borderaxespad=0.5)
# Add transparency to colors
for patch in ax.artists:
r, g, b, a = patch.get_facecolor()
patch.set_facecolor((r, g, b, .4))
ax.set_xlabel("Hours",fontsize=14)
ax.set_ylabel("M1 Efficiency",fontsize=14)
ax.tick_params(labelsize=10)
plt.show()
I also tried the FacetGrid but to no avail (Stops at 'Evaluating...'):
g = sns.FacetGrid(group1, col="M1_eff", hue="labels",hue_kws=dict(marker=["^", "v"]))
g = (g.map(plt.boxplot, "hour", "M1_eff")
.add_legend())
g.show()
Any help is appreciated!
I don't think you can do this using sns.boxplot() directly. I think you'll have to draw the means "by hand"
N=100
df = pd.DataFrame({'hour':np.random.randint(0,3,size=(N,)),
'M1_eff': np.random.random(size=(N,)),
'labels':np.random.choice([7,8],size=(N,))})
x_col = 'hour'
y_col = 'M1_eff'
hue_col = 'labels'
width = 0.8
hue_order=[7,8]
marker_colors = ['red','blue']
# get the offsets used by boxplot when hue-nesting is used
# https://github.com/mwaskom/seaborn/blob/c73055b2a9d9830c6fbbace07127c370389d04dd/seaborn/categorical.py#L367
n_levels = len(hue_order)
each_width = width / n_levels
offsets = np.linspace(0, width - each_width, n_levels)
offsets -= offsets.mean()
fig, ax = plt.subplots()
ax = sns.boxplot(data=df, x=x_col, y=y_col, hue=hue_col, hue_order=hue_order, showfliers=False, showmeans=False)
means = df.groupby([hue_col,x_col])[y_col].mean()
for (gr,temp),o,c in zip(means.groupby(level=0),offsets,marker_colors):
ax.plot(np.arange(temp.values.size)+o, temp.values, 's', c=c)

Matplotlib: coloring violinplots with multiple colors depending on modalities

I'm searching how to achieve a plot with multiple violinplot of the measures of an response from a dataset with two modalities (time and treatment).
I succeeded to plot this violinplot but cannot succeed to color each treatment responses with a specific color. I search for a simple stuff: I d'ont want to color edges, etc.
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy
Lcolor = ['red','green','blue'] # targeted colors per treatment
## create a dataset with 2 columns of two modalities (remark: not the purpose of the question)
a = numpy.random.randn(60,1) # create the random variable "measure"
# create the modalities
Ltime = [1,3]
Ltreatment = [0.2,0.6,0.8]
modalities = [[0.2,1], [0.2,3], [0.6, 1], [0.6, 3], [0.8, 1], [0.8,3]]
tempList = []
for i in modalities:
tempList.extend([i]*10)
NpModLis = numpy.array(tempList)
# create a list of violinplot positions
position = [1.2,1.6, 1.8, 3.2, 3.6, 3.8]
# merging into a 2d-array of modalities and the random variable
DATA = numpy.c_[NpModLis,a]
## Dataset is made of first column: treatment, second column: time of mesure, third column: response measure
# Now I want to plot with violin plot for each time, the three violinplots due to treatment, each violinplot with a color specific to treatment
Lcolors = ['red','green','blue'] # respectively fro treatment 0.2, 0.6, 0.8
fig, ax = plt.subplots(figsize=(9, 4))
data=[]
for i in range(len(Ltime)):
j=Ltime[i]
for k in Ltreatment:
data.append(DATA[numpy.logical_and(DATA[:,0]==k,DATA[:,1]==j)][:,2])
ax.violinplot(data, position) # which isd violinplot(measure of one treatment at one time, position)
plt.show()
thanks for helping and commented answers to understand ^^
violinplot() returns a dictionary with the artists that were created, so you can modify their properties. Here is something that seems to get you the desired ouput
out = ax.violinplot(data, position) # which isd violinplot(measure of one treatment at one time, position)
for b,c in zip(out['bodies'],itertools.islice(itertools.cycle(Lcolors), 0, len(out['bodies']))):
b.set_facecolor(c)
However, I would suggest that you use seaborn's violingplot instead. You would have to refactor the format of your data, but it would simplify the plotting part.

Color nodes by networkx

I am generating a network topology diagram through the data in a csv file where s0..s2 and c1..c3 are nodes of the diagram.
network.csv:
source,port,destination
s1,1,c3
s2,1,c1
s0,1,c2
s1,2,s2
s2,2,s0
I need to make all the source to be blue and destinations to be green.
How can I do it without overriding the source nodes?
Following is a working solution:
import csv
import networkx as nx
from matplotlib import pyplot as plt
with open('../resources/network.csv') as csvfile:
reader = csv.DictReader(csvfile)
edges = {(row['source'], row['destination']) for row in reader }
print(edges) # {('s1', 'c3'), ('s1', 's2'), ('s0', 'c2'), ('s2', 's0'), ('s2', 'c1')}
G = nx.DiGraph()
source_nodes = set([edge[0] for edge in edges])
G.add_edges_from(edges)
for n in G.nodes():
G.nodes[n]['color'] = 'b' if n in source_nodes else 'g'
pos = nx.spring_layout(G)
colors = [node[1]['color'] for node in G.nodes(data=True)]
nx.draw_networkx(G, pos, with_labels=True, node_color=colors)
plt.show()
We first read the csv to an edge list, which is later used for the construction of G. For well defining the colors we set each source node with blue and the rest of the nodes as green (i.e., all destination nodes that also not source nodes).
We also use nx.draw_networkx to get a more compact implementation for drawing the graph.
The result should be something like:

Multiple heatmaps with fixed grid size

I am using seaborn(v.0.7.1) together with matplotlib(1.5.1) and pandas (v.0.18.1) to plot different clusters of data of different sizes as heat maps within a for loop as shown in the following code.
My issue is that since each cluster contains different number of rows, the final figures are of different sizes (i.e. the height and width of each box in the heat map is different across different heat maps)(see figures). Eventually, I would like to have figures of the same size (as explained above).
I have checked some parts of seabornand matplotlib documentations as well as stackoverflowbut since I do not know what the exact keywords are to look for (as evident in the question title itself) I have not been able to find any answer. [EDIT: Now I have updated the title based on a suggestion from #ImportanceOfBeingErnest. Previously the title was read: "Enforcing the same width across multiple plots".]
import numpy as np
import pandas as pd
clusters = pd.DataFrame([(1,'aaaaaaaaaaaaaaaaa'),(1,'b'), (1,'c'), (1,'d'), (2,'e'), (2,'f')])
clusters.columns = ['c', 'p']
clusters.set_index('c', inplace=True)
g = pd.DataFrame(np.ones((6,4)))
c= pd.DataFrame([(1,'aaaaaaaaaaaaaaaaa'),(2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f')])
c.columns = ['i', 'R']
for i in range(1,3,1):
ee = clusters[clusters.index==i].p
inds = []
for v in ee:
inds.append(np.where(c.R.values == v)[0][0])
f, ax = plt.subplots(1, figsize=(13, 15))
ax = sns.heatmap(g.iloc[inds], square=True, ax=ax, cbar=True, linewidths=2, linecolor='k', cmap="Reds", cbar_kws={"shrink": .5},
vmin = math.floor(g.values.min()), vmax =math.ceil(g.values.max()))
null = ax.set_xticklabels(['a', 'b', 'c', 'd'], fontsize=15)
null = ax.set_yticklabels(c.R.values[inds][::-1], fontsize=15, rotation=0)
plt.tight_layout(pad=3)
[EDIT]: Now I have added some code to create a minimal, functional example as suggested by #Brian. Now I have noticed that the issue might have been caused by the text!
Under the following conditions
If only the squares in the saved images should have the same size and we don't care about the plot on screen and
We can omit the colorbar
the solution is rather straight forward.
One would define the size that one square should have in the final image squaresize = 50, find out the number of squares to draw in each dimension (n, m) and adjust the figure size as
figwidth = m*squaresize/float(dpi)
figheight = n*squaresize/float(dpi)
where dpi denotes the pixels per inch.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
dpi=100
squaresize = 50 # pixels
n = 3
m = 4
data = np.random.rand(n,m)
figwidth = m*squaresize/float(dpi)
figheight = n*squaresize/float(dpi)
f, ax = plt.subplots(1, figsize=(figwidth, figheight), dpi=dpi)
f.subplots_adjust(left=0, right=1, bottom=0, top=1)
ax = sns.heatmap(data, square=True, ax=ax, cbar=False)
plt.savefig(__file__+".png", dpi=dpi, bbox_inches="tight")
The bbox_inches="tight" makes sure that the labels etc. are still drawn (i.e. the final figure size will be larger than the one calculated here, depending on how much space the labels need).
To apply this example to your case you'd still need to find out how many rows and columns you have in the heatmap depending on the dataframe, but as I don't have it's structure, it's hard to provide a general solution.

Resources