genererTableau.py 25 KB

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