Aller au contenu principal

Matplotlib : lire un comportement sur une courbe

Ce que ce chapitre apporte 6 points
  • Comprendre qu'un tracé est une ligne brisée passant par un nombre fini de points calculés.
  • Respecter la règle du graphique technique : titre, axes nommés, unités, grille.
  • Distinguer par le style ce qui est mesuré de ce qui est calculé.
  • Superposer des courbes, les légender, et séparer les unités incompatibles en sous-graphiques.
  • Tracer des données lues dans un fichier, et enregistrer la figure pour un rapport.
  • Montrer la répartition d'une série par un histogramme, une boîte à moustaches ou un violon, et choisir entre les trois.

En ingénierie, un résultat numérique isolé, « vitesse = 12,4 m/s », dit rarement ce qu'il faut savoir. Ce qui compte est l'évolution : le planeur descend-il de plus en plus vite, le moteur se stabilise-t-il, la température finit-elle par plafonner ?

C'est le rôle de Matplotlib, l'outil standard en Python pour transformer des listes de nombres en courbes lisibles. Ce chapitre vise moins la variété des commandes que la production de graphiques scientifiquement valides : ceux qui portent leurs unités, distinguent une mesure d'un modèle, et ne mentent pas par leur découpage.

Les figures s'affichent dans la page. NumPy, Matplotlib et SciPy sont installés dans l'éditeur du navigateur : l'image apparaît sous le code après exécution. Ces exemples fonctionnent à l'identique sur une machine.

Ce qu'un tracé est réellement

Un ordinateur ne sait pas dessiner une courbe. Il sait calculer la fonction en un nombre fini de points, puis relier ces points par des segments de droite. Ce que l'écran montre n'est donc jamais la courbe : c'est une ligne brisée qui lui ressemble plus ou moins.

C'est ce que fait np.linspace(debut, fin, n) : il fabrique les n abscisses où le calcul aura lieu. Le choix de n n'est pas un détail d'affichage, c'est le degré de fidélité du dessin.

123456789101112-1,2-1-0,8-0,6-0,4-0,20,20,40,60,811,2tsin(t)
Valeurs de f(x) sur l'intervalle [0 ; 12,6]
tf
00
0,630,5891
1,260,9521
1,890,9495
2,520,5823
3,15-0,0084
3,78-0,5959
4,41-0,9546
5,04-0,9468
5,67-0,5755
6,30,0168
6,930,6026
7,560,9571
8,190,9441
8,820,5686
9,45-0,0252
10,08-0,6093
10,71-0,9595
11,34-0,9413
11,97-0,5616
12,60,0336

écart maximal entre la ligne brisée et la courbe 0,4409

f(x) = sin(x)
La même sinusoïde, calculée en un nombre réglable de points. La courbe continue est la fonction, la ligne brisée qui porte les points est ce qu'un programme trace réellement, et l'écart entre les deux se lit sous la figure.
À manipuler
Partir de sept points et regarder l'écart affiché sous la figure : la sinusoïde a perdu ses sommets et ses creux, et rien dans l'image ne signale que le tracé est faux. Monter ensuite jusqu'à ce que l'écart passe sous un centième, et noter combien de points il a fallu. Doubler enfin ce nombre : l'écart ne se divise pas par deux, il s'effondre bien plus vite, parce que chaque segment couvre un morceau de courbe deux fois plus court et deux fois moins courbé.

La règle pratique qui en découle : quand une courbe présente des angles inattendus, des sommets aplatis ou une allure trop anguleuse, la première chose à augmenter est le nombre de points, pas la taille de l'image.

Vérification rapideon peut se reprendre

Une sinusoïde tracée avec trop peu de points ressemble à quoi ?

Un premier tracé

Trois gestes suffisent : fabriquer les abscisses, calculer les ordonnées, tracer.

main.py
Sortie
>_ Prêt à exécuter…

Les marqueurs o rendent visible ce que la section précédente décrivait : entre deux ronds, il n'y a aucun calcul, seulement un segment.

L'anatomie d'un graphique d'ingénieur

Le graphique ci-dessus est mathématiquement juste et techniquement inutilisable dans un rapport. Il est muet : rien ne dit si xx compte des secondes ou des kilomètres, ni si yy est une température ou une vitesse. Un nombre sans unité n'a aucune valeur physique.

Quatre éléments sont exigibles :

ÉlémentCommandeCe qu'il évite
Nom et unité de l'axe horizontalplt.xlabel("Temps (s)")une abscisse indéchiffrable
Nom et unité de l'axe verticalplt.ylabel("Vitesse (m/s)")une ordonnée indéchiffrable
Titre décrivant le phénomèneplt.title(...)une figure qui dépend du texte autour
Grilleplt.grid(True)un dessin dont on ne peut lire aucune valeur

La grille mérite un mot : elle transforme une image en instrument de lecture. Sans elle, personne ne peut dire ce que vaut la vitesse à t=3t = 3 s, et la figure ne sert plus qu'à montrer une allure.

main.py
Sortie
>_ Prêt à exécuter…

Modèle ou mesures : ligne ou points ?

Comparer une théorie à des relevés est l'usage le plus fréquent de Matplotlib en sciences. Deux natures de données se rencontrent alors, et le style les distingue :

  • Des mesures : des valeurs isolées, entachées d'incertitude, connues seulement là où le capteur a relevé. Elles se tracent en marqueurs non reliés.
  • Un modèle : une fonction définie partout. Il se trace en ligne continue.

Relier des points de mesure par des segments affirme quelque chose de faux : que le phénomène a suivi une droite entre deux relevés, alors que rien n'a été observé dans cet intervalle.

main.py
Sortie
>_ Prêt à exécuter…
Vérification rapideon peut se reprendre

1.Un graphique d'ingénieur doit toujours porter quoi, sans exception ?

2.Mesures expérimentales et modèle théorique sur le même graphique : comment les distinguer ?

Dire l'incertitude : les barres d'erreur

Une mesure sans incertitude est une mesure incomplète. plt.errorbar ajoute à chaque point la barre qui représente sa dispersion, et la figure cesse de prétendre à une précision qu'elle n'a pas.

main.py
Sortie
>_ Prêt à exécuter…

L'argument fmt='o' impose les marqueurs seuls, et capsize dessine les petits traits horizontaux au bout des barres. Un modèle qui traverse toutes les barres est compatible avec l'expérience ; un modèle qui passe systématiquement au-dessus ne l'est pas, et aucun coefficient de corrélation ne le dira aussi clairement.

Comparer plusieurs courbes

Tant que plt.show() n'est pas appelé, Matplotlib dessine sur la même figure. Plusieurs plt.plot s'y empilent donc, et c'est le label de chacun que plt.legend() va chercher.

main.py
Sortie
>_ Prêt à exécuter…

L'appel à plt.axis('equal') n'est pas cosmétique. Une trajectoire dessinée avec des échelles différentes en xx et en yy donne une impression d'angle fausse, et c'est sur cette impression que le lecteur se fera un avis.

Vérification rapideon peut se reprendre

Dans un script lancé sur une machine, plt.show() placé avant plt.plot() produit quoi ?

Dans l'éditeur de cette page, show() ne vide rien
Le navigateur n'ouvre aucune fenêtre. La page récupère, à la fin de l'exécution du bloc, toutes les figures encore ouvertes, et les affiche sous le code. plt.show() n'y interrompt donc rien et ne remet pas la figure à zéro : un tracé placé après lui s'ajoute à la même image, au lieu de se perdre.
Garder malgré tout l'ordre habituel, show() en dernier : c'est celui qui reste juste partout, et le code d'un chapitre doit pouvoir être recopié dans un fichier sans se comporter autrement.

Plusieurs tracés côte à côte

Des mètres et des mètres par seconde ne partagent pas un axe vertical : superposées, la grandeur aux petites valeurs devient une ligne plate. plt.subplots découpe alors la feuille en zones distinctes.

La syntaxe change : le travail ne se fait plus sur plt mais sur chaque zone, appelée un axe, et les commandes prennent le préfixe set_ (plt.title devient ax.set_title).

main.py
Sortie
>_ Prêt à exécuter…
Vérification rapideon peut se reprendre

À quoi servent les sous-graphiques ?

Quand les valeurs s'étalent sur plusieurs décades

Une grandeur qui passe de 0,001 à 10 000 est illisible en échelle linéaire : tout ce qui est petit s'écrase sur l'axe. L'échelle logarithmique corrige cela, et transforme au passage toute loi de puissance en droite, dont la pente donne l'exposant.

main.py
Sortie
>_ Prêt à exécuter…

which="both" affiche aussi la grille intermédiaire, celle des 2, 3, 5 entre deux puissances de dix, sans laquelle une lecture précise redevient impossible.

Tracer ce qui vient d'un fichier

Les données réelles ne sont pas fabriquées par linspace : elles arrivent dans un fichier. Le chapitre sur les fichiers donne la lecture, et il ne reste qu'à en faire deux listes.

main.py
Sortie
>_ Prêt à exécuter…

Le style 'o-' se justifie ici, alors qu'il était déconseillé plus haut : les relevés sont assez rapprochés et assez peu bruités pour que le trait aide à suivre la chronologie sans rien affirmer de faux entre deux points.

Annoter ce qui compte

Une figure de rapport doit désigner ce qu'elle démontre. Trois commandes y suffisent :

main.py
Sortie
>_ Prêt à exécuter…

xy est le point désigné, xytext l'endroit où se pose le texte. Une annotation bien placée remplace souvent un paragraphe de commentaire.

Enregistrer la figure

Une capture d'écran d'un graphique est floue à l'impression. plt.savefig produit un fichier propre, et doit être appelé avant plt.show() : sur une machine, la fenêtre refermée emporte la figure, et le fichier écrit après coup serait vide.

plt.savefig("reponse_moteur.png", dpi=200, bbox_inches="tight")
plt.show()
ArgumentRôle
dpi=200résolution suffisante pour une impression A4
bbox_inches="tight"rogne les marges blanches qui déséquilibrent la mise en page
extension .pdfformat vectoriel, net à n'importe quel agrandissement

Montrer une distribution

Jusqu'ici, chaque point de chaque figure était une mesure à un instant, et la question portait sur l'évolution. Il arrive que la question soit autre : comment se répartissent trois cents temps de réponse, trois cents temps de tour, trois cents relevés d'un capteur ? L'ordre des mesures n'a plus d'importance, seule compte la façon dont elles s'étalent.

Trois figures y répondent, et elles ne disent pas la même chose. Le chapitre de statistiques explique comment les lire ; celui-ci montre comment les tracer, sur deux services web mesurés sur trois cents requêtes chacun. Le premier interroge sa base à chaque fois, le second sert la moitié de ses réponses depuis un cache.

main.py
Sortie
>_ Prêt à exécuter…

Les deux boîtes sont presque superposables : moins d'une milliseconde d'écart sur chaque quartile, ce que les nombres imprimés sous la figure confirment. Les deux violons, eux, n'ont rien de commun : une bosse d'un côté, deux de l'autre, avec un creux autour de 40 ms là où tombe justement la médiane. Les histogrammes disent la même chose que les violons, mais superposés ils se lisent mal, et au-delà de deux séries ils deviennent illisibles. C'est la raison d'être du violon : la forme de chaque série, côte à côte, sur un axe commun.

AppelCe qu'il traceRéglage qui compte
plt.hist(x, bins=...)une série, comptée classe par classebins : le nombre ou les bornes des classes
plt.boxplot([x, y])une boîte par sériewhis=1.5, la longueur des moustaches en écarts interquartiles
plt.violinplot([x, y])un violon par sériebw_method, la largeur des noyaux ; showmedians=True
Les quartiles de matplotlib ne sont pas ceux du cours
plt.boxplot calcule ses quartiles comme np.percentile, en interpolant entre deux rangs, et non par la règle des deux moitiés qu'applique le cours de statistiques. Sur trois cents valeurs, la différence ne se voit pas. Sur les douze relevés du chapitre de statistiques, elle donne 17,25 au lieu de 16,5 pour Q₁ : une boîte tracée par matplotlib et une boîte calculée à la main ne coïncident donc pas toujours, et aucune des deux n'est fausse.
À manipuler
Ajouter bw_method=0.1 dans l'appel à violinplot : le violon du service sans cache se hérisse de petites bosses qui ne sont que du bruit. Essayer ensuite bw_method=1 : les deux bosses du service avec cache se fondent en une seule, et le violon ne montre plus rien de plus que la boîte. La forme d'un violon dépend de ce réglage, et le laisser par défaut est un choix, pas une neutralité.

Deux détails pour qui compare des figures produites par des outils différents. violinplot arrête chaque violon à la plus petite et à la plus grande valeur observées ; la bibliothèque seaborn, courante sur une machine mais absente de l'éditeur du navigateur, prolonge au contraire la courbe au-delà, jusqu'à produire des durées négatives. Et violinplot ne trace pas les quartiles par défaut : quantiles=[[0.25, 0.75]] * 2 les ajoute, une liste par série.

Vérification rapideon peut se reprendre

Deux séries donnent deux boîtes à moustaches presque identiques. Que montre en plus un violon ?

Trois façons de mentir sans le vouloir

PiègeEffetCorrection
Axe vertical ne partant pas de zéroune variation de 2 % paraît spectaculaireplt.ylim(0, ...), ou le signaler dans le titre
Trop peu de points calculéssommets rabotés, allure fausseaugmenter le troisième argument de linspace
Courbes distinguées par la seule couleurindiscernables en noir et blanc et pour un daltonienvarier aussi linestyle et marker

Le premier piège est le plus répandu, et le seul qui soit parfois légitime : zoomer sur une plage étroite est utile quand la variation est le sujet. Ce qui ne l'est pas, c'est de laisser croire à un effondrement là où il y a une fluctuation de quelques pour cent.

Mettre en pratique

Exercice : la décharge du condensateur. La tension aux bornes d'un condensateur qui se décharge suit u(t)=U0e−t/τu(t) = U_0 e^{-t/\tau}. Avec U0=5U_0 = 5 V et τ=0,2\tau = 0{,}2 s, tracer la courbe sur une seconde, marquer la constante de temps, et habiller la figure aux normes.

main.py
Sortie
>_ Prêt à exécuter…
Afficher la solution
main.py
Sortie
>_ Prêt à exécuter…

Trois choix méritent d'être remarqués : les 300 points, sans lesquels la chute initiale serait tronquée ; le ylim(0, 5.5), qui ancre l'axe à zéro et rend l'amplitude honnête ; et le savefig placé avant show, sans quoi le fichier enregistré serait vide.

Pour aller plus loin : superposer la décharge pour trois valeurs de τ\tau (0,1, 0,2 et 0,5 s) et vérifier sur la figure que la tension restante à t=τt = \tau est la même dans les trois cas.

Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?

Dans la bibliothèque d'exercices