diff --git a/README.md b/README.md index 9fe32b9..39edcc1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,61 @@ graphical-pca =============== -Script Python permettant de visualiser une PCA \ No newline at end of file +Script Python permettant de visualiser une PCA + +#Command Line# + +##Help## + +|Short |Long |Description |Required | +|----------------|-----------------------|---------------------------------------|------------------------| +|-h, |--help |show this help message and exit | | +|-r RESOURCES |--resources RESOURCES |path of resources JSON file |Required | +|-comp COMPONENTS|--components COMPONENTS|number of components |Optionnal, default=3 | +|-f2d |--figure2d |show all 2D representation of PCA (GUI)|Optionnal, default=false| +|-f3d |--figure3d |show a 3D representation of PCA (GUI) |Optionnal, default=false| +|-o OUTPUT |--output OUTPUT |path of output file (record of 3D) |Optionnal, default='' | + +##Exemples## + +*Pour une visualisation 3D avec enregistrement* +``` +python pca.py --resources ./resources.default.json -f3d --output ./output.mp4 +python pca.py -r ./resources.default.json -f3d -o ./output.mp4 +``` + +*Pour une visualisation 2D* +``` +python pca.py --resources ./resources.default.json -f2d +python pca.py -r ./resources.default.json -f2d +``` + +##Structure du fichier resources## + +```json +{ + "colors": "rgb", + "classes_names": ["A", "B", "C"], + "matrix": [ + [0, 0, 0, 0], + [1, 1, 1, 1], + [2, 2, 2, 2] + ], + "targets": [0, 1, 2], + "video": { + "dpi": 600, + "frames": 360, + "fps": 30, + "interval": 20, + "writer": "mencoder" + } +} +``` + +|Key |Type |Description | +|-------------|------|----------------------------------------------| +|colors |String|Liste des couleurs utilisées (pour la légende)| +|classes_names|Array |Étiquette de chaque classe (pour la légende) | +|matrix |Array |Matrice (Liste de tous les vecteurs) | +|targets |Array |Liste de la classe associée à chaque vecteurs | +|video |Object|Options pour la vidéo | \ No newline at end of file diff --git a/graphics.py b/graphics.py new file mode 100644 index 0000000..de5fe23 --- /dev/null +++ b/graphics.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import division +from io import open +from sklearn.decomposition import PCA +from sklearn.preprocessing import scale +from mpl_toolkits.mplot3d import Axes3D +from matplotlib import animation + +import matplotlib.pyplot as plt +import matplotlib +import math +import numpy +import json +import argparse +# constantes PAR DEFAUT + +# Affichage souhaité +FIGURE_2D = False +FIGURE_3D = False + +# Fichier de sortie par defaut +OUTPUT = '' + +# Nombre de dimensions de la PCA +# (toujours inférieur au nombre max de dimensions de la matrice) +NB_COMPONENTS = 3 + +# constantes redefinies par l'utilisateur +parser = argparse.ArgumentParser(description='This script make PCA of resource matrice.json file.') +parser.add_argument('-r','--resources', help='Path of resources JSON file [Required]', type=str, required=True) +parser.add_argument('-comp','--components', help='Number of components [Optionnal, default=3]', type=int, required=False) +parser.add_argument('-f2d','--figure2d', help='Show 2D PCA (GUI) [Optionnal, default=false]', action='store_true', required=False) +parser.add_argument('-f3d','--figure3d', help='Show 3D PCA (GUI) [Optionnal, default=false]', action='store_true', required=False) +parser.add_argument('-o','--output', help='Path of output file (record of 3D) [Optionnal, default=\'\']', type=str, required=False) +args = parser.parse_args() + +if hasattr(args, 'components'): + if args.components != None: + NB_COMPONENTS = args.components +if hasattr(args, 'figure2d'): + if args.figure2d != None: + FIGURE_2D = args.figure2d +if hasattr(args, 'figure3d'): + if args.figure3d != None: + FIGURE_3D = args.figure3d +if hasattr(args, 'resources'): + if args.resources != None: + RESOURCES = json.loads(open(args.resources).read()) +if hasattr(args, 'output'): + if args.output != None: + OUTPUT = args.output + +# Liste des couleurs +# Exemple : 'rgb' => red blue green +COLORS = RESOURCES["colors"] + +# Clés de chaques classes +# Exemple : [1, 2, 3] +KEYS = range(len(COLORS)) + +# Liste des noms des classes +# Exemple : numpy.array(['A', 'B', 'C']) +# len(CLASSES_NAMES) === LEN(COLORS) +CLASSES_NAMES = numpy.array(RESOURCES["classes_names"]) + +# Matrice numpy +# Exemple : numpy.matrix([[0, 0, 0, 0], [1, 1, 1, 1], [2, 2, 2, 2], ...]) +MATRIX = numpy.matrix(RESOURCES["matrix"]) + +# Tableau qui définit la classe de chaque vecteurs +# Exemple : numpy.array([1, 2, 3]) => Le premier vecteur [0, 0, 0, 0] appartient à la classe CLASSES_NAMES[1] -> 'A' +# len(TARGET) === len(MATRIX) +TARGETS = numpy.array(RESOURCES["targets"]) # Matrice numpy Tableau de classes de chaque individu + +# PCA +data = scale(MATRIX) +pca = PCA(n_components=NB_COMPONENTS) +space = pca.fit(data) +matrix = space.transform(data) + +# Dessine chaque dimension +if FIGURE_2D: + plt.figure() + for c, j, name in zip(COLORS, KEYS, CLASSES_NAMES): + plt.scatter(matrix[TARGETS == j, 0], matrix[TARGETS == j, NB_COMPONENTS - 1], c=c, label=name) + plt.legend() + plt.title('PCA of my dataset axes [' + str(0) + '-' + str(NB_COMPONENTS - 1) + ']') + for i in range(NB_COMPONENTS - 1): + plt.figure() + for c, j, name in zip(COLORS, KEYS, CLASSES_NAMES): + plt.scatter(matrix[TARGETS == j, i], matrix[TARGETS == j, i + 1], c=c, label=name) + plt.legend() + plt.title('PCA of my dataset axes [' + str(i) + '-' + str(i + 1) + ']') + plt.show() + +# Creation de la vue 3D +if FIGURE_3D: + figure3D = plt.figure() + ax = figure3D.add_subplot(111, projection='3d') + for c, j, name in zip(COLORS, KEYS, CLASSES_NAMES): + ax.scatter(matrix[TARGETS == j, 0], matrix[TARGETS == j, 1], matrix[TARGETS == j, 2], c=c) + ax.set_xlabel('X') + ax.set_ylabel('Y') + ax.set_zlabel('Z') + + scatters_proxy = [] + for color in COLORS: + scatters_proxy.append(matplotlib.lines.Line2D([0],[0], linestyle="none", c=color, marker = 'o')) + + ax.legend(scatters_proxy, CLASSES_NAMES, numpoints = 1) + + plt.show() + +# Creation de la video +if (len(OUTPUT) > 0): + print "Recording..." + # Create a figure and a 3D Axes + scene = plt.figure() + ax = Axes3D(scene) + + # Create an init function and the animate functions. + # Both are explained in the tutorial. Since we are changing + # the the elevation and azimuth and no objects are really + # changed on the plot we don't have to return anything from + # the init and animate function. (return value is explained + # in the tutorial. + def init(): + for c, j, name in zip(COLORS, KEYS, CLASSES_NAMES): + ax.scatter(matrix[TARGETS == j, 0], matrix[TARGETS == j, 1], matrix[TARGETS == j, 2], c=c, s=20) + ax.set_xlabel('X') + ax.set_ylabel('Y') + ax.set_zlabel('Z') + + def animate(i): + ax.view_init(elev=10., azim=i) + + # Animate + anim = animation.FuncAnimation(scene, animate, init_func=init, frames=RESOURCES["video"]["frames"], interval=RESOURCES["video"]["interval"], blit=True) + # Save + anim.save(filename=OUTPUT, writer=RESOURCES["video"]["writer"], fps=RESOURCES["video"]["fps"], dpi=RESOURCES["video"]["dpi"]) + print "File saved at " + OUTPUT \ No newline at end of file diff --git a/resources.default.json b/resources.default.json new file mode 100644 index 0000000..d1b7ee4 --- /dev/null +++ b/resources.default.json @@ -0,0 +1,17 @@ +{ + "colors": "rgb", + "classes_names": ["A", "B", "C"], + "matrix": [ + [0, 0, 0, 0], + [1, 1, 1, 1], + [2, 2, 2, 2] + ], + "targets": [0, 1, 2], + "video": { + "dpi": 600, + "frames": 360, + "fps": 30, + "interval": 20, + "writer": "mencoder" + } +} \ No newline at end of file