TP3: Un peu d'analyse de données

On va faire un peu d'analyse de données, avec ou sans numpy et ses matrices.

Au passage, on va voir comment faire un peu de visualisation en Python, en utilisant le module matplotlib (librairie en plus, mais disponible avec tous les outils scientifiques)

La commande essentielle, plot, prend deux listes de coordonnées (abscisses et ordonnées), et trace la courbe correspondante reliant les points. Spyder gère automatiquement l'affichage dans sa console IPython.

\iffalse
In [14]:
import matplotlib 
matplotlib.use("TkAgg",warn=False, force=True)
from IPython.display import Image
%matplotlib inline
\fi
In [10]:
import matplotlib.pyplot as plt

plt.plot([1,2,3,4,5],[1,4,9,16,25])
print "Parabole:"
Parabole:

En option, on peut définir une couleur et une forme avec une chaine en 3e argument ("r" pour red, "o" pour un point)

In [11]:
plt.plot([1,2,3,4,5],[1,4,9,16,25],"ro")
print "Parabole"
Parabole

Pour contrôler un peu l'affichage, on peut fixer les extrémités des axes, et les forcer à être à la même échelle

In [12]:
plt.xlim(0,25)
plt.ylim(0,25)
plt.axes().set_aspect('equal')
plt.plot([1,2,3,4,5],[1,4,9,16,25],"ro")
print "Parabole encore"
Parabole encore

Première partie: classification

Passons maintenant aux choses sérieuses. On va considérer que l'on a des données de deux types différents (appelons les "rouge" et "bleu"), caractérisées par deux valeurs entre 0 et 1. On pourrait alors avoir une visualisation comme ceci:

Le but de ce TP est de définir un moyen de "classer" une nouvelle donnée pour dire si elle est plutôt de la classe bleue ou rouge. Pour cela, un moyen simple (voire simplet, mais vous creuserez la question au 2e semestre), est de calculer le barycentre (centre de gravité) des points de chaque classe, ici en jaune:

Puis de considérer leur médiatrice:

En enfin de considérer que tout ce qui est du même côté que le groupe bleu/rouge doit être bleu/rouge:

Evidemment, ça ne marchera pas pour tout type de données:

Cette méthode nécessite que les données soit séparables linéairement. Mais passons. Votre tâche:

  1. en utilisant la fonction random.random(), faire une fonction qui génère un "nuage" de n points autour d'une position donnée ($x_0$,$y_0$), avec un étalage réglable (distance maxi par rapport à la position donnée). Par exemple, les points de la figure initiale ont été générés avec deux appels avec les paramètres (vous être libre du nom de la fonction):

    g1 = blob(0.3,0.6,20,scale=0.1)

    g2 = blob(0.6,0.4,15,scale=0.15)

  2. Une fonction qui détermine le centre d'un groupe

  3. Une fonction qui calcule le séparateur de deux groupes (la médiatrice): pour cela il suffit de renvoyer un point de la droite, par exemple le milieu M des centres des 2 groupes, et un vecteur normal à la droite $\vec{w}$ (par exemple le vecteur défini par les deux centres)
  4. Une fonction qui classifie un point P par rapport à cette droite: ici le critère est déterminé par rapport à l'angle entre $\vec{MP}$ et $\vec{w}$, soit $cos(\vec{MP},\vec{w})>0$ (ou inférieur), ou encore $\vec{MP}\cdot\vec{w}>0$ (ou inférieur).
  5. Faire des fonctions de visualisations de tout ça (groupes de points, séparatrice, nouveaux points à classer; pour faire des points en forme de crois, le symbole est 'x').

Deuxième partie: de "vraies" données

Vous pouvez maintenant utiliser le fichier "iris.txt", en annexe du sujet. Celui-ci contient des caractéristiques de différentes fleurs de l'espèce iris, réparties en trois sous-espèces "setosa", "virginica", "versicolor".

Vous pouvez utiliser numpy pour lire directement les données:

In [17]:
import numpy as np
data = np.genfromtxt('iris.txt', dtype=None,delimiter=',',names=True,encoding="utf8")

Si on regarde le résultat, on voit que cela donne une matrice de tuples, avec les caractéristiques et la classe de chaque instance. On a aussi une liste de noms des "colonnes" de ces données:

In [18]:
data[:4]
Out[18]:
array([(5.1, 3.5, 1.4, 0.2, u'Iris-setosa'),
       (4.9, 3. , 1.4, 0.2, u'Iris-setosa'),
       (4.7, 3.2, 1.3, 0.2, u'Iris-setosa'),
       (4.6, 3.1, 1.5, 0.2, u'Iris-setosa')],
      dtype=[('sepal_l', '<f8'), ('sepal_w', '<f8'), ('petal_l', '<f8'), ('petal_w', '<f8'), ('classe', '<U15')])

On peut alors récupérer les colonnes par leur nom:

In [19]:
data["classe"][:10]
Out[19]:
array([u'Iris-setosa', u'Iris-setosa', u'Iris-setosa', u'Iris-setosa',
       u'Iris-setosa', u'Iris-setosa', u'Iris-setosa', u'Iris-setosa',
       u'Iris-setosa', u'Iris-setosa'], dtype='<U15')
In [21]:
data["sepal_l"][:10]
Out[21]:
array([5.1, 4.9, 4.7, 4.6, 5. , 5.4, 4.6, 5. , 4.4, 4.9])

Rappel: on peut filtrer des éléments d'une matrice numpy avec des conditions booléennes, par exemple pour garder les instances d'une classe particulière:

In [23]:
setosa  = data[data["classe"]=="Iris-setosa"]
print(setosa[:5])
[(5.1, 3.5, 1.4, 0.2, u'Iris-setosa') (4.9, 3. , 1.4, 0.2, u'Iris-setosa')
 (4.7, 3.2, 1.3, 0.2, u'Iris-setosa') (4.6, 3.1, 1.5, 0.2, u'Iris-setosa')
 (5. , 3.6, 1.4, 0.2, u'Iris-setosa')]

A vous de jouer, pour:

  1. Faire une fonction qui affiche 2 caractéristiques choisies sur ces données, donnant une couleur différente à chaque groupe. Utilisez les fonctionalités de numpy. Par exemple:
  2. Appliquer la première partie pour trouver des séparateurs des données, en considérant qu'il faut un séparateur pour chaque groupe par rapport aux deux autres ensembles (toujours restreint à deux caractéristiques). Trouver les 2 meilleures caractéristiques à prendre pour une bonne séparation.

Et voilà, vous avez fait votre premier programme qui apprend automatiquement ...