genererTableau.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  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. for label in liste_labels:
  275. # On ignore le label total
  276. if label != "total":
  277. ligne = []
  278. ligne.append("") # Première ligne vide
  279. ligne.append(self._generer_texte_propre(label)) # Libelé analysé
  280. ligne.append(self._generer_str_n_p(
  281. n = self.data[variable]["global"][label]["n"],
  282. p = self.data[variable]["global"][label]["p"]
  283. )) # Données générales
  284. # Valeur par axe
  285. for axe in axes:
  286. liste_valeurs = list(self.data[variable]["sous_groupes"][axe].keys())
  287. # Pour chaque valeur de l'axe
  288. for valeur in liste_valeurs: # On met 0 si la valeur n'existe pas
  289. donnees_axe = self.data[variable]["sous_groupes"][axe][valeur]
  290. n = donnees_axe[label]["n"] if label in donnees_axe.keys() else 0
  291. p = donnees_axe[label]["p"] if label in donnees_axe.keys() else 0
  292. ligne.append(self._generer_str_n_p(
  293. n = n,
  294. p = p
  295. )) # Données spécifique à chaque axe
  296. ligne = ligne + ["", "", ""] # Espaces vides entre 2 axes
  297. lignes.append(ligne)
  298. return(lignes)
  299. def _generer_lignes_detail(self, variable, axe = None):
  300. """
  301. Genere des ligne d'analyse d'un tableau détaillé pour une variable
  302. Input :
  303. variable : nom de la variable
  304. axe : axe sur lesquels générer les ligne
  305. """
  306. lignes = []
  307. if self.data[variable]["type"] == "quantitative":
  308. # Génération de la ligne simple
  309. if axe is None:
  310. ligne = [] # On instancie la ligne
  311. ligne = ligne + ["", ""] # Blanc technique
  312. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["n"])) # Taille d'échantillon
  313. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["mean"])) # Moyenne
  314. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["std"])) # Ecart type
  315. ligne.append(self._generer_str_ci(self.data[variable]["global"]["ci_95"])) # IC à 95%
  316. ligne = ligne + ["", "", ""] # Blanc technique
  317. lignes.append(ligne)
  318. else:
  319. # On vérifie l'axe
  320. self._verifier_existence_axes([axe])
  321. # Données sur l'axe
  322. donnees_axe = self.data[variable]["sous_groupes"][axe]
  323. # On écrit le nom de l'axe
  324. ligne = []
  325. ligne.append(axe) # Nom de l'axe
  326. ligne = ligne + ["","","","",""]
  327. # Résultat du test
  328. ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
  329. lignes.append(ligne)
  330. # On parcours toutes les modalités de l'axe
  331. for valeur_axe in donnees_axe.keys():
  332. ligne = []
  333. ligne.append("") # Ligne blanche technique
  334. ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
  335. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["n"])) # Taille d'échantillon
  336. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["mean"])) # Moyenne
  337. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["std"])) # Ecart type
  338. ligne.append(self._generer_str_ci(donnees_axe[valeur_axe]["ci_95"])) # IC à 95%
  339. ligne = ligne + ["", "", ""] # Blanc technique
  340. lignes.append(ligne)
  341. else:
  342. # Génération de la ligne simple
  343. if axe is None:
  344. ligne = [] # On instancie la ligne
  345. ligne = ligne + ["", ""] # Blanc technique
  346. ligne.append(self._generer_str_valeur(self.data[variable]["global"]["total"])) # Taille d'échantillon
  347. for variable_valeur in self.data[variable]["global"].keys():
  348. if variable_valeur != "total":
  349. ligne.append(self._generer_str_n_p(
  350. n = self.data[variable]["global"][variable_valeur]["n"],
  351. p = self.data[variable]["global"][variable_valeur]["p"]
  352. ))
  353. ligne = ligne + ["", "", ""] # Blanc technique
  354. lignes.append(ligne)
  355. else:
  356. # On vérifie l'axe
  357. self._verifier_existence_axes([axe])
  358. # Données sur l'axe
  359. donnees_axe = self.data[variable]["sous_groupes"][axe]
  360. # On écrit le nom de l'axe
  361. ligne = []
  362. ligne.append(axe) # Nom de l'axe
  363. ligne = ligne + ["",""] + ["" for x in range(len(self.liste_modalites_variables[variable]))] # Blanc technique
  364. # Résultat du test
  365. ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
  366. lignes.append(ligne)
  367. # On parcours toutes les modalités de l'axe
  368. for valeur_axe in donnees_axe.keys():
  369. ligne = []
  370. ligne.append("") # Ligne blanche technique
  371. ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
  372. ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["total"])) # Taille d'échantillon
  373. for variable_valeur in donnees_axe[valeur_axe].keys():
  374. if variable_valeur != "total":
  375. ligne.append(self._generer_str_n_p(
  376. n = donnees_axe[valeur_axe][variable_valeur]["n"],
  377. p = donnees_axe[valeur_axe][variable_valeur]["p"]
  378. ))
  379. ligne = ligne + ["", "", ""] # Blanc technique
  380. lignes.append(ligne)
  381. return lignes
  382. def _generer_sortie (self, chemin, tableau, format_fichier):
  383. """
  384. Génère le fichier de sortie
  385. input :
  386. chemin : chemin du fichier de sortie
  387. tableau : liste contenant les lignes à écrire
  388. format_fichier : format du fichier de sortie
  389. output :
  390. si chemin textuel : aucune sortie, écriture du fichier
  391. si chemin du fichier vaut None : retour du stream
  392. """
  393. # Création du fichier
  394. if format_fichier in ["csv", "tsv"]:
  395. f = StringIO()
  396. elif format_fichier in ["ods", "xlsx"]:
  397. f = BytesIO()
  398. # Ecriture du fichier en stream
  399. if format_fichier == 'csv':
  400. csv.writer(f, delimiter = ",").writerows(tableau)
  401. elif format_fichier == 'tsv':
  402. csv.writer(f, delimiter = "\t").writerows(tableau)
  403. elif format_fichier in ['ods','xlsx']:
  404. sheet = pe.Sheet(data)
  405. f = sheet.save_to_memory(format_fichier, f)
  406. elif format_fichier == 'raw':
  407. f = tableau
  408. else:
  409. raise Exception("Format de sortie non supporté.")
  410. if type(chemin) == type(str()):
  411. # On écrit le fichier
  412. if format_fichier in ["csv", "tsv"]:
  413. fichier_sortie = open(chemin, "w")
  414. else:
  415. fichier_sortie = open(chemin, "wb")
  416. fichier_sortie.write(f.getvalue())
  417. else:
  418. # On renvoie le buffer
  419. return (f)
  420. def tableau_descriptif (self, chemin, variables, axes = None, format_sortie = "csv"):
  421. """
  422. Décrit le contenu de plusieurs variables selon un axe ou plusieurs axes donnés.
  423. L'axe correspond forcément à une variable qualitative.
  424. Input :
  425. chemin : chemin du fichier de sortie
  426. variables : liste des variables à traiter
  427. axes : liste des axes d'analyse
  428. format_sortie :
  429. 'tsv' pour un tableau séparé par une tabulation
  430. 'csv' pour un tableau séparé par une virgule
  431. 'ods' pour un fichier ods
  432. 'xlsx' pour un fichier xlsx
  433. 'raw' pour un envoie brut des données (liste python)
  434. Output :
  435. si chemin textuel : aucune sortie, écriture du fichier
  436. si chemin du fichier vaut None : retour du stream
  437. """
  438. # Vérification que toutes les variables et axes existent
  439. self._verifier_existence_variables(variables)
  440. self._verifier_existence_axes(axes)
  441. # Récupération des donnée
  442. # Génération de l'en-tête
  443. en_tete = self._generer_en_tete_descriptif(axes)
  444. # Création du tableau
  445. tableau = []
  446. tableau.append(en_tete)
  447. # Traitement des variables une à une
  448. for variable in variables:
  449. lignes = self._generer_lignes_descriptif(variable, axes)
  450. tableau = tableau+lignes
  451. # Generation de la sortie
  452. sortie = self._generer_sortie(chemin, tableau, format_sortie)
  453. return(sortie)
  454. def tableau_detail_variable (self, chemin, variable, axes = None, format_sortie = "csv"):
  455. """
  456. Décrit le contenu d'une variable selon un axe ou plusieurs axes donnés.
  457. Les axes correspondent forcément à des variable qualitatives.
  458. Input :
  459. chemin : chemin du fichier de sortie
  460. variable : nom de la variable à traiter
  461. axes : liste des axes d'analyse
  462. format_sortie :
  463. 'tsv' pour un tableau séparé par une tabulation
  464. 'csv' pour un tableau séparé par une virgule
  465. 'ods' pour un fichier ods
  466. 'xlsx' pour un fichier xlsx
  467. 'raw' pour un envoi brut des données pythons
  468. Output :
  469. si chemin textuel : aucune sortie, écriture du fichier
  470. si chemin du fichier vaut None : retour du stream
  471. """
  472. # Vérification que toutes les variables et axes existent
  473. self._verifier_existence_variables([variable])
  474. self._verifier_existence_axes(axes)
  475. # Récupération des donnée
  476. # Génération de l'en-tête
  477. en_tete = self._generer_en_tete_detail(variable)
  478. # Création du tableau
  479. tableau = []
  480. tableau.append(en_tete)
  481. # On génère la première ligne d'analyse
  482. tableau = tableau + self._generer_lignes_detail(variable, None)
  483. # Ligne vide
  484. tableau.append(["" for x in range(len(tableau[-1]))])
  485. # Ajout des axes
  486. for axe in axes:
  487. tableau = tableau + self._generer_lignes_detail(variable, axe)
  488. return(tableau)