genererTableau.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. from io import StringIO, BytesIO
  2. import pyexcel as pe
  3. import csv
  4. import math
  5. class genererTableau ():
  6. """
  7. ecrire_tableau :
  8. Permet de créer un tableau et l'exporter au format excel, ods ou csv à partir de données d'analyses.
  9. Input :
  10. data : données aggrégées généré par les méthodes de analyseStatistiques
  11. precision : nombre de décimales de précision
  12. quantitative_string_format : format d'écriture de la moyenne, écart type et taille d'échantillon des variables quantitatives
  13. qualitative_string_format : format d'écriture du n et de la proportion des variables qualitatives
  14. """
  15. def __init__ (self, data, precision = 2, quantitative_column_title = "{variable} mean +/- std (n)", quantitative_string_format = "{mean:.2f} +/- {std:.2f} ({n})", qualitative_column_title="{variable} no. (%)", qualitative_string_format = "{n} ({p:.2%})"):
  16. """
  17. Data : data contenant les données aggrégées, généré par analyseStatistiques.analyse_univarie
  18. """
  19. # Parametres
  20. self.quantitative_column_title = quantitative_column_title
  21. self.quantitative_string_format = quantitative_string_format
  22. self.qualitative_column_title = qualitative_column_title
  23. self.qualitative_string_format = qualitative_string_format
  24. self.absence_test = "Absence de test effectué (CI des tests conventionnels non remplis)"
  25. self.precision = precision
  26. self.data = data
  27. self.liste_variables = list(data.keys()) # Liste des variables analysables
  28. if "sous_groupes" in list(data.values())[0]:
  29. self.liste_axes = list(list(data.values())[0]["sous_groupes"].keys()) # Liste des axes d'analyses
  30. else:
  31. self.liste_axes = []
  32. # On récupère la liste des modalités de chaque axes
  33. self.liste_modalites = dict(zip(
  34. self.liste_axes,
  35. [self._obtenir_modalite_axe(x, True) for x in self.liste_axes]
  36. ))
  37. # On récupère la liste des modalités de chaque variable
  38. self.liste_modalites_variables = dict(zip(
  39. self.liste_variables,
  40. [self._obtenir_modalite_variable(x, True) for x in self.liste_variables]
  41. ))
  42. def _generer_texte_propre (self, texte):
  43. """
  44. Nettoie un texte
  45. Si il s'agit d'un flottant : le met en int
  46. """
  47. if type(texte) != type(str()):
  48. texte = str(int(texte))
  49. return(texte)
  50. def _generer_str_mean_std_n(self, mean, std, n):
  51. """
  52. Simple fonction qui écrit un string mélangeant moyenne, écart-type et taille de l'effectif
  53. Format par défault :
  54. {mean:.2f} +/- {std:.2f} ({n})
  55. Peut-être personalisé par le paramètre string_format.
  56. Input :
  57. mean, std et n
  58. """
  59. texte = self.quantitative_string_format.format(
  60. mean=round(mean, self.precision),
  61. std=round(std, self.precision),
  62. n=round(n, self.precision)
  63. )
  64. return(texte)
  65. def _generer_str_n_p(self, n, p):
  66. """
  67. Simple fonction qui écrit un string mélangeant moyenne, écart-type et taille de l'effectif
  68. Format par défault :
  69. {n} ({p:.0%})
  70. Peut-être personalisé par le paramètre string_format.
  71. Input :
  72. mean, std et n
  73. Sortie :
  74. texte
  75. """
  76. texte = self.qualitative_string_format.format(
  77. p = round(p, self.precision),
  78. n = round(n, self.precision)
  79. )
  80. return(texte)
  81. def _generer_str_valeur(self, valeur):
  82. """
  83. Simple fonction qui écrit un string à partir d'une valeur
  84. Input :
  85. valeur
  86. Sortie :
  87. texte
  88. """
  89. texte = "{}".format(
  90. round(valeur, self.precision)
  91. )
  92. return(texte)
  93. def _generer_str_ci(self, liste_ci):
  94. """
  95. Simple fonction qui écrit un string à partir d'une liste représentant un intervale de confiance
  96. Input :
  97. liste_ci : liste de 2 items décrivant un intervalle de confiance
  98. Sortie :
  99. texte
  100. """
  101. texte = "{}-{}".format(
  102. self._generer_str_valeur(liste_ci[0]),
  103. self._generer_str_valeur(liste_ci[1])
  104. )
  105. return(texte)
  106. def _generer_str_resultat_test(self, test):
  107. """
  108. Génère une liste avec formatage textuel du résultat d'un test
  109. Vérifie la bonne application du test avant.
  110. Input :
  111. test : liste contenant le résultat du test
  112. Sortie :
  113. liste de 3 éléments comprenant : Test, Paramètre et p
  114. """
  115. resultat = []
  116. if len(test[1].keys()) > 1:
  117. resultat.append(test[0]) # Nom du test
  118. resultat.append(self._generer_str_valeur(test[1]["statistic"])) # Paramètre de test
  119. resultat.append(self._formater_p_value(test[1]["p_value"])) # Petit p du test
  120. else:
  121. resultat.append(self.absence_test)
  122. resultat = resultat + ["", ""]
  123. return(resultat)
  124. def _formater_p_value(self, p_value):
  125. # Nombre de décimales concernées
  126. n_digit = len(str(p_value).split(".")[-1].split("0"))
  127. borne_basse = 5*math.pow(10, -n_digit)
  128. borne_sup = math.pow(10, -n_digit+1)
  129. estimation_p = borne_basse if p_value <= borne_basse else borne_sup
  130. # Affichage
  131. if p_value > 0.01:
  132. formated_p_value = str(round(p_value, 3))
  133. else:
  134. if p_value > 0.001:
  135. formated_p_value = "< "+str(estimation_p)
  136. else:
  137. formated_p_value = "< 10^"+str(n_digit-1)
  138. return(formated_p_value)
  139. def _verifier_existence_variables(self, variables):
  140. """
  141. Vérifie que les variables existent bien dans le jeu de données.
  142. Input : variables : liste des variables à tester
  143. Output : None
  144. """
  145. variables_manquantes = [x for x in variables if x not in self.liste_variables]
  146. if len(variables_manquantes) > 0:
  147. raise Exception("Erreur, les variables {} sont inexistantes des données aggrégées.".format(
  148. ", ".join(variables_manquantes)
  149. ))
  150. def _verifier_existence_axes(self, axes):
  151. """
  152. Vérifie que les axes existent bien dans le jeu de données.
  153. Input : variables : liste des axes à tester
  154. Output : None
  155. """
  156. if axes is not None:
  157. axes_manquants = [x for x in axes if x not in self.liste_axes]
  158. else:
  159. axes_manquants = []
  160. if len(axes_manquants) > 0:
  161. raise Exception("Erreur, les axes {} sont inexistantes des données aggrégées.".format(
  162. ", ".join(axes_manquants)
  163. ))
  164. def _obtenir_modalite_axe (self, axe, ajouter_nom = True):
  165. """
  166. Retourne une liste contenant toutes les modalités pour un axe donné
  167. Input :
  168. axe : nom de l'axe à analyse
  169. ajouter_nom : Si True, alors le nom de l'axe est ajouté à la modalité (concaténation)
  170. """
  171. # Liste des modalités
  172. modalites = list(self.data.values())[0]["sous_groupes"][axe].keys()
  173. # Post-traitement : on corrige les float et boolean pour les afficher en int
  174. modalites = [int(x) if type(x) != type(str()) else x for x in modalites]
  175. # On affiche le nom de l'axe avec si nécessaire
  176. modalites_avec_str = [axe+" "+str(x) for x in modalites]
  177. if ajouter_nom:
  178. return(modalites_avec_str)
  179. else:
  180. return(modalites)
  181. def _obtenir_modalite_variable (self, variable, ajouter_nom = True):
  182. """
  183. Retourne une liste contenant toutes les modalités pour une variable donnée
  184. Input :
  185. variable : nom de la variable à analyser
  186. ajouter_nom : Si True, alors le nom de l'axe est ajouté à la modalité (concaténation)
  187. """
  188. # Liste des modalités
  189. modalites = [x for x in list(self.data[variable]["global"].keys()) if x != "total"]
  190. # Post-traitement : on corrige les float et boolean pour les afficher en int
  191. modalites = [int(x) if type(x) != type(str()) else x for x in modalites]
  192. # On affiche le nom de l'axe avec si nécessaire
  193. modalites_avec_str = [variable+" "+str(x) for x in modalites]
  194. if ajouter_nom:
  195. return(modalites_avec_str)
  196. else:
  197. return(modalites)
  198. def _generer_en_tete_descriptif(self, axes):
  199. """
  200. Genere l'en-tête d'un tableau Descriptif
  201. Input :
  202. axes : liste des axes d'analyse
  203. Output : None
  204. """
  205. # Initialisation de l'en-tête
  206. en_tete = ["","",""]
  207. # Lecture des axes
  208. for axe in axes:
  209. en_tete = en_tete+self.liste_modalites[axe]+["Test","Paramètre", "p"]
  210. return en_tete
  211. def _generer_en_tete_detail(self, variable):
  212. """
  213. Genere l'en-tête d'un tableau Descriptif
  214. Input :
  215. axes : liste des axes d'analyse
  216. Output : None
  217. """
  218. # Type de variable
  219. type_variable = self.data[variable]["type"]
  220. # Initialisation de l'en-tête
  221. en_tete = []
  222. en_tete.append(variable) # Nom de la variable
  223. en_tete.append("") # Espace technique
  224. ## Pour les variables quantitatives
  225. if type_variable == "quantitative":
  226. en_tete = en_tete + ["n", "mean", "std", "IC 95%"]
  227. elif type_variable == "qualitative":
  228. en_tete.append("n")
  229. en_tete = en_tete + self.liste_modalites_variables[variable]
  230. en_tete = en_tete + ["Test", "Parameter", "p"]
  231. return en_tete
  232. def _generer_lignes_descriptif (self, variable, axes):
  233. """
  234. Genere des ligne d'analyse d'un tableau descriptif pour une variable
  235. Input :
  236. variable : nom de la variable
  237. axes : axes sur lesquels générer la ligne
  238. """
  239. lignes = []
  240. if self.data[variable]["type"] == "quantitative":
  241. # Ligne d'une variable quantitative : une seule ligne
  242. ligne = [] # On instancie la ligne
  243. ligne.append(self.quantitative_column_title.format(variable=variable)) # Ajout du nom de variable
  244. ligne.append("") # Espace vide, technique
  245. ligne.append(self._generer_str_mean_std_n(
  246. self.data[variable]["global"]["mean"],
  247. self.data[variable]["global"]["std"],
  248. self.data[variable]["global"]["n"]
  249. )) # Données globales
  250. # Données liées à chaque axe
  251. for axe in axes:
  252. donnees_axe = self.data[variable]["sous_groupes"][axe]
  253. # Pour chaque valeur de l'axe
  254. for valeur in donnees_axe.keys():
  255. ligne.append(self._generer_str_mean_std_n(
  256. donnees_axe[valeur]["mean"],
  257. donnees_axe[valeur]["std"],
  258. donnees_axe[valeur]["n"]
  259. )) # Données hors test
  260. ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe]) # Données du test
  261. lignes.append(ligne)
  262. else:
  263. # Lignes d'un variable qualitative : une ligne par modalité
  264. ## On écrit la première ligne : nom de variable et test
  265. ligne = [] # On instancie la ligne
  266. ligne.append(self.qualitative_string_format.format(variable=variable)) # Nom de variable
  267. ligne = ligne + ["", ""] # Variable vides
  268. # Données liées à chaque axe
  269. for axe in axes:
  270. blank_list = ["" for x in range(len(self.liste_modalites[axe]))]
  271. ligne = ligne + blank_list # Blanc lié aux tests
  272. ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe]) # Données du test
  273. lignes.append(ligne)
  274. ## On écrit les lignes propres à chaque variable
  275. liste_labels = self.data[variable]["global"].keys()
  276. for label in liste_labels:
  277. # On ignore le label total
  278. if label != "total":
  279. ligne = []
  280. ligne.append("") # Première ligne vide
  281. ligne.append(self._generer_texte_propre(label)) # Libelé analysé
  282. ligne.append(self._generer_str_n_p(
  283. n = self.data[variable]["global"][label]["n"],
  284. p = self.data[variable]["global"][label]["p"]
  285. )) # Données générales
  286. # Valeur par axe
  287. for axe in axes:
  288. liste_valeurs = list(self.data[variable]["sous_groupes"][axe].keys())
  289. # Pour chaque valeur de l'axe
  290. for valeur in liste_valeurs: # On met 0 si la valeur n'existe pas
  291. donnees_axe = self.data[variable]["sous_groupes"][axe][valeur]
  292. n = donnees_axe[label]["n"] if label in donnees_axe.keys() else 0
  293. p = donnees_axe[label]["p"] if label in donnees_axe.keys() else 0
  294. ligne.append(self._generer_str_n_p(
  295. n = n,
  296. p = p
  297. )) # Données spécifique à chaque axe
  298. ligne = ligne + ["", "", ""] # Espaces vides entre 2 axes
  299. lignes.append(ligne)
  300. return(lignes)
  301. def _generer_lignes_detail(self, variable, axe = None):
  302. """
  303. Genere des ligne d'analyse d'un tableau détaillé pour une variable
  304. Input :
  305. variable : nom de la variable
  306. axe : axe sur lesquels générer les ligne
  307. """
  308. lignes = []
  309. if self.data[variable]["type"] == "quantitative":
  310. # Génération de la ligne simple
  311. if axe is None:
  312. ligne = [] # On instancie la ligne
  313. ligne = ligne + ["", ""] # Blanc technique
  314. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["n"])) # Taille d'échantillon
  315. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["mean"])) # Moyenne
  316. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["std"])) # Ecart type
  317. ligne.append(self._generer_str_ci(self.data[variable]["global"]["ci_95"])) # IC à 95%
  318. ligne = ligne + ["", "", ""] # Blanc technique
  319. lignes.append(ligne)
  320. else:
  321. # On vérifie l'axe
  322. self._verifier_existence_axes([axe])
  323. # Données sur l'axe
  324. donnees_axe = self.data[variable]["sous_groupes"][axe]
  325. # On écrit le nom de l'axe
  326. ligne = []
  327. ligne.append(axe) # Nom de l'axe
  328. ligne = ligne + ["","","","",""]
  329. # Résultat du test
  330. ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
  331. lignes.append(ligne)
  332. # On parcours toutes les modalités de l'axe
  333. for valeur_axe in donnees_axe.keys():
  334. ligne = []
  335. ligne.append("") # Ligne blanche technique
  336. ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
  337. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["n"])) # Taille d'échantillon
  338. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["mean"])) # Moyenne
  339. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["std"])) # Ecart type
  340. ligne.append(self._generer_str_ci(donnees_axe[valeur_axe]["ci_95"])) # IC à 95%
  341. ligne = ligne + ["", "", ""] # Blanc technique
  342. lignes.append(ligne)
  343. else:
  344. # Génération de la ligne simple
  345. if axe is None:
  346. ligne = [] # On instancie la ligne
  347. ligne = ligne + ["", ""] # Blanc technique
  348. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["total"])) # Taille d'échantillon
  349. for variable_valeur in self.data[variable]["global"].keys():
  350. if variable_valeur != "total":
  351. ligne.append(self._generer_str_n_p(
  352. n = self.data[variable]["global"][variable_valeur]["n"],
  353. p = self.data[variable]["global"][variable_valeur]["p"]
  354. ))
  355. ligne = ligne + ["", "", ""] # Blanc technique
  356. lignes.append(ligne)
  357. else:
  358. # On vérifie l'axe
  359. self._verifier_existence_axes([axe])
  360. # Données sur l'axe
  361. donnees_axe = self.data[variable]["sous_groupes"][axe]
  362. # On écrit le nom de l'axe
  363. ligne = []
  364. ligne.append(axe) # Nom de l'axe
  365. ligne = ligne + ["",""] + ["" for x in range(len(self.liste_modalites_variables[variable]))] # Blanc technique
  366. # Résultat du test
  367. ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
  368. lignes.append(ligne)
  369. # On parcours toutes les modalités de l'axe
  370. for valeur_axe in donnees_axe.keys():
  371. ligne = []
  372. ligne.append("") # Ligne blanche technique
  373. ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
  374. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["total"])) # Taille d'échantillon
  375. for variable_valeur in donnees_axe[valeur_axe].keys():
  376. if variable_valeur != "total":
  377. ligne.append(self._generer_str_n_p(
  378. n = donnees_axe[valeur_axe][variable_valeur]["n"],
  379. p = donnees_axe[valeur_axe][variable_valeur]["p"]
  380. ))
  381. ligne = ligne + ["", "", ""] # Blanc technique
  382. lignes.append(ligne)
  383. return lignes
  384. def _generer_sortie (self, chemin, tableau, format_fichier):
  385. """
  386. Génère le fichier de sortie
  387. input :
  388. chemin : chemin du fichier de sortie
  389. tableau : liste contenant les lignes à écrire
  390. format_fichier : format du fichier de sortie
  391. output :
  392. si chemin textuel : aucune sortie, écriture du fichier
  393. si chemin du fichier vaut None : retour du stream
  394. """
  395. # Création du fichier
  396. if format_fichier in ["csv", "tsv"]:
  397. f = StringIO()
  398. elif format_fichier in ["ods", "xlsx"]:
  399. f = BytesIO()
  400. # Ecriture du fichier en stream
  401. if format_fichier == 'csv':
  402. csv.writer(f, delimiter = ",").writerows(tableau)
  403. elif format_fichier == 'tsv':
  404. csv.writer(f, delimiter = "\t").writerows(tableau)
  405. elif format_fichier in ['ods','xlsx']:
  406. sheet = pe.Sheet(tableau)
  407. f = sheet.save_to_memory(format_fichier, f)
  408. elif format_fichier == 'raw':
  409. f = tableau
  410. else:
  411. raise Exception("Format de sortie non supporté.")
  412. if type(chemin) == type(str()):
  413. # On écrit le fichier
  414. if format_fichier in ["csv", "tsv"]:
  415. fichier_sortie = open(chemin, "w")
  416. else:
  417. fichier_sortie = open(chemin, "wb")
  418. fichier_sortie.write(f.getvalue())
  419. else:
  420. # On renvoie le buffer
  421. return (f)
  422. def tableau_descriptif (self, chemin, variables, axes = None, format_sortie = "csv"):
  423. """
  424. Décrit le contenu de plusieurs variables selon un axe ou plusieurs axes donnés.
  425. L'axe correspond forcément à une variable qualitative.
  426. Input :
  427. chemin : chemin du fichier de sortie
  428. variables : liste des variables à traiter
  429. axes : liste des axes d'analyse
  430. format_sortie :
  431. 'tsv' pour un tableau séparé par une tabulation
  432. 'csv' pour un tableau séparé par une virgule
  433. 'ods' pour un fichier ods
  434. 'xlsx' pour un fichier xlsx
  435. 'raw' pour un envoie brut des données (liste python)
  436. Output :
  437. si chemin textuel : aucune sortie, écriture du fichier
  438. si chemin du fichier vaut None : retour du stream
  439. """
  440. # Vérification que toutes les variables et axes existent
  441. self._verifier_existence_variables(variables)
  442. self._verifier_existence_axes(axes)
  443. # Récupération des donnée
  444. # Génération de l'en-tête
  445. en_tete = self._generer_en_tete_descriptif(axes)
  446. # Création du tableau
  447. tableau = []
  448. tableau.append(en_tete)
  449. # Traitement des variables une à une
  450. for variable in variables:
  451. lignes = self._generer_lignes_descriptif(variable, axes)
  452. tableau = tableau+lignes
  453. # Generation de la sortie
  454. sortie = self._generer_sortie(chemin, tableau, format_sortie)
  455. return(sortie)
  456. def tableau_detail_variable (self, chemin, variable, axes = None, format_sortie = "csv"):
  457. """
  458. Décrit le contenu d'une variable selon un axe ou plusieurs axes donnés.
  459. Les axes correspondent forcément à des variable qualitatives.
  460. Input :
  461. chemin : chemin du fichier de sortie
  462. variable : nom de la variable à traiter
  463. axes : liste des axes d'analyse
  464. format_sortie :
  465. 'tsv' pour un tableau séparé par une tabulation
  466. 'csv' pour un tableau séparé par une virgule
  467. 'ods' pour un fichier ods
  468. 'xlsx' pour un fichier xlsx
  469. 'raw' pour un envoi brut des données pythons
  470. Output :
  471. si chemin textuel : aucune sortie, écriture du fichier
  472. si chemin du fichier vaut None : retour du stream
  473. """
  474. # Vérification que toutes les variables et axes existent
  475. self._verifier_existence_variables([variable])
  476. self._verifier_existence_axes(axes)
  477. # Récupération des donnée
  478. # Génération de l'en-tête
  479. en_tete = self._generer_en_tete_detail(variable)
  480. # Création du tableau
  481. tableau = []
  482. tableau.append(en_tete)
  483. # On génère la première ligne d'analyse
  484. tableau = tableau + self._generer_lignes_detail(variable, None)
  485. # Ligne vide
  486. tableau.append(["" for x in range(len(tableau[-1]))])
  487. # Ajout des axes
  488. if axes is not None:
  489. for axe in axes:
  490. tableau = tableau + self._generer_lignes_detail(variable, axe)
  491. return(tableau)