... | ... | @@ -4,6 +4,10 @@ Cette session "G" a été ajoutée pour permettre de manipuler les widgets sur |
|
|
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...
|
|
|
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...). 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
|
|
|
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.
|
|
|
|
|
|
|
|
|
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).
|
|
|
|
... | ... | @@ -12,7 +16,7 @@ Dans cet exemple, on cherche à montrer comment se comporte une formule paramét |
|
|
### 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...
|
|
|
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(obs) = ( rang(obs) - a ) / ( N + b )
|
|
|
freq_depassement(obs) = ( rang(obs) - a ) / ( N + b )
|
|
|
|
|
|
On vérifie que pour les rangs petits proches de un on tend vers une fréquence nulle, pour des rangs proches de N on tend vers une fréquence de 1 en restant en dessous, et entre deux observations la différence de fréquence est constante, égale à 1 / (N+b) donc légèrement inférieure à 1/N.
|
|
|
|
... | ... | @@ -55,14 +59,18 @@ b = 0.5 |
|
|
N = 10
|
|
|
echantillon = sorted(range(1, N+1), reverse=True)
|
|
|
en_periode_de_retour = True
|
|
|
``` python
|
|
|
|
|
|
```
|
|
|
|
|
|
## 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.
|
|
|
|
|
|
### la grille et le tracé des deux premières courbes
|
|
|
|
|
|
|
|
|
### code définissant la grille et le tracé des deux premières courbes
|
|
|
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)
|
|
|
|
|
|
|
|
|
``` 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]})
|
... | ... | @@ -71,19 +79,25 @@ 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é ! ")
|
|
|
|
|
|
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)
|
|
|
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)
|
|
|
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)
|
|
|
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.15), loc='lower right')
|
|
|
ax.legend(title="formules", bbox_to_anchor=(0.85, 0.10), loc='lower right')
|
|
|
# plt.show()
|
|
|
|
|
|
```
|
|
|
|
|
|
### remarque sur la prise en compte des actions des widgets après show()
|
|
|
Après le plt.show(), la figure est tracée ; cependant, les actions sur les différents widgets vont la modifier. Après chaque action qui modifie la figure, par appel d'une méthode sur l'un de ses objets, il faudra donc appeler une méthode qui met à jour si l'on veut que cela soit pris en compte:
|
|
|
|
|
|
``` python
|
|
|
fig_pp.canvas.draw_idle()
|
|
|
```
|
|
|
|
|
|
### définition des widgets et liaison avec une fonction
|
|
|
|
|
|
Entre la définition de la figure et de ax et le show(), on définit ce qui se passe dans les 4 autres lignes de la grille, où l'on va placer les sliders.
|
|
|
On va d'abord enlever le cadre des vignettes du bas, avec ax.xaxis/yaxis.set_visible(False)
|
|
|
Ensuite on définit les widgets, les arguments utilisés ici sont :
|
|
|
*pour les sliders*
|
... | ... | @@ -135,3 +149,94 @@ chb_enT = CheckButtons(ax_chb, [ "En période de retour (années)"], actives=[0] |
|
|
chb_enT.on_clicked(switch_freq)
|
|
|
|
|
|
```
|
|
|
|
|
|
### définition des fonctions associées aux widgets
|
|
|
*pour les sliders*
|
|
|
Le slider_a (respectivement slider_b) récupère une valeur de a (resp. b) et la transmet en tant qu'argument *val* à la fonction qui lui a été liée, ici respectivement update_a et update_b.
|
|
|
|
|
|
Si on change a ou b, il suffit de retracer la fonction paramétrée par a et b, celle de Tchegodayev ne change pas. On va passer par des fonctions intermédiaires retracer_parametree et retracer_Tchego que l'on décrira dans la paragraphe suivant.
|
|
|
Pour que l'on puisse changer la valeur de *a* pour tout le code, et pas seulement en tant qu'argument pour retracer_parametree(echantillon, a, b), on a choisi de préciser dans la fonction que l'on travaille avec la variable de niveau global:
|
|
|
global a # respectivement global b
|
|
|
Ainsi, quand on écrit ensuite a = val, on remplace donc la valeur pour tout le corps du programme ("main").
|
|
|
|
|
|
Si on change N, l'échantillon change et il faut retracer les deux courbes, et redimensionner l'axe des y.
|
|
|
Dans cette fonction, on remarque qu'il n'était pas nécessaire de passer *ax* en argument, on travaille l'objet ax du main et on va lui appliquer des méthodes, ce que l'on peut faire même dans une fonction - c'est remplacer complètement un objet avec une assignation ( = ) qui n'a qu'une portée locale.
|
|
|
Si on suit la même logique, on n'a pas besoin non plus de préciser a, b et même N comme arguments des fonctions T_emp_parametre et T_emp_Tchego puisque la fonction va aller chercher ces variables "manquantes" dans le code principal. Cependant, il est toujours intéressant de voir quels sont les arguments, surtout si l'on voulait en faire une utilisation pédagogique.
|
|
|
|
|
|
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 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.
|
|
|
|
|
|
On retrace les fonctions, dans lesquelles la valeur de en_periode_de_retour sera prise en compte pour le calcul.
|
|
|
|
|
|
``` python
|
|
|
def update_a(val):
|
|
|
global a
|
|
|
a = val
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
|
|
|
def update_b(val):
|
|
|
global b
|
|
|
b = val
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
|
|
|
def update_N(val):
|
|
|
global N, echantillon
|
|
|
N = int( val)
|
|
|
echantillon = sorted(range(1, N+1), reverse = True)
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
retracer_Tchego(echantillon)
|
|
|
ax.set_ylim(bottom=0, top=N*1.1)
|
|
|
fig_pp.canvas.draw_idle()
|
|
|
|
|
|
def switch_freq(label):
|
|
|
global en_periode_de_retour
|
|
|
|
|
|
en_periode_de_retour = not en_periode_de_retour
|
|
|
retracer_parametree(echantillon, a, b)
|
|
|
retracer_Tchego(echantillon)
|
|
|
|
|
|
```
|
|
|
### définition des fonctions pour retracer les courbes, appelées par les fonctions associées aux widgets
|
|
|
|
|
|
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.
|
|
|
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().
|
|
|
|
|
|
Pour le titre, on utilise les *f_string* : f précède la chaîne de caractère pour annoncer qu'il faudra interpréter les variables formattées placées entre accolades à l'intérieur, {nom_de_variable:code_format}.
|
|
|
f"a={a:.2f}" signifie que l'on va écrire "a=" suivi de la valeur de a, comme flottant (f) avec deux chiffres après la virgule.
|
|
|
|
|
|
|
|
|
``` python
|
|
|
def retracer_Tchego(echantillon):
|
|
|
if en_periode_de_retour:
|
|
|
courbe_estim_Tchego.set_data(T_emp_Tchego(len(echantillon)), echantillon)
|
|
|
else:
|
|
|
courbe_estim_Tchego.set_data([ 1 - (1 / T) for T in T_emp_Tchego(len(echantillon))], echantillon)
|
|
|
|
|
|
|
|
|
def retracer_parametree(echantillon, a, b):
|
|
|
|
|
|
T_emp = T_emp_parametre(len(echantillon), a,b)
|
|
|
|
|
|
if en_periode_de_retour:
|
|
|
courbe_estim_param.set_data(T_emp, echantillon)
|
|
|
ax.set_title(f"Estimations de T empirique\n de Tchegodayev et paramétrée a={a:.2f} et b={b:.2f} ")
|
|
|
ax.set_xlim(xmin=min(T_emp), xmax=max(T_emp) * 1.1)
|
|
|
else:
|
|
|
freq = [ 1 - (1 / T) for T in T_emp]
|
|
|
courbe_estim_param.set_data(freq, echantillon)
|
|
|
ax.set_title(f"Estimations de la fréquence empirique\n de Tchegodayev et paramétrée a={a:.2f} et b={b:.2f} ")
|
|
|
ax.set_xlim(xmin=min(freq), xmax=max(freq) * 1.1)
|
|
|
#ax.set_xbounds(lower=min(T_emp), upper=max(T_emp))
|
|
|
|
|
|
fig_pp.canvas.draw_idle()
|
|
|
```
|
|
|
|
|
|
Le code est fourni, n'hésitez pas à le modifier à votre sauce et à me faire remonter vos remarques.
|
|
|
Pour un autre exemple, intégré à un projet bâti autour d'une interface Tkinter, voir ST2Shape. |