Extrapolation améliorée + possibilité de voir le point voulu
[perso/Denise/oms.git] / trace_courbe.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 from configuration import CONFIG
4 import gestionOMS as oms
5 import gestion_unites as u
6 from gestion_donnees import calcule_max_graphique, convertit_jours_vers_python, convertit_age_vers_texte
7 from gestion_erreurs import debug, erreur, warning
8 from calculs_extrapole import calcule_donnee_extrapolee, calcule_age_extrapole, interpole_lineaire, interpole_lineaire_ordonnee, formate_resultat_donnee, formate_resultat_age, formate_interpole, formate_extrapole
9
10 from numpy import arange
11
12
13 import matplotlib.pyplot as plt
14
15 # Essentiellement, la fonction qui trace la courbe, mais on y trouve également les fonctions d'extrapolation.
16 # Ainsi que les calculs additionnels.
17
18
19 def cree_figure(conf,l_jours,l_poids,typedonnee,liste_extracalculs, liste_err):
20 """ conf est le dictionnaire de config. l_jours et l_poids les listes des temps (en jours) et de données
21 (donc pas forcément du poids)
22 typedonnee est le type de données (voir CONFIG["liste_typedonnees"]
23 liste_err la liste des erreurs à compléter (voir gestion_erreurs))
24 Renvoie la figure tracée, et les calculs additionnels sont mis sous forme de chaîne dans la liste
25 liste_extracalculs"""
26 debug("debut de cree_figure. Config : "+str(conf),liste_err)
27 try:
28 liste_data_labels_p,liste_data_labels_z = oms.renvoie_liste_labels(conf,CONFIG["liste_data_choisie_p"],CONFIG["liste_data_choisie_z"],liste_err)
29 except:
30 erreur("bug avec liste data labels",liste_err)
31 return ""
32
33 ######################## Gestion des bornes #############################
34 # y a-t-il un maxi saisi par l'utilisateur ?
35 if conf["maxi"] ==0:
36 # Est-ce qu'on a donné un maxi quand même (car même échelle) ?
37 if conf["non_sauve"].get("maxi",0) == 0:
38 jour_maxi = calcule_max_graphique(l_jours)
39 else:
40 jour_maxi = conf["non_sauve"]["maxi"]+1
41 else:
42 jour_maxi = conf["maxi"]+1
43
44 # On s'assure que c'est bien compris dans les bornes
45 jour_maxi = max(CONFIG["jours_mini_courbe"],min(jour_maxi,CONFIG["jours_maxi_courbe"]))
46 debug("cree_figure : gestion du jour max : "+str(jour_maxi),liste_err)
47
48 ##################### Gestion des unités ###############################
49 # si l'unité n'est pas précisée, ni en "non sauvé" ni par l'utilisateur
50 if conf["unite"] == "" and conf["non_sauve"].get("unite","") == "":
51 unite = u.choix_unite(jour_maxi)
52 debug("Unité non précisée, on choisit "+unite,liste_err)
53 elif conf["unite"] != "":
54 unite = conf["unite"]
55 else:
56 unite = conf["non_sauve"]["unite"]
57
58 ##################### Gestion de la prématurité #######################"
59 prema = int(convertit_jours_vers_python(conf["prematurite"],liste_err))
60 ## Gestion des prémas, deux cas :
61 # Si agecorrige est oui, alors on veut juste soustraire la valeur de préma
62 # à toutes les données.
63 # Si agecorrige est non, alors on veut ajouter la valeur de préma aux courbes de référence.
64 debug("Prématurité : "+str(prema)+" age corrigé : "+conf["agecorrige"],liste_err)
65 if prema>0 and conf["agecorrige"] == "oui":
66 l_jours = [j-prema for j in l_jours]
67 jour_maxi = jour_maxi - prema
68
69
70 ###################### Conversion des unités ###########################""
71 l_jours_conv = u.convertit_tableau(l_jours,unite,liste_err)
72 # Attention, comme les jours commencent à partir de 0, faut enlever 1 pour avoir la borne...
73 age_maxi = u.convertitunite(jour_maxi-1,unite,liste_err)
74
75 debug("cree_figure : conversion des unités ok : "+str(l_jours_conv),liste_err)
76
77 #####################" Courbes OMS et titre ######################################"
78 titre = "Courbe de "+typedonnee+" OMS"
79 if conf["typecourbe"] == "P":
80 # percentiles
81 liste_data_labels = liste_data_labels_p
82 if conf["sexe"] == "M":
83 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_garcon"]#f_poids_perc_garcon
84 titre += " (percentiles, garçon)"
85 elif conf["sexe"] == "F":
86 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_fille"]
87 titre += " (percentiles, fille)"
88 else:
89 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_mixte"]
90 titre += " (percentiles)"
91 elif conf["typecourbe"] == "Z":
92 liste_data_labels = liste_data_labels_z
93 if conf["sexe"] == "M":
94 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_garcon"]
95 titre += " (moyenne et écarts-types, garçon)"
96 elif conf["sexe"] == "F":
97 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_fille"]
98 titre += " (moyenne et écarts-types, fille)"
99 else:
100 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_mixte"]
101 titre += " (moyenne et écarts-types)"
102 else:
103 erreur("Type de courbe invalide"+conf["typecourbe"],liste_err)
104 return ""
105
106 # Si y'a un nom on met "courbe de machin"
107 if conf["nom"] !="":
108 titre += " de " +conf["nom"]
109
110 if prema>0:
111 titre+= ", préma de "+conf["prematurite"]
112 if conf["agecorrige"] == "oui":
113 titre+=" (courbe en âge corrigé)"
114 else:
115 titre+=" (courbe en âge réel, données OMS décalées)"
116
117 #debug("cree_figure : géré le type de courbe ok. Liste des data labels : "+str(liste_data_labels),liste_err)
118 debug("Fichier d'où extraire les données : "+fichier_oms,liste_err)
119
120
121 #### On extrait les données des courbes, et on convertit les jours dans l'unité voulues
122 try:
123 t = oms.lire_fichier_csv(fichier_oms)
124 except:
125 erreur("cree_figure : Impossible d'ouvrir le fichier "+fichier_oms, liste_err)
126 return ""
127
128 debug("cree_figure : Conversion des données OMS à la bonne unité",liste_err)
129 try:
130 coljour= (oms.extraire_colonne(t,0,jour_maxi))
131 if prema>0 and conf["agecorrige"] != "oui":
132 coljour = [j + prema for j in coljour]
133 coljour = u.convertit_tableau(coljour,unite,liste_err)
134 except:
135 erreur("Problème à la conversion du tableau OMS. jour_maxi = "+str(jour_maxi)+" unite = "+unite,liste_err)
136 return ""
137
138 ##################### Création de la figure et du graphique ###################
139 debug("cree_figure : prête à créer la figure",liste_err)
140 #### La figure, params
141
142 fig = plt.figure(num=None, figsize=(conf["largeur"], conf["hauteur"]), dpi=100, facecolor=conf["couleurs"]["fond"])
143 plt.rcParams['axes.facecolor'] = conf["couleurs"]["fond"]
144 plt.rcParams['axes.edgecolor']= conf["couleurs"]["cadretxt"]
145 plt.rcParams['xtick.color'] = conf["couleurs"]["cadretxt"]
146 plt.rcParams['ytick.color'] = conf["couleurs"]["cadretxt"]
147 plt.rcParams['grid.color'] = conf["couleurs"]["grille"]
148 plt.rcParams['legend.edgecolor'] = conf["couleurs"]["grille"]
149 ax = plt.axes()
150
151 ###################### Tracé des différentes courbes
152 #Tracé des courbes OMS
153 for (i,label,couleur) in liste_data_labels:
154 ax.plot(coljour,oms.extraire_colonne(t,i,jour_maxi),label=label,color=couleur)
155
156 debug("cree_figure : tracé des courbes OMS ok",liste_err)
157
158 ### Tracé pour de bon
159 if l_jours != []:
160 ax.plot(l_jours_conv,l_poids,label=conf["nom"],color=conf["couleurs"]["cadretxt"],marker='o')
161 debug("Tracé de la courbe enfant, avec les jours "+str(l_jours_conv),liste_err)
162
163 #### extrapolatios éventuelles
164 # a-t-on demndé des calculs ?
165 jextrapole = conf["non_sauve"]["prolongercourbes"] == "oui"
166 # Est-ce qu'on a demandé un calcul sur cette donnée ?
167 print()
168 for calextra in CONFIG["extradata"]:
169 jextrapole = jextrapole or conf["non_sauve"][calextra+"_type"] == typedonnee
170
171 #print(jextrapole)
172 ############################## Là où on extrapole ################################
173 if jextrapole:
174 try:
175 debug("Il faut extrapoler les courbes !", liste_err)
176 # Prendre l'ensemble des dates "source"
177 # print(conf["non_sauve"]["nbdataextra"])
178 if conf["non_sauve"]["nbextradata"] == 0:
179 sources_extrap = l_jours
180 sources_extrap_data = l_poids
181 else:
182 sources_extrap = l_jours[-conf["non_sauve"]["nbextradata"]:] # les derniers jours
183 sources_extrap_data = l_poids[-conf["non_sauve"]["nbextradata"]:]
184
185 debug("On extrapole sur les jours : "+str(sources_extrap), liste_err)
186
187 # On récupère toutes les données extrapolées
188 dates_extrapole, donnees_extrapole = prolongecourbe(t, sources_extrap, sources_extrap_data, conf["typecourbe"], liste_err)
189 debug("données extrapolées !", liste_err)
190 #debug(str(dates_extrapole[0:10])+str(donnees_extrapole[0:10]), liste_err)
191
192 # QUe veut-on maintenant sur ces données extrapolées ?
193 # Afficher la courbe
194 if conf["non_sauve"]["prolongercourbes"] == "oui":
195 # On va prendre les extrapolations de la dernière donnée jusqu'à l fin du graphe
196 debut_extr = int(l_jours[-conf["non_sauve"]["nbextradata"]])
197 i_debut_extr = dates_extrapole.index(debut_extr)
198 i_fin_extr = dates_extrapole.index(jour_maxi)
199 # Voilà ce qu'on veut tracer
200 dates_extrapole_trace = dates_extrapole[i_debut_extr:i_fin_extr+1]
201 donnees_extrapole_trace = donnees_extrapole[i_debut_extr:i_fin_extr+1]
202 dates_extrapole_trace = u.convertit_tableau(dates_extrapole_trace,unite,liste_err)
203
204 # tracé des données extrapolées
205 plt.plot(dates_extrapole_trace, donnees_extrapole_trace,color=conf["couleurs"]["cadretxt"], linestyle=(0, (5,7)), marker=None)
206 debug("Tracé de la courbe extrapolée ok", liste_err)
207
208 ### Calculer une donnée à l'âge x
209 if conf["non_sauve"]["calculextradata_type"] == typedonnee:
210 # On essaie l'interpolation
211 r = interpole_lineaire(l_jours,l_poids,conf["non_sauve"]["calculextradata_age"], liste_err)
212 if r==-1:
213 # ça sera donc une extrapolation
214 r = calcule_donnee_extrapolee(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextradata_age"], liste_err)
215 message=formate_extrapole(conf["non_sauve"]["nbextradata"])
216 # if == 0:
217 # message+="l'ensemble des données"
218 # else:
219 # message+="les "+str(conf["non_sauve"]["nbextradata"])+" dernière"+met_s(conf["non_sauve"]["nbextradata"])+" données"
220 else:
221 message=formate_interpole()
222
223 texte = formate_resultat_donnee(conf["non_sauve"]["calculextradata_age"], r, typedonnee, message, liste_err)
224 debug("calcul de la donnée extrapolée : "+texte, liste_err)
225 if texte!="":
226 liste_extracalculs.append(texte)
227 print(liste_extracalculs)
228 # Ajouter le trait ?
229 if conf["non_sauve"]["calculextradata_trace"] == "oui":
230 dessine_guides(conf["non_sauve"]["calculextradata_age"], r, conf["couleurs"]["cadretxt"], unite, ax, liste_err)
231
232 ### Calculer un âge où on atteint cette donnée
233 if conf["non_sauve"]["calculextratemps_type"] == typedonnee:
234 # interpolation
235 r = interpole_lineaire_ordonnee(l_jours,l_poids,conf["non_sauve"]["calculextratemps_val"], liste_err)
236 if r==-1:
237 # ça sera donc une extrapolation
238 r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], liste_err)
239 message=formate_extrapole(conf["non_sauve"]["nbextradata"])
240 else:
241 message=formate_interpole()
242
243 texte = formate_resultat_age(r, conf["non_sauve"]["calculextratemps_val"], typedonnee, message, liste_err)
244
245 #r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], typedonnee, liste_err)
246 if texte!="":
247 liste_extracalculs.append(texte)
248 print(liste_extracalculs)
249 # Ajouter le trait ?
250 if conf["non_sauve"]["calculextratemps_trace"]:
251 dessine_guides(r, conf["non_sauve"]["calculextratemps_val"], conf["couleurs"]["cadretxt"], unite, ax, liste_err)
252
253 except:
254 warning("Des problèmes pour extrapoler...", liste_err)
255
256 else:
257 debug("On ne trace pas de courbe enfant", liste_err)
258
259 ###################" Gestion de l'échelle #####################
260 debug("Courbes tracées. Il n'y a plus qu'à gérer l'échelle", liste_err)
261 ### échelle à régler
262
263 # On extrait la valeur min et la valeur max des poids des courbes OMS et des données
264 (colonne_min,_,_) = liste_data_labels[-1]
265 (colonne_max,_,_) = liste_data_labels[0]
266
267 # poids max OMS
268 poids_min = min(oms.extraire_colonne(t,colonne_min,jour_maxi))
269 poids_max = max(oms.extraire_colonne(t,colonne_max,jour_maxi))
270 if l_jours != []:
271 poids_min = min(min(l_poids),poids_min)
272 # Pour le poids max, voir la dernière valeur du tableau
273 i = 0
274 while i<len(l_jours) and l_jours[i]<jour_maxi:
275 i=i+1
276 poids_max = max(max(l_poids[0:i+1]),poids_max)
277 # On ajuste un peu ces min et max
278 # min : valeur min -1kg
279 poids_min = max(0,poids_min-1)
280 #max : +5%
281 poids_max = poids_max * 1.05
282
283
284 # Grille custom ?
285 if conf["non_sauve"]["grilleamelio"] == "oui":
286 debug("On a choisi la grille plus jolie", liste_err)
287 pas=u.choix_echelle_data(typedonnee, poids_max)
288 # data_min_arrondie
289 minechelle = int(poids_min/pas[0])*pas[0]
290
291 debug("pas choisis pour l'échelle en y : "+str(pas), liste_err)
292 echellemajeure = arange(minechelle, poids_max, pas[0])
293
294 if pas[1] >0:
295 echellemineure = arange(minechelle, poids_max, pas[1])
296 else:
297 echellemineure = []
298
299 ax.set_yticks(echellemajeure, minor=False)
300 ax.set_yticks(echellemineure, minor=True)
301
302 # échelle en temps
303 pas=u.choix_echelle_temps(unite, age_maxi)
304 debug("pas choisis pour l'échelle en x : "+str(pas), liste_err)
305
306 echellemajeure = arange(0,age_maxi, pas[0])
307 if pas[1] >0:
308 echellemineure = arange(0,age_maxi, pas[1])
309 else:
310 echellemineure = []
311 ax.set_xticks(echellemajeure, minor=False)
312 ax.set_xticks(echellemineure, minor=True)
313
314 ################################# Aspect du graphique
315
316 debug("On commende la déco du graphique", liste_err)
317
318 # La grille
319 ax.grid(conf["grille"]=="oui")
320 ax.grid(conf["grille"] == "oui", which="minor", linestyle="--")
321
322
323 plt.xlabel("Âge en "+unite,color=conf["couleurs"]["cadretxt"])
324 plt.ylabel(typedonnee.capitalize()+" en "+CONFIG["unites_typedonnees"][typedonnee],color=conf["couleurs"]["cadretxt"])
325 plt.title(titre,color=conf["couleurs"]["cadretxt"])
326 if l_jours_conv == []:
327 plt.axis([0,age_maxi, poids_min, poids_max])
328 else:
329 plt.axis([min(0,l_jours_conv[0]),age_maxi,poids_min,poids_max])
330
331
332 if conf['legende']=="oui":
333 legende = plt.legend(loc=conf['positionlegende'])
334 plt.setp(legende.get_texts(), color=conf["couleurs"]["cadretxt"])
335
336
337 fig.tight_layout()
338
339 debug("Fin de cree_figure, tout va bien.", liste_err)
340 return fig
341
342
343
344
345 ######################################## Pour extrapoler la courbe
346
347 def prolongecourbe(tableauOMS, dates, donnees, typecourbe, liste_err):
348 """ tableauOMS est le ableau des données OMS. dates et donnees sont les dates (jours)
349 et les données d'où on extrapole. On calcule toutes les dates comme des sauvages.
350 On renvoie la liste des jours totale et la liste des data_totales
351 (tableaux de jours)
352 typecourbe est P ou Z. Pour P il faut commencer à regarder à l'indice 4, pour Z
353 à l'indice 1
354 On renvoie [],[] si pas pu extrapoler. """
355 # les lignes OMS correspondant aux dates données
356 lignesoms = [tableauOMS[int(date)] for date in dates]
357 debug("prolongecourbe : Lignes OMS :"+str(lignesoms)+" valeur de données : "+str(donnees), liste_err)
358
359
360
361 # Principe : on cherche dans quel intervalle de "colonnes" on se situe.
362 # On va donc regarder pour chaque donnée entre quels i on se situe,et après
363 # prendre le plus grand intervalle.
364 # Numéros de colonnes d'où on part. Pour la fin c'est forcément longueur-1
365 if typecourbe == "P":
366 idep=4
367 else:
368 idep = 1
369
370 liste_indices = []
371 for k in range(len(dates)):
372 i= idep
373 ligne = lignesoms[k]
374 while i<len(ligne) and ligne[i]<donnees[k]:
375 i+=1
376 debug("prolongecourbe : on a trouvé la valeur de i : "+str(i),liste_err)
377 if i>=len(ligne):
378 warning("prolongation de courbe : pas réussi... donnée trop haute !", liste_err)
379 return [],[]
380 if i==idep:
381 warning("prolongation de courbe : pas réussi... donnée trop basse !", liste_err)
382 return [],[]
383 liste_indices.append(i)
384 imin=min(liste_indices) -1
385 imax=max(liste_indices)
386 debug("Les données se situent dans les indices : "+str(imin)+", "+str(imax),liste_err)
387 # Maintenant on doit trouver les coeffs : on se situe en coeff * l[imin]+ (1-coeff)*ligne[imax]
388 # Et faire la moyenne de ces coeffs
389 total = 0
390 for k in range(len(dates)):
391 ligne = lignesoms[k]
392 donnee = donnees[k]
393 total += (donnee - ligne[imax])/(ligne[imin] - ligne[imax])
394 #print(k)
395 coeff_moyen = total/len(dates)
396
397 debug("Coeff moyen calculé : "+str(coeff_moyen), liste_err)
398
399 # On utilisera la même chose pour les nouvelle donnee
400
401 # extrapolations
402 nouvdates =oms.extraire_colonne(tableauOMS,0) # On sort tout.
403 #print(nouvdates)
404 nouvdonnees = []
405 for j in nouvdates:
406 ligne2 = tableauOMS[int(j)]
407 nouvdonnees.append(coeff_moyen*ligne2[imin]+ (1-coeff_moyen)*ligne2[imax])
408
409 return nouvdates,nouvdonnees
410
411
412 def dessine_guides(t, data, couleur, unite, ax, liste_err):
413 """ dessine deux lignes, horizontales et verticales qui vont "vers" la courbe
414 jusqu'aux points (t, data). En pointillés et avec un point dessus."""
415 debug("Début de dessine_guides"+str(t)+", "+str(data), liste_err)
416 t_conv = u.convertitunite(t,unite,liste_err)
417 ax.vlines(t_conv, 0, data, colors=couleur, linestyles="dashed")
418 ax.hlines(data, 0, t_conv, color=couleur, linestyles="dashed")
419 ax.plot([t_conv], [data], color=couleur, marker="*", ms=13)