matplotlib - graphiques en Python

Ce TD propose une découverte de la bibliothèque Python de dessin matplotlib. Avant de commencer, vous allez créer un compte sur le site http://cloud.sagemath.com, qui offre gratuitement, entre autres, la possiblité de créer des notebooks IPython (Python 2).

Une fois connectés, créez un nouveau projet (bouton "create new project"), ouvrez-le, cliquez sur "Create new files in home directory of project", et créez un nouveau notebook en cliquant sur le bouton "IPython Notebook".

Vous pouvez créer autant de notebooks que vous le souaitez dans un projet, ainsi que d'autres outils (feuilles de calcul Sage, terminaux Unix, fichiers LaTeX, ...)

Prise en main de matplotlib

Le point de départ pour l'apprentissage de matplotlib est le tutoriel http://matplotlib.org/users/pyplot_tutorial.html.

Une introduction utile est le tutoriel http://github.com/jrjohansson/scientific-python-lectures.

de J.R. Johansson ([email protected]) http://dml.riken.jp/~rob/

matplotlib est une bibliothèque Python de dessin très populaire dans le monde scientifique. Parmi ses caractéristiques, il y a :

  • Support $\LaTeX$,
  • Sortie de haute qualité en PNG, PDF, SVG, EPS, PGF, ...
  • Interactivité.

Elle s'intègre parfaitement avec le notebook IPython. Le notebook typique commence par une ligne de configuration :

In [1]:
# Cette commande active l'affichage des graphiques matplotlib
# dans le notebook. Elle doit apparaître une fois, au début du
# notebook

%matplotlib inline

Ensuite on importe les bibliothèques matplotlib.pyplot et numpy. Par commodité et convention, on donne les alias plt et np à ces bibliothèques :

In [2]:
import matplotlib.pyplot as plt
import numpy as np

On commence immédiatement avec un exemple: on approxime une parabole par morceaux de droites

In [3]:
plt.plot([0, 1, 4, 9, 16, 25, 36, 49])
Out[3]:
[<matplotlib.lines.Line2D at 0x7f0b275ab510>]

Expliquons ce qui se passe ici :

  • plt.plot prend en paramètre une liste de points, ce sont les ordonnées,
  • les abscisses sont par défaut $0, 1, 2, \dots$
  • plot.plot renovie un objet de type lines.Line2D. IPython sait comment afficher cet objet, grâce à la commande magique %matplotlib que nous avons saisie touten haut.

Voyons maintenant comment donner des abscisses autres que $0, 1, 2, ...$

In [4]:
plt.plot([1, 3, 5, 7], [10, 2, 8, 5], 'r--')
Out[4]:
[<matplotlib.lines.Line2D at 0x7f0b27505a50>]

Ceci mérite quelques explications :

Mais ce dessin est déformé ! Rémédions !

In [5]:
plt.plot([1, 3, 5, 7], [10, 2, 8, 5], 'go-', linewidth=2)
plt.axis([0,11,0,11])
plt.xlabel('abscisses')
plt.ylabel('ordonnees')
plt.title('des donnees')
Out[5]:
<matplotlib.text.Text at 0x7f0b2748d510>

On voit dans cet exemple que les fonctions de plt s'appliquent de façon incrémentale au dessin.

Voici un dernier exemple avec plusieurs courbes.

In [6]:
plt.plot([0, 1, 4, 9, 16, 25], 'bo', label="parabole")
plt.plot([0, 1, 27, 64, 125], 'rx-.', label="cubique")
plt.legend()
Out[6]:
<matplotlib.legend.Legend at 0x7f0b274c37d0>

Exercices

  1. Dessinnez le graphe de votre nombre d'heures de cours par jour de la semaine (numéroter les jours de 1 à 7).

  2. Dessinner un pentagone (pas nécessairement réguilier).

  3. Dessinner la courbe d'équation $y = x^3$ entre $-10$ et $10$ en utilisant $200$ échantillons (c'est à dire $200$ valeurs pour $x$). Aidez-vous avec une boucle for.

Compréhensions

Les boucles for ne sont pas la seule façon de construire des gros ensembles de données. Python offre une syntaxe très pratique, dite des compréhensions de listes. En voici un exemple

In [7]:
a = [x**2 for x in range(20) if x % 2 == 0]
a
Out[7]:
[0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

Exercices

  1. Dessinnez à nouveau la courbe d'équation $y = x^3$, avec $20$ points d'échantillonnage, en utilisant une seule ligne de code

Dessinner des fonctions

numpy et matplotlib offrent une syntaxe encore plus simple pour représenter des fonctions mathématiques. Observez le code ci-dessous.

In [8]:
a = np.arange(10)
a
Out[8]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [9]:
b = range(10)
b
Out[9]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [10]:
type(a), type(b)
Out[10]:
(numpy.ndarray, list)
In [11]:
np.arange(0, 10, 2)
Out[11]:
array([0, 2, 4, 6, 8])

La fonction np.arange(a,b,c), comme la fonction range, renvoie une liste comprise entre a et b avec des incréments de c. Mais l'objet renvoyé n'est pas une liste normale Python : il s'agit d'un array numpy.

Les arrays numpy se comportent différemment des listes Python. Lisez et comprenez ces exemples :

In [12]:
2 * a
Out[12]:
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
In [13]:
2 * b
Out[13]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [14]:
a * a
Out[14]:
array([ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81])
In [15]:
b * b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-aa818ac5610b> in <module>()
----> 1 b * b

TypeError: can't multiply sequence by non-int of type 'list'

Les opérations arithmétiques sont exécutées composante par composante sur les arrays numpy, ceci nous permet de dessinner des fonctions mathématiques avec une syntaxe très intuitive.

Voici la fonction $\sin x$ entre $0$ et $6\pi$.

In [16]:
x = 2 * np.pi * np.arange(0, 3, 0.01)   # np.pi est la constante π
y = np.sin(x)
plt.plot(x, y)
Out[16]:
[<matplotlib.lines.Line2D at 0x7f0b2727d090>]

Exercices

  1. Dessinner la fonction $\cos$ entre $-\pi$ et $\pi$.

  2. Dessinner la fonction $y = x^3$ entre $-10$ et $10$ en utilisant un array numpy.

  3. On rappelle que le cercle de rayon $1$ a pour équation $x^2 + y^2 = 1$. La fonction racine carrée s'appelle np.sqrt. Dessinner un cercle.

  4. On rappelle que le cercle unité est paramétrisé par les fonctions $(\cos(x), \sin(x))$, avec $-\pi\le x\le\pi$. Dessinner un cercle en utilisant les fonctions np.sin et np.cos.

Sous-graphes

On peut afficher plusieurs graphes côte à côte avec la fonction plt.subplots. Celle ci prend le nombre de lignes, le nombre de colonnes, et le graphe sur lequel travailler. Voici un exemple.

In [17]:
x = 2 * np.pi * np.arange(0, 3, 0.01)   # np.pi est la constante π
y = np.sin(x)

plt.subplot(1,2,1)
plt.plot(x, y, 'r--')
plt.subplot(1,2,2)
plt.plot(y, x, 'g*-');

Exercices

  1. Reproduire le dessin ci-dessous (suggestion la première ressemble méchamment à $e^{-x} \cos(cx)$... à vous de trouver la constante $c$):

Autres types de graphes

Voici quelques autres types de graphe offerts par matplotlib:

  • scatter : la même chose qu'un plot sans lignes, mais permet de controler taille et couleur des points.
  • step : graphe en escalier.
  • bar : graphe en barres.
  • fill : remplissage entre deux courbes.
In [18]:
n = np.array([0,1,2,3,4,5])
In [19]:
plt.subplot(2, 2, 1)
plt.scatter(n, -n, s=10*n+1)
plt.title("diffusion")

plt.subplot(2, 2, 2)
plt.step(n, n**2)
plt.title("escalier")

plt.subplot(2, 2, 3)
plt.bar(n, n**2)
plt.title("barres")

plt.subplot(2, 2, 4)
plt.fill_between(x, x**2, x**3, color="green");
plt.title("remplissage");

On peut aussi faire des gâteaux

In [20]:
plt.pie([5, 10, 5, 80], labels=['a', 'b', 'c', 'd'], colors=['r', 'w', 'b', 'y'])
Out[20]:
([<matplotlib.patches.Wedge at 0x7f0b26db8050>,
  <matplotlib.patches.Wedge at 0x7f0b26db8a10>,
  <matplotlib.patches.Wedge at 0x7f0b26dc23d0>,
  <matplotlib.patches.Wedge at 0x7f0b26dc2d50>],
 [<matplotlib.text.Text at 0x7f0b26db85d0>,
  <matplotlib.text.Text at 0x7f0b26db8fd0>,
  <matplotlib.text.Text at 0x7f0b26dc2990>,
  <matplotlib.text.Text at 0x7f0b26dce350>])

Histogrammes

Un histogramme est une représentation de la fréquence d'occcurence d'une valeur dans un ensemble.

In [21]:
n = [1, 2, 2, 3, 4, 10, 2, 1]

plt.hist(n)
Out[21]:
(array([ 2.,  3.,  1.,  1.,  0.,  0.,  0.,  0.,  0.,  1.]),
 array([  1. ,   1.9,   2.8,   3.7,   4.6,   5.5,   6.4,   7.3,   8.2,
          9.1,  10. ]),
 <a list of 10 Patch objects>)

Par défaut un histogramme est séparé en au plus 10 plages de valeurs. Cela peut être contrôlé par le paramètre bins.

In [22]:
n = [x % 30 for x in range(10000) if np.sqrt(x).is_integer()]
print n

plt.subplot(2,1,1)
plt.hist(n)

plt.subplot(2,1,2)
plt.hist(n, bins=30)

plt.show()
[0, 1, 4, 9, 16, 25, 6, 19, 4, 21, 10, 1, 24, 19, 16, 15, 16, 19, 24, 1, 10, 21, 4, 19, 6, 25, 16, 9, 4, 1, 0, 1, 4, 9, 16, 25, 6, 19, 4, 21, 10, 1, 24, 19, 16, 15, 16, 19, 24, 1, 10, 21, 4, 19, 6, 25, 16, 9, 4, 1, 0, 1, 4, 9, 16, 25, 6, 19, 4, 21, 10, 1, 24, 19, 16, 15, 16, 19, 24, 1, 10, 21, 4, 19, 6, 25, 16, 9, 4, 1, 0, 1, 4, 9, 16, 25, 6, 19, 4, 21]

Exercices

  1. Dessinner le gâteau de vos heures de cours par jour de la semaine.

  2. Dessinner l'histogramme de vos notes au premier semestre.

Analyse de données

Pour terminer, nous allons travailler sur des données réelles. Copiez-collez ce code

In [23]:
import urllib2
import json
prenoms = urllib2.urlopen('http://opendata.paris.fr/api/records/1.0/download?dataset=liste_des_prenoms_2004_a_2012&format=json')
donnees_brutes = json.load(prenoms)

La variable donnees_brutes contient la liste des tous les noms enregistrés par la mairie de Paris entre 2004 et 2014 (extrêmes inclus), avec le nombre d'occurrences par année et par prénom.

Il s'agit d'un grosse liste !

In [24]:
len(donnees_brutes)
Out[24]:
13546

Chaque élément de la liste est un dictionnaire Python.

In [25]:
donnees_brutes[0]
Out[25]:
{u'datasetid': u'liste_des_prenoms_2004_a_2012',
 u'fields': {u'annee': 2011,
  u'nombre': 12,
  u'prenoms': u'Zachary',
  u'sexe': u'M'},
 u'record_timestamp': u'2015-03-16T17:33:33.316694',
 u'recordid': u'aa8805d011cdcc1b9cf1abcd8febd8946028050b'}

Le seul champs intéressant est fields, qui contient les champs suivants :

  • prenoms : préonom enregistré,
  • annee : année de l'enregistrement,
  • nombre : nombre de fois que le nom a été donné,
  • sexe : genre du prénom : M, F et X (après 2010 X n'est plus utilisé, et les prénoms unisex sont séparés en enregistrements masculins et enregistrements féminins)
In [26]:
donnees_brutes[0]['fields']
Out[26]:
{u'annee': 2011, u'nombre': 12, u'prenoms': u'Zachary', u'sexe': u'M'}

Par conséquent, on ne va garder que ce contenu, dans un liste nommée donnees.

In [27]:
donnees = [d['fields'] for d in donnees_brutes]
donnees[50:60]
Out[27]:
[{u'annee': 2012, u'nombre': 5, u'prenoms': u'Annabelle', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 46, u'prenoms': u'Anouk', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 8, u'prenoms': u'Anya', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 10, u'prenoms': u'Ariane', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 10, u'prenoms': u'Aristide', u'sexe': u'M'},
 {u'annee': 2012, u'nombre': 7, u'prenoms': u'Arthus', u'sexe': u'M'},
 {u'annee': 2012, u'nombre': 7, u'prenoms': u'Assetou', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 6, u'prenoms': u'Assil', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 5, u'prenoms': u'Astou', u'sexe': u'F'},
 {u'annee': 2012, u'nombre': 17, u'prenoms': u'Augustine', u'sexe': u'F'}]

Exercices

  1. Dessinner l'histogramme du nombre d'enregistrements par année.

  2. Dessinner, sur un même graphe, le nombre d'enregistrements par année de ces noms :

    • Alexandre
    • Gabriel
    • Adam
    • Louise
    • Arthur
    • Gautier
    • Annabelle
    • votre nom
  3. Trouver le nom le plus populaire pour chaque année.

  4. Dessiner le nombre de naissances de garçons et de filles en fonction de l'année.