Graphiques ========== La bibliothèque de fonctions `Matplotlib`__ permet d'obtenir des représentations graphiques de toutes sortes, statiques ou animées. Le module racine de cette bibliothèque est :py:obj:`matplotlib`. __ https://matplotlib.org/ Le sous-module le plus utilisé est :py:obj:`matplotlib.pyplot`, que nous importerons au moyen d'un alias : .. code-block:: python :linenos: import matplotlib.pyplot as plt Le module `pyplot`__ apporte des fonctions qui permettent d'obtenir très simplement des représentations graphiques de fonctions mathématiques ou de données tabulaires. __ https://matplotlib.org/stable/api/pyplot_summary.html Dans cette page, nous décrivons seulement l'utilisation de la fonction :py:obj:`matplotlib.pyplot.plot`, qui permet de tracer des nuages de points et des courbes. Création d'une figure --------------------- La fonction `pyplot.figure`__ crée une figure dans laquelle les fonctions suivantes opèrent. Pour afficher toutes les figures, on utilise la fonction `pyplot.show`__. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.show.html#matplotlib.pyplot.show Par exemple, voici comment créer et afficher deux figures : .. code-block:: python :linenos: plt.figure() # création de la figure 1 # fonctions graphiques opérant sur la figure 1 plt.figure() # création de la figure 2 # fonction graphiques opérant sur la figure 2 plt.show() # affichage des figures La fonction :py:obj:`plt.show` a pour effet d'ouvrir une fenêtre pour chaque figure. Cette fonction est bloquante : il faut fermer toute les fenêtres pour que l'exécution du script se poursuive. Il est donc conseillé de placer l'appel de cette fonction à la fin du script. Représentation graphique d'un nuage de points --------------------------------------------- La fonction `pyplot.plot`__ permet de tracer des points dans un plan muni de deux axes. Pour l'utiliser, il faut tout d'abord placer les coordonnées de ces points dans deux tableaux :py:obj:`numpy.ndarray` (:ref:`tableaux_une_dimension`). Il est aussi possible d'utiliser des listes, mais les calculs numériques et les lectures de fichiers conduisent en général à des tableaux. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot La fonction prend au minimum deux arguments, selon la syntaxe suivante : .. code-block:: python :linenos: plt.plot(X,Y) où X est un tableau à une dimension (ou une liste) contenant les abscisses des points et Y un tableau à une dimension (ou une liste) contenant les ordonnées de ces points. Ces deux tableaux doivent avoir la même longueur (N). Il est cependant possible d'omettre le tableau des abscisses, auquel cas il est remplacé par un tableau contenant les nombres entiers de 0 à N-1, qui correspondent aux indices du tableau Y. Ces deux tableaux peuvent provenir d'un calcul numérique ou bien de données expérimentales. Considérons ce dernier cas et supposons que les données soient sous forme tabulaire dans un fichier texte. L'expérience est un dosage potentiométrique d'une solution de sulfate de fer par une solution de sulfate de cérium. Les données ont été saisies dans un fichier CSV dont voici les premières lignes : :: v (mL) U (mV) 0 318 0.5 343 1 357 1.5 366 La première colonne contient le volume versé, la seconde contient la tension mesurée. Le fichier est nommé `titrageFeSO4-Ce4.csv`. La lecture du fichier se fait comme expliqué dans :ref:`enregistrement_tableau_fichier` : .. code-block:: python :linenos: v,U = np.loadtxt('titrageFeSO4-Ce4.csv',skiprows=1,unpack=True) La structure des deux tableaux est obtenue avec l'attribut :py:obj:`shape` : >>> v.shape,U.shape ((54,), (54,)) Il s'agit donc de deux tableaux de longueur 54. Il est souvent nécessaire de faire des calculs sur les données avant de tracer les points. Dans le cas présent, on veut calculer le potentiel d'oxydoréduction et le convertir en volts : .. code-block:: python :linenos: E = (U+244)*1e-3 Voici comment obtenir la représentation graphique du potentiel en fonction du volume, sous forme de points : .. code-block:: python :linenos: plt.figure() plt.plot(v,E,'ro') plt.xlabel('v (mL)',fontsize=18) plt.ylabel('E (V)',fontsize=18) plt.grid() plt.savefig('dosage-potentiometrique.png') plt.show() Le troisième argument fourni à la fonction :py:obj:`plot` correspond au paramètre optionnel :py:obj:`fmt`, qui précise le format des points sous la forme d'une chaîne de caractères. Le caractère 'r' signifie que les points sont coloriés en rouge, le caractère 'o' que les marqueurs sont des ronds. Les différents marqueurs et couleurs sont listés dans la documentation de `plot`__. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot Les fonctions `pyplot.xlabel`__ et `pyplot.ylabel`__ permettent de légender les axes. La fonction `pyplot.grid`__ permet d'ajouter une grille. La figure peut être enregistrée sous différents formats d'image (PNG, JPG, PDF). Il est possible de le faire à la main depuis la fenêtre graphique, ou bien directement dans le script au moyen de la fonction `pyplot.savefig`__. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.xlabel.html#matplotlib.pyplot.xlabel __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.ylabel.html#matplotlib.pyplot.ylabel __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.grid.html#matplotlib.pyplot.grid __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html#matplotlib.pyplot.savefig Voici la figure obtenue : .. image:: dosage-potentiometrique.png :scale: 50 La fonction :py:obj:`plot` calcule les intervalles d'abscisses et d'ordonnées qui permettent de représenter tous les points demandés, mais il est fréquent qu'on souhaite ajuster soi-même ces intervalles. Cela se fait au moyen des fonctions `pyplot.xlim`__ et `pyplot.ylim`__. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.xlim.html#matplotlib.pyplot.xlim __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.ylim.html#matplotlib.pyplot.ylim Voici par exemple comment refaire le graphique précédent avec une échelle de potentiel s'étendant de 0 à 2 V. On ajoute aussi un titre au moyen de la fonction `pyplot.title`__. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.title.html#matplotlib.pyplot.title .. code-block:: python :linenos: plt.figure() plt.plot(v,E,"ro") plt.ylim(0,2) plt.xlabel('v (mL)',fontsize=18) plt.ylabel('E (V)',fontsize=18) plt.title('Dosage de FeSO4 par CeSO4') plt.grid() plt.show() .. image:: dosage-potentiometrique-2.png :scale: 50 Représentation graphique d'une fonction --------------------------------------- La représentation graphique d'une fonction (d'une variable réelle et à valeurs réelles) s'obtient aussi avec la fonction :py:obj:`plot`. Il faut pour cela échantillonner la fonction sur l'intervalle choisi et tracer les points correspondants. Prenons comme exemple le tracé de la courbe de gain et de déphasage d'un système linéaire dont la fonction de transfert en régime harmonique est définie par : .. math:: \underline{H}(f)=\frac{1}{1+j\frac{f}{f_c}} Il s'agit d'un filtre passe-bas du premier ordre dont la fréquence de coupure est :math:`f_c`. Un tracé général est obtenu au moyen de la variable sans dimensions :math:`x=f/f_c`. On commence par échantillonner l'intervalle de x choisi puis on génère un tableau contenant les valeurs correspondantes de :math:`\underline{H}` : .. code-block:: python :linenos: x = np.linspace(0,10,100) H = 1/(1+1j*x) Le tableau :py:obj:`H` contient des nombres complexes (type :py:obj:`numpy.complex128`). Voici comment calculer le module pour obtenir le gain : .. code-block:: python :linenos: G = np.absolute(H) Le tracé de la courbe représentant le gain en fonction de x se fait comme celui d'un nuage de points, avec la fonction :py:obj:`plot`, mais il ne faut pas marquer les points et les relier par des segments rectilignes : .. code-block:: python :linenos: plt.figure() plt.plot(x,G,'b-') # points reliés par des segments rectilignes de couleur bleue plt.grid() plt.xlabel('f/fc',fontsize=18) plt.ylabel('G',fontsize=18) plt.savefig('gain-filtre.png') plt.show() .. image:: gain-filtre.png :scale: 50 Les autres types de traits sont listés dans la documentation de `plot`__. Le fait de relier les points de l'échantillonnage par des segments rectilignes revient à faire une interpolation linéaire entre ces points. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot La ligne obtenue est une bonne représentation de la fonction si le nombre de points de l'échantillonnage est assez grand. Celui-ci doit être adapté aux variations de la fonction sur l'intervalle considéré et il est parfois nécessaire de faire quelques essais avant de trouver le nombre d'échantillons suffisants. Voici ce qu'on obtient avec seulement 10 échantillons : .. image:: gain-filtre-2.png :scale: 50 Si l'on souhaite faire varier x sur plusieurs décades et faire un tracé en échelle logarithmique, l'échantillonnage peut être fait avec la fonction `numpy.logspace`__, qui répartit les points uniformément sur une échelle logarithmique : __ https://numpy.org/doc/1.20/reference/generated/numpy.logspace.html#numpy.logspace .. code-block:: python :linenos: x = np.logspace(-2,2,100) H=1/(1+1j*x) GdB = 20*np.log10(np.absolute(H)) plt.figure() plt.plot(x,GdB,"b-") plt.xlabel('f/fc',fontsize=18) plt.ylabel('G dB',fontsize=18) plt.grid() plt.xscale('log') plt.ylim(-40,10) plt.savefig('gaindB-filtre.png') plt.show() La fonction `pyplot.xscale`__ permet d'obtenir une échelle d'abscisse logarithmique. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.xscale.html#matplotlib.pyplot.xscale .. image:: gaindB-filtre.png :scale: 50 Tracé de plusieurs courbes -------------------------- Il est fréquent qu'on ait besoin de tracer plusieurs courbes sur la même figure afin de les comparer. Considérons par exemple la fonction de transfert suivante : .. math:: \underline{H}(f)=\frac{1}{1+j\frac{1}{Q}\frac{f}{f_c}-\left(\frac{f}{f_c}\right)^2} Nous souhaitons tracer la représentation graphique du gain pour différentes valeurs de Q. Lorsqu'une expression doit être évaluée plusieurs fois, il est judicieux de définir une fonction (:ref:`fonction_python`) : .. code-block:: python :linenos: def gain(x,Q): H = 1/(1+1j*x/Q-x**2) return np.absolute(H) Pour tracer plusieurs courbes, il suffit d'appeler la fonction :py:obj:`plot` autant de fois que nécessaire. Le paramètre optionnel :py:obj:`label` permet de donner un nom à chaque courbe et les noms sont affichés grace à la fonction `pyplot.legend`__. __ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend .. code-block:: python :linenos: x = np.logspace(-1,1,500) plt.figure() Q = 0.5 GdB = gain_db(x,Q) plt.plot(x,GdB,"k-",label='Q=%0.1f'%Q) Q = 1 GdB = gain_db(x,Q) plt.plot(x,GdB,"r-",label='Q=%0.1f'%Q) Q = 10 GdB = gain_db(x,Q) plt.plot(x,GdB,"b-",label='Q=%0.1f'%Q) plt.xlabel('f/fc',fontsize=18) plt.ylabel('G dB',fontsize=18) plt.grid() plt.xscale('log') plt.ylim(-40,20) plt.legend(loc='upper right') plt.savefig('gaindB-filtre2.png') plt.show() .. image:: gaindB-filtre2.png :scale: 50 Courbe plane paramétrée ----------------------- Considérons le mouvement d'un point situé sur un cercle de rayon :math:`R` qui roule sur un plan en tournant à la vitesse angulaire :math:`\omega` et dont le centre se déplace à la vitesse :math:`V`. On cherche à tracer la trajectoire de ce point. Si le roulement se fait sans glissement (:math:`V=R\omega`), il s'agit d'une cycloïde. .. math:: \begin{align} &x=Vt+R\cos(\omega t)\\ &y=R(1+\sin(\omega t)) \end{align} Après avoir échantillonné le paramètre sur l'intervalle choisi, on calcule deux tableaux contenant respectivement x et y : .. code-block:: python :linenos: R = 1 omega = 2*np.pi V = R*omega t = np.linspace(0,4,500) x = V*t+R*np.cos(omega*t) y = R*(1+np.sin(omega*t)) Le tracé de la courbe paramétrée se fait avec la fonction :py:obj:`plot`. Afin d'obtenir une représentation correcte de la forme de la courbe, il faut que l'échelle soit identique sur les deux axes, ce qui s'obtient en ajoutant :py:obj:`plt.axes().set_aspect('equal')`. .. code-block:: python :linenos: plt.figure() plt.plot(x,y,"r-") plt.axes().set_aspect('equal') plt.xlabel('x',fontsize=16) plt.ylabel('y',fontsize=16) plt.grid() plt.show() .. image:: trajectoire.png :scale: 100