|
# "Widgets" directement avec matplotlib
|
|
# "Widgets" directement avec matplotlib
|
|
|
|
|
|
Correspond au code Chegodaiev.pi "version 0.2" ; le graphique "Tchegodaiev" est à présent tracé avec des verticales, au moyen de vlines. Le code GenerateurCruesMaxAnnuelles.py également ajouté au dépôt reprend les bases de Chegodaiev.pi (en corrigeant quelques maladresses) pour créer une chronique d'observation de max annuels, de 10 ans en 10 ans, pour observer la variabilité de la chronique et de l'estimation de Q(T) qui en est tirée ; 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...).
|
|
Correspond au code Chegodaiev.pi "version 0.2" et GenerateurCruesMaxAnnuelles.py.
|
|
|
|
|
|
|
|
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...).
|
|
|
|
|
|
|
|
Dans cet exemple, on cherche à montrer comment se comporte une formule paramétrique si on change les paramètres. L'utilisateur pourra modifier les valeurs de ces paramètres par des curseurs (widget Slider) et changer la définition de l'axe des x (en fréquence ou en période de retour = 1/f) avec une case à cocher (CheckButtons).
|
|
|
|
|
|
Cette session "G" a été ajoutée pour permettre de manipuler les widgets sur un exemple simple, sans avoir à lire un fichier ni utiliser les rééchantillonnages (une difficulté à la fois...).
|
|
|
|
On en profitera quand même pour ajouter une manipulation de variable globale, pour pouvoir changer la valeur d'une variable dans une fonction. Cela est à utiliser avec précaution toutefois...
|
|
On en profitera quand même pour ajouter une manipulation de variable globale, pour pouvoir changer la valeur d'une variable dans une fonction. Cela est à utiliser avec précaution toutefois...
|
|
|
|
|
|
|
|
### à propos des widgets de matplotlib,
|
|
Il s'agit bien des widgets de matplotlib, ce qui peut rendre service en phase de test sans assurer un rendu parfait... mais en s'épargnant le recours à une bibliothèque d'interface (Tkinter, PyQT ou PySide...).
|
|
Il s'agit bien des widgets de matplotlib, ce qui peut rendre service en phase de test sans assurer un rendu parfait... mais en s'épargnant le recours à une bibliothèque d'interface (Tkinter, PyQT ou PySide...).
|
|
Il existe des bibliothèques tierces avec des améliorations de ces widgets, signalée [dans la rubrique Third Party Packages de MatPlotLib](https://matplotlib.org/stable/thirdpartypackages/index.html ). Pour les curseurs, on trouve ainsi: mplcursors (interactive data cursors for Matplotlib), MplDataCursor ( toolkit to provide interactive "data cursors" (clickable annotation boxes) et mpl_interactions (easy to create interactive plots controlled by sliders and other widgets), non encore testées.
|
|
Il existe des bibliothèques tierces avec des améliorations de ces widgets, signalée [dans la rubrique Third Party Packages de MatPlotLib](https://matplotlib.org/stable/thirdpartypackages/index.html ). Pour les curseurs, on trouve ainsi: mplcursors (interactive data cursors for Matplotlib), MplDataCursor ( toolkit to provide interactive "data cursors" (clickable annotation boxes) et mpl_interactions (easy to create interactive plots controlled by sliders and other widgets), non encore testées.
|
|
|
|
|
|
On signale ici qu'il existe aussi une bibliothèque iPython pour ajouter des widgets à un notebook Jupyter (non testé ici).
|
|
On signale ici qu'il existe aussi une bibliothèque iPython pour ajouter des widgets à un notebook Jupyter (non testé ici).
|
|
|
|
|
|
:warning: si vous définissez des widgets à l'intérieur d'une fonction, ils vont fonctionner un moment, jusqu'à ce que le "garbage collector" (ramasse-miette en français) considère que ces objets ne sont pas référencés dans le "main" même si la fenêtre matplotlib est toujours ouverte, et va les supprimer. Ils seront toujours tracés, mais inactifs (non-responsive en anglais, si vous cherchez des compléments sur les forums). Il faut donc veiller à ce que leur nom soit déclaré au-dehors de la fonction, soit en le définissant comme retour de fonction, soit en créant un attribut d'un objet
|
|
|
|
|
|
:warning: erreur courante : si vous définissez des widgets à l'intérieur d'une fonction, ils vont fonctionner un moment, jusqu'à ce que le "garbage collector" (ramasse-miette en français) considère, une fois la fonction terminée, que ces objets ne sont plus référencés même si la fenêtre matplotlib est toujours ouverte avec les widgets dessus ! Il va donc les supprimer. Ils seront toujours tracés, mais inactifs (non-responsive en anglais, si vous cherchez des compléments sur les forums). Il faut donc veiller à ce que leur nom soit déclaré au-dehors de la fonction, soit en le définissant comme retour de fonction, soit en créant un attribut d'un objet
|
|
mon_objet_qui_existe_dans_le_main.slider = mon_slider_dans_la_fonction.
|
|
mon_objet_qui_existe_dans_le_main.slider = mon_slider_dans_la_fonction.
|
|
Le même problème existe pour les images intégrées dans une interface, il faut absolument leur donner un nom pour que l'objet correspondant ne soit pas jeté par le GC.
|
|
Le même problème existe pour les images intégrées dans une interface, il faut absolument leur donner un nom pour que l'objet correspondant ne soit pas jeté par le GC.
|
|
|
|
|
|
|
|
### à 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.
|
|
|
|
|
|
|
|
**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)...
|
|
|
|
|
|
Dans cet exemple, on cherche à montrer comment se comporte une formule paramétrique si on change les paramètres. L'utilisateur pourra modifier les valeurs de ces paramètres par des curseurs (widget Slider) et changer la définition de l'axe des x (en fréquence ou en période de retour = 1/f) avec une case à cocher (CheckButtons).
|
|
|
|
|
|
|
|
## Les fonctions paramétrées et leurs paramètres
|
|
## 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.
|
|
|
|
|
|
### Les fonctions paramétrées
|
|
### Les fonctions paramétrées
|
|
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)...
|
|
|
|
L'expression générale de la fréquence empirique d'une observation s'exprime uniquement en fonction de son rang si l'on classe les N observations par ordre décroissant, :
|
|
L'expression générale de la fréquence empirique d'une observation s'exprime uniquement en fonction de son rang si l'on classe les N observations par ordre décroissant, :
|
|
freq_depassement(obs) = ( rang(obs) - a ) / ( N + b )
|
|
freq_depassement(obs) = ( rang(obs) - a ) / ( N + b )
|
|
|
|
|
... | @@ -70,7 +83,7 @@ ech = [1]*10 |
... | @@ -70,7 +83,7 @@ ech = [1]*10 |
|
ech
|
|
ech
|
|
Out[7]: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
|
Out[7]: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
|
```
|
|
```
|
|
Enfin, plus en rapport avec l'objet initial de ce code, on peut tirer un échantillon au sort selon une loi de Gumbel.
|
|
Enfin, plus en rapport avec l'objet initial de ce code, on peut tirer un échantillon au sort selon une loi de Gumbel. C'est ce que l'on utilise dans GenerateurCruesMaxAnnuelles.py.
|
|
Le code comprend ces 3 fonctions, et une ligne de code indique quelle fonction on assigne à methode_echantillonnage ; ici methode_echantillonnage = echantillon_uniforme. En quelque sorte, methode_echantillonnage est un alias de la fonction choisie, et c'est cet alias qui est utilisé ensuite dans le code. Ainsi, pour changer de fonction, il suffit de changer une seule ligne.
|
|
Le code comprend ces 3 fonctions, et une ligne de code indique quelle fonction on assigne à methode_echantillonnage ; ici methode_echantillonnage = echantillon_uniforme. En quelque sorte, methode_echantillonnage est un alias de la fonction choisie, et c'est cet alias qui est utilisé ensuite dans le code. Ainsi, pour changer de fonction, il suffit de changer une seule ligne.
|
|
|
|
|
|
``` python
|
|
``` python
|
... | @@ -130,6 +143,8 @@ On a spécifié une marge de 15% en haut pour permettre l'affichage d'un titre d |
... | @@ -130,6 +143,8 @@ On a spécifié une marge de 15% en haut pour permettre l'affichage d'un titre d |
|
On remarque que l'on a nommé le *premier objet renvoyé par la fonction plot*, respectivement _courbe_estim_Tchego_ et _courbe_estim_param_, afin de pouvoir appliquer des méthodes sur ces objets, à savoir modifier le vecteur des x et le vecteur des y donnant les coordonnées des points. Comme _plot_ renvoie plusieurs objets, on ajoute une virgule après le nom donné à la variable qui nous intéresse, ce qui est équivalent à :
|
|
On remarque que l'on a nommé le *premier objet renvoyé par la fonction plot*, respectivement _courbe_estim_Tchego_ et _courbe_estim_param_, afin de pouvoir appliquer des méthodes sur ces objets, à savoir modifier le vecteur des x et le vecteur des y donnant les coordonnées des points. Comme _plot_ renvoie plusieurs objets, on ajoute une virgule après le nom donné à la variable qui nous intéresse, ce qui est équivalent à :
|
|
nom_du_premier_objet_du_tuple*,* reste_du_tuple_qui_ne_m_interesse_pas = ax.plot(mes_arguments)
|
|
nom_du_premier_objet_du_tuple*,* reste_du_tuple_qui_ne_m_interesse_pas = ax.plot(mes_arguments)
|
|
|
|
|
|
|
|
La représentation des T (ou f) empiriques de Tchégodaiev se fera par des verticales, avec la fonction vlines. Dans l'e'autre code, on a choisi plutôt "stem" qui combine une ligne et un marker. La méthode "stem" n'était pas très performante il y a peu (trop de points => erreur mémoire) mais elle a été améliorée, donc il était intéressant de la tester. Autre différence, ici on modifie vlines avec une fonction update_vlines trouvée dans un forum au lieu d'effacer et de refaire. Cette fonction update_vlines pose problème pour utiliser des lignes de hauteur différentes (ymax = array), donc finalement effacer et refaire serait plus judicieux.
|
|
|
|
|
|
|
|
|
|
``` python
|
|
``` python
|
|
fig_pp, (ax, ax_slide_a, ax_slide_b, ax_slide_n, ax_chb) = plt.subplots(nrows=5, ncols=1, gridspec_kw={'height_ratios': [10, 1, 1, 1,2]})
|
|
fig_pp, (ax, ax_slide_a, ax_slide_b, ax_slide_n, ax_chb) = plt.subplots(nrows=5, ncols=1, gridspec_kw={'height_ratios': [10, 1, 1, 1,2]})
|
... | @@ -243,6 +258,7 @@ Pour le slider, il existe un autre exemple dans le projet ST2shape, avec comme a |
... | @@ -243,6 +258,7 @@ Pour le slider, il existe un autre exemple dans le projet ST2shape, avec comme a |
|
|
|
|
|
*pour la checkbox*
|
|
*pour la checkbox*
|
|
Une action sur la checkbox fait passer le booléen en_periode_de_retour de True à False ou de False à True. Cela peut s'écrire avec des tests (*if ma_checkbox == True:* ou même *if ma_checkbox :* puis (*if ma_checkbox == False:* ou même *if not ma_checkbox :* ou bien plus simplement en disant que la valeur est à présent le contraire de la valeur initiale : mon_booleen = *not* mon_booleen.
|
|
Une action sur la checkbox fait passer le booléen en_periode_de_retour de True à False ou de False à True. Cela peut s'écrire avec des tests (*if ma_checkbox == True:* ou même *if ma_checkbox :* puis (*if ma_checkbox == False:* ou même *if not ma_checkbox :* ou bien plus simplement en disant que la valeur est à présent le contraire de la valeur initiale : mon_booleen = *not* mon_booleen.
|
|
|
|
Dans le code GenerateurCruesMaxAnnuelles.py on utilise un bouton à la place de cette checkbox, car son rendu est tout à fait raté... La checkbox permet de voir si l'option est cochée ou pas ; pour avoir la même information sur un bouton il suffit de changer le texte à chaque fois qu'on l'active (T => f ou f => T) ; dans les deux cas il est important de changer aussi le titre de l'axe des x.
|
|
|
|
|
|
On retrace les fonctions, dans lesquelles la valeur de en_periode_de_retour sera prise en compte pour le calcul.
|
|
On retrace les fonctions, dans lesquelles la valeur de en_periode_de_retour sera prise en compte pour le calcul.
|
|
|
|
|
... | @@ -264,7 +280,7 @@ def update_N(val): |
... | @@ -264,7 +280,7 @@ def update_N(val): |
|
|
|
|
|
retracer_Tchego(echantillon)
|
|
retracer_Tchego(echantillon)
|
|
retracer_parametree(echantillon, a, b)
|
|
retracer_parametree(echantillon, a, b)
|
|
ax.set_ylim(bottom=0, top=N*1.1)
|
|
ax.set_ylim(bottom=0, top=N*1.1) #améliorable ; définir xlim aussi
|
|
fig_pp.canvas.draw_idle()
|
|
fig_pp.canvas.draw_idle()
|
|
|
|
|
|
def switch_freq(label):
|
|
def switch_freq(label):
|
... | @@ -273,7 +289,7 @@ def switch_freq(label): |
... | @@ -273,7 +289,7 @@ def switch_freq(label): |
|
en_periode_de_retour = not en_periode_de_retour
|
|
en_periode_de_retour = not en_periode_de_retour
|
|
retracer_parametree(echantillon, a, b)
|
|
retracer_parametree(echantillon, a, b)
|
|
retracer_Tchego(echantillon)
|
|
retracer_Tchego(echantillon)
|
|
#ax.set_ylim(bottom=0, top=N * 1.1)
|
|
#ax.set_ylim(bottom=0, top=N * 1.1) #améliorable ; ; définir xlim aussi
|
|
|
|
|
|
fig_pp.canvas.draw_idle()
|
|
fig_pp.canvas.draw_idle()
|
|
```
|
|
```
|
... | @@ -317,6 +333,7 @@ f"a={a:.2f}" signifie que l'on va écrire "a=" suivi de la valeur de a, comme fl |
... | @@ -317,6 +333,7 @@ f"a={a:.2f}" signifie que l'on va écrire "a=" suivi de la valeur de a, comme fl |
|
def update_vlines(courbe_en_vlines, x, ymin=None, ymax=None):
|
|
def update_vlines(courbe_en_vlines, x, ymin=None, ymax=None):
|
|
# mise à jour de courbes de type vlines
|
|
# mise à jour de courbes de type vlines
|
|
# https://stackoverflow.com/questions/29331401/updating-pyplot-vlines-in-interactive-plot
|
|
# https://stackoverflow.com/questions/29331401/updating-pyplot-vlines-in-interactive-plot
|
|
|
|
# ne permet de travailler que avec ymax = scalaire ?
|
|
seg_old = courbe_en_vlines.get_segments()
|
|
seg_old = courbe_en_vlines.get_segments()
|
|
if ymin is None:
|
|
if ymin is None:
|
|
ymin = seg_old[0][0, 1]
|
|
ymin = seg_old[0][0, 1]
|
... | | ... | |