# Visual Analytics Course

graphpca_class

## Visual Analytics Course

### PCA (Principal Components Analysis)

This python code is meant to be used with the ''PCA'' class. You provide it a graph and a list of properties form which a data matrix is computed. The matrix can then be passed on to the PCA class to compute a 2D embedding for the graph. You would typically use it as:

gPCA = GraphPCA(graph, ['propertyName1', ...]

pca = PCA(gPCA.dataMatrix)
gPCA.layout(pca)

# the drawAxis method is used to double-check the final result
gPCA.drawAxis()

GraphPCA.py
from tulip import *
from numpy import *
from PCA import *

class GraphPCA(object):
'''
This is a utilitary class to the PCA class.
It builds a matrix data from several graph (double) properties.
The matrix can then be handled to the PCA class to perform PCA analysis.

Note: all properties used by the class must have been pre-computed.
'''

def __init__(self, graph, propertyNameList, compute = False):
'''
Constructor
'''
self.graph = graph
self.viewLayout = self.graph.getLayoutProperty('viewLayout')
m = []
self.propertyNameList = propertyNameList
self.propertyList = []
for i in range(len(self.propertyNameList)):
self.propertyList.append(self.graph.getDoubleProperty(self.propertyNameList[i]))
if compute:
'''
will only compute if property has not been computed already
this might be the case when using user defined properties
'''
self.graph.computeDoubleProperty(self.propertyNameList[i], self.propertyList[i])
for n in self.graph.getNodes():
nVect = []
for p in self.propertyList:
nVect.append(p.getNodeValue(n))
m.append(nVect)
self.dataMatrix = array(m)

def layout(self, pca):
pca.computePrincipalComponents()
coords = pca.computeDataProjection()
xCoords = coords[0]
yCoords = coords[1]
i = 0
for node in self.graph.getNodes():
cNode = self.viewLayout[node]
cNode.setX(xCoords[i])
cNode.setY(yCoords[i])
self.viewLayout[node] = cNode
i += 1

def drawAxis(self, pca):
'''
Need to call layout first so the self.pca variable is initialized
'''
self.viewLayout[n1] = tlp.Coord(1,0,0)
self.viewLayout[n2] = tlp.Coord(-1,0,0)
self.viewLayout[n3] = tlp.Coord(0,1,0)
self.viewLayout[n4] = tlp.Coord(0,-1,0)
color = self.graph.getColorProperty('viewColor')
color[n1] = tlp.Color(0,255,0)
color[n2] = tlp.Color(0,255,0)
color[n3] = tlp.Color(0,255,0)
color[n4] = tlp.Color(0,255,0)
size = self.graph.getSizeProperty('viewSize')
size[n1] = tlp.Size(0.01,0.01,0.01)
size[n2] = tlp.Size(0.01,0.01,0.01)
size[n3] = tlp.Size(0.01,0.01,0.01)
size[n4] = tlp.Size(0.01,0.01,0.01)
return True

def layoutFromProperties(self, propId1, propId2):
'''
Handy. This method will lay out nodes of a graph using
two properites as x, y coordinates
'''
for n in self.graph.getNodes():
c = viewLayout.getNodeValue(n)
c.setX(self.propertyList[propId1].getNodeValue(n))
c.setY(self.propertyList[propId2].getNodeValue(n))
self.viewLayout.setNodeValue(n, c)

def totalSquaredDistances(self):
'''
computes the sum of squared distances of points to a given line
the line is implicitly defined by two selected nodes
although they are part of the view, those nodes are not part
of the point cloud and should be treated as outer items

first compute the unit vector along the given line
then compute the sum of squared dot products of other points (vectors)
with the unit vector
also, we assume the viewLayout is in 2D, with a null 3rd coordinates
(makes it possible to rely on tlp.Coord dot product)
'''
viewSelection = self.graph.getBooleanProperty('viewSelection')
selectedNodes = []
for n in self.graph.getNodes():
if viewSelection.getNodeValue(n):
selectedNodes.append(n)
node1 = selectedNodes[0]
node2 = selectedNodes[1]

c1 = self.viewLayout.getNodeValue(node1)
c2 = self.viewLayout.getNodeValue(node2)
m = (c2.getY() - c1.getY()) / (c2.getX() - c1.getX())
length = sqrt(1 + m * m)
u = tlp.Coord(1.0 / length, m / length, 0.0)

sqDist = 0.0
for n in self.graph.getNodes():
if not n.id == node1.id and not n.id == node2.id:
c = self.viewLayout.getNodeValue(n)
sqDist += u.dotProduct(c) * u.dotProduct(c)
return sqDist