Python et Visualisation : de l’anatomie de Matplotlib aux surfaces 3D
Python doit une bonne partie de sa popularité scientifique à un trio de bibliothèques : NumPy pour le calcul vectoriel, Matplotlib pour le tracé, et Pandas pour la manipulation de données tabulaires. Ensemble, elles couvrent tout le chemin qui va d’une fonction mathématique abstraite à un graphique lisible.
Cet article suit un fil progressif : d’abord comprendre comment Matplotlib construit un graphique (Figure, Axes, et les deux façons de les piloter), puis l’appliquer à des fonctions d’une variable, à des surfaces en trois dimensions, au choix des couleurs, et enfin aux statistiques descriptives d’un jeu de données.
0. Anatomie d’un graphique Matplotlib
Avant de tracer quoi que ce soit, il est utile de comprendre les deux objets que Matplotlib manipule en permanence :
- la
Figureest la toile entière — le conteneur qui peut accueillir un ou plusieurs graphiques, une légende globale, un titre, etc. ; - l’
Axesest un graphique individuel à l’intérieur de cette toile, avec ses propres axes X/Y, ses graduations, son titre. C’est sur l’Axesque l’on trace les courbes.
Matplotlib propose alors deux façons d’écrire le même graphique, selon la documentation officielle : l’interface orientée objet (fig, ax = plt.subplots(), en gardant une référence explicite à fig et ax) et l’interface pyplot (un état implicite modifié par des fonctions globales comme plt.plot()). Les deux produisent un résultat identique, mais l’interface orientée objet est plus explicite et plus flexible : elle permet de garder la main sur chaque élément du graphique avant de l’afficher, notamment lorsqu’on combine plusieurs Axes dans une même Figure. C’est celle que nous utiliserons pour les graphiques les plus riches de cet article — les exemples les plus simples ci-dessous gardent volontairement l’interface pyplot, plus courte à lire.
1. Fonctions d’une variable réelle :
Cette section parcourt trois familles de fonctions représentatives — chacune illustre un comportement mathématique différent : croissance, saturation, borne.
Croissance et décroissance exponentielles : et
L’exponentielle est la fonction qui vérifie : sa vitesse de variation est à chaque instant proportionnelle à sa propre valeur. C’est le modèle de base de toute croissance (population, intérêts composés) ou décroissance (désintégration radioactive, décharge d’un condensateur) proportionnelle à la quantité présente.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2, 2, 300)
y_croissance = np.exp(x)
y_decroissance = np.exp(-x)
plt.figure(figsize=(10, 5))
plt.plot(x, y_croissance, color='steelblue', linewidth=2, label='croissance : e^x')
plt.plot(x, y_decroissance, color='tomato', linewidth=2, label='décroissance : e^-x')
plt.title('Croissance et décroissance exponentielles')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(loc='upper center')
plt.grid(True, alpha=0.3)
plt.show()
La fonction logistique et sa dérivée :
La sigmoïde transforme n’importe quel nombre réel en une valeur entre et : elle sature vers quand et vers quand , avec un point d’inflexion en . C’est la fonction d’activation historique des réseaux de neurones, et le cœur de la régression logistique.
Sa dérivée a une propriété remarquable, utile pour la rétropropagation du gradient : , une courbe en cloche maximale exactement là où varie le plus vite.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-6, 6, 400)
sigmoide = 1 / (1 + np.exp(-x))
derivee = sigmoide * (1 - sigmoide)
plt.figure(figsize=(10, 5))
plt.plot(x, sigmoide, color='steelblue', linewidth=2, label='sigmoïde')
plt.plot(x, derivee, color='tomato', linewidth=2, linestyle='--', label='dérivée')
plt.title('Fonction logistique (sigmoïde)')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
plt.show()
Une fonction rationnelle bornée :
Contrairement à l’exponentielle vue plus haut, qui explose vers l’infini, cette fonction reste bornée () sur tout , tout en restant infiniment différentiable — sans jamais utiliser d’exponentielle ni de trigonométrie. Son maximum global vaut en , et elle décroît vers de part et d’autre, un peu comme la cloche gaussienne de la section suivante, mais avec une décroissance beaucoup plus lente (en plutôt qu’en ).
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 400)
y = 1 / (1 + x**2)
plt.figure(figsize=(10, 5))
plt.plot(x, y, color='steelblue', linewidth=2)
plt.title('Fonction rationnelle bornée')
plt.xlabel('x')
plt.ylabel('y')
plt.grid(True, alpha=0.3)
plt.show()
2. Fonctions de deux variables :
Lorsqu’une fonction dépend de deux variables réelles et , son graphe n’est plus une courbe dans le plan mais une surface dans l’espace . On représente alors en trois dimensions.
Pour cela, Matplotlib propose le module mpl_toolkits.mplot3d et la fonction plot_surface. Sur cette page, les graphiques 3D ci-dessous sont interactifs : cliquez-glissez pour les faire pivoter et observer la surface sous un autre angle.
Préparation : la grille de points
La visualisation d’une surface nécessite de créer une grille régulière de points couvrant le domaine souhaité. La fonction np.meshgrid génère cette grille :
import numpy as np
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
# X et Y sont des matrices 100×100
# chaque point (X[i,j], Y[i,j]) correspond à un point de la grille
Surface paraboloïde :
Le paraboloïde elliptique est la surface la plus simple à deux variables. Son point minimal est en et la surface s’élève symétriquement dans toutes les directions. C’est la généralisation en 2D de la parabole.
Cette surface est différentiable partout. Son gradient est nul en , qui est un minimum global.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = X**2 + Y**2
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.5, label='z = f(x, y)')
ax.set_title('Paraboloïde : $f(x, y) = x^2 + y^2$')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.tight_layout()
plt.show()
Selle de cheval (col) :
La selle de cheval est l’exemple typique d’un point critique qui n’est ni un minimum ni un maximum. En , la dérivée est nulle dans toutes les directions, mais la surface monte dans la direction et descend dans la direction : on appelle ce point un point selle ou col.
La matrice hessienne a un déterminant négatif (), ce qui confirme le point selle.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = X**2 - Y**2
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='coolwarm', alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.5, label='z = f(x, y)')
ax.set_title('Selle de cheval : $f(x, y) = x^2 - y^2$')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.tight_layout()
plt.show()
Surface ondulée :
La combinaison de fonctions trigonométriques sur deux variables produit une surface périodique dans les deux directions. Cette surface est différentiable partout et possède une infinité de points critiques correspondant aux maxima (), minima () et points selles.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-2 * np.pi, 2 * np.pi, 200)
y = np.linspace(-2 * np.pi, 2 * np.pi, 200)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='plasma', alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.5, label='z = f(x, y)')
ax.set_title('Surface ondulée : $f(x, y) = \\sin(x)\\cos(y)$')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.tight_layout()
plt.show()
Cloche gaussienne :
La cloche gaussienne en deux dimensions est la généralisation de la distribution normale. Elle possède un maximum global en où , et tend vers dans toutes les directions. Elle est différentiable partout et ses courbes de niveau sont des cercles concentriques.
Cette surface intervient notamment en probabilités (densité de la loi normale bivariée) et en traitement du signal (filtre gaussien).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2))
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='magma', alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.5, label='z = f(x, y)')
ax.set_title('Cloche gaussienne : $f(x, y) = e^{-(x^2+y^2)}$')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.tight_layout()
plt.show()
Fonction non différentiable :
Toutes les fonctions ne sont pas différentiables. La fonction est continue partout mais non différentiable le long des axes et , car la dérivée partielle n’y existe pas (la pente change brutalement).
Son graphe forme un croisement de plis : quatre faces planes qui se rejoignent sur les axes avec des arêtes nettes. Les courbes de niveau sont des carrés, et le minimum global est en .
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-5, 5, 200)
y = np.linspace(-5, 5, 200)
X, Y = np.meshgrid(x, y)
Z = np.abs(X) + np.abs(Y)
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='YlOrRd', alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.5, label='z = f(x, y)')
ax.set_title('Non différentiable : $f(x, y) = |x| + |y|$')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.tight_layout()
plt.show()
Courbes de niveau : une autre façon de lire une surface
Les courbes de niveau (ou isocourbes) sont les projections horizontales de la surface : pour chaque valeur , elles représentent l’ensemble des points tels que . C’est la représentation utilisée sur les cartes topographiques — souvent plus lisible qu’une surface 3D pour repérer précisément un extremum ou comparer deux valeurs.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 300)
y = np.linspace(-3, 3, 300)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Surface en couleur
c1 = axes[0].contourf(X, Y, Z, levels=30, cmap='plasma')
fig.colorbar(c1, ax=axes[0], label='f(x, y)')
axes[0].set_title('Carte de chaleur : $f(x,y) = \\sin(x)\\cos(y)$')
axes[0].set_xlabel('x')
axes[0].set_ylabel('y')
# Courbes de niveau
c2 = axes[1].contour(X, Y, Z, levels=20, cmap='plasma')
axes[1].clabel(c2, inline=True, fontsize=8)
axes[1].set_title('Courbes de niveau')
axes[1].set_xlabel('x')
axes[1].set_ylabel('y')
plt.tight_layout()
plt.show()
3. Bien choisir ses couleurs
Le choix d’une carte de couleurs (cmap) n’est pas qu’une question esthétique : une mauvaise carte peut faire apparaître de faux motifs ou masquer de vraies variations. Jusqu’en 2015, la carte par défaut de Matplotlib était jet, un dégradé arc-en-ciel dont la luminosité varie de façon irrégulière — deux valeurs proches peuvent y sembler très différentes, et inversement.
En 2015, Nathaniel Smith et Stéfan van der Walt ont conçu quatre cartes perceptuellement uniformes — viridis, plasma, magma, inferno — où la luminosité croît de façon monotone avec la valeur représentée. Elles restent lisibles en niveaux de gris et pour les daltoniens, et viridis est devenue la carte par défaut de Matplotlib à partir de la version 2.0 (voir le guide officiel des colormaps).
On distingue en pratique trois grandes familles :
| Famille | Usage | Exemples | Utilisée plus haut pour |
|---|---|---|---|
| Séquentielle | une grandeur qui croît du plus faible au plus fort, sans valeur centrale particulière | viridis, plasma, magma, YlOrRd | le paraboloïde (toujours ), la cloche gaussienne, la surface |
| Divergente | une grandeur avec un centre neutre significatif (souvent zéro), qui s’écarte dans deux directions | coolwarm, RdBu, PiYG | la selle de cheval , positive d’un côté, négative de l’autre |
| Qualitative | des catégories sans ordre naturel | tab10, Set2, Paired | courbes multiples (voir steelblue / tomato plus haut) |
C’est pourquoi la selle de cheval utilise coolwarm plutôt que viridis : sa valeur change de signe autour de , et une carte divergente rend ce basculement immédiatement visible.
4. Statistiques descriptives avec NumPy et Pandas
La visualisation ne se limite pas aux fonctions mathématiques : elle est aussi au cœur de l’analyse de données. Avant toute modélisation, il est essentiel de comprendre la distribution des variables.
La distribution normale
La distribution normale (ou gaussienne) est caractérisée par deux paramètres : la moyenne (centre de la distribution) et l’écart-type (dispersion). Sa densité de probabilité est :
L’histogramme permet de visualiser la distribution empirique d’un échantillon et de la comparer à la courbe théorique :
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
mu, sigma = 100, 15
data = np.random.normal(mu, sigma, 1000)
x_range = np.linspace(mu - 4*sigma, mu + 4*sigma, 300)
fig, ax = plt.subplots(figsize=(10, 5))
ax.hist(data, bins=40, density=True, color='steelblue', alpha=0.6, label='Échantillon empirique')
ax.plot(x_range, norm.pdf(x_range, mu, sigma), color='tomato', linewidth=2,
label=r'Densité théorique $\mathcal{N}(100, 15^2)$')
ax.set_title('Distribution normale : échantillon vs densité théorique')
ax.set_xlabel('Valeur')
ax.set_ylabel('Densité')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Plus l’échantillon est grand, plus l’histogramme empirique (en bleu) se rapproche de la courbe théorique (en rouge) — c’est une illustration directe de la loi des grands nombres.
Résumé statistique avec Pandas
La méthode describe() de Pandas fournit en une seule commande les principales mesures de tendance centrale et de dispersion :
import numpy as np
import pandas as pd
data = np.random.normal(100, 15, 1000)
serie = pd.Series(data, name='mesures')
print(serie.describe())
| Statistique | Signification |
|---|---|
count | Nombre d’observations |
mean | Moyenne arithmétique |
std | Écart-type empirique |
min / max | Valeurs extrêmes |
25% / 50% / 75% | Quartiles , médiane, |
Fonctions statistiques NumPy
NumPy propose un ensemble de fonctions directement applicables sur des tableaux :
import numpy as np
notes = np.random.normal(12, 3, 50)
print(f"Moyenne : {np.mean(notes):.2f}")
print(f"Écart-type: {np.std(notes):.2f}")
print(f"Minimum : {np.min(notes):.2f}")
print(f"Maximum : {np.max(notes):.2f}")
print(f"Médiane : {np.median(notes):.2f}")
La moyenne mesure le centre de gravité des données. L’écart-type quantifie leur dispersion autour de cette moyenne. La médiane est la valeur qui sépare les 50% inférieurs des 50% supérieurs : elle est plus robuste que la moyenne en présence de valeurs aberrantes.
Inspection rapide : head() et tail()
Pour un premier aperçu d’un jeu de données, head() affiche les premières lignes et tail() les dernières :
import numpy as np
import pandas as pd
temperatures = np.random.uniform(15, 35, 100)
serie = pd.Series(temperatures, name='température (°C)')
print("Premières valeurs :")
print(serie.head(8))
print("\nDernières valeurs :")
print(serie.tail(5))
Récapitulatif
| Type de visualisation | Fonction clé | Cas d’usage |
|---|---|---|
| Courbe 2D | plt.plot() | Fonctions |
| Histogramme | plt.hist() | Distribution d’un échantillon |
| Surface 3D | ax.plot_surface() | Fonctions |
| Carte de chaleur | plt.contourf() | Vue en surplomb d’une surface |
| Courbes de niveau | plt.contour() | Isocourbes, topographie |
describe() | Pandas | Résumé statistique d’une série |
Trois réflexes suffisent pour aborder n’importe quel nouveau jeu de données ou fonction : préférer l’interface orientée objet (fig, ax) dès que le graphique se complexifie, choisir une carte de couleurs séquentielle ou divergente selon que la grandeur a ou non un centre neutre, et toujours commencer l’analyse d’un échantillon par describe() avant de se lancer dans un modèle.
Python offre ainsi un environnement complet pour explorer les fonctions mathématiques dans toutes leurs dimensions : des oscillations périodiques aux surfaces complexes, des comportements différentiables aux singularités, des données brutes aux résumés statistiques. La visualisation n’est pas une étape accessoire : c’est souvent le premier geste qui révèle la structure d’un problème.