possibilité de tracer les repères
[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
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, enfants_add = []):
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
27 Les enfants en plus sont dans la liste enfants_add. Pour chaque item de la liste, il faut prendre
28 item[typed] pour avoir accès au nom, ljours, et ldonnees
29
30 """
31 debug("debut de cree_figure. Config : "+str(conf)+". Nombre d'enfants additionnels : "+str(len(enfants_add)),liste_err)
32 try:
33 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)
34 except:
35 erreur("bug avec liste data labels",liste_err)
36 return ""
37
38 ######################## Gestion des bornes #############################
39 # y a-t-il un maxi saisi par l'utilisateur ?
40 if conf["maxi"] ==0:
41 # Est-ce qu'on a donné un maxi quand même (car même échelle) ?
42 if conf["non_sauve"].get("maxi",0) == 0:
43 jour_maxi = calcule_max_graphique(l_jours)
44 else:
45 jour_maxi = conf["non_sauve"]["maxi"]+1
46 else:
47 jour_maxi = conf["maxi"]+1
48
49 # Si on cherche à extrapoler au-delà
50 if conf["non_sauve"]["calculextradata_type"] == typedonnee and conf["non_sauve"]["calculextradata_age"]>jour_maxi:
51 jour_maxi = int(conf["non_sauve"]["calculextradata_age"]) +1
52
53 # On s'assure que c'est bien compris dans les bornes
54 jour_maxi = max(CONFIG["jours_mini_courbe"],min(jour_maxi,CONFIG["jours_maxi_courbe"]))
55 debug("cree_figure : gestion du jour max : "+str(jour_maxi),liste_err)
56
57 ##################### Gestion des unités ###############################
58 # si l'unité n'est pas précisée, ni en "non sauvé" ni par l'utilisateur
59 if conf["unite"] == "" and conf["non_sauve"].get("unite","") == "":
60 unite = u.choix_unite(jour_maxi)
61 debug("Unité non précisée, on choisit "+unite,liste_err)
62 elif conf["unite"] != "":
63 unite = conf["unite"]
64 else:
65 unite = conf["non_sauve"]["unite"]
66
67 ##################### Gestion de la prématurité #######################"
68 prema = int(convertit_jours_vers_python(conf["prematurite"],liste_err))
69 ## Gestion des prémas, deux cas :
70 # Si agecorrige est oui, alors on veut juste soustraire la valeur de préma
71 # à toutes les données.
72 # Si agecorrige est non, alors on veut ajouter la valeur de préma aux courbes de référence.
73 debug("Prématurité : "+str(prema)+" age corrigé : "+conf["agecorrige"],liste_err)
74 if prema>0 and conf["agecorrige"] == "oui":
75 l_jours = [j-prema for j in l_jours]
76 jour_maxi = jour_maxi - prema
77
78
79 ###################### Conversion des unités ###########################""
80 l_jours_conv = u.convertit_tableau(l_jours,unite,liste_err)
81 # Attention, comme les jours commencent à partir de 0, faut enlever 1 pour avoir la borne...
82 age_maxi = u.convertitunite(jour_maxi-1,unite,liste_err)
83
84 debug("cree_figure : conversion des unités ok : "+str(l_jours_conv),liste_err)
85
86 #####################" Courbes OMS et titre ######################################"
87 titre = "Courbe de "+typedonnee+" OMS"
88 if conf["typecourbe"] == "P":
89 # percentiles
90 liste_data_labels = liste_data_labels_p
91 if conf["sexe"] == "M":
92 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_garcon"]#f_poids_perc_garcon
93 titre += " (percentiles, garçon)"
94 elif conf["sexe"] == "F":
95 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_fille"]
96 titre += " (percentiles, fille)"
97 else:
98 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_mixte"]
99 titre += " (percentiles)"
100 elif conf["typecourbe"] == "Z":
101 liste_data_labels = liste_data_labels_z
102 if conf["sexe"] == "M":
103 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_garcon"]
104 titre += " (moyenne et écarts-types, garçon)"
105 elif conf["sexe"] == "F":
106 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_fille"]
107 titre += " (moyenne et écarts-types, fille)"
108 else:
109 fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_mixte"]
110 titre += " (moyenne et écarts-types)"
111 else:
112 erreur("Type de courbe invalide"+conf["typecourbe"],liste_err)
113 return ""
114
115 ## On finira le titre plus tard quand on aura su qui est concerné
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 print(conf["symbole"])
161 ax.plot(l_jours_conv,l_poids,label=conf["nom"],color=conf["couleurs"]["courbeenfant"],marker=conf["symbole"])
162 debug("Tracé de la courbe enfant, avec les jours "+str(l_jours_conv),liste_err)
163
164
165 listenoms = []
166 if conf["nom"] != "": # Ajouter le nom de l'enfant
167 listenoms.append(conf["nom"])
168
169 # if enfants_add != []:
170 # debug("Il y a des enfants en plus à tracer. Données : "+str(enfants_add), liste_err)
171 for enfant in enfants_add: # Enfants additionnels éventuels
172 conf_add, lj, ld = enfant[typedonnee] # On récuère les données
173 if lj != []: # pas la peine de tracer si y'a rien à tracer
174 # Ajouter le nom de cet enfant-là
175 listenoms.append(conf_add["nom"])
176 lj_conv = u.convertit_tableau(lj,unite,liste_err)
177 debug("Tracé de la courbe additionnelle de "+conf_add["nom"]+" config : "+str(conf_add), liste_err)
178 ax.plot(lj_conv, ld, label=conf_add["nom"], color=conf_add["couleurcourbe"],marker=conf_add["symbole"])
179 print("bla")
180 if conf["sexe"] != conf_add["sexe"]:
181 warning("Attention, tous les enfants n'ont pas le même sexe. La courbe de référence est celle de "+conf["nom"]+" et ne sera pas forcément pertinente pour les autres. Vous pouvez éventuellement essayer la courbe neutre. Remarque : cette alerte s'affichera quand même.", liste_err)
182
183
184 # Si y'a un nom on met "courbe de machin"
185 if listenoms != []:
186 titre += " de " +", ".join(listenoms)
187
188 if prema>0:
189 titre+= ", préma de "+conf["prematurite"]
190 if conf["agecorrige"] == "oui":
191 titre+=" (courbe en âge corrigé)"
192 else:
193 titre+=" (courbe en âge réel, données OMS décalées)"
194
195 #### extrapolatios éventuelles
196 # a-t-on demndé des calculs ?
197 jextrapole = conf["non_sauve"]["prolongercourbes"] == "oui"
198 # Est-ce qu'on a demandé un calcul sur cette donnée ?
199
200 for calextra in CONFIG["extradata"]:
201 jextrapole = jextrapole or conf["non_sauve"][calextra+"_type"] == typedonnee
202
203 #print(jextrapole)
204 ############################## Là où on extrapole ################################
205 if jextrapole:
206 try:
207 debug("Il faut extrapoler les courbes !", liste_err)
208 # Prendre l'ensemble des dates "source"
209 # print(conf["non_sauve"]["nbdataextra"])
210 if conf["non_sauve"]["nbextradata"] == 0:
211 sources_extrap = l_jours
212 sources_extrap_data = l_poids
213 else:
214 sources_extrap = l_jours[-conf["non_sauve"]["nbextradata"]:] # les derniers jours
215 sources_extrap_data = l_poids[-conf["non_sauve"]["nbextradata"]:]
216
217 debug("On extrapole sur les jours : "+str(sources_extrap), liste_err)
218
219 # On récupère toutes les données extrapolées
220 dates_extrapole, donnees_extrapole = prolongecourbe(t, sources_extrap, sources_extrap_data, conf["typecourbe"], liste_err)
221 debug("données extrapolées !", liste_err)
222 #debug(str(dates_extrapole[0:10])+str(donnees_extrapole[0:10]), liste_err)
223
224 # QUe veut-on maintenant sur ces données extrapolées ?
225 # Afficher la courbe
226 if conf["non_sauve"]["prolongercourbes"] == "oui":
227 # On va prendre les extrapolations de la dernière donnée jusqu'à l fin du graphe
228 debut_extr = int(l_jours[-conf["non_sauve"]["nbextradata"]])
229 i_debut_extr = dates_extrapole.index(debut_extr)
230 i_fin_extr = dates_extrapole.index(jour_maxi)
231 # Voilà ce qu'on veut tracer
232 dates_extrapole_trace = dates_extrapole[i_debut_extr:i_fin_extr+1]
233 donnees_extrapole_trace = donnees_extrapole[i_debut_extr:i_fin_extr+1]
234 dates_extrapole_trace = u.convertit_tableau(dates_extrapole_trace,unite,liste_err)
235
236 # tracé des données extrapolées
237 plt.plot(dates_extrapole_trace, donnees_extrapole_trace,color=conf["couleurs"]["cadretxt"], linestyle=(0, (5,7)), marker=None)
238 debug("Tracé de la courbe extrapolée ok", liste_err)
239
240 ### Calculer une donnée à l'âge x
241 if conf["non_sauve"]["calculextradata_type"] == typedonnee:
242 # On essaie l'interpolation
243 r = interpole_lineaire(l_jours,l_poids,conf["non_sauve"]["calculextradata_age"], liste_err)
244 if r==-1:
245 # ça sera donc une extrapolation
246 r = calcule_donnee_extrapolee(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextradata_age"], liste_err)
247 message=formate_extrapole(conf["non_sauve"]["nbextradata"])
248 # if == 0:
249 # message+="l'ensemble des données"
250 # else:
251 # message+="les "+str(conf["non_sauve"]["nbextradata"])+" dernière"+met_s(conf["non_sauve"]["nbextradata"])+" données"
252 else:
253 message=formate_interpole()
254
255 texte = formate_resultat_donnee(conf["non_sauve"]["calculextradata_age"], r, typedonnee, message, liste_err)
256 debug("calcul de la donnée extrapolée : "+texte, liste_err)
257 if texte!="":
258 liste_extracalculs.append(texte)
259 print(liste_extracalculs)
260 # Ajouter le trait ?
261 if conf["non_sauve"]["calculextradata_trace"] == "oui":
262 dessine_guides(conf["non_sauve"]["calculextradata_age"], r, conf["couleurs"]["cadretxt"], unite, ax, liste_err)
263
264 ### Calculer un âge où on atteint cette donnée
265 if conf["non_sauve"]["calculextratemps_type"] == typedonnee:
266 # interpolation
267 r = interpole_lineaire_ordonnee(l_jours,l_poids,conf["non_sauve"]["calculextratemps_val"], liste_err)
268 if r==-1:
269 # ça sera donc une extrapolation
270 r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], liste_err)
271 message=formate_extrapole(conf["non_sauve"]["nbextradata"])
272 else:
273 message=formate_interpole()
274
275 texte = formate_resultat_age(r, conf["non_sauve"]["calculextratemps_val"], typedonnee, message, liste_err)
276
277 #r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], typedonnee, liste_err)
278 if texte!="":
279 liste_extracalculs.append(texte)
280 print(liste_extracalculs)
281 # Ajouter le trait ?
282 if conf["non_sauve"]["calculextratemps_trace"]:
283 dessine_guides(r, conf["non_sauve"]["calculextratemps_val"], conf["couleurs"]["cadretxt"], unite, ax, liste_err)
284
285 except:
286 warning("Des problèmes pour extrapoler...", liste_err)
287
288 else:
289 debug("On ne trace pas de courbe enfant", liste_err)
290
291 ###################" Gestion de l'échelle #####################
292 debug("Courbes tracées. Il n'y a plus qu'à gérer l'échelle", liste_err)
293 ### échelle à régler
294
295 # On extrait la valeur min et la valeur max des poids des courbes OMS et des données
296 (colonne_min,_,_) = liste_data_labels[-1]
297 (colonne_max,_,_) = liste_data_labels[0]
298
299 # poids max OMS
300 poids_min = min(oms.extraire_colonne(t,colonne_min,jour_maxi))
301 poids_max = max(oms.extraire_colonne(t,colonne_max,jour_maxi))
302 if l_jours != []:
303 poids_min = min(min(l_poids),poids_min)
304 # Pour le poids max, voir la dernière valeur du tableau
305 i = 0
306 while i<len(l_jours) and l_jours[i]<jour_maxi:
307 i=i+1
308 poids_max = max(max(l_poids[0:i+1]),poids_max)
309 # On ajuste un peu ces min et max
310 # min : valeur min -1kg
311 poids_min = max(0,poids_min-1)
312 #max : +5%
313 poids_max = poids_max * 1.05
314
315
316 ### Repères additionnels éventuels.
317 #reperes est une liste qui contient des dictionnaires avec "typed" (type de donnée : âge, etc), "donnee" :
318 #la donnée (en jours pour l'âge), et "texte": le texte à mettre sur le repère en question.
319 #Pour tracer des repères verticaux (horizontaux plus tard) sur la courbe.
320 for rep in conf["liste_reperes"]:
321 if rep.get("trace", "") == "oui": # SI on veut tracer
322 agec = u.convertitunite(rep["donnee"], unite, liste_err)
323 # Tracé de la ligne verticale
324 ax.vlines(agec, poids_min, poids_max, linestyles="dashed", color=conf["couleurs"]["cadretxt"])
325 # Tracé éventuel du texte
326 if rep["texte"] != "":
327 ax.text(agec, poids_min, " "+rep["texte"], rotation=90, verticalalignment='bottom', horizontalalignment='right', color=conf["couleurs"]["cadretxt"])
328
329
330
331
332
333 # Grille custom ?
334 if conf["non_sauve"]["grilleamelio"] == "oui":
335 debug("On a choisi la grille plus jolie", liste_err)
336 pas=u.choix_echelle_data(typedonnee, poids_max)
337 # data_min_arrondie
338 minechelle = int(poids_min/pas[0])*pas[0]
339
340 debug("pas choisis pour l'échelle en y : "+str(pas), liste_err)
341 echellemajeure = arange(minechelle, poids_max, pas[0])
342
343 if pas[1] >0:
344 echellemineure = arange(minechelle, poids_max, pas[1])
345 else:
346 echellemineure = []
347
348 ax.set_yticks(echellemajeure, minor=False)
349 ax.set_yticks(echellemineure, minor=True)
350
351 # échelle en temps
352 pas=u.choix_echelle_temps(unite, age_maxi)
353 debug("pas choisis pour l'échelle en x : "+str(pas), liste_err)
354
355 echellemajeure = arange(0,age_maxi, pas[0])
356 if pas[1] >0:
357 echellemineure = arange(0,age_maxi, pas[1])
358 else:
359 echellemineure = []
360 ax.set_xticks(echellemajeure, minor=False)
361 ax.set_xticks(echellemineure, minor=True)
362
363 ################################# Aspect du graphique
364
365 debug("On commende la déco du graphique", liste_err)
366
367 # La grille
368 ax.grid(conf["grille"]=="oui")
369 ax.grid(conf["grille"] == "oui", which="minor", linestyle="--")
370
371
372 plt.xlabel("Âge en "+unite,color=conf["couleurs"]["cadretxt"])
373 plt.ylabel(typedonnee.capitalize()+" en "+CONFIG["unites_typedonnees"][typedonnee],color=conf["couleurs"]["cadretxt"])
374 plt.title(titre,color=conf["couleurs"]["cadretxt"])
375 if l_jours_conv == []:
376 plt.axis([0,age_maxi, poids_min, poids_max])
377 else:
378 plt.axis([min(0,l_jours_conv[0]),age_maxi,poids_min,poids_max])
379
380
381 if conf['legende']=="oui":
382 legende = plt.legend(loc=conf['positionlegende'])
383 plt.setp(legende.get_texts(), color=conf["couleurs"]["cadretxt"])
384
385
386 fig.tight_layout()
387
388 debug("Fin de cree_figure, tout va bien.", liste_err)
389 return fig
390
391
392
393
394 ######################################## Pour extrapoler la courbe
395
396 def prolongecourbe(tableauOMS, dates, donnees, typecourbe, liste_err):
397 """ tableauOMS est le ableau des données OMS. dates et donnees sont les dates (jours)
398 et les données d'où on extrapole. On calcule toutes les dates comme des sauvages.
399 On renvoie la liste des jours totale et la liste des data_totales
400 (tableaux de jours)
401 typecourbe est P ou Z. Pour P il faut commencer à regarder à l'indice 4, pour Z
402 à l'indice 1
403 On renvoie [],[] si pas pu extrapoler. """
404 # les lignes OMS correspondant aux dates données
405 lignesoms = [tableauOMS[int(date)] for date in dates]
406 debug("prolongecourbe : Lignes OMS :"+str(lignesoms)+" valeur de données : "+str(donnees), liste_err)
407
408
409
410 # Principe : on cherche dans quel intervalle de "colonnes" on se situe.
411 # On va donc regarder pour chaque donnée entre quels i on se situe,et après
412 # prendre le plus grand intervalle.
413 # Numéros de colonnes d'où on part. Pour la fin c'est forcément longueur-1
414 if typecourbe == "P":
415 idep=4
416 else:
417 idep = 1
418
419 liste_indices = []
420 for k in range(len(dates)):
421 i= idep
422 ligne = lignesoms[k]
423 while i<len(ligne) and ligne[i]<donnees[k]:
424 i+=1
425 debug("prolongecourbe : on a trouvé la valeur de i : "+str(i),liste_err)
426 if i>=len(ligne):
427 warning("prolongation de courbe : pas réussi... donnée trop haute !", liste_err)
428 return [],[]
429 if i==idep:
430 warning("prolongation de courbe : pas réussi... donnée trop basse !", liste_err)
431 return [],[]
432 liste_indices.append(i)
433 imin=min(liste_indices) -1
434 imax=max(liste_indices)
435 debug("Les données se situent dans les indices : "+str(imin)+", "+str(imax),liste_err)
436 # Maintenant on doit trouver les coeffs : on se situe en coeff * l[imin]+ (1-coeff)*ligne[imax]
437 # Et faire la moyenne de ces coeffs
438 total = 0
439 for k in range(len(dates)):
440 ligne = lignesoms[k]
441 donnee = donnees[k]
442 total += (donnee - ligne[imax])/(ligne[imin] - ligne[imax])
443 #print(k)
444 coeff_moyen = total/len(dates)
445
446 debug("Coeff moyen calculé : "+str(coeff_moyen), liste_err)
447
448 # On utilisera la même chose pour les nouvelle donnee
449
450 # extrapolations
451 nouvdates =oms.extraire_colonne(tableauOMS,0) # On sort tout.
452 #print(nouvdates)
453 nouvdonnees = []
454 for j in nouvdates:
455 ligne2 = tableauOMS[int(j)]
456 nouvdonnees.append(coeff_moyen*ligne2[imin]+ (1-coeff_moyen)*ligne2[imax])
457
458 return nouvdates,nouvdonnees
459
460
461 def dessine_guides(t, data, couleur, unite, ax, liste_err):
462 """ dessine deux lignes, horizontales et verticales qui vont "vers" la courbe
463 jusqu'aux points (t, data). En pointillés et avec un point dessus."""
464 debug("Début de dessine_guides"+str(t)+", "+str(data), liste_err)
465 t_conv = u.convertitunite(t,unite,liste_err)
466 ax.vlines(t_conv, 0, data, colors=couleur, linestyles="dashed")
467 ax.hlines(data, 0, t_conv, color=couleur, linestyles="dashed")
468 ax.plot([t_conv], [data], color=couleur, marker="*", ms=13)