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