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