|
|
# "Widgets" directement avec matplotlib
|
|
|
|
|
|
Correspond au code Chegodaiev.pi
|
|
|
Correspond au code Chegodaiev.pi "version 0.1" ; une mise à jour est en cours
|
|
|
|
|
|
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...
|
... | ... | @@ -39,6 +39,7 @@ On peut écrire une fonction spécifique (exemple du code fourni) ou faire des a |
|
|
|
|
|
``` python
|
|
|
def T_emp_parametre_avec_defaut_Tchego(n_annees, a=0.3, b=0.4):
|
|
|
# fonction non inclue dans le code
|
|
|
|
|
|
plotting_positions_param = [((n_annees + b) / ((indice + 1) - a)) for indice in range(n_annees)]
|
|
|
print("Paramétré par curseur ", plotting_positions_param) # à mettre en commentaire
|
... | ... | @@ -53,22 +54,42 @@ Le critère retenu ici est que le slider qui sert à modifier b va déclencher u |
|
|
|
|
|
On donc définir *a*, *b* et *N* dès le départ, avec une valeur par défaut.
|
|
|
Dans cette version, on insiste sur le fait que la fréquence empirique d'une observation s'exprime uniquement en fonction de son rang ; on propose de créer un *échantillon* de N valeurs de 1 à N... ce qui correspondra de fait aux rangs !
|
|
|
On pourrait même être plus radical et décider que echantillon=[1]*N mais tous les graphiques seraient alors horizontaux et plus difficiles à lire... mais c'est une option quand même.
|
|
|
Une autre solution, plus radicale, serait de décider que echantillon=[1]*N mais tous les graphiques seraient alors horizontaux et plus difficiles à lire... mais c'est une option quand même.
|
|
|
|
|
|
``` python
|
|
|
ech = [1]*10
|
|
|
ech
|
|
|
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.
|
|
|
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.
|
|
|
|
|
|
On le trie d'emblée, ce qui permet d'utiliser la fonction *sorted* avec son attribut reverse ; elle ne change pas le conteneur qui lui est passé mais renvoie un conteneur avec les valeurs triées. La méthode *sort* trie aussi des conteneurs mais l'emploi est différent ; ma_liste.sort(*)) trie ma_liste _en place_ (elle change le conteneur et ne retourne rien).
|
|
|
``` python
|
|
|
|
|
|
def echantillon_uniforme(nb):
|
|
|
return sorted(range(1, N+1), reverse=True)
|
|
|
|
|
|
def echantillon_constant(nb):
|
|
|
# non encore testé
|
|
|
return [1]*nb
|
|
|
|
|
|
def echantillon_gumbel(nb):
|
|
|
# non encore testé
|
|
|
x0 = 196
|
|
|
gr = 102
|
|
|
return sorted(gumbel(loc=x0, scale=gr, size=nb))
|
|
|
```
|
|
|
|
|
|
On remarque que l'on trie l'échantillon dès la création, avec la fonction *sorted* avec son attribut reverse ; elle ne change pas le conteneur qui lui est passé mais renvoie un conteneur avec les valeurs triées. La méthode *sort* trie aussi des conteneurs mais l'emploi est différent ; ma_liste.sort(*)) trie ma_liste _en place_ (elle change le conteneur et ne retourne rien).
|
|
|
On définit ensuite les variables a, b et N car on a besoin dès le début d'une valeur initiale ; on prend a et b égaux aux paramètres de Tchégodaiev.
|
|
|
Le booléen en_periode_de_retour permettra de gérer l'affichage sur l'axe des x, en fréquence ou converti en période de retour.
|
|
|
|
|
|
``` python
|
|
|
a = 0.5
|
|
|
b = 0.5
|
|
|
a = 0.3
|
|
|
b = 0.4
|
|
|
N = 10
|
|
|
echantillon = sorted(range(1, N+1), reverse=True)
|
|
|
methode_echantillonnage = echantillon_uniforme
|
|
|
echantillon = methode_echantillonnage(N)
|
|
|
en_periode_de_retour = True
|
|
|
```
|
|
|
|
... | ... | @@ -88,13 +109,20 @@ fig_pp, (ax, ax_slide_a, ax_slide_b, ax_slide_n, ax_chb) = plt.subplots(nrows=5, |
|
|
plt.subplots_adjust(wspace=1, hspace=0.5,left=0.1,top=0.85,right=0.9,bottom=0.1)
|
|
|
fig_pp.canvas.set_window_title("Démo périodes de retour 'empiriques'")
|
|
|
|
|
|
fig_pp.suptitle(f"les valeurs importent peu, seul leur rang est utilisé ! ")
|
|
|
fig_pp.suptitle(f"seul le rang des observations est utilisé dans le calcul des T_empiriques ! ")
|
|
|
|
|
|
courbe_estim_Tchego, = ax.plot(T_emp_Tchego(N), echantillon, color="red", alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2, label= "Tchegodaiev", marker="o" , ls="--", markersize=7)
|
|
|
ax.set_xlabel("Période de retour (années)")
|
|
|
|
|
|
# la représentation des T empiriques de Tchégodaiev se fera par des verticales ; la méthode vlines ne renvoie qu'une variable, que l'on va nommer courbe_estim_Tchego
|
|
|
courbe_estim_Tchego = ax.vlines(x=T_emp_Tchego(N), ymin=0, ymax=max(echantillon), color="red", alpha=0.7, linewidth=2, zorder=2, label= "Tchegodaiev", ls="--")
|
|
|
|
|
|
# la représentation de la relation paramétrable fera par un "plot" classique, avev lignes et marqueurs ; la méthode plot renvoie un tuple de variables, on ne nomme que la première, courbe_estim_param eu on n'oublie pas la VIRGULE pour dire que l'on ne veut qu'un terme, pas tout le tuple
|
|
|
courbe_estim_param, = ax.plot(T_emp_parametre(N, a, b), echantillon, color='blue', alpha=0.7, linewidth=5, solid_capstyle='round', zorder=10, label = "paramétrée", marker="x" , ls=':', markersize=7)
|
|
|
|
|
|
# pour faire tourner ce code seul, après avoir défini a, b et N
|
|
|
ax.legend(title="formules", bbox_to_anchor=(0.85, 0.10), loc='lower right')
|
|
|
# ici on définira ce qui se passe dans les autres "Axes" ; slider et checkbuttons
|
|
|
|
|
|
# pour faire tourner ce code seul, après avoir défini a, b et N, on ajouterait la légende et plt.show()
|
|
|
# ax.legend(title="formules", bbox_to_anchor=(1.05, 0.10), loc='lower right')
|
|
|
# plt.show()
|
|
|
|
|
|
```
|
... | ... | @@ -118,7 +146,7 @@ nom_du_slider = Slider( |
|
|
On définit la fonction qui sera appelée si on change le curseur du slider :
|
|
|
nom_du_slider.on_changed(fonction_appellée)
|
|
|
|
|
|
Pour le slider, il existe un autre exemple dans le projet ST2shape, avec comme arguments supplémentaires la liste des valeurs permises (on transforme la saisie continue en saisie discrète).
|
|
|
Pour le slider qui gouverne N, il y a deux spécificités : comme on veut un entier, on définit comme arguments supplémentaires valstep, la liste des valeurs permises (on transforme la saisie continue en saisie discrète), et comme ce sont des flottants on définit un affichage avec zéro cdhiffre après la virgule. Il existe un autre exemple dans le projet ST2shape,
|
|
|
|
|
|
*pour la checkbox*
|
|
|
nom_du_checkbox= CheckButtons(ax_ou_le_checkbox_est_installe, [ liste des titres des items"], actives= [liste des entiers = indices des checkbox activées au départ]).
|
... | ... | @@ -131,35 +159,36 @@ nom_du_checkbox.on_clicked(fonction_appellée) |
|
|
for ax_s in [ax_slide_a, ax_slide_b, ax_slide_n, ax_chb]:
|
|
|
ax_s.xaxis.set_visible(False)
|
|
|
ax_s.yaxis.set_visible(False)
|
|
|
#for pos in ['right', 'top', 'bottom', 'left']:
|
|
|
# ax_s.spines[pos].set_visible(False)
|
|
|
|
|
|
for pos in ['right', 'top', 'bottom', 'left']:
|
|
|
ax_chb.spines[pos].set_visible(False)
|
|
|
|
|
|
# a
|
|
|
slider_a = Slider(
|
|
|
ax_slide_a, "paramètre a ", valmin=0.01, valmax = 0.99, valfmt='%0.2f', valinit=0.5, color="green")
|
|
|
ax_slide_a, "paramètre a ", valmin=0.01, valmax = 0.99, valfmt='%0.2f', valinit=a, color="green")
|
|
|
|
|
|
slider_a.on_changed(update_a)
|
|
|
|
|
|
# b
|
|
|
slider_b = Slider(
|
|
|
ax_slide_b, "paramètre b ", valmin=0.01, valmax = 0.99, valfmt='%0.2f', valinit=0.5, color="blue")
|
|
|
ax_slide_b, "paramètre b ", valmin=0.01, valmax = 0.99, valfmt='%0.2f', valinit=b, color="blue")
|
|
|
|
|
|
slider_b.on_changed(update_b)
|
|
|
|
|
|
# N
|
|
|
valeurs_possibles_b = np.linspace(0, 100, 1)
|
|
|
valeurs_possibles_N = np.linspace(0, 100, 1)
|
|
|
slider_n = Slider(
|
|
|
ax_slide_n, "paramètre N ", valmin=5, valmax = 200, valfmt='%0.0f', valinit=N, valstep=valeurs_possibles_b, color="purple")
|
|
|
ax_slide_n, "paramètre N ", valmin=5, valmax = 200, valfmt='%0.0f', valinit=N, valstep=valeurs_possibles_N, color="purple")
|
|
|
|
|
|
slider_n.on_changed(update_N)
|
|
|
|
|
|
# freq
|
|
|
valeurs_possibles_b = np.linspace(0, 100, 1)
|
|
|
chb_enT = CheckButtons(ax_chb, [ "En période de retour (années)"], actives=[0])
|
|
|
# switch T / freq
|
|
|
|
|
|
chb_enT = CheckButtons(ax_chb, [ "Période de retour (années) ; sinon fréquence (de 0 à 1)"], [False])
|
|
|
chb_enT.on_clicked(switch_freq)
|
|
|
|
|
|
```
|
|
|
Si on exécute le code, on voit déjà la courbe initiale et les widgets mais ils ne sont pas actifs. On remarque que la case à cocher du checkbutton est en fait un très vilain rectangle aplati...
|
|
|
|
|
|
### définition des fonctions associées aux widgets, version une par widget
|
|
|
|
... | ... | @@ -203,10 +232,11 @@ def update_b(val): |
|
|
|
|
|
def update_N(val):
|
|
|
global N, echantillon
|
|
|
N = int( val)
|
|
|
echantillon = sorted(range(1, N+1), reverse = True)
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
N = int( val) # ou bien N = slider_a.val
|
|
|
echantillon = methode_echantillonnage(N)
|
|
|
|
|
|
retracer_Tchego(echantillon)
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
ax.set_ylim(bottom=0, top=N*1.1)
|
|
|
fig_pp.canvas.draw_idle()
|
|
|
|
... | ... | @@ -216,7 +246,9 @@ def switch_freq(label): |
|
|
en_periode_de_retour = not en_periode_de_retour
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
retracer_Tchego(echantillon)
|
|
|
#ax.set_ylim(bottom=0, top=N * 1.1)
|
|
|
|
|
|
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
|
... | ... | @@ -242,7 +274,9 @@ La fonction associée à l'update de N devrait elle aussi utiliser slider_a.val |
|
|
On a vu au-dessus que changer a ou b amène à retracer une seule des courbes, la même dans les deux cas, tandis que changer N ou le booleen en_periode_de_retour demande de retracer les deux.
|
|
|
Pour factoriser le code, on va donc définir séparément une fonction pour retracer la courbe paramétrée et une autre pour la courbe d'après Tchegodayev.
|
|
|
|
|
|
On redéfinit les données, vecteurs x et y, associées aux courbes, avec la méthode set_data.
|
|
|
Pour une courbe simple, avec la méthode *plot*, il est facile de redéfinir les données, vecteurs x et y, associées aux courbes, avec la méthode set_data.
|
|
|
Pour une courbe de type *vlines* c'est plus compliqué : il s'agit d'une collection de lignes. Heureusement, on a trouvé sur un forum une solution toute prête, avec la fonction update_vlines qui utiise set_segments.
|
|
|
|
|
|
Dans chaque cas, on va définir différemment le vecteur des x selon la valeur de en_periode_de_retour, soit le vecteur de périodes de retour renvoyé par T_emp_Tchego(len(echantillon), soit le vecteur qui contient l'inverse de chaque élément.
|
|
|
|
|
|
Comme on n'appelle jamais retracer_Tchego sans appeler ensuite retracer_parametree, on ne va écrire les instructions de modification du titre (ax.set_title) et des limites de l'axe des x (ax.set_xlim) et enfin fig_pp.canvas.draw_idle().
|
... | ... | @@ -252,11 +286,33 @@ f"a={a:.2f}" signifie que l'on va écrire "a=" suivi de la valeur de a, comme fl |
|
|
|
|
|
|
|
|
``` python
|
|
|
|
|
|
def update_vlines(courbe_en_vlines, x, ymin=None, ymax=None):
|
|
|
# mise à jour de courbes de type vlines
|
|
|
# https://stackoverflow.com/questions/29331401/updating-pyplot-vlines-in-interactive-plot
|
|
|
seg_old = courbe_en_vlines.get_segments()
|
|
|
if ymin is None:
|
|
|
ymin = seg_old[0][0, 1]
|
|
|
if ymax is None:
|
|
|
ymax = seg_old[0][1, 1]
|
|
|
|
|
|
seg_new = [np.array([[xx, ymin], [xx, ymax]]) for xx in x]
|
|
|
|
|
|
courbe_en_vlines.set_segments(seg_new)
|
|
|
|
|
|
def retracer_Tchego(echantillon):
|
|
|
"""
|
|
|
on met à jour la courbe en vlines grâce à la fonction update_vlines ci-dessus
|
|
|
"""
|
|
|
y_max = max(echantillon)
|
|
|
T_emp = T_emp_Tchego(len(echantillon))
|
|
|
|
|
|
if en_periode_de_retour:
|
|
|
courbe_estim_Tchego.set_data(T_emp_Tchego(len(echantillon)), echantillon)
|
|
|
update_vlines(courbe_estim_Tchego, [T for T in T_emp], ymin=0, ymax=y_max)
|
|
|
else:
|
|
|
courbe_estim_Tchego.set_data([ 1 - (1 / T) for T in T_emp_Tchego(len(echantillon))], echantillon)
|
|
|
update_vlines(courbe_estim_Tchego, [1 - (1 / T) for T in T_emp], ymin=0, ymax=y_max)
|
|
|
|
|
|
# pas d'appel à redessiner le canevas car si cette fontion est appelée elle est toujours suivie de retracer_parametree
|
|
|
|
|
|
|
|
|
def retracer_parametree(echantillon, a, b):
|
... | ... | |