Press "Enter" to skip to content

Faire de jolies graphiques avec Python (1/?): Introduction à Matplotlib

Dans le domaine scientifique, avoir de bons résultats c’est important. Mais faire de jolies figures pour bien les présenter l’est tout autant et franchement beaucoup plus difficile (de mon point de vue). Avec le temps on voit régulièrement des collègues présenter des résultats intéressants sur des graphes tout à fait hideux ou, à l’inverse, d’autres vendre de la glace pour esquimaux à l’aide d’un graphe super joli mais sans intérêt (quand ce n’est pas notre tour de faire passer les vessies pour des lanternes).

Dans cette séries d’articles je souhaite donner mes conseils pour faire facilement le minimum syndical en terme de graphique et de présentation. Avec quelque exemples et mes lignes de code. Ils me serviront de notes mais si quelqu’un trouve ces astuces utiles, libre à vous de vous servir.

Mettre Python en place

Je ne m’étendrai pas sur l’installation de Python sur votre système. Vous pouvez consulter les très nombreux tutoriels qui existent sur le net quelque soit votre système d’exploitation. Vous pouvez consulter le site officiel pour télécharger le paquet correspondant à votre système ou une distribution plus complète comme Anaconda. Vérifiez bien que vous utilisez la dernière version de Python (3.x.y, la version 2 n’est plus maintenue de toute façon) et regardez comment installer les différentes bibliothèque utiles, surtout matplotlib qui permet de tracer des graphiques. J’utilise aussi des outils de numpy et scipy occasionnellement.

Si les paquets en question sont absents, vérifiez donc bien que vous pouvez les installer. Par exemple sur un système Linux, si vous avez simplement installé Python, utilisez le système de gestion de paquets Pip:

pip install numpy scipy matplotlib

Si vous utilisez Anaconda, utilisez l’interface graphique ou la ligne de commande en vous référant à la documentation pour les installer sur un de vos environnement.

Dans la suite les exemples que je vais donner seront de courts scripts en Python. Ces scripts sont des fichiers textes qui peuvent être lus et exécutés par l’interpréteur Python. Si votre installation fonctionne, vous devriez pouvoir copier les exemples dans un fichier texte toto.py et l’exécuter à l’aide de la commande python toto.py dans un terminal1Si vous préférez utiliser un IDE ou une interface graphique, consultez la documentation ou un tutoriel quand à l’exécution des scripts dans ces environnements. Si j’ai le temps et la motivation, je ferais des articles sur l’exécution des scripts dans le terminal, au moins avec Linux.. Vérifiez bien que votre interpréteur pointe vers Python 3 (la commande which python sous Linux ou Mac devrait vous renvoyer l’adresse d’un exécutable terminant par python3, notez que c’est cette adresse qu’il faut rentrer dans le shebang si vous voulez rendre vos scripts exécutable en tapant simplement ./toto.py dans le terminal).

Matplotlib et pyplot premiers pas

Le script minimal (à enregistrer dans un fichier texte) pour tracer une figure ressemblera à ceci:

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

for i in range(10):
    x = np.array([0, i])
    plt.plot(x, linewidth=3)
plt.show()
Résultat de notre premier script Matplotlib!

Pour les débutants en python je vais détailler l’effet de chaque éléments:

  • Les deux premières lignes sont le *shebang* et l’encodage. Elles ne servent que si vous rendez votre script exécutable. Si cette phrase n’a pas beaucoup de sens pour vous, retenez juste qu’elles permettent à votre système d’exploitation de savoir qu’il doit appeler Python si vous exécuter le script indépendamment. La première ligne doit contenir le chemin vers l’interpréteur Python. La seconde détaille l’encodage du fichier. La plupart des éditeurs de texte encode en UTF-8 et je vous conseille de l’utiliser le plus possible.
  • Les lignes commençant par import et from signale à Python que nous allons utiliser la bibliothèque numpy et la bibliothèque pyplot contenue dans matplotlib. La commande as nous permet de donner un alias aux bibliothèques. Regardons le bloc suivant pour comprendre.
  • La boucle for fait varier la valeur de i entre 0 et 9 (soit 10 valeurs). Pour chaque valeur, un vecteur de deux valeurs est crée contenant les valeurs 0 et i. La commande plot permet d’ajouter une courbe au graphique généré par matplotlib. Par défaut les courbes placent les valeurs sur l’axe des y et les indices (la place dans le vecteur) sur l’axe des x. Chaque courbe reliera donc un premier point en (0,0)et un second point en (1, i). On notera que les commandes array (qui génère le vecteur), plot (qui ajoute les courbes) et show (qui trace le graphique final) sont précédées des alias définis plus tôt. La fonction array appartient effectivement à la bibliothèque numpy et les fonctions plot et show à pyplot. Les alias permettent de raccourcir l’appel de ces bibliothèques.

Cet exemple minimal utilise cependant les paramètres par défaut de matplotlib, qui ne seront donc pas au goût de tout le monde (sans blague?), et ne présente pas les bonnes habitudes à prendre pour écrire des scripts lisibles et pratiques. Dans la suite de cet article j’entends présenter quelque changements au niveau des bonnes habitudes à prendre et de quelque modifications minimales à apporter au niveau des tailles et des couleurs.

Plot et replot: Bien utiliser Matplotlib

Matplotlib utilise divers objets pour générer les graphs affichés à l’écran. L’objet dessinant la fenêtre est le canvas. Ce canvas ne nous intéresse pas mais il contient la figure qui contient elle même les différents axes2Attention, le nom est trompeur. Chaque axes est un objet qui contient les informations sur les axes x et y (et z en 3D) ainsi que les labels, les titres etc… Un objet axes ne se limite pas à un axe selon une dimension!. Ce sont ces deux derniers éléments qui nous intéressent. pyplot sert d’interface en générant la figure et un axe de base mais il est plus intéressant d’interagir directement avec les axes pour définir leur propriétés propres.

Le code suivant génère la même figure que précédemment mais ajoute les données à tracer directement dans l’objet axe:

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

fig, ax = plt.subplots()
for i in range(10):
    x = np.array([0, i])
    ax.plot(x, linewidth=3)
plt.show()
Pas très différent hein?

En réalité l’utilisation de la commande subplots permet de générer une liste d’axes et facilite beaucoup l’accès à ces derniers. Par exemples, on peut séparer les courbes selon que i est pair ou impair:

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

fig, axes = plt.subplot(2,1)
for i in range(10):
    x = np.array([0, i])
    if i % 2 == 0:
        ax = axes[0]
    else:
        ax = axes[1]
    ax.plot(x, linewidth=3)
axes[0].set_ylabel('i pair')
axes[1].set_ylabel('i impair')
axes[1].set_title('Pouet')
plt.show()
Notez que chaque axe a son propre compteur de couleurs.

Comme le montre ce exemple, on peut accéder à tous les éléments de la liste axes indépendamment et gérer les label (x et y), le titre, la légende, l’épaisseur des ticks etc… D’une manière générale, modifier directement les propriétés des axes est plus propre et plus souple que de passer par l’interface pyplot.

Avant de conclure cette partie, j’aimerais mentionner la méthode tight_layout qui comme son nom l’indique, resserre le cadre autour de la figure (ou plutôt élargi la figure à la taille de la fenêtre). Comme vous pouvez le voir si dessous, cela permet à la figure de prendre toute la taille de l’image disponible3Parfois votre gestionnaire de fenêtre peut redimensionner automatiquement la fenêtre après que Python ait calculé les dimensions optimales, ce qui va tout foutre en l’air. Il ne faut pas hésiter à réutiliser l’option tight_layout via l’interface graphique si nécessaire.:

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

fig, axes = plt.subplot(2,1)
for i in range(10):
    x = np.array([0, i])
    if i % 2 == 0:
        ax = axes[0]
    else:
        ax = axes[1]
    ax.plot(x, linewidth=3)
axes[0].set_ylabel('i pair')
axes[1].set_ylabel('i impair')
axes[1].set_title('Pouet')
plt.tight_layout()
plt.show()
Les graphes prennent toute la place maintenant!

Les fontes, la taille etc…: Gros c’est pas assez gros

Comme on peut le constater sur ces exemples simples, la taille des lettres et l’épaisseur des courbes est relativement faible (et pourtant, les plus attentif et anglophones auront peut être remarqué que j’ai tracé avec une épaisseur de trai de 3 en utilisant le paramètre linewidth). De manière générale, les paramètres par défaut rendront le texte confortable à lire sur écran mais seront systématiquement trop petits pour qu’il soit lisible sur des diapositives ou des images projetées. Il est plus commode de les modifier séparément plutôt que de changer les paramètres globaux. Voyons comment en ajoutant différents éléments à notre graphique:

Ajouter une légende

La légende s’ajoute avec la méthode legend sur chaque axe (potentiellement, parfois on peut faire une légende commune et ne la tracer que sur un seul) ou sur tout la figure. On peut lui passer un paquet d’argument qui déterminent la forme de la fenêtre de légende comme le nombre de colonnes ou la forme du cadre. Dans notre cas l’option importante est fontsize. La légende peut prendre les noms des courbes en argument mais on pourra aussi les passer au moment de tracer chaque courbe avec l’argument label4Notez que si le label commence par un underscore « _ », la courbe sera exclue du légendage automatique. Par exemple:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

fig, axes = plt.subplots(2, 1)
for i in range(10):
    x = np.array([0, i])
    if i % 2 == 0:
        ax = axes[0]
    else:
        ax = axes[1]
    ax.plot(x, linewidth=3,
            label="Courbe numéro {:d}".format(i)
            )
    axes[0].legend(fontsize=15)
    axes[0].set_ylabel('i pair')
    axes[1].set_ylabel('i impair')
    axes[1].set_title('Pouet')
    
    plt.tight_layout()
    plt.show()
    
    return
La légende n’affiche que les labels des courbes paires.

La légende est un des éléments les plus importants de vos figures. Il ne faut pas hésiter à la rendre lisible. Par ailleurs Matplotlib essaie de trouver un emplacement optimal par défaut avec le moins de recouvrement possible sur les courbes mais vous pouvez lui imposer un emplacement avec le paramètre loc (‘upper right’, ‘lower left’, ‘center’, ‘center left’ etc.).

Je déconseille d’appliquer la légende sur l’ensemble de la figure car elle sera plus chargée (toutes les courbes de tous les axes s’affichent) et elle sera difficile à placer correctement si vous n’avez pas une idée précise de ce que vous voulez.

Les ticks marks et les axes

De la même manière que pour la légende, il est possible de modifier la légende est axes, la taille et le nombre des ticks. On utilisera l’unique méthode tick_params. Elle peut appliquer les changement d’un coup sur les axes x et y ainsi que sur les ticks majeurs et mineurs et leur labels:

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

fig, ax = plt.subplots()
for i in range(10):
    x = np.array([0, i])
    ax.plot(x, linewidth=3)
    ax.grid(True, linestyle='-,')
    ax.tick_params(
        axis='y',
        which='both',
        labelsize=15,
        grid_linewidth=2
        )
plt.show()
Ici les changements ne sont appliqués qu’à l’axe y. Notez que la fonction peut aussi modifier la grille si on la trace.

Un mot sur les couleurs

Les formats de couleurs en informatique ne sont pas nombreux et Matplotlib en prend la majorité. La plupart sont calqués sur la nomenclature RGBA (RVBT en français, le A désigne le canal alpha où se gère la transparence) dans laquelle les couleurs sont représenté par leurs contenance en rouge, vert, bleu et leur transparence (d’où le raccourci RVBT). On peut les représenter soit sur une échelle allant de 0 à 1 pour chaque entrée. Ainsi le tuple (1., 0., 0.) représente le rouge pur, (1., 0., 1.)du violet, (0., 0., 1.) du bleu, etc. La transparence se gère avec un quatrième indice et par exemple (1., 0., 1., 0.5) sera un violet un peu transparent. On peut aussi passer à Matplotlib des arguments sous forme hexadécimale et le même violet sera la chaîne de caractères '#FF00FF. Un petit exemple ne sera pas de trop:

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

fig, ax = plt.subplots()
x = np.array([0, 0])
y = np.array([0, 1])
z = np.array([0, 2])
ax.plot(x, linewidth=3
        label='#FF00FF'
        color='#FF00FF'
        )
ax.plot(y, linewidth=3
        label='1., 0., 1.'
        color=(1.,0.,1.)
        linestyle='--'
        )
ax.plot(z, linewidth=3
        label='1., 0., 1., 0.5'
        color=(1.,0.,1.,0.5)
        linestyle='-.'
        )
plt.legend(fontsize=15)
plt.show()
À la limite remarquez l’argument linestyle mais sinon l’exemple suit ce qui est décrit dans le texte…

On peut aussi utiliser une liste de couleurs prédéfinies en passant leur noms à l’argument color.

Personnellement je ne suis pas très fan des couleurs par défaut de Matplotlib et j’ai fait un petit module que j’ai ajouté à mon pythonpath et qui modifie les paramètres par défaut de Matplotlib si je le charge.

#! /path/to/python python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt

GC_pink = "#FF01C0"
GC_dblue = "#122070"
GC_blue = "#2CBDFE"
GC_green = "#558D3A"
GC_orange = "#FF8C00"
color_list = [GC_pink,
              GC_dblue,
              GC_blue,
              GC_green,
              GC_orange]
plt.rcParams[
    'axes.prop_cycle'
    ] = plt.cycler(color=color_list)

fig, ax = plt.subplots()
for i in range(10):
    x = np.array([0, i])
    ax.plot(x, linewidth=3)
plt.show()
C’est bien le seul cas où je suis d’accord pour modifier les paramètres par défaut avec l’épaisseur des axes (dont je parlerai peut être plus tard).

Ici on modifie directement le dictionnaire rcParams qui contient les paramètres par défaut, comme les 10 couleurs de base. Je n’en ai mis que 5 dans mon cycle car je pense qu’il est rarement nécessaire de charger un graph avec plus de 5 courbes et je préfère m’adapter au cas par cas quand la situation l’exige.

Conclusion

Ce billet est déjà long et je préfère m’arrêter ici. Je reprendrai sans doute cette série avec d’autres conseils et astuces concernant Matplotlib car je trouve les ressources en français assez rare et pauvres en exemples. Si ce billet vous a intéressé n’hésitez pas à me le dire, à commenter les exemples, poser vos questions ou proposer vos méthodes et même aborder d’autres sujets dont vous aimeriez discuter.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.