Python - Plot Node Hierarchy using iGraph - python

I have a dataset with staff and their job roles, and each job role is assigned a code: 0 for top-management, 1 for middle-management, and 2 for general staff. I now want to plot these roles using a hierarchical graph, so that all code 0 staff are on the top, 1 in the middle, and 2 at the bottom. I've found the layout in iGraph to do this (see below), however don't know how to control which nodes appear where. Is there a parameter that I'm missing to control this? Any help would be appreciated.
CSV:
https://github.com/Laurie-Bamber/Enron_Corpus/blob/master/15Below_60Employees_1.csv
Role Codes:
https://github.com/Laurie-Bamber/Enron_Corpus/blob/master/Dict_role_code.csv
GML:
https://github.com/Laurie-Bamber/Enron_Corpus/blob/master/15Below_60Employees_1.gml
P.S. edges refer to emails between staff, not measures of hierarchy.
Code:
G = Graph.Read_GML('Test.gml')
visual_style['layout'] = G.layout_reingold_tilford()
plot(G, **visual_style)

I am proposing a solution with a slight modification to what you asked for. If you plot the levels vertically and the people at a role level horizontally, there are many people at one level so the labels run into each other. Instead, I am plotting the role levels horizontally and the individuals at a level are spread out vertically, leaving plenty of room to see the labels.
I do not think that there is a pre-built layout function that does what you are asking. However, it is not very hard to make your own layout. The essential part of doing that is to assign x-y coordinates where you want the nodes to be plotted. After that, you can just use the Layout function to convert the coordinates into a layout object.
My scheme for assigning x-y coordinates will be that the x coordinate will be the role level ( 1,2, or 3). I will just assign y-coordinates by making each node at a role level one higher than the previous node at that level. I use a small dictionary to keep track of what height comes next for each of the levels.
I will use the file names of the files that you provided and will assume that these files are in the current working directory.
import csv
from igraph import *
## Load graph
G = Graph.Read_GML('15Below_60Employees_1.gml')
## Load role levels
reader = csv.reader(open('Dict_role_code.csv'))
dx = dict(reader)
## Create a layout
height = { '1':0, '2':0, '3':0 }
COORD = []
for L in G.vs['label']:
height[dx[L]] = height[dx[L]] + 1
COORD.append((float(dx[L]), height[dx[L]]))
LO = Layout(COORD)
## Create the style
visual_style = {}
visual_style['vertex_size'] = 8
visual_style['vertex_frame_color'] = 'orange'
visual_style['layout'] = LO
visual_style['margin'] = 60
visual_style['edge_color'] = '#00000044'
plot(G, **visual_style)
I think that this provides you with a good starting place. You can tweak the placement from here.

Related

How to display graph in Pyvis more clearly?

I want to visualize a graph in Pyvis which its nodes has labels. I am completely able to visualize it in Pyvis but my problem is about the ways of visualizing it. The graph displayed in Pyvis is not clear and edges are messed up. Is there any way to visualize the graph more clear?
The image below shows the graph.
For example in the graph, node 15 is displayed well. I want other nodes to be displayed in a clear way that the connections can be displayed more clearly
Update:
This is the code i use for drawing graph using Pyvis:
def showGraph(FileName, labelList):
Txtfile = open("./results.txt")
G = nx.read_weighted_edgelist(Txtfile)
Txtfile.close()
palette = (sns.color_palette("Pastel1", n_colors=len(set(labelList.values()))))
palette = palette.as_hex()
colorDict = {}
counter = 0
for i in palette:
colorDict[counter] = i
counter += 1
N = Network(height='100%', width='100%', directed=False, notebook=False)
for n in G.nodes:
N.add_node(n, color=(colorDict[labelList[n]]), size=5)
for e in G.edges.data():
N.add_edge(e[0], e[1], title=str(e[2]), value=e[2]['weight'])
N.show('result.html')
results.txt is my edge list file and labelList holds label of each node. Labels are numerical. For example label of node 48 is 5, it can be anything. I use labels to give different colors to nodes.
The NetworkX circular layouts tend to make individual nodes and the connections between them easier to see, so you could try that as long as you don't want nodes to move (without dragging) after you've drawn them.
Before creating your pyvis network, run the following on your NetworkX graph to create a dictionary that will be keyed by node and have (x, y) positions as values. You might need to mess around with the scale parameter a bit to see what works best for you.
pos = nx.circular_layout(G, scale = 1000)
You can then add x and y values from pos to your pyvis network when you add each node. Adding physics = False keeps the nodes in one place unless you click and drag them around.
for n in G.nodes:
N.add_node(n,
color=(colorDict[labelList[n]]),
size=5,
x = pos[n][0],
y = pos[n][1],
physics = False)
I'm not sure how the edge weights will play into things, so you should probably also add physics = False to the add_edge parameters to ensure that nothing will move.
Since I didn't have your original data, I just generated a random graph with 10 nodes and this was the result in pyvis.

Holoviews: how to customize histogram for linked time series Curve plots

I am just getting started with Holoviews. My questions are on customizing histograms, but also I am sharing a complete example as it may be helpful for other newbies to look at, since the documentation for Holoviews is very thorough but can be overwhelming.
I have a number of time series in text files loaded as Pandas DataFrames where:
each file is for a specific location
at each location about 10 time series were collected, each with about 15,000 points
I am building a small interactive tool where a Selector can be used to choose the location / DataFrame, and then another Selector to pick 3 of 10 of the time series to be plotted together.
My goal is to allow linked zooms (both x and y scales). The questions and code will focus on this aspect of the tool.
I cannot share the actual data I am using, unfortunately, as it is proprietary, but I have created 3 random walks with specific data ranges that are consistent with the actual data.
## preliminaries ##
import pandas as pd
import numpy as np
import holoviews as hv
from holoviews.util.transform import dim
from holoviews.selection import link_selections
from holoviews import opts
from holoviews.operation.datashader import shade, rasterize
import hvplot.pandas
hv.extension('bokeh', width=100)
## create random walks (one location) ##
data_df = pd.DataFrame()
npoints=15000
np.random.seed(71)
x = np.arange(npoints)
y1 = 1300+2.5*np.random.randn(npoints).cumsum()
y2 = 1500+2*np.random.randn(npoints).cumsum()
y3 = 3+np.random.randn(npoints).cumsum()
data_df.loc[:,'x'] = x
data_df.loc[:,'rand1'] = y1
data_df.loc[:,'rand2'] = y2
data_df.loc[:,'rand3'] = y3
This first block is just to plot the data and show how, by design, one of the random walks have different range from the other two:
data_df.hvplot(x='x',y=['rand1','rand2','rand3'],value_label='y',width=800,height=400)
As a result, although hvplot subplots work out of the box (for linking), ranges are different so the scaling is not quite there:
data_df.hvplot(x='x',y=['rand1','rand2','rand3'],
value_label='y',subplots=True,width=800,height=200).cols(1)
So, my first attempt was to adapt the Python-based Points example from Linked brushing in the documentation:
colors = hv.Cycle('Category10').values
dims = ['rand1', 'rand2', 'rand3']
layout = hv.Layout([
hv.Points(data_df, dim).opts(color=c)
for c, dim in zip(colors, [['x', d] for d in dims])
])
link_selections(layout).opts(opts.Points(width=1200, height=300)).cols(1)
That is already an amazing result for a 20 minutes effort!
However, what I would really like is to plot a curve rather than points, and also see a histogram, so I adapted the comprehension syntax to work with Curve (after reading the documentation pages Applying customization, and Composing elements):
colors = hv.Cycle('Category10').values
dims = ['rand1', 'rand2', 'rand3']
layout = hv.Layout([hv.Curve(data_df,'x',dim).opts(height=300,width=1200,
color=c).hist(dim) for c,
dim in zip(colors,[d for d in dims])])
link_selections(layout).cols(1)
Which is almost exactly what I want. But I still struggle with the different layers of opts syntax.
Question 1: with the comprehension from the last code block, how would I make the histogram share color with the curves?
Now, suppose I want to rasterize the plots (although I do not think is quite yet necessary with 15,000 points like in this case), I tried to adapt the first example with Points:
cmaps = ['Blues', 'Greens', 'Reds']
dims = ['rand1', 'rand2', 'rand3']
layout = hv.Layout([
shade(rasterize(hv.Points(data_df, dims),
cmap=c)).opts(width=1200, height = 400).hist(dims[1])
for c, dims in zip(cmaps, [['x', d] for d in dims])
])
link_selections(layout).cols(1)
This is a decent start, but again I struggle with the options/customization.
Question 2: in the above cod block, how would I pass the colormaps (it does not work as it is now), and how do I make the histogram reflect data values as in the previous case (and also have the right colormap)?
Thank you!
Sander answered how to color the histogram, but for the other question about coloring the datashaded plot, Datashader renders your data with a colormap rather than a single color, so the parameter is named cmap rather than color. So you were correct to use cmap in the datashaded case, but (a) cmap is actually a parameter to shade (which does the colormapping of the output of rasterize), and (b) you don't really need shade, as you can let Bokeh do the colormapping in most cases nowadays, in which case cmap is an option rather than an argument. Example:
from bokeh.palettes import Blues, Greens, Reds
cmaps = [Blues[256][200:], Greens[256][200:], Reds[256][200:]]
dims = ['rand1', 'rand2', 'rand3']
layout = hv.Layout([
rasterize(hv.Points(data_df, ds)).opts(cmap=c,width=1200, height = 400).hist(dims[1])
for c, ds in zip(cmaps, [['x', d] for d in dims])
])
link_selections(layout).cols(1)
To answer your first question to make the histogram share the color of the curve, I've added .opts(opts.Histogram(color=c)) to your code.
When you have a layout you can specify the options of an element inside the layout like that.
colors = hv.Cycle('Category10').values
dims = ['rand1', 'rand2', 'rand3']
layout = hv.Layout(
[hv.Curve(data_df,'x',dim)
.opts(height=300,width=600, color=c)
.hist(dim)
.opts(opts.Histogram(color=c))
for c, dim in zip(colors,[d for d in dims])]
)
link_selections(layout).cols(1)

Custom legend position with python-pptx

I would like to set the legend on a self defined, custom position.
My final goal would be to get the settings of an already existing chart and use the same settings for a new chart.
I read in the docs it's possible to set the legend like this:
(http://python-pptx.readthedocs.io/en/latest/api/enum/XlLegendPosition.html#xllegendposition)
from pptx.enum.chart import XL_LEGEND_POSITION
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.CUSTOM
But I get a ValueError:
ValueError: CUSTOM (-4161) not a member of XL_LEGEND_POSITION enumeration
Did I miss anything or how can I set the legend on a custom position?
The .CUSTOM member of the XL_LEGEND_POSITION is a reporting member only (roughly like "read-only"). It is intended as the value of the Legend.position property when the legend has been manually adjusted (dragged and dropped with the mouse using the UI). Unlike the other members of that enumeration, it is not "assignable" and could not by itself of course set the position to where you wanted it.
Custom placement of the legend is not yet supported by the python-pptx API. If you wanted to do it you'd have to manipulate the underlying XML with low-level lxml calls. You'd need to understand the relevant XML schema and semantics to know what to do with that XML to produce the result you were after. This sort of thing is commonly called a "workaround function" in python-pptx and python-docx (they work very similarly being based on the same architecture). A Google search on "python-pptx" OR "python-docx" workaround function will find you some examples used for other purposes that you may find helpful if you decide to take that approach.
I couldn't find a fully formed answer to this, so I thought it would be worth posting the workaround that I used:
from pptx.oxml.xmlchemy import OxmlElement
def SubElement(parent, tagname, **kwargs):
element = OxmlElement(tagname)
element.attrib.update(kwargs)
parent.append(element)
return element
def manuallySetLegendPosition(
chart,
x,
y,
w,
h
):
## Inside layout, add manualLayout
L = chart.legend._element.get_or_add_layout()
mL = L.get_or_add_manualLayout()
## Add xMode and yMode and set vals to edge
xM = SubElement(mL, 'c:xMode', val="edge")
xY = SubElement(mL, 'c:yMode', val="edge")
## Add x, value is between -1 and 1 as a proportion of the chart width
## point of reference on the legend is its centre, not top left
xE = SubElement(mL, 'c:x', val=str(x))
## Add y, same concept as above
yE = SubElement(mL, 'c:y', val=str(y))
## Add w, legend height as a proportion of chart height
wE = SubElement(mL, 'c:w', val=str(w))
## Add h, same concept as above
hE = SubElement(mL, 'c:h', val=str(h))

Python Igraph community cluster colors

I want to have clusters in different colours after community infomap, but problem is when I deleted single nodes it makes a mess an each node is different color or everything is red. How to do that in python?
Code:
E = ig.Graph(edges)
E.vs\['label'\] = labels
degree = 0
community = E.community_infomap()
cg = community.graph
singletons = cg.vs.select(_degree = 0)
cg.delete_vertices(singletons)
color_list =['red','blue','green','cyan','pink','orange','grey','yellow','white','black','purple' ]
ig.plot(cg)
It is not clear how did you try to assign colors to vertices. You should be aware that igraph reindexes vertices and edges upon deletion and addition of either vertices or edges. This reindexing should be considered unpredictable, the only things we know that indices go from 0 to n-1 at all time, and attributes remain assigned to the correct vertex or edge. Considering these, you can either do the deletion before or after the community detection, only you need to assign colors to a vertex attribute:
import igraph
g = igraph.Graph.Barabasi(n = 20, m = 1)
i = g.community_infomap()
pal = igraph.drawing.colors.ClusterColoringPalette(len(i))
g.vs['color'] = pal.get_many(i.membership)
igraph.plot(g)
Now let's see what happens if we delete a vertex:
colors_original = pal.get_many(i.membership)
g.delete_vertices([7])
# the clustering object is still the same length
# (so it is not valid any more, you can't be sure
# if the i.th vertex in the clustering is the
# i.th one in the graph)
len(i) # 20
# while the graph has less vertices
g.vcount() # 19
# if we plot this with the original colors, indeed we get a mess:
igraph.plot(g, vertex_color = colors_original)
But the colors in the g.vs['color'] vertex attribute are still correct, they show the clusters, only the deleted vertex is missing (from the dark blue cluster):
igraph.plot(g,
vertex_color = g.vs['color']) # this is automatic, I am
# writing here explicitely only to be clear
I found solution. First delete single nodes than convert to igraph and do community.

maya python iterating a big number of vertex

I am writing a script in python for maya to swap vertex position from one side to another.
Since I want the flipping to be topology based I am using the topological symmetry selection tool to find the vertex correspondence.
I managed to do that using filterExpand and xform.
The problem is that it is quite slow on a large poly count mesh and I was wondering how this could be done using openMaya instead.
import maya.cmds as cmds
def flipMesh():
sel=cmds.ls(sl=1)
axis={'x':0,'y':1,'z':2}
reverse=[1.0,1.0,1.0]
#quring the axtive symmetry axis
activeaxis=cmds.symmetricModelling(q=1, axis=1)
reverse[axis[activeaxis]]=-1.0
#getting the vertex count
verts=cmds.polyEvaluate(v=1)
#selecting all vertex
cmds.select(sel[0]+'.vtx[0:'+str(verts)+']')
#getting all the positive vertex
posit=cmds.filterExpand(sm=31,ex=1,smp=1)
seam=cmds.filterExpand(sm=31,ex=1,sms=1)
#swapping position on the positive side with the negative side
for pos in posit:
cmds.select(pos, sym=True)
neg=cmds.filterExpand(sm=31,ex=1,smn=1)
posT=cmds.xform(pos, q=1, t=1)
negT=cmds.xform(neg[0], q=1, t=1)
cmds.xform(pos,t=[a*b for a,b in zip(negT,reverse)])
cmds.xform(neg[0],t=[a*b for a,b in zip(posT,reverse)])
#inverting position on the seam
for each in seam:
seamP=cmds.xform(each, q=1, t=1)
seaminvP=[a*b for a,b in zip(seamP,reverse)]
cmds.xform(each, t=(seaminvP))
cmds.select(sel)
Thanks
Maurizio
You can try out OpenMaya.MFnMesh to get and set your vertices.
Here's an example that will simply mirror all points of a selected object along their z axis:
import maya.OpenMaya as OpenMaya
# Get selected object
mSelList = OpenMaya.MSelectionList()
OpenMaya.MGlobal.getActiveSelectionList(mSelList)
sel = OpenMaya.MItSelectionList(mSelList)
path = OpenMaya.MDagPath()
sel.getDagPath(path)
# Attach to MFnMesh
MFnMesh = OpenMaya.MFnMesh(path)
# Create empty point array to store new points
newPointArray = OpenMaya.MPointArray()
for i in range( MFnMesh.numVertices() ):
# Create a point, and mirror it
newPoint = OpenMaya.MPoint()
MFnMesh.getPoint(i, newPoint)
newPoint.z = -newPoint.z
newPointArray.append(newPoint)
# Set new points to mesh all at once
MFnMesh.setPoints(newPointArray)
Instead of moving them one at at time you can use MFnMesh.setPoints to set them all at once. You'll have to adapt your logic to this, but hopefully this will help you out manipulating with Maya's api. I should also note that you would also have to resolve normals afterwards.

Categories