|
|
# "Widgets" directement avec matplotlib
|
|
|
|
|
|
On s'appuie pour les explications sur le code Chegodaiev.pi "version 0.2" et GenerateurCruesMaxAnnuelles.py qui fait passer un message plus complexe.
|
|
|
Le code ProbaCruesMaxAn_SurNannees.py est un autre exemple d'utilisation de sliders, avec des fonctions simples. Il a été écrit après, mais serait un meilleur candidat pour expliquer simplement comment fonctionne un widget (deux "sliders" en l'espèce").
|
|
|
On s'appuie pour les explications sur :
|
|
|
- Le code ProbaCruesMaxAn_SurNannees.py est un exemple simple d'utilisation du widget "slider" be, tracée avec une fonction très simple. .
|
|
|
- le code Chegodaiev.pi "version 0.2" combine plusieurs types de courbes (plot, vlines) et utilise deux sliders et une checkbox (widget CheckButtons) ; GenerateurCruesMaxAnnuelles.py s'appuie sur ce code qu'il développe pour faire passer un message plus complexe. Il manipule d'autres types de courbes (scatter, stem).
|
|
|
|
|
|
|
|
|
Cette session "G" a été ajoutée pour permettre de manipuler les **widgets de Matplotlib** sur un exemple simple, sans avoir à lire un fichier ni utiliser les rééchantillonnages (une difficulté à la fois...).
|
|
|
|
... | ... | @@ -22,13 +24,167 @@ Le même problème existe pour les images intégrées dans une interface, il fau |
|
|
|
|
|
### à propos des codes :
|
|
|
|
|
|
**Chegodaiev.py** : c'est ce code qui est commenté ici. Il est utilisable, mais il reste quelques maladresses à rectifier, dans les définitions des xlim par exemple ou le choix des méthodes de tracé pour Tchgodaiev (les vlines allant de 0 à la valeur max ne sont pas forcément le meilleur choix). Comme il a aussi une vocation d'exercice, on a utilisé le widget "checkbutton" même si le rendu est catastrophique...
|
|
|
C'est une petite démonstration des fréquence empiriques, avec la formule de Tchégodaiev et une formule plus générique paramétrée par a et b, dont les valeurs sont modifiables avec des "sliders". Une case à cocher permet de changer l'abscisse de fréquence en période de retour.
|
|
|
**ProbaCruesMaxAn_SurNannees.py** : c'est le code le plus simple, qui traite d'une seule fonction qui tient en une ligne et qui dépend de deux paramètres, N (nombre d'années) et T (période de retour, inverse d'une fréquence). Sa courbe est tracée avec "plot". La figure est munie de deux sliders qui font varier N et T, et qui activent tous les deux la même fonction de mise à jour du tracé : recalcul et remplacement des vecteurs x et y de la courbe (méthode set_data). Le titre change aussi.
|
|
|
|
|
|
**Chegodaiev.py** : c'est ce code qui est commenté ici. C'est une petite démonstration des fréquence empiriques, avec la formule de Tchégodaiev et une formule plus générique paramétrée par a et b, dont les valeurs sont modifiables avec des "sliders". Une case à cocher permet de changer l'abscisse de fréquence en période de retour.
|
|
|
Il est utilisable, mais il reste quelques maladresses à rectifier, dans les définitions des xlim par exemple ou le choix des méthodes de tracé pour Tchgodaiev (les vlines allant de 0 à la valeur max ne sont pas forcément le meilleur choix, et surtout ils sont bien plus compliqués à mettre à jour !). Comme il a aussi une vocation d'exercice, on a utilisé le widget "checkbutton" même si le rendu est catastrophique... Un simple bouton serait mieux.
|
|
|
|
|
|
**GenerateurCruesMaxAnnuelles.py** est un peu plus complexe et un peu mieux fini ; il est également ajouté au dépôt pour lecture (retours toujours appréciés ! on pourrait proposer de déclarer beaucoup d'objets comme attributs de la classe SerieGumbel pour éviter de les trimballer en file indienne comme arguments de fonction ou, pire, comme global...).
|
|
|
Il reprend les bases de Chegodaiev.py (en corrigeant quelques maladresses) pour créer une chronique d'observation de max annuels suivant une loi connue de Gumbel, de 10 ans en 10 ans, afin d'observer la variabilité de la chronique. On compare sur une autre vignette la loi ayant servi à la constitution de l'échantillon avec la relation Q(T) déduite des observations: plus on étend la période d'observation, plus on va converger vers la 'vraie' distribution (que l'on ne connaît pas dans le monde réel...). Un bouton "remise à zéro" efface la chronique et repart d'une série de 10 ans, ce qui permet aussi de voir que quel que soit le tirage de 10 ans les positions empiriques sont identiques (tant que a et b restent inchangées)...
|
|
|
|
|
|
|
|
|
# CODE **ProbaCruesMaxAn_SurNannees.py**
|
|
|
|
|
|
## 1. La fonction et ses paramètres
|
|
|
On rappelle que la période de retour T est l’inverse de la fréquence au dépassement : T= 1/f.
|
|
|
On raisonne avec les crues maximales annuelles, on ne retient de chaque année que la plus forte occurence de crue. Une crue centennale est dépassée en moyenne une fois tous les 100 ans et a donc une chance sur 100 d’être dépassée une année donnée : T=100 ans et f=1/100.
|
|
|
|
|
|
Si on illustre par un arbre des possibles, pour avoir p crues supérieures au niveau fixé il faut retenir les réalisations pour lesquelles on a exactement p crues supérieures au niveau voulu, et leur nombre est égal au nombre de façons de sélectionner p positions parmi N, soit C(n,p). Pour ces C(n,p) réalisations, on a p fois un tirage de probabilité f et le reste du temps, soit (N-p) tirages, un tirage de probabilité complémentaire (1-f).
|
|
|
|
|
|
La probabilité d’avoir sur N années données exactement p crues qui dépassent le niveau de la crue de période de retour T=1/f se calcule donc par la formule : C(n,p).f^p.(1-f)^(N-p).
|
|
|
|
|
|
``` python
|
|
|
from scipy.special import comb
|
|
|
def proba_crue(n, p, f):
|
|
|
""" Probabilité d’avoir exactement p crues de fréquence f en n années : C(n,p)*f^p*(1-f)^(N-p)
|
|
|
"""
|
|
|
return comb(n, p)*(f**p)*((1-f)**(n-p))
|
|
|
```
|
|
|
On représente la probabilité ainsi calculée en fonction de p, pour N et T fixés.
|
|
|
A chaque fois que l'on a une probabilité pour p, on peut aussi calculer par cumul la probabilité d'avoir entre 0 et p crues de période de retour supérieure à N : quand cette probabilité cumulée dépasse un seuil, ici 0.999 on arrête la boucle sur p. les entiers p à partir de 0 jusqu’à ce que ‘probabilité pour p crues ou moins de p’ dépasse 0,999 (courbe orange sur la copie d’écran suivante).
|
|
|
|
|
|
``` python
|
|
|
def calcul_courbes(n,f):
|
|
|
|
|
|
cumul_au_plus = 0
|
|
|
liste_probas_exactement=[]
|
|
|
liste_probas_auplus = []
|
|
|
p= 0
|
|
|
while cumul_au_plus < 0.999:
|
|
|
proba = proba_crue(n,p,f)
|
|
|
liste_probas_exactement.append(proba)
|
|
|
cumul_au_plus += proba
|
|
|
liste_probas_auplus.append(cumul_au_plus)
|
|
|
p+=1
|
|
|
|
|
|
return p-1, liste_probas_exactement, liste_probas_auplus
|
|
|
```
|
|
|
|
|
|
On définit *N* et *T* dès le départ, avec une valeur par défaut.
|
|
|
|
|
|
On calcule les probabilités avex ces valeurs par défaut, et on recueille les résultats dans 3 variables qui seront utilisées pour le tracé.
|
|
|
``` python
|
|
|
p, liste_probas_exactement, liste_probas_auplus = calcul_courbes(n, 1 / T)
|
|
|
```
|
|
|
Deux sliders vont permettre ensuite de modifier N et T ; on les utilise ensuite pour calculer et tracer des résultats via une fonction ; il faut donc que la modification soit répercutée dans tout le code. Cela va nous permettre de manipuler la notion de portée des variables.
|
|
|
|
|
|
|
|
|
|
|
|
## 2. La figure
|
|
|
La figure est divisée en une grille de 1 colonne et 4 lignes, dont une ligne plus haute pour le graphique, les ratios sont précisés avec l'argument gridspec_kw, par mots-clés (KeyWord) : à la clé 'height_ratios' on associe un vecteur avec les proportions . Deux autres lignes vont recevoir un curseur, pour éviter les superpositions de texte on a défini entre le graphique et les sliders une ligne "vide"
|
|
|
|
|
|
### définition des vignettes
|
|
|
|
|
|
``` python
|
|
|
fig, (ax, ax_espace, ax_sn, ax_sT) = plt.subplots(nrows=4, ncols=1, gridspec_kw={'height_ratios':[6,1, 1,1]}, sharex=False)
|
|
|
```
|
|
|
Ensuite, il faut enlever les traits matérialisant les vignettes et rendre la vignette "intervalle" transparente, afin justement qu'elle joue sont rôle de marge,
|
|
|
``` python
|
|
|
for ax_s in [ax_espace, ax_sn, ax_sT]:
|
|
|
ax_s.xaxis.set_visible(False)
|
|
|
ax_s.yaxis.set_visible(False)
|
|
|
|
|
|
ax_espace.patch.set_alpha(0.01) #sinon cet axe cache le titre de l'axe des x au-dessus !
|
|
|
|
|
|
for pos in ['right', 'top', 'bottom', 'left']:
|
|
|
ax_espace.spines[pos].set_visible(False)
|
|
|
|
|
|
(...)
|
|
|
plt.show()
|
|
|
```
|
|
|
|
|
|
### première vignette, la courbe
|
|
|
|
|
|
En haut, on a donc une vignette "ax" dans laquelle on va tracer deux courbes avec "plot", avec les résultats qui viennent d'être calculés. On nomme le premier objet retourné par la fonction "plot", puisqu'on aura besoin d'agir sur lui : on remplacera les données (vecteurs des x et vecteurs des y) après chaque action sur les sliders.
|
|
|
|
|
|
``` python
|
|
|
courbe_exactement, = ax.plot(range(p + 1), liste_probas_exactement, label="exactement p crues")
|
|
|
courbe_cumul, = ax.plot(range(p + 1), liste_probas_auplus, label="p crues ou moins de p")
|
|
|
```
|
|
|
|
|
|
### définition des sliders et liaison avec une fonction "par widget"mise à jour de la courbe"
|
|
|
|
|
|
Sous la vignette "ax" il y a une vignette "ax_espace" dans laquelle on n'ajoute rien, puis les vignettes des deux sliders.
|
|
|
Les arguments utilisés ici pour les sliders sont :
|
|
|
|
|
|
nom_du_slider = Slider(
|
|
|
ax_ou_le_slider_est_installe, "titre", valmin=valeur_mini, valmax = valeur_max, valstep=incrément, valfmt=format précédé de % : '%0.2f', valinit=valeur de départ, color="nom_de_couleur")
|
|
|
|
|
|
|
|
|
Comme on veut des entiers, on définit pour valstep un incrément égal à 1, et comme ce sont des flottants on définit un affichage avec zéro chiffre après la virgule.
|
|
|
|
|
|
On définit enfin la fonction qui sera appelée si on change le curseur du slider :
|
|
|
nom_du_slider.on_changed(fonction_appellée)
|
|
|
|
|
|
``` python
|
|
|
# nombre d'années d'observation
|
|
|
slider_n = Slider(
|
|
|
ax_sn, "nombre d'années d'observation ", valmin=1, valmax = 1000, valfmt='%0.0f', valinit=n, color="green")
|
|
|
|
|
|
slider_n.on_changed(update_graphique)
|
|
|
|
|
|
# T, période de retour
|
|
|
|
|
|
slider_T = Slider(
|
|
|
ax_sT, "période de retour T ", 2, 1000, valinit=T, valstep=1, valfmt='%0.0f', color="blue")
|
|
|
|
|
|
slider_T.on_changed(update_graphique)
|
|
|
```
|
|
|
|
|
|
### définition de la fonction commune associée aux deux widgets
|
|
|
|
|
|
On a associé la même fonction, update_graphique, aux deux sliders car finalement ils vont tous les deux déclancher la même suite d'actions : recalcul et retracé.
|
|
|
|
|
|
Le slider_N (respectivement slider_T) récupère une valeur numérique et la transmet en tant qu'argument *val* à la fonction qui lui a été liée.
|
|
|
Les fonctions liées à un widget ne peuvent prendre qu'un argument, imposé par le type du widget. Pour un slider, l'argument ne peut être que la valeur du curseur, val par convention.
|
|
|
|
|
|
Cependant, on va appeler une fonction qui va retracer les courbes en fonction de N et T ; il faut donc connaître ces deux variables dans cette fonction, et en modifier une (celle du curseur actif) pour tout le code.
|
|
|
|
|
|
On a choisi de préciser dans la fonction que l'on travaille avec les deux variables N et T au niveau global:
|
|
|
global N, T
|
|
|
|
|
|
Il n'est pas possible d'utiliser l'argument passé val car dans la fonction appelée on ne sait pas quel est le curseur qui a été activé, cela peut être slider_N ou slider_T.
|
|
|
Heureusement, on peut appeler directement la valeur de chaque curseur en tant qu'attribut ([ source utilisée ](http://www.math.buffalo.edu/~badzioch/MTH337/PT/PT-matplotlib_slider/PT-matplotlib_slider.html)):
|
|
|
|
|
|
N = slider_N.val
|
|
|
T = slider_T.val
|
|
|
|
|
|
``` python
|
|
|
def update_graphique(val):
|
|
|
global n, T
|
|
|
n = int(slider_n.val) # on précise que ce sont des entiers pour l'utilisation dans C(n,p)
|
|
|
T = int(slider_T.val)
|
|
|
|
|
|
p, liste_probas_exactement, liste_probas_auplus = calcul_courbes(n,1/T)
|
|
|
fig.suptitle(f"Démo : probabilité d'avoir p crues > T={T}ans en {n} années")
|
|
|
courbe_exactement.set_data(range(p + 1), liste_probas_exactement)
|
|
|
courbe_cumul.set_data(range(p + 1), liste_probas_auplus)
|
|
|
ax.set_xlim(0, p+1)
|
|
|
fig.canvas.draw_idle()
|
|
|
```
|
|
|
|
|
|
Le recalcul ne présente pas de difficulté ; on modifie ensuite les données des courbes avec set_data, ce qui revient à leur passer le nouveau vecteur des x (entiers de 0 à p) et des y (probabilités pour p ou cumulées jusque p) . On modifie aussi le titre et l'étendue de l'axe des x.
|
|
|
fig.canvas.draw_idle() force les objets modifiés à se retracer.
|
|
|
|
|
|
:warning: les objets tracés avec **plot** sont très faciles à mettre à jour avex **set_data**. Ce n'est malheureusement pas le cas de tous les types de graphiques. Dans certains cas, il sera même peut-être plus simple d'effacer la courbe (on doit donc avoir la permission de le faire, par exemple avec global) et de la retracer.
|
|
|
|
|
|
#### remarque sur la portée des variables
|
|
|
Ni fig ni ax ni les courbes n'ont été passés en argument, pourtant ils sont connus depuis l'intérieur de la fonction. Comme c'est un petit code, on sait que l'on va manipuler les bons objets, il n'y a pas d'ambiguité. Dans la fonction, on ne pourrait pas écraser l'un ou l'autre objet, par contre si on applique sur eux une méthode (suptitle, set_data, set_xlim...) la modification de leurs attributs correspondant est répercutée.
|
|
|
|
|
|
|
|
|
|
|
|
# CODE **Chegodaiev.py**
|
|
|
|
|
|
## 1. Les fonctions paramétrées utilisées et leurs paramètres
|
|
|
Pour représenter de manière empirique les fréquences des points d'un échantillon, il existe plusieurs formules : de Hazen, de Tchegodayev (ou Chegodaiev)... Elles ne varient en fait que par deux paramètres, a et b.
|
|
|
|
... | ... | @@ -133,8 +289,7 @@ Il y a deux façons de répercuter les modifications de a, b et N dans tout le p |
|
|
C'est sans doute à discuter : quelle méthode est la plus claire ? la plus sûre ?
|
|
|
|
|
|
|
|
|
|
|
|
## La figure
|
|
|
## 2. La figure
|
|
|
La figure est divisée en une grille de 1 colonne et composée de 5 lignes, dont une ligne plus haute pour le graphique. 3 des autres lignes vont recevoir un curseur, et la dernière une case à cocher. On va donc définir le nom des espaces pour le tracé ("Axes"), en fonction de leur rôle.
|
|
|
On a spécifié une marge de 15% en haut pour permettre l'affichage d'un titre de figure et d'un titre de la vignette du haut, ax. On mettra à jour les valeurs des paramètres dans le titre de ax pour vérifier que les signaux se transmettent bien.
|
|
|
|
... | ... | @@ -295,22 +450,23 @@ def switch_freq(label): |
|
|
fig_pp.canvas.draw_idle()
|
|
|
```
|
|
|
|
|
|
### autre manière de définir des fonctions associées aux widgets, avec une fonction commune pour a et b
|
|
|
### autre manière de définir des fonctions associées aux widgets, avec une fonction commune pour a et b ?
|
|
|
[ source utilisée ](http://www.math.buffalo.edu/~badzioch/MTH337/PT/PT-matplotlib_slider/PT-matplotlib_slider.html)
|
|
|
|
|
|
Cette variante n'est pas dans le code fourni, cependant elle est intéressante.
|
|
|
On voit bien que les fonctions update_a et update_b sont très proches.
|
|
|
On ne peut renvoyer qu'un argument, lié au widget cliqué, mais par contre il est facile de récupérer la valeur d'un slider, car elle est aussi un argument : *mon_slider.val*
|
|
|
Les fonctions update_a et update_b sont très proches, et pourraient partager une même fonction liée, comme dans le programme ProbaCrueMaxAn_SurNannees.
|
|
|
On rappelle que l'on peut récupérer la valeur d'un slider, car elle est aussi un argument : *mon_slider.val*
|
|
|
|
|
|
``` python
|
|
|
def update_a_ou_b(val):
|
|
|
|
|
|
retracer_parametree(echantillon, slider_a.val, slider_b.val)
|
|
|
# solution avec les variables globales
|
|
|
global a,b
|
|
|
a = slider_a.val
|
|
|
b= slider_b.val
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
|
|
|
```
|
|
|
Ainsi, on n'aurait plus qu'une fonction, que l'on lie à chacun des deux sliders.
|
|
|
Il faudrait ajuster d'autres parties du code pour se débarrasser des variables globales : l'initialisation, actuellement utile pour le premier tracé, devrait être enlevée, et il faudra que les sliders soit définis avant le premier tracé, qui utiliserait de même slider_a.val et slider_b.val.
|
|
|
La fonction associée à l'update de N devrait elle aussi utiliser slider_a.val et slider_b.val au lieu de chercher a et b à l'extérieur de la fonction (ils ne devraient plus s'y trouver).
|
|
|
On peut aussi imaginer se débarrasser des variables globales : on pourrait utiliser slider_a.val et slider_b.val, puisqu'ils ne sont modifiés que par les curseurs.
|
|
|
Il faudrait vérifier ce qui se passe pour le premier tracé : les sliders devraient avoir été définis avant.
|
|
|
|
|
|
|
|
|
### définition des fonctions pour retracer les courbes, appelées par les fonctions associées aux widgets
|
... | ... | |