Premiers graphiques#

«Un bon croquis vaut mieux qu’un long discours». Cela vaut aussi en analyse des données: la visualisation est un outil fondamental pour observer, explorer, synthétiser, comprendre les données et pour transmettre cette compréhension. Dans cette fiche, nous allons découvrir la bibliothèque matplotlib qui permet de réaliser en quelques lignes de jolis graphiques.

Visualisation de données par un nuage de points#

Dans cet exemple – purement fictif – nous considérons des données mesurant l’évolution de la concentration d’un produit dans le sang (exprimé en mg/L) en fonction du temps (exprimé en heures):

Tableau 1 Mesures#

[Temps (h)

Concentration (mg/L)]

1

3.5

2

5.8

3

9.1

4

11.8

6

17.5

7

21.3

8

3.2

9

26.8

Nous allons maintenant représenter l’évolution de la concentration en fonction du temps:

import matplotlib.pyplot as plt
%matplotlib widget
plt.ioff()

temps = [1, 2, 3, 4, 6, 7, 8, 9]
concentration = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 3.2, 29.4]

fig, ax = plt.subplots()
ax.scatter(temps, concentration, marker="o", color="blue")
ax.grid()
ax.set_xlabel("Temps (h)")
ax.set_ylabel("Concentration (mg/L)")
ax.set_title("Concentration de produit en fonction du temps")
fig.show()

On appelle une telle figure un nuage de points (scatter plot). Chaque point représente une mesure: son abscisse indique le temps et son ordonnée la concentration mesurée. Par exemple, le premier point correspond à la mesure effectuée au bout d’une heure, où la concentration était de 5.5 mg/L.

Reprenons pas à pas ce code. Tout d’abord, on importe le sous-module pyplot de la bibliothèque Matplotlib et on lui donne l’alias plt pour l’utiliser plus commodément ensuite. Cet alias est standard, utilisez-le systématiquement. La commande %matplotlib widget permet d’avoir des figures interactives (possibilité de redimensioner, de zoomer, …). La commande plt.ioff() permet de contrôler explicitement quand la figure est affichée.

import matplotlib.pyplot as plt
%matplotlib widget
plt.ioff()
<contextlib.ExitStack at 0x7fd7a9d389d0>

On définit ensuite deux listes temps et concentration contenant les valeurs respectives du temps et de la concentration lors de chaque mesure. Ces deux listes doivent avoir la même longueur (sept éléments dans le cas présent):

temps = [1, 2, 3, 4, 6, 7, 8, 9]
concentration = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 40.2, 29.4]

On crée une figure avec la fonction subplots() qui renvoie deux objets : une figure (fig) et un graphique (ax):

fig, ax = plt.subplots()

Pour le moment, la figure est vide:

fig
../_images/b116d57a62f73100763cfd98956c774aff52846104e7aa97c2cb75189e8d31a0.png

La méthode .scatter() permet de représenter un nuage de points sur le graphique. Les deux premiers arguments correspondent respectivement aux abscisses et aux ordonnées des points. Des arguments facultatifs sont ensuite précisés comme le symbole (marker) et la couleur (color).

ax.scatter(temps, concentration, marker="o", color="blue")
fig
../_images/ba1aab49583f74f1b079292d87199394b39533b1fa7a5f5a5eae6a5aeee57641.png

La méthode .grid ajoute une grille de fond:

ax.grid()
fig
../_images/19b74a65aef7737d0d4556a56aec5c35e97c890c5b13a1d9ca963993e2aca54a.png

Les méthodes .set_xlabel() et .set_ylabel() définissent la légende des axes des abscisses et des ordonnées:

ax.set_xlabel("Temps (h)")
ax.set_ylabel("Concentration (mg/L)")
fig
../_images/162dccad8285b745339acdd2c9ad0c5d58084dc32eb220317a52289a295cb2b5.png

La méthode .set_title() définit le titre du graphique:

ax.set_title("Concentration de produit en fonction du temps")
fig
../_images/5dc019d45a3d99608d4e42582e03c4a6c93d8d38eb5e6eb6e1cbbcc9a93f3734.png

Enfin la méthode .show() affiche la figure avec interactivité:

fig.show()

Exercice

Il y a eu une erreur de prise de note au moment des mesures: les concentrations étaient en fait mesurées en cg/L. Corrigez ci-dessous la légende des abscisses.

### BEGIN SOLUTION
ax.set_ylabel("Concentration (cg/L)")
### END SOLUTION
fig
../_images/168cd285b77da4b346ce1304c4c0330c2a48059db36762d5417c0c0fca8d723f.png
assert "cg/L" in ax.yaxis.label._text, "La légende des ordonnées devrait contenir cg/L"

Exercice

Il y a eu une erreur de mesure au temps 8, ce qui explique la concentration aberrante. Reconstruisez la figure avec cette mesure en moins.

### BEGIN SOLUTION
temps = [1, 2, 3, 4, 6, 7, 9]
concentration = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 29.4]

fig, ax = plt.subplots()
ax.scatter(temps, concentration, marker="o", color="blue")
ax.grid()
ax.set_xlabel("Temps (h)")
ax.set_ylabel("Concentration (cg/L)")
ax.set_title("Concentration de produit en fonction du temps")
### END SOLUTION
fig.show()
assert len(temps) == 7
assert len(concentration) == 7
assert 8 not in temps

assert ax.xaxis.label._text == "Temps (h)", "Légende des abscisses incorrecte"
assert ax.yaxis.label._text == "Concentration (cg/L)", "Légende des ordonnées incorrecte"
assert ax.title._text == 'Concentration de produit en fonction du temps', "titre incorrect"

Visualisation d’une courbe#

Revenons sur notre expérience. On suppose que l’évolution de la concentration du produit en fonction du temps peut-être modélisée par la fonction \(C(t) = 2 + 3 \times t\) exprimant la concentration en fonction du temps. Pour comparer ce modèle théorique avec nos données, nous allons compléter la figure précédente en y superposant un graphe de la fonction \(C(t)\) sous forme d’une courbe.

Pour cela, il suffit de définir la fonction C(t):

def C(t):
    return 2 + 3 * t

de calculer la concentration théorique à chaque pas de temps en utilisant une compréhension:

concentration_théorique = [C(t) for t in temps]

et d’utiliser la méthode .plot() pour construit une courbe – en fait une ligne brisée – à partir des coordonnées en abscisse et en ordonnées des points à représenter; les arguments facultatifs de spécifient le style de la ligne (linestyle) et sa couleur (color):

ax.plot(temps, concentration_théorique, color="green", linestyle="--")
fig.show()

La cellule suivante résume la construction complète de la figure, en ajoutant une légende et en modifiant l’étendue des axes des abscisses et des ordonnées:

import numpy as np
import matplotlib.pyplot as plt

temps = [1, 2, 3, 4, 6, 7, 9]
concentration = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 29.4]

def C(t):
    return 2 + 3 * t
concentration_théorique = [C(t) for t in temps]

fig, ax = plt.subplots()

ax.scatter(temps, concentration, marker="o", color="blue", label="mesures")
ax.plot(temps, concentration_théorique, color="green", linestyle="--", label="modèle")

ax.set_xlim(0, 10)     # Étendue de l'axe des x
ax.set_ylim(0, 35)
ax.grid()

ax.set_xlabel("Temps (h)")
ax.set_ylabel("Concentration (g/L)")
ax.set_title("Concentration de produit en fonction du temps")
ax.legend(loc="upper left")

fig.show()

Exercice

  1. Repérez dans la cellule ci-dessus où sont définies l’étendue de l’axes des abscisses (avec set_xlim) et de l’axe des ordonnées (avec set_ylim).

  2. Repérez de même où sont définis les labels utilisés pour la légende, ainsi que la position de celle-ci dans la figure.

  3. Modifiez la légende pour préciser que le modèle utilise la fonction \(C(t) = 2 + 3 \times t\).
    Indication: utilisez le texte modèle: $C(t) = 2 + 3\times t$. Les $ qui y apparaissent indique que ce qui les sépare est une formule mathématique au format LaTeX.

  4. Déplacez la légende de en haut à gauche (upper left) à en haut à droite.

Nous enregistrons maintenant la figure (méthode savefig) sous la forme d’une image au format svg. Les arguments optionnels configurent les marges autour du graphique (bbox_inches) ainsi que la résolution de l’image (dpi):

fig.savefig("media/concentration_vs_temps.svg", bbox_inches="tight", dpi=200)

À faire

Remettre un lien vers l’image lorsque la construction du site web pourra utiliser les ressources construites lors de l’exécution de fiches.

Vous pouvez maintenant retrouver la figure dans le fichier media/concentration_vs_temps.svg.

Bilan#

Dans cette fiche, nous avons vu comment construire un graphique, avec un nuage de points (scatter) et une courbe (plot) représentant respectivement des données de mesures et un modèle théorique. Nous avons aussi vu comment enrichir ce graphique en configurant les axes (set_xlim, set_ylim, set_xlabel, set_ylabel), choisissant le style (grid, marker, color, ls) et ajoutant une légende et un titre (set_title).

Définitions

Avec Matplotlib, une figure (figure) peut être composée d’un ou plusieurs graphiques. Dans la documentation de Matplotlib un tel graphique est appelé axe (matplotlib axis) (d’où le nom de variable ax). De fait, il contient toutes les informations sur les axes du graphiques. Cela peut cependant porter à confusion et nous utiliserons plutôt le terme graphique.

Sur un graphique peuvent apparaître de nombreux objets visuels: labels, marqueurs, traits, … Ces objet sont appelés artistes (matplotlib artist) dans la documentation de Matplotlib.

Interfaces implicite et explicite de matplotlib

Si vous faites des recherches sur internet, il vous sera souvent proposé d’utiliser l’interface suivante, dite implicite, pour construire un graphique:

import matplotlib.pyplot as plt

temps = [1, 2, 3, 4, 6, 7, 8, 9]
concentration = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 40.2, 29.4]

plt.figure()
plt.scatter(temps, concentration, marker="o", color="blue")
plt.grid()
plt.xlabel("Temps (h)")
plt.ylabel("Concentration (mg/L)")
plt.title("Concentration de produit en fonction du temps");

Interfaces implicite et explicite de Matplotlib (suite)

Vous noterez que la figure construite n’est nommée nulle part ci-dessus. La commande plt.figure() construit et affiche implicitement une image, et toutes les commandes suivantes y font implicitement référence. On parle d’effet de bord et d’état global. Vous noterez aussi le ; à la fin de la cellule. Il est nécessaire pour cacher le résultat de la dernière instructions dont la valeur n’est pas la figure.

Cette approche quelque peu datée est traditionnelle dans des systèmes comme Matlab. La bibliothèque matplotlib.pyplot l’a reproduite pour faciliter la migration d’utilisateurs de ces systèmes. Par habitude beaucoup d’exemples sur internet utilisent encore cette approche; cela peut rester pratique comme raccourci dans des exemples en une ligne comme ci-dessus.

Mais on sait depuis – et c’est le parti pris de ce cours – que l’on obtient du code beaucoup plus modulaire si l’on sépare proprement les traitements et calculs (par exemple construire une figure) des entrées et sorties (par exemple afficher la figure), et si on évite les variables globales et effets de bord. Voir aussi le mantra «Préfère l’explicite à l’implicite» du Zen de Python.

De ce fait, pour tout usage non trivial, il est préférable de procéder comme nous l’avons fait en utilisant l’interface diteexplicite ou objet de Matplotlib. On y construit explicitement des objets représentant respectivement la figure (fig) et son ou ses axes (ax). Et l’affichage se fait explicitement en affichant la valeur de ces objets.

Pour en savoir plus.

Matplotlib, numpy, pandas

Pour simplifier, nous avons fait le choix dans les exemples ci-dessus de représenter les données par des listes Python. Dans la pratique, les données sont le plus souvent représentées par des structures plus riches et/ou plus efficaces comme des tableaux numpy ou des tables pandas. C’est ce que vous trouverez dans la plupart des exemples d’usage de Matplotlib sur internet, et ce que nous verrons dans les fiches ultérieures.