|
|
## C) Atelier 2 : un graphique avec 3 variables d'unités différentes : 2e axe des y, subplots ; les objets d'une figure
|
|
|
Cet objectif ne semble pas hyper ambitieux, et vous trouverez des tutos pour réaliser facilement cette figure, et pourtant la syntaxe peut sembler étrange... Nous allons passer par un peu de théorie pour comprendre ce qui se passe et afficher une légende correcte.
|
|
|
|
|
|
Les diapos introduisant l'Atelier ont déjà abordé ce cas, en expliquant ce qui se "cache derrière" un plt.plot, et le pourquoi de la syntaxe plus explicite " fig, ax = plt.suplots() ",
|
|
|
On y explique aussi ce qu'est vraiment le type de "ax", qui n'est pas que un axe mais se caractérise par une surface dans la figure munie d'un système d'axes...
|
|
|
Les diapos de cette partie reviennent sur ce point théorique de manière plus détaillée.
|
|
|
Cet objectif ne semble pas hyper ambitieux, et vous trouverez des tutos pour réaliser facilement cette figure, et pourtant la syntaxe peut sembler étrange... Nous allons passer par un peu de théorie pour comprendre ce qui se passe et afficher une légende correcte.
|
|
|
|
|
|
Les diapos introduisant l'Atelier ont déjà abordé ce cas, en expliquant ce qui se "cache derrière" un plt.plot, et le pourquoi de la syntaxe plus explicite **" fig, ax = plt.subplots() "**, On y explique aussi ce qu'est vraiment le type de "ax", qui n'est pas que un axe mais se caractérise par une surface dans la figure munie d'un système d'axes... Les diapos de cette partie reviennent sur ce point théorique de manière plus détaillée.
|
|
|
|
|
|
### C1) le fichier d'un TD de bilan hydrologique avec 4 colonnes : date, T, pluie, Q
|
|
|
On vous a mis plusieurs versions du fichier, qui était initialement un fichier tableur.
|
|
|
Le format original, qui comporte des dates comme 'jan-00' ou 'fév-01' est manifestement le plus compliqué à interpréter. Cela peut être un bon exercice dans la suite (en utilisant un dictionnaire qui ferait correspondre à chaque code de moi son numéro : dico_mois["jan"]=1.
|
|
|
Pour le moment, on va utiliser une version reconditionnée en un format plus standard, sur lequel on pourra utiliser la méthode read_csv de pandas qui faciliter la lecture de la création d'un tableau, avec les titres de colonne lus dans le fichier.
|
|
|
|
|
|
On vous a mis plusieurs versions du fichier, qui était initialement un fichier tableur. Le format original, qui comporte des dates comme 'jan-00' ou 'fév-01' est manifestement le plus compliqué à interpréter. Cela peut être un bon exercice dans la suite (en utilisant un dictionnaire qui ferait correspondre à chaque code de moi son numéro : dico_mois\["jan"\]=1. Pour le moment, on va utiliser une version reconditionnée en un format plus standard, sur lequel on pourra utiliser la méthode read_csv de pandas qui faciliter la lecture de la création d'un tableau, avec les titres de colonne lus dans le fichier.
|
|
|
|
|
|
Si vous voulez le faire "à la main", vous trouverez en commentaire plusieurs manières de décoder les dates, à partir de la ligne lue, toujours en format "string".
|
|
|
|
|
|
```python
|
|
|
```python
|
|
|
ligne_dateTPQ = "01/04/2000;9,3;49;8,6"
|
|
|
```
|
|
|
Comme dans la partie A, le découpage se fait avec l'instruction "split". La date est le premier de ces éléments, qui est comme les autres une chaîne de caractères (string), à convertir en date. Le plus simple est de "re-splitter" avec comme argument le séparateur "/".
|
|
|
On peut en profiter pour s'exercer sur les **tuples** (notions que l'on reverra plus loin)
|
|
|
|
|
|
```python
|
|
|
Comme dans la partie A, le découpage se fait avec l'instruction "split". La date est le premier de ces éléments, qui est comme les autres une chaîne de caractères (string), à convertir en date. Le plus simple est de "re-splitter" avec comme argument le séparateur "/". On peut en profiter pour s'exercer sur les **tuples** (notions que l'on reverra plus loin)
|
|
|
|
|
|
```python
|
|
|
|
|
|
jma = "01/04/2000".split('/') # jma est donc une liste de strings ['01', '04', '2000']
|
|
|
j, m, a = jma # unpacking : en une seule opération on affecte j= '01', m='04', a = '2000'
|
... | ... | @@ -29,52 +27,41 @@ date = datetime(int(a), int(m), int(j)) |
|
|
```
|
|
|
|
|
|
### C2) on demande à Pandas de faire tout le boulot : lire le fichier et faire un graphique
|
|
|
|
|
|
Dans un premier temps, on va profiter de la bibliothèque pandas, qui crée et travaille sur des **DataFrames**.
|
|
|
``` python
|
|
|
|
|
|
```python
|
|
|
import pandas as pd
|
|
|
```
|
|
|
On vous a donné du code déjà écrit, **donnees_TD_ETP_csv_panda_seul** ; vous pouvez sans doute l'améliorer.
|
|
|
On va s'appuyer sur la méthode read_csv pour lire le fichier et le mettre sous forme de tableau (DataFrame).
|
|
|
Il devrait être possible de tout récupérer au format voulu en une seule opération, mais ici on a dû écrire quelques étapes.
|
|
|
Pistes pour amélioration :
|
|
|
|
|
|
``` python
|
|
|
pd.read_csv(chemin_complet_du_fichier, parse_dates=True, index_col="date")
|
|
|
```
|
|
|
On vous a donné du code déjà écrit, **donnees_TD_ETP_csv_panda_seul** ; vous pouvez sans doute l'améliorer. On va s'appuyer sur la méthode read_csv pour lire le fichier et le mettre sous forme de tableau (DataFrame). Il devrait être possible de tout récupérer au format voulu en une seule opération, mais ici on a dû écrire quelques étapes. Pistes pour amélioration :
|
|
|
|
|
|
```python
|
|
|
pd.read_csv(chemin_complet_du_fichier, parse_dates=True, index_col="date")
|
|
|
```
|
|
|
|
|
|
On utilise des instructions comme "print" pour vérifier que l'on a bien lu correctement, et header pour jeter un oeil aux premières lignes du DF.
|
|
|
Dans un deuxième temps, on va convertir la première colonne (donc d'indice 0) de texte en date, et avec pd.to_datetime ; l'argument day_first permet de lever une ambiguité : le jour ou le mois est écrit en premier
|
|
|
On utilise des instructions comme "print" pour vérifier que l'on a bien lu correctement, et header pour jeter un oeil aux premières lignes du DF. Dans un deuxième temps, on va convertir la première colonne (donc d'indice 0) de texte en date, et avec pd.to_datetime ; l'argument day_first permet de lever une ambiguité : le jour ou le mois est écrit en premier
|
|
|
|
|
|
Cette colonne doit devenir l'index de la table.
|
|
|
On a donc au final un DF avec un index date et 3 colonnes T, P et Q.
|
|
|
Le module pandas permet de tracer en une opération les données : mon_data_frame.plot().
|
|
|
Cette colonne doit devenir l'index de la table. On a donc au final un DF avec un index date et 3 colonnes T, P et Q. Le module pandas permet de tracer en une opération les données : mon_data_frame.plot().
|
|
|
|
|
|
``` python
|
|
|
```python
|
|
|
DF_TD4.plot()
|
|
|
plt.suptitle("En utilisant les possibilités de pandas")
|
|
|
plt.legend()
|
|
|
plt.show()
|
|
|
```
|
|
|
![Graphique P,T, Q avec pandas](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/blob/master/illustrations/Figure_2_graphique_pandas.PNG)
|
|
|
La bonne nouvelle : l'outil zoom montre que l'affichage des étiquettes de date est correct quel que soit le niveau de zoom, contrairement à la première expérience.
|
|
|
On utilisera dans la suite des instructions qui donnent ce rendu même en dehors de Pandas.
|
|
|
La mauvaise nouvelle : les trois variables, qui s'expriment dans des unités différentes, sont toutes les 3 tracées dans le même repère. Cela permet de voir les données rapidement, mais évidemment la mise en forme n'est pas du tout satisfaisante pour un rapport. On va donc retravailler cela dans la suite.
|
|
|
|
|
|
![Graphique P,T, Q avec pandas](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/blob/master/illustrations/Figure_2_graphique_pandas.PNG) La bonne nouvelle : l'outil zoom montre que l'affichage des étiquettes de date est correct quel que soit le niveau de zoom, contrairement à la première expérience. On utilisera dans la suite des instructions qui donnent ce rendu même en dehors de Pandas. La mauvaise nouvelle : les trois variables, qui s'expriment dans des unités différentes, sont toutes les 3 tracées dans le même repère. Cela permet de voir les données rapidement, mais évidemment la mise en forme n'est pas du tout satisfaisante pour un rapport. On va donc retravailler cela dans la suite.
|
|
|
|
|
|
### C3) Mise en forme des données : une figure avec deux vignettes, dont une avec deux axes des y.
|
|
|
|
|
|
Vous pouvez partir de la fonction précédente ou de **donnees_TD_ETP_2subplots** déjà écrit; dans le développement on trouve souvent en ligne des bouts de code qu'il faut s'approprier et adapter.
|
|
|
Cette fonction reprend le début de la fonction précédente (lecture avec pd.read_csv), on trace aussi avec la méthode pd plot pour vérifier que la lecture s'est bien passée (phase que l'on peut ensuite mettre en commentaire).
|
|
|
Ensuite on a du code spécifique.
|
|
|
Si vous l'exécutez, vous verrez que ça marche, le rendu n'est pas forcément à votre goût, donc ce sera l'occasion de vous exercer à mettre en forme. On proposera par la suite une variante, vous pourrez donc étoffer votre palette.
|
|
|
![Figure mise en forme, chaque variable a son axe](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/blob/master/illustrations/Figure_3_graphique_PTQ_subplots.PNG)
|
|
|
Vous pouvez partir de la fonction précédente ou de **donnees_TD_ETP_2subplots** déjà écrit; dans le développement on trouve souvent en ligne des bouts de code qu'il faut s'approprier et adapter. Cette fonction reprend le début de la fonction précédente (lecture avec pd.read_csv), on trace aussi avec la méthode pd plot pour vérifier que la lecture s'est bien passée (phase que l'on peut ensuite mettre en commentaire). Ensuite on a du code spécifique. Si vous l'exécutez, vous verrez que ça marche, le rendu n'est pas forcément à votre goût, donc ce sera l'occasion de vous exercer à mettre en forme. On proposera par la suite une variante, vous pourrez donc étoffer votre palette. ![Figure mise en forme, chaque variable a son axe](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/blob/master/illustrations/Figure_3_graphique_PTQ_subplots.PNG)
|
|
|
|
|
|
De prime abord, certaines syntaxes peuvent paraître vraiment bizarres.
|
|
|
Pour comprendre, on n'échappera pas à un peu de théorie ; certains sites expliquent très bien la structure des figures, et cela aide vraiment à progresser et à s'approprier les notions.
|
|
|
De prime abord, certaines syntaxes peuvent paraître vraiment bizarres. Pour comprendre, on n'échappera pas à un peu de théorie ; certains sites expliquent très bien la structure des figures, et cela aide vraiment à progresser et à s'approprier les notions.
|
|
|
|
|
|
Voir les diapos.
|
|
|
|
|
|
``` python
|
|
|
```python
|
|
|
# on découpe la figure en deux subplots, sur 1 colonne et 2 lignes.
|
|
|
# La méthode subplots renvoie un objet figure et un tuple de deux subplots
|
|
|
# sharex = True lie l'axe des x des deux subplots : on zoome sur les 2 en même temps
|
... | ... | @@ -108,71 +95,63 @@ ax_q.set_ylabel(etiquette_debit, color='blue') |
|
|
ax_q.plot(liste_dates, DF_TD4[etiquette_debit].tolist(), marker='>', color='blue', ls=':', label=etiquette_debit)
|
|
|
```
|
|
|
|
|
|
|
|
|
Les messages essentiels sont :
|
|
|
* même si le mot-clé "plt" permet en général de déclencher les bonnes instructions (façon de faire inspirée de matlab) , il est plus rigoureux d'utiliser la syntaxe orientée objet. Il est possible de nommer les objets (fig, ax, et même les courbes si besoin) ce qui facilite les actions, et donne davantage de contrôle (fig.legend() n'est pas tout à fait la même chose que ax.legend()).
|
|
|
Attention, les méthodes de la la classe Axes sont parfois différentes : on va écrire par exemple:
|
|
|
plt.xlabel() → ax.set_xlabel() ; plt.ylabel() → ax.set_ylabel()
|
|
|
plt.xlim() → ax.set_xlim() ; plt.ylim() → ax.set_ylim()
|
|
|
plt.title() → ax.set_title()
|
|
|
["matplotlib gotchas" dans le livre de JVanderPlas](https://www.oreilly.com/library/view/python-data-science/9781491912126/ch04.html)
|
|
|
|
|
|
* même si le mot-clé "plt" permet en général de déclencher les bonnes instructions (façon de faire inspirée de matlab) , il est plus rigoureux d'utiliser la syntaxe orientée objet. Il est possible de nommer les objets (fig, ax, et même les courbes si besoin) ce qui facilite les actions, et donne davantage de contrôle (fig.legend() n'est pas tout à fait la même chose que ax.legend()). Attention, les méthodes de la la classe Axes sont parfois différentes : on va écrire par exemple: plt.xlabel() → ax.set_xlabel() ; plt.ylabel() → ax.set_ylabel() plt.xlim() → ax.set_xlim() ; plt.ylim() → ax.set_ylim() plt.title() → ax.set_title() ["matplotlib gotchas" dans le livre de JVanderPlas](https://www.oreilly.com/library/view/python-data-science/9781491912126/ch04.html)
|
|
|
|
|
|
On met donc en pratique sur ce graphique deux notions importantes :
|
|
|
* **la notion de subplot** qui permet de définir plusieurs vignettes sur une même figure ; cette architecture est plutôt bien décrite dans les tutoriels (voir aussi gridspec pour définir les proportions). Voir aussi [Point Subplots](Point Subplots) pour quelques précisions, et renvoi vers les autres exemples de l'atelier avec des subplots. L'argument "sharex=True" ) permet de coupler les graphiques : on zoome en même temps sur les deux. (même chose avec "sharey = True".)
|
|
|
* **la notion d'ax** qui est en fait une **aire sur laquelle on peut tracer des points selon un système de coordonnées**, que l'on se contentera d'appeler **système d'axes".
|
|
|
Quand on a compris que "un_axe" est un système d'axes, il est plus facile de décoder la méthode twinx : l'instruction un_axe.twinx() permet de créer un autre système d'axes qui partage le même axe des x que un_axe (et idem avec twiny pour l'axe des y).
|
|
|
Heureusement, plusieurs tutoriels mettent l'accent sur cette notion, en disant que le nom de classe Axes, avec le nom d'instance habituel "ax", est sans doute mal choisi. Grâce à ces ressources, notamment python-simple, on comprend ce que l'on manipule et il est ensuite plus facile d'assimiler les procédures.
|
|
|
|
|
|
* **la notion de subplot** qui permet de définir plusieurs vignettes sur une même figure ; cette architecture est plutôt bien décrite dans les tutoriels (voir aussi gridspec pour définir les proportions). Voir aussi [Point Subplots](Point%20Subplots) pour quelques précisions, et renvoi vers les autres exemples de l'atelier avec des subplots. L'argument "sharex=True" ) permet de coupler les graphiques : on zoome en même temps sur les deux. (même chose avec "sharey = True".)
|
|
|
* **la notion d'ax** qui est en fait une **aire sur laquelle on peut tracer des points selon un système de coordonnées**, que l'on se contentera d'appeler \*\*système d'axes". Quand on a compris que "un_axe" est un système d'axes, il est plus facile de décoder la méthode twinx : l'instruction un_axe.twinx() permet de créer un autre système d'axes qui partage le même axe des x que un_axe (et idem avec twiny pour l'axe des y). Heureusement, plusieurs tutoriels mettent l'accent sur cette notion, en disant que le nom de classe Axes, avec le nom d'instance habituel "ax", est sans doute mal choisi. Grâce à ces ressources, notamment python-simple, on comprend ce que l'on manipule et il est ensuite plus facile d'assimiler les procédures.
|
|
|
|
|
|
### C4)La légende, toute une histoire.
|
|
|
|
|
|
Dans le code, on a utilisé une méthode legend appliquée à l'objet fig. Ainsi, cette légende regroupe les symboles et étiquettes de l'ensemble des plots de la figure. Les diapos s'étendent davantage sur ses arguments.
|
|
|
``` python
|
|
|
|
|
|
```python
|
|
|
fig.legend(bbox_to_anchor=(1, 0), loc="lower right",
|
|
|
bbox_transform=fig.transFigure, ncol=4)
|
|
|
```
|
|
|
Pour se rendre compte de la différence, mettez cette instruction en commentaire (faire précéder de # et **essayez les syntaxes suivantes** :
|
|
|
``` python
|
|
|
```
|
|
|
|
|
|
Pour se rendre compte de la différence, mettez cette instruction en commentaire (faire précéder de # et **essayez les syntaxes suivantes** :
|
|
|
|
|
|
```python
|
|
|
plt.legend()
|
|
|
```
|
|
|
```
|
|
|
|
|
|
``` python
|
|
|
```python
|
|
|
ax_pluie.legend()
|
|
|
ax_t.legend()
|
|
|
ax_q.legend()
|
|
|
|
|
|
```
|
|
|
```
|
|
|
|
|
|
### C5) Variantes "esthétiques" et tests d'autres types de courbes (step, scatter...)
|
|
|
La figure tracée par la fonction **donnees_TD_ETP_2subplots** souffre de défauts esthétiques (couleurs qui se voient mal...) et de plus elle représente par des lignes des variables qui sont en fait des moyennes à pas de temps fixe.
|
|
|
On va donc utiliser la méthode **step** sur laquelle j'attire votre attention car j'ai eu du mal à la trouver !
|
|
|
A priori, la méthode **bar** convient aussi, et on la trouve plus facilement ; la doc prévient cependant que cette méthode convient à des valeurs par catégories, et toutes les étiquettes sont tracées ! On peut s'en sortir en imposant les étiquettes (mais en perdant alors l'ajustement automatique au zoom). Pire, si l'on trace plusieurs courbes qui ne partage pas exactement les mêmes étiquettes de dates (par exemple, la série originale puis la même mais rééchantillonnée au mois), on constate alors que les points sont tracés en fonction du rang et pas du tout de la date !
|
|
|
Tant qu'on y est, on va aussi utilise **scatter** à titre d'exercice, même si ce n'est pas vraiment pertinent ici. Scatter permet de passer comme argument color et size soit une valeur unique soit in vecteur de même taille de x et y.
|
|
|
Ici, il n'y a pas matière à faire varier la taille (sauf pour s'exercer), on propose de jouer sur la couleur. On en profite au passage pour manipuler un dictionnaire qui vous propose une relation entre les entiers de 1 à 12 et des couleurs.
|
|
|
|
|
|
``` python
|
|
|
La figure tracée par la fonction **donnees_TD_ETP_2subplots** souffre de défauts esthétiques (couleurs qui se voient mal...) et de plus elle représente par des lignes des variables qui sont en fait des moyennes à pas de temps fixe. On va donc utiliser la méthode **step** sur laquelle j'attire votre attention car j'ai eu du mal à la trouver ! A priori, la méthode **bar** convient aussi, et on la trouve plus facilement ; la doc prévient cependant que cette méthode convient à des valeurs par catégories, et toutes les étiquettes sont tracées ! On peut s'en sortir en imposant les étiquettes (mais en perdant alors l'ajustement automatique au zoom). Pire, si l'on trace plusieurs courbes qui ne partage pas exactement les mêmes étiquettes de dates (par exemple, la série originale puis la même mais rééchantillonnée au mois), on constate alors que les points sont tracés en fonction du rang et pas du tout de la date ! Tant qu'on y est, on va aussi utilise **scatter** à titre d'exercice, même si ce n'est pas vraiment pertinent ici. Scatter permet de passer comme argument color et size soit une valeur unique soit in vecteur de même taille de x et y. Ici, il n'y a pas matière à faire varier la taille (sauf pour s'exercer), on propose de jouer sur la couleur. On en profite au passage pour manipuler un dictionnaire qui vous propose une relation entre les entiers de 1 à 12 et des couleurs.
|
|
|
|
|
|
```python
|
|
|
DICO_COULEURS_MOIS = {1: "dimgrey", 2: "black", 3: "palegreen", 4: 'mediumspringgreen', 5: 'forestgreen', 6: 'gold', 7: 'orange',
|
|
|
8:'orangered', 9:'deepskyblue', 10:'royalblue', 11:'navy', 12:'silver'}
|
|
|
```
|
|
|
Evidemment, vous pouvez aussi intervenir dans ce dictionnaire.
|
|
|
Les données étant dans le pandas DataFrame DF_PTQ, on va remplir une liste en parcourant son index, dont les éléments sont au format date, et en allant chercher la valeur qui correspond à la clé "numéro de mois de cette date".
|
|
|
```
|
|
|
|
|
|
Evidemment, vous pouvez aussi intervenir dans ce dictionnaire. Les données étant dans le pandas DataFrame DF_PTQ, on va remplir une liste en parcourant son index, dont les éléments sont au format date, et en allant chercher la valeur qui correspond à la clé "numéro de mois de cette date".
|
|
|
|
|
|
``` python
|
|
|
```python
|
|
|
# couleurs par mois : prétexte pour manipuler dictionnaires et scatter
|
|
|
liste_couleurs = []
|
|
|
for date in DF_PTQ.index:
|
|
|
liste_couleurs.append(DICO_COULEURS_MOIS[date.month])
|
|
|
```
|
|
|
|
|
|
```
|
|
|
Evidemment, on peut écrire la même chose avec une liste en intension (comprehension list).
|
|
|
Il ne reste ensuite qu'à appliquer **scatter** en précisant pour l'argument **c** la liste de couleurs.
|
|
|
``` python
|
|
|
Evidemment, on peut écrire la même chose avec une liste en intension (comprehension list). Il ne reste ensuite qu'à appliquer **scatter** en précisant pour l'argument **c** la liste de couleurs.
|
|
|
|
|
|
```python
|
|
|
liste_couleurs = [ DICO_COULEURS_MOIS[date.month] for date in DF_PTQ.index]
|
|
|
|
|
|
ax_t.scatter(DF_PTQ.index, DF_PTQ[etiquette_temperatures].tolist(), marker='*', ls=':',
|
|
|
label=etiquette_temperatures, c=liste_couleurs)
|
|
|
```
|
|
|
|
|
|
```
|
|
|
Si vous avez bien assimilé cette manip avec les dictionnaires, vous pourriez écrire une fonction adaptée au fichier dans sa forme initiale, avec des "janv-01" et autres "sept-02".
|
|
|
|
|
|
Si vous avez bien assimilé cette manip avec les dictionnaires, vous pourriez écrire une fonction adaptée au fichier dans sa forme initiale, avec des "janv-01" et autres "sept-02".
|
|
|
|
|
|
[Retour à la page principale](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/wikis/home) |
|
|
\ No newline at end of file |
|
|
[Retour à la page principale](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/wikis/home)![]()![Figure_2_graphique_pandas](uploads/4438d78280976838e8b0742b5bf84653/Figure_2_graphique_pandas.PNG) |
|
|
\ No newline at end of file |