source: mds-and-trees/mds_plot.py @ 598

Last change on this file since 598 was 598, checked in by oriona, 8 years ago

MDS algorithm changed to cmdscale. Coordinates export to file added.

File size: 5.6 KB
RevLine 
[565]1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
[596]3
[565]4import sys
5import numpy as np
6from sklearn import manifold
[596]7
8#to make it work in console, http://stackoverflow.com/questions/2801882/generating-a-png-with-matplotlib-when-display-is-undefined
9#import matplotlib
10#matplotlib.use('Agg')
11
[565]12import matplotlib.pyplot as plt
13from mpl_toolkits.mplot3d import Axes3D
14from matplotlib import cm
15import argparse
16
[596]17
[598]18#http://www.nervouscomputer.com/hfs/cmdscale-in-python/
19def cmdscale(D):
20    """                                                                                       
21    Classical multidimensional scaling (MDS)                                                 
22                                                                                               
23    Parameters                                                                               
24    ----------                                                                               
25    D : (n, n) array                                                                         
26        Symmetric distance matrix.                                                           
27                                                                                               
28    Returns                                                                                   
29    -------                                                                                   
30    Y : (n, p) array                                                                         
31        Configuration matrix. Each column represents a dimension. Only the                   
32        p dimensions corresponding to positive eigenvalues of B are returned.                 
33        Note that each dimension is only determined up to an overall sign,                   
34        corresponding to a reflection.                                                       
35                                                                                               
36    e : (n,) array                                                                           
37        Eigenvalues of B.                                                                     
38                                                                                               
39    """
40    # Number of points                                                                       
41    n = len(D)
42 
43    # Centering matrix                                                                       
44    H = np.eye(n) - np.ones((n, n))/n
45 
46    # YY^T                                                                                   
47    B = -H.dot(D**2).dot(H)/2
48 
49    # Diagonalize                                                                             
50    evals, evecs = np.linalg.eigh(B)
51 
52    # Sort by eigenvalue in descending order                                                 
53    idx   = np.argsort(evals)[::-1]
54    evals = evals[idx]
55    evecs = evecs[:,idx]
56 
57    # Compute the coordinates using positive-eigenvalued components only                     
58    w, = np.where(evals > 0)
59    L  = np.diag(np.sqrt(evals[w]))
60    V  = evecs[:,w]
61    Y  = V.dot(L)
62 
63    return Y, evals
[596]64
[565]65def rand_jitter(arr):
66        stdev = arr.max() / 100.
67        return arr + np.random.randn(len(arr)) * stdev * 2
68
69
70def read_file(fname, separator):
71        distances = np.genfromtxt(fname, delimiter=separator)
72        if np.isnan(distances[0][len(distances[0])-1]):#separator after the last element in row
73                distances = np.array([row[:-1] for row in distances])
74        return distances
75
76
77def compute_mds(distance_matrix, dim):
[598]78        embed, evals = cmdscale(distance_matrix)
79        variances = compute_variances(embed)
80        embed = np.asarray([embed[:,i] for i in range(dim)]).T
81
82        percent_variances = [sum(variances[:i+1])/sum(variances) for i in range(dim)]
83        for i,pv in enumerate(percent_variances):
84                print(i+1,"dimension:",pv)
85
[565]86        return embed
87
88
89def compute_variances(embed):
90        variances = []
91        for i in range(len(embed[0])):
92                variances.append(np.var(embed[:,i]))
[598]93        return variances
[565]94
95
96def plot(coordinates, dimensions, jitter=0, outname=""):
97        fig = plt.figure()
98
99        if dimensions < 3:
100                ax = fig.add_subplot(111)
101        else:
102                ax = fig.add_subplot(111, projection='3d')
103
104        add_jitter = lambda tab : rand_jitter(tab) if jitter==1 else tab
105
106        x_dim = len(coordinates[0])
107        y_dim = len(coordinates)
108
109        ax.scatter(*[add_jitter(coordinates[:, i]) for i in range(x_dim)], alpha=0.5)
110
111        plt.title('Phenotypes distances')
112        plt.tight_layout()
113        plt.axis('tight')
114
115        if outname == "":
116                plt.show()
117
118        else:
119                plt.savefig(outname+".pdf")
[598]120                np.savetxt(outname+".csv", coordinates, delimiter=";")
[565]121
122
[597]123def main(filename, dimensions=3, outname="", jitter=0, separator='\t'):
[598]124        dimensions = int(dimensions)
[565]125        distances = read_file(filename, separator)
126        embed = compute_mds(distances, dimensions)
127
128        if dimensions == 1:
129                embed = np.array([np.insert(e, 0, 0, axis=0) for e in embed])
130       
[597]131        plot(embed, dimensions, jitter, outname)
[565]132
133
134if __name__ == '__main__':
135        parser = argparse.ArgumentParser()
136        parser.add_argument('--in', dest='input', required=True, help='input file with dissimilarity matrix')
137        parser.add_argument('--out', dest='output', required=False, help='output file name without extension')
138        parser.add_argument('--dim', required=False, help='number of dimensions of the new space')
139        parser.add_argument('--sep', required=False, help='separator of the source file')
140        parser.add_argument('--j', required=False, help='for j=1 random jitter is added to the plot')
141
142        args = parser.parse_args()
143        set_value = lambda value, default : default if value == None else value
144        main(args.input, set_value(args.dim, 3), set_value(args.output, ""), set_value(args.j, 0), set_value(args.sep, "\t"))
Note: See TracBrowser for help on using the repository browser.