Browse Source

Merge branch
Merge branch 'master' of https://gogs.alibellamine.me/alibell/py_thesis_toolbox

19-0009Cse 3 năm trước cách đây
mục cha
commit
0aa781b872

+ 13 - 0
.ipynb_checkpoints/HISTORY-checkpoint.rst

@@ -0,0 +1,13 @@
+=======
+History
+=======
+
+0.0.1 (2021-03-30)
+------------------
+
+* Release initiale
+
+0.0.2 (2021-04-14)
+------------------
+
+* Ajout de la méthode : générerTableau : permet de sortir une analyse sous forme de tableau

+ 147 - 0
.ipynb_checkpoints/README-checkpoint.md

@@ -0,0 +1,147 @@
+# py_thesis_toolbox
+
+Main repository : https://gogs.alibellamine.me/alibell/py_thesis_toolbox/
+
+Outils d'analyse des données de thèses.
+Applique une description des données suivi d'une série de tests univariés.
+
+## Installation du plugin
+
+```
+    git clone https://gogs.alibellamine.me/alibell/py_thesis_toolbox.git
+    cd py_thesis_toolbox
+    pip install -r requirements.txt
+    pip install .
+```
+
+## Utilisation
+
+### Analyses d'un jeu de donnée
+
+L'analyse d'un jeu de données procède aux traitements suivant :
+- Analyse descriptive : moyenne, nombre de sujet, médiane, intervales inter-quartiles
+- Analyse explicative descriptive
+- Application de test lors de l'analyse explicative descriptive
+
+**@TODO : Implémenter la création d'un modèle multivariés**
+
+```
+    from thesis_analysis import analyseStatistiques
+
+    analyses = analyseStatistiques(df)
+    analyses.analyse_univarie(
+        variable_interet,
+        variables_explicatives
+    )
+```
+
+#### variable_interet
+
+La variable **variable d'intérêt** comprend un dictionnaire décrivant une liste de variables qualitatives ou quantitative.
+Le dictionnaire doit être de la forme :
+
+```
+{
+    nom_de_variable:type_de_variable["qualitative","quantitative"]
+    ...
+}
+```
+
+#### variables_explicatives
+
+Liste de variables explicatives.
+L'ensemble des variables doit être de type qualitatif.
+Il s'agit d'une liste :
+
+```
+[
+    nom_de_variable
+    ...
+]
+```
+
+### Application d'un test spécifique
+
+
+```
+    from thesis_analysis.test import testQualitatif, testQuantitatif
+
+    test = testQualitatif(df, y, x)
+    test.best_test()
+```
+
+Applique pour un jeu x et y le meilleur test possible.
+Paramètres :
+- df : DataFrame contenant l'ensemble du jeu de données
+- y : variable d'intérêt sur laquelle on mesure l'impact de la variable x
+- x : variable dont on mesure l'impact sur y
+
+La fonction **best_test** détermine le meilleur test applicable aux données.
+
+Il est possible d'executer une série de test manuellement.
+La liste des test peut être obtenue en éxecutant :
+
+```
+    dir(test)
+```
+
+### Restitution d'une analyse dans un tableau
+
+```
+    from thesis_analysis import analyseStatistiques
+    from thesis_analysis import genererTableau
+    
+    # Analyse du jeu de données
+    analyses = analyseStatistiques(df)
+    resultat = analyses.analyse_univarie(
+        variable_interet,
+        variables_explicatives
+    )
+    
+    # Ecriture du tableau
+    tableau = genererTableau(resultat)
+    tableau.tableau_descriptif("tableau.xlsx", variables, axes, format_sortie = "xlsx") # Tableau descriptif, souvent table 1
+    tableau.tableau_detail_variable("tableau.xlsx", variable, axes, format_sortie = "xlsx") # Tableau détaillé : analyse d'une variable
+```
+
+#### Types de tableaux
+
+Il existe 2 types de tableau :
+
+- Le tableau descriptif :
+    - Il correspond essentiellement au tableau 1 des publications scientifiques. Il décrit un ensemble de variables, chacunes dans une ou plusieurs lignes dédiés, éventuellement croisé avec un ou plusieurs axes (- ie - des variables qualitatives) qui sont représentés dans une ou plusieurs colonnes séparées
+    - Il est généré à l'aide de la méthode : tableau_descriptif
+- Le tableau détaillé :
+    - Il correspond à un tableau détaillé pour une variable, il décrit pour uen variable donnée l'ensemble des caractéristiques en colonnes et pour chaque variables avec laquelle cette axe avec laquelle cette variable est croisé les principaux indicateurs est resulté de test. Ainsi chaque axe est analysé sur une ou plusieurs lignes dédiés.
+    - Il est généré à l'aide de la méthode : tableau_detail_variable
+    
+#### Paramètre de la classe genererTableau
+
+```
+    genererTableau(data, precision = 2, quantitative_string_format = "{mean:.2f} +/- {std:.2f} ({n})", qualitative_string_format = "{n} ({p:.2%})")
+```
+
+- data : données à afficher, il s'agit d'un dictionnaire produit grace à la classe analyseStatistiques
+- precision : degré de précision en décimal des résultats affichés
+- quantitative_string_format : format d'affichage des valeurs pour les variable quantitatives dans le tableau descriptif
+- qualitative_string_format : format d'affichage des valeurs pour les variable qualitatives dans le tableau descriptif
+
+#### Paramètre de la génération de tableau : méthodes tableau_descriptif et tableau_detail_variable
+
+```
+    tableau.tableau_descriptif(chemin = "tableau.xlsx", variables, axes, format_sortie = "xlsx")
+    tableau.tableau_detail_variable(chemin = "tableau.xlsx", variable, axes, format_sortie = "xlsx")
+```
+
+- chemin : Chemin du fichier de sortie :
+    - Ecrit le fichier dans le chemin indiqué
+    - Si le chemin prend la valeur None : est renvoyé un buffer : soit un objet StringIO ou BytesIO si un fichier texte ou binaire est généré, soit une liste si le format "raw" est sélectionné
+- variables : variables à analyses, la méthode quantitative_string_format analyse plusieurs variables à la fois
+- variable (pour tableau_detail_variable) : variable à analyse, la méthode qualitative_string_format n'analyse qu'une seule variable à la fois 
+- axes : axes d'analyse à restituer
+- format_sortie : format de sortie du tableau, formats attendus :
+    - csv
+    - tsv
+    - xlsx
+    - ods
+    - raw : si raw est sélectioné, le chemin attendu est None, dans ce cas une liste est directement retourné. Cela peut-être utile pour insérer plusieurs tableaux dans des feuilles différentes d'un même fichier XLSX / ODS.

+ 7 - 0
.ipynb_checkpoints/requirement-checkpoint.txt

@@ -0,0 +1,7 @@
+statsmodels
+scipy
+pandas
+numpy
+pyexcel_ods3
+pyexcel_xlsx
+pyexcel

+ 46 - 0
.ipynb_checkpoints/setup-checkpoint.py

@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+"""The setup script."""
+
+from setuptools import setup, find_packages
+
+with open('README.rst') as readme_file:
+    readme = readme_file.read()
+
+with open('HISTORY.rst') as history_file:
+    history = history_file.read()
+
+requirements = [ ]
+
+setup_requirements = [ ]
+
+test_requirements = [ ]
+
+setup(
+    author="Ali BELLAMINE",
+    author_email='contact@alibellamine.me',
+    python_requires='>=3.5',
+    classifiers=[
+        'Development Status :: 2 - Pre-Alpha',
+        'Intended Audience :: Developers',
+        'Natural Language :: English',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+    ],
+    description="Python Boilerplate contains all the boilerplate you need to create a Python package.",
+    install_requires=requirements,
+    long_description=readme + '\n\n' + history,
+    include_package_data=True,
+    keywords='py_thesis_toolbox',
+    name='py_thesis_toolbox',
+    packages=find_packages(),
+    setup_requires=setup_requirements,
+    test_suite='tests',
+    tests_require=test_requirements,
+    url='https://github.com/alibell/py_thesis_toolbox',
+    version='0.0.2',
+    zip_safe=False,
+)

+ 6 - 1
HISTORY.rst

@@ -5,4 +5,9 @@ History
 0.0.1 (2021-03-30)
 ------------------
 
-* First release on PyPI.
+* Release initiale
+
+0.0.2 (2021-04-14)
+------------------
+
+* Ajout de la méthode : générerTableau : permet de sortir une analyse sous forme de tableau

+ 62 - 1
README.md

@@ -83,4 +83,65 @@ La liste des test peut être obtenue en éxecutant :
 
 ```
     dir(test)
-```
+```
+
+### Restitution d'une analyse dans un tableau
+
+```
+    from thesis_analysis import analyseStatistiques
+    from thesis_analysis import genererTableau
+    
+    # Analyse du jeu de données
+    analyses = analyseStatistiques(df)
+    resultat = analyses.analyse_univarie(
+        variable_interet,
+        variables_explicatives
+    )
+    
+    # Ecriture du tableau
+    tableau = genererTableau(resultat)
+    tableau.tableau_descriptif("tableau.xlsx", variables, axes, format_sortie = "xlsx") # Tableau descriptif, souvent table 1
+    tableau.tableau_detail_variable("tableau.xlsx", variable, axes, format_sortie = "xlsx") # Tableau détaillé : analyse d'une variable
+```
+
+#### Types de tableaux
+
+Il existe 2 types de tableau :
+
+- Le tableau descriptif :
+    - Il correspond essentiellement au tableau 1 des publications scientifiques. Il décrit un ensemble de variables, chacunes dans une ou plusieurs lignes dédiés, éventuellement croisé avec un ou plusieurs axes (- ie - des variables qualitatives) qui sont représentés dans une ou plusieurs colonnes séparées
+    - Il est généré à l'aide de la méthode : tableau_descriptif
+- Le tableau détaillé :
+    - Il correspond à un tableau détaillé pour une variable, il décrit pour uen variable donnée l'ensemble des caractéristiques en colonnes et pour chaque variables avec laquelle cette axe avec laquelle cette variable est croisé les principaux indicateurs est resulté de test. Ainsi chaque axe est analysé sur une ou plusieurs lignes dédiés.
+    - Il est généré à l'aide de la méthode : tableau_detail_variable
+    
+#### Paramètre de la classe genererTableau
+
+```
+    genererTableau(data, precision = 2, quantitative_string_format = "{mean:.2f} +/- {std:.2f} ({n})", qualitative_string_format = "{n} ({p:.2%})")
+```
+
+- data : données à afficher, il s'agit d'un dictionnaire produit grace à la classe analyseStatistiques
+- precision : degré de précision en décimal des résultats affichés
+- quantitative_string_format : format d'affichage des valeurs pour les variable quantitatives dans le tableau descriptif
+- qualitative_string_format : format d'affichage des valeurs pour les variable qualitatives dans le tableau descriptif
+
+#### Paramètre de la génération de tableau : méthodes tableau_descriptif et tableau_detail_variable
+
+```
+    tableau.tableau_descriptif(chemin = "tableau.xlsx", variables, axes, format_sortie = "xlsx")
+    tableau.tableau_detail_variable(chemin = "tableau.xlsx", variable, axes, format_sortie = "xlsx")
+```
+
+- chemin : Chemin du fichier de sortie :
+    - Ecrit le fichier dans le chemin indiqué
+    - Si le chemin prend la valeur None : est renvoyé un buffer : soit un objet StringIO ou BytesIO si un fichier texte ou binaire est généré, soit une liste si le format "raw" est sélectionné
+- variables : variables à analyses, la méthode quantitative_string_format analyse plusieurs variables à la fois
+- variable (pour tableau_detail_variable) : variable à analyse, la méthode qualitative_string_format n'analyse qu'une seule variable à la fois 
+- axes : axes d'analyse à restituer
+- format_sortie : format de sortie du tableau, formats attendus :
+    - csv
+    - tsv
+    - xlsx
+    - ods
+    - raw : si raw est sélectioné, le chemin attendu est None, dans ce cas une liste est directement retourné. Cela peut-être utile pour insérer plusieurs tableaux dans des feuilles différentes d'un même fichier XLSX / ODS.

+ 4 - 1
requirement.txt

@@ -1,4 +1,7 @@
 statsmodels
 scipy
 pandas
-numpy
+numpy
+pyexcel_ods3
+pyexcel_xlsx
+pyexcel

+ 2 - 2
setup.py

@@ -40,7 +40,7 @@ setup(
     setup_requires=setup_requirements,
     test_suite='tests',
     tests_require=test_requirements,
-    url='https://github.com/audreyr/py_thesis_toolbox',
-    version='0.0.1',
+    url='https://github.com/alibell/py_thesis_toolbox',
+    version='0.0.2',
     zip_safe=False,
 )

+ 6 - 1
thesis_analysis/.ipynb_checkpoints/__init__-checkpoint.py

@@ -1 +1,6 @@
-from .analyseStatistiques import analyseStatistiques
+<<<<<<< HEAD
+from .analyseStatistiques import analyseStatistiques
+=======
+from .analyseStatistiques import analyseStatistiques
+from .genererTableau import genererTableau
+>>>>>>> bd16659386e33e7eeb0e5b3796a4d04948d03358

+ 4 - 0
thesis_analysis/.ipynb_checkpoints/analyseStatistiques-checkpoint.py

@@ -158,7 +158,11 @@ class analyseStatistiques ():
                     Key = Nom de la variable, Value : type de variable : quantitative ou qualitative
                 axes : axes d'analyse de la variable, doivent être de type qualitative. Liste de variables.                
         """
+<<<<<<< HEAD
                 
+=======
+        
+>>>>>>> bd16659386e33e7eeb0e5b3796a4d04948d03358
         # Sortie des résultats
         resultats = {}
         

+ 649 - 0
thesis_analysis/.ipynb_checkpoints/genererTableau-checkpoint.py

@@ -0,0 +1,649 @@
+from io import StringIO, BytesIO
+import pyexcel as pe
+import csv 
+import math
+
+class genererTableau ():
+    
+    """
+        ecrire_tableau :
+        Permet de créer un tableau et l'exporter au format excel, ods ou csv à partir de données d'analyses.
+        
+        Input :
+            data : données aggrégées généré par les méthodes de analyseStatistiques
+            precision : nombre de décimales de précision
+            quantitative_string_format : format d'écriture de la moyenne, écart type et taille d'échantillon des variables quantitatives
+            qualitative_string_format : format d'écriture du n et de la proportion des variables qualitatives
+    """
+    
+    def __init__ (self, data, precision = 2, quantitative_string_format = "{mean:.2f} +/- {std:.2f} ({n})", qualitative_string_format = "{n} ({p:.2%})"):
+        
+        """
+            Data : data contenant les données aggrégées, généré par analyseStatistiques.analyse_univarie
+        """
+        
+        # Parametres
+        self.quantitative_string_format = quantitative_string_format
+        self.qualitative_string_format = qualitative_string_format
+        self.absence_test = "Absence de test effectué (CI des tests conventionnels non remplis)"
+        self.precision = precision
+        
+        self.data = data
+        self.liste_variables = list(data.keys()) # Liste des variables analysables
+        self.liste_axes = list(list(data.values())[0]["sous_groupes"].keys()) # Liste des axes d'analyses
+        
+        # On récupère la liste des modalités de chaque axes
+        self.liste_modalites = dict(zip(
+            self.liste_axes,
+            [self._obtenir_modalite_axe(x, True) for x in self.liste_axes]
+        ))
+        
+        # On récupère la liste des modalités de chaque variable
+        self.liste_modalites_variables = dict(zip(
+            self.liste_variables,
+            [self._obtenir_modalite_variable(x, True) for x in self.liste_variables]
+        ))
+        
+    def _generer_texte_propre (self, texte):
+        """
+            Nettoie un texte
+            Si il s'agit d'un flottant : le met en int
+        """
+        
+        if type(texte) != type(str()):
+            texte = str(int(texte))
+            
+        return(texte)
+        
+    def _generer_str_mean_std_n(self, mean, std, n):
+        
+        """
+            Simple fonction qui écrit un string mélangeant moyenne, écart-type et taille de l'effectif
+            Format par défault :
+                {mean:.2f} +/- {std:.2f} ({n})
+                
+            Peut-être personalisé par le paramètre string_format.
+            
+            Input :
+                mean, std et n
+        """
+        
+        texte = self.quantitative_string_format.format(
+            mean = round(mean, self.precision), 
+            std=round(std, self.precision), 
+            n=round(n, self.precision)
+        )
+        
+        return(texte)
+    
+    def _generer_str_n_p(self, n, p):
+        
+        """
+            Simple fonction qui écrit un string mélangeant moyenne, écart-type et taille de l'effectif
+            Format par défault :
+                {n} ({p:.0%})
+                
+            Peut-être personalisé par le paramètre string_format.
+            
+            Input :
+                mean, std et n
+            Sortie :
+                texte
+        """
+        
+        texte = self.qualitative_string_format.format(
+            p = round(p, self.precision), 
+            n = round(n, self.precision)
+        )
+        
+        return(texte)
+    
+    def _generer_str_valeur(self, valeur):
+        
+        """
+            Simple fonction qui écrit un string à partir d'une valeur
+                            
+            Input :
+                valeur
+            Sortie :
+                texte
+        """
+        
+        texte = "{}".format(
+            round(valeur, self.precision)
+        )
+        
+        return(texte)
+    
+    def _generer_str_ci(self, liste_ci):
+        
+        """
+            Simple fonction qui écrit un string à partir d'une liste représentant un intervale de confiance
+                            
+            Input :
+                liste_ci : liste de 2 items décrivant un intervalle de confiance
+            Sortie :
+                texte
+        """
+        
+        texte = "{}-{}".format(
+            self._generer_str_valeur(liste_ci[0]),
+            self._generer_str_valeur(liste_ci[1])
+        )
+        
+        return(texte)
+    
+    def _generer_str_resultat_test(self, test):
+        
+        """
+            Génère une liste avec formatage textuel du résultat d'un test
+            Vérifie la bonne application du test avant.
+            
+            Input :
+                test : liste contenant le résultat du test
+            Sortie :
+                liste de 3 éléments comprenant : Test, Paramètre et p
+        """
+        
+        resultat = []
+        
+        if len(test[1].keys()) > 1:
+            resultat.append(test[0]) # Nom du test
+            resultat.append(self._generer_str_valeur(test[1]["statistic"])) # Paramètre de test
+            resultat.append(self._formater_p_value(test[1]["p_value"])) # Petit p du test
+        else:
+            resultat.append(self.absence_test)
+            resultat = resultat + ["", ""]
+            
+        return(resultat)
+    
+    def _formater_p_value(self, p_value):
+    
+        # Nombre de décimales concernées
+        n_digit = len(str(p_value).split(".")[-1].split("0"))
+
+        borne_basse = 5*math.pow(10, -n_digit)
+        borne_sup = math.pow(10, -n_digit+1)
+
+        estimation_p = borne_basse if p_value <= borne_basse else borne_sup
+
+        # Affichage
+        if p_value > 0.01:
+            formated_p_value = str(round(p_value, 3))
+        else:
+            if p_value > 0.001:
+                formated_p_value = "< "+str(estimation_p)
+            else:
+                formated_p_value = "< 10^"+str(n_digit-1)    
+
+        return(formated_p_value)
+        
+    def _verifier_existence_variables(self, variables):
+        
+        """
+           Vérifie que les variables existent bien dans le jeu de données.
+           Input : variables : liste des variables à tester
+           Output : None
+        """
+        
+        variables_manquantes = [x for x in variables if x not in self.liste_variables]
+            
+        if len(variables_manquantes) > 0:
+            raise Exception("Erreur, les variables {} sont inexistantes des données aggrégées.".format(
+                ", ".join(variables_manquantes)
+            ))
+            
+    def _verifier_existence_axes(self, axes):
+        
+        """
+           Vérifie que les axes existent bien dans le jeu de données.
+           Input : variables : liste des axes à tester
+           Output : None
+        """
+        
+        if axes is not None:
+            axes_manquants = [x for x in axes if x not in self.liste_axes]
+        else:
+            axes_manquants = []
+            
+        if len(axes_manquants) > 0:
+            raise Exception("Erreur, les axes {} sont inexistantes des données aggrégées.".format(
+                ", ".join(axes_manquants)
+            ))
+            
+    def _obtenir_modalite_axe (self, axe, ajouter_nom = True):
+        
+        """
+            Retourne une liste contenant toutes les modalités pour un axe donné
+            Input :
+                axe : nom de l'axe à analyse
+                ajouter_nom : Si True, alors le nom de l'axe est ajouté à la modalité (concaténation)
+        """
+        
+        # Liste des modalités
+        modalites = list(self.data.values())[0]["sous_groupes"][axe].keys()
+        
+        # Post-traitement : on corrige les float et boolean pour les afficher en int
+        modalites = [int(x) if type(x) != type(str()) else x for x in modalites]
+        
+        # On affiche le nom de l'axe avec si nécessaire
+        modalites_avec_str = [axe+" "+str(x) for x in modalites]
+        
+        if ajouter_nom:
+            return(modalites_avec_str)
+        else:
+            return(modalites)
+        
+    def _obtenir_modalite_variable (self, variable, ajouter_nom = True):
+        
+        """
+            Retourne une liste contenant toutes les modalités pour une variable donnée
+            Input :
+                variable : nom de la variable à analyser
+                ajouter_nom : Si True, alors le nom de l'axe est ajouté à la modalité (concaténation)
+        """
+        
+        # Liste des modalités
+        modalites = [x for x in list(self.data[variable]["global"].keys()) if x != "total"]
+        
+        # Post-traitement : on corrige les float et boolean pour les afficher en int
+        modalites = [int(x) if type(x) != type(str()) else x for x in modalites]
+        
+        # On affiche le nom de l'axe avec si nécessaire
+        modalites_avec_str = [axe+" "+str(x) for x in modalites]
+        
+        if ajouter_nom:
+            return(modalites_avec_str)
+        else:
+            return(modalites)
+            
+    def _generer_en_tete_descriptif(self, axes):
+        
+        """
+            Genere l'en-tête d'un tableau Descriptif
+            Input :
+                axes : liste des axes d'analyse
+            Output : None
+        """
+        
+        # Initialisation de l'en-tête
+        en_tete = ["","",""]
+        
+        # Lecture des axes
+        for axe in axes:
+            en_tete = en_tete+self.liste_modalites[axe]+["Test","Paramètre", "p"]
+            
+        return en_tete
+    
+    def _generer_en_tete_detail(self, variable):
+        
+        """
+            Genere l'en-tête d'un tableau Descriptif
+            Input :
+                axes : liste des axes d'analyse
+            Output : None
+        """
+        
+        # Type de variable
+        type_variable = self.data[variable]["type"]
+        
+        # Initialisation de l'en-tête
+        en_tete = []
+        
+        en_tete.append(variable) # Nom de la variable
+        en_tete.append("") # Espace technique
+        
+        ## Pour les variables quantitatives
+        if type_variable == "quantitative":
+            en_tete = en_tete + ["n", "mean", "std", "IC 95%"]
+        elif type_variable == "qualitative":
+            en_tete.append("n")
+            en_tete = en_tete + self.liste_modalites_variables[variable]
+        
+        en_tete = en_tete + ["Test", "Parameter", "p"]
+            
+        return en_tete
+    
+    def _generer_lignes_descriptif (self, variable, axes):
+        
+        """
+            Genere des ligne d'analyse d'un tableau descriptif pour une variable
+            Input :
+                variable : nom de la variable
+                axes : axes sur lesquels générer la ligne
+        """
+        
+        lignes = []
+        
+        if self.data[variable]["type"] == "quantitative":
+            # Ligne d'une variable quantitative : une seule ligne
+            
+            ligne = [] # On instancie la ligne
+            ligne.append(variable+" mean +/- std (n)") # Ajout du nom de variable
+            ligne.append("") # Espace vide, technique
+            ligne.append(self._generer_str_mean_std_n(
+                self.data[variable]["global"]["mean"],
+                self.data[variable]["global"]["std"],
+                self.data[variable]["global"]["n"]
+            )) # Données globales
+            
+            # Données liées à chaque axe
+            for axe in axes:
+                
+                donnees_axe = self.data[variable]["sous_groupes"][axe]
+                # Pour chaque valeur de l'axe
+                for valeur in donnees_axe.keys():
+                    
+                    ligne.append(self._generer_str_mean_std_n(
+                        donnees_axe[valeur]["mean"],
+                        donnees_axe[valeur]["std"],
+                        donnees_axe[valeur]["n"]
+                    )) # Données hors test
+                
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe]) # Données du test
+                
+            lignes.append(ligne)
+        else:
+            # Lignes d'un variable qualitative : une ligne par modalité
+            
+            ## On écrit la première ligne : nom de variable et test
+            ligne = [] # On instancie la ligne
+            ligne.append(variable+" no. (%)") # Nom de variable
+            ligne = ligne + ["", ""] # Variable vides
+            
+            # Données liées à chaque axe
+            for axe in axes:
+                
+                blank_list = ["" for x in range(len(self.liste_modalites[axe]))]
+                
+                ligne = ligne + blank_list # Blanc lié aux tests
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe]) # Données du test
+                
+            lignes.append(ligne)
+            
+            ## On écrit les lignes propres à chaque variable
+            
+            liste_labels = self.data[variable]["global"].keys()
+            
+            for label in liste_labels:
+                                
+                # On ignore le label total
+                if label != "total":
+                    
+                    ligne = []
+                    
+                    ligne.append("") # Première ligne vide
+                    ligne.append(self._generer_texte_propre(label)) # Libelé analysé   
+                    ligne.append(self._generer_str_n_p(
+                        n = self.data[variable]["global"][label]["n"], 
+                        p = self.data[variable]["global"][label]["p"]
+                    )) # Données générales
+                    
+                    # Valeur par axe
+                    for axe in axes:
+                        
+                        liste_valeurs = list(self.data[variable]["sous_groupes"][axe].keys())
+                        
+                        # Pour chaque valeur de l'axe
+                        for valeur in liste_valeurs: # On met 0 si la valeur n'existe pas
+                            
+                            donnees_axe = self.data[variable]["sous_groupes"][axe][valeur]
+                                
+                            n = donnees_axe[label]["n"] if label in donnees_axe.keys() else 0
+                            p = donnees_axe[label]["p"] if label in donnees_axe.keys() else 0
+
+                            ligne.append(self._generer_str_n_p(
+                                n = n, 
+                                p = p
+                            )) # Données spécifique à chaque axe
+                    
+                    ligne = ligne + ["", "", ""] # Espaces vides entre 2 axes
+
+                lignes.append(ligne)
+        
+        return(lignes)
+    
+    def _generer_lignes_detail(self, variable, axe = None):
+        
+        """
+            Genere des ligne d'analyse d'un tableau détaillé pour une variable
+            Input :
+                variable : nom de la variable
+                axe : axe sur lesquels générer les ligne
+        """        
+    
+        lignes = []
+        
+        if self.data[variable]["type"] == "quantitative":
+            # Génération de la ligne simple
+            if axe is None:
+                ligne = [] # On instancie la ligne
+                ligne = ligne + ["", ""] # Blanc technique
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["n"])) # Taille d'échantillon
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["mean"])) # Moyenne
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["std"])) # Ecart type
+                ligne.append(self._generer_str_ci(self.data[variable]["global"]["ci_95"])) # IC à 95%
+                ligne = ligne + ["", "", ""] # Blanc technique
+                
+                lignes.append(ligne)
+            else:
+                # On vérifie l'axe
+                self._verifier_existence_axes([axe])
+                
+                # Données sur l'axe
+                donnees_axe = self.data[variable]["sous_groupes"][axe]
+                
+                # On écrit le nom de l'axe
+                ligne = []
+                ligne.append(axe) # Nom de l'axe
+                ligne = ligne + ["","","","",""]
+                
+                # Résultat du test
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
+                
+                lignes.append(ligne)
+                
+                # On parcours toutes les modalités de l'axe
+                for valeur_axe in donnees_axe.keys():
+                    
+                    ligne = []
+                    
+                    ligne.append("") # Ligne blanche technique
+                    ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["n"])) # Taille d'échantillon
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["mean"])) # Moyenne
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["std"])) # Ecart type
+                    ligne.append(self._generer_str_ci(donnees_axe[valeur_axe]["ci_95"])) # IC à 95%
+                    
+                    ligne = ligne + ["", "", ""] # Blanc technique
+                    lignes.append(ligne)
+        else:
+            # Génération de la ligne simple
+                        
+            if axe is None:
+                ligne = [] # On instancie la ligne
+                ligne = ligne + ["", ""] # Blanc technique
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["total"])) # Taille d'échantillon
+                for variable_valeur in self.data[variable]["global"].keys():
+                    if variable_valeur != "total":
+                        ligne.append(self._generer_str_n_p(
+                                n = self.data[variable]["global"][variable_valeur]["n"], 
+                                p = self.data[variable]["global"][variable_valeur]["p"]
+                            ))
+                ligne = ligne + ["", "", ""] # Blanc technique
+                
+                lignes.append(ligne)
+                
+            else:
+                # On vérifie l'axe
+                self._verifier_existence_axes([axe])
+                
+                # Données sur l'axe
+                donnees_axe = self.data[variable]["sous_groupes"][axe]
+                
+                # On écrit le nom de l'axe
+                ligne = []
+                ligne.append(axe) # Nom de l'axe
+                ligne = ligne + ["",""] + ["" for x in range(len(self.liste_modalites_variables[variable]))] # Blanc technique
+                
+                # Résultat du test
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
+                
+                lignes.append(ligne)
+                
+                # On parcours toutes les modalités de l'axe
+                for valeur_axe in donnees_axe.keys():
+                    
+                    ligne = []
+                    
+                    ligne.append("") # Ligne blanche technique
+                    ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["total"])) # Taille d'échantillon
+                    
+                    for variable_valeur in donnees_axe[valeur_axe].keys():
+                        if variable_valeur != "total":
+                            ligne.append(self._generer_str_n_p(
+                                    n = donnees_axe[valeur_axe][variable_valeur]["n"], 
+                                    p = donnees_axe[valeur_axe][variable_valeur]["p"]
+                                )) 
+                    
+                    ligne = ligne + ["", "", ""] # Blanc technique
+                    lignes.append(ligne)
+            
+        return lignes
+    
+    def _generer_sortie (self, chemin, tableau, format_fichier):
+        
+        """
+            Génère le fichier de sortie
+                input :
+                    chemin : chemin du fichier de sortie
+                    tableau : liste contenant les lignes à écrire
+                    format_fichier : format du fichier de sortie
+                output :
+                    si chemin textuel : aucune sortie, écriture du fichier
+                    si chemin du fichier vaut None : retour du stream
+        """
+        
+        # Création du fichier
+        if format_fichier in ["csv", "tsv"]:
+            f = StringIO()
+        elif format_fichier in ["ods", "xlsx"]:
+            f = BytesIO()
+            
+        # Ecriture du fichier en stream
+        if format_fichier == 'csv':
+            csv.writer(f, delimiter = ",").writerows(tableau)
+        elif format_fichier == 'tsv':
+            csv.writer(f, delimiter = "\t").writerows(tableau)
+        elif format_fichier in ['ods','xlsx']:
+            sheet = pe.Sheet(data)
+            f = sheet.save_to_memory(format_fichier, f)
+        elif format_fichier == 'raw':
+            f = tableau
+        else:
+            raise Exception("Format de sortie non supporté.")
+            
+        if type(chemin) == type(str()):
+            # On écrit le fichier
+            if format_fichier in ["csv", "tsv"]:
+                fichier_sortie = open(chemin, "w")
+            else:
+                fichier_sortie = open(chemin, "wb")
+            
+            fichier_sortie.write(file)
+        else:
+            # On renvoie le buffer
+            return (f)
+
+            
+    def tableau_descriptif (self, chemin, variables, axes = None, format_sortie = "csv"):
+        
+        """
+            Décrit le contenu de plusieurs variables selon un axe ou plusieurs axes donnés.
+            L'axe correspond forcément à une variable qualitative.
+            
+            Input :
+                chemin : chemin du fichier de sortie
+                variables : liste des variables à traiter
+                axes : liste des axes d'analyse
+                format_sortie :
+                    'tsv' pour un tableau séparé par une tabulation
+                    'csv' pour un tableau séparé par une virgule
+                    'ods' pour un fichier ods
+                    'xlsx' pour un fichier xlsx
+                    'raw' pour un envoie brut des données (liste python)
+            Output :
+                si chemin textuel : aucune sortie, écriture du fichier
+                si chemin du fichier vaut None : retour du stream
+        """
+        
+        # Vérification que toutes les variables et axes existent
+        self._verifier_existence_variables(variables)
+        self._verifier_existence_axes(axes)
+        
+        # Récupération des donnée
+        
+        # Génération de l'en-tête
+        en_tete = self._generer_en_tete_descriptif(axes)
+        
+        # Création du tableau
+        tableau = []
+        tableau.append(en_tete)
+        
+        # Traitement des variables une à une
+        for variable in variables:
+            
+            lignes = self._generer_lignes_descriptif(variable, axes)
+            tableau = tableau+lignes
+            
+        # Generation de la sortie
+        sortie = self._generer_sortie(chemin, tableau, format_sortie)
+        
+        return(sortie)
+    
+    def tableau_detail_variable (self, chemin, variable, axes = None, format_sortie = "csv"):
+        
+        """
+            Décrit le contenu d'une variable selon un axe ou plusieurs axes donnés.
+            Les axes correspondent forcément à des variable qualitatives.
+            
+            Input :
+                chemin : chemin du fichier de sortie
+                variable : nom de la variable à traiter
+                axes : liste des axes d'analyse
+                format_sortie :
+                    'tsv' pour un tableau séparé par une tabulation
+                    'csv' pour un tableau séparé par une virgule
+                    'ods' pour un fichier ods
+                    'xlsx' pour un fichier xlsx
+                    'raw' pour un envoi brut des données pythons
+            Output :
+                si chemin textuel : aucune sortie, écriture du fichier
+                si chemin du fichier vaut None : retour du stream
+        """
+        
+        # Vérification que toutes les variables et axes existent
+        self._verifier_existence_variables([variable])
+        self._verifier_existence_axes(axes)
+        
+        # Récupération des donnée
+        
+        # Génération de l'en-tête
+        en_tete = self._generer_en_tete_detail(variable)
+                
+        # Création du tableau
+        tableau = []
+        tableau.append(en_tete)
+        
+        # On génère la première ligne d'analyse
+        tableau = tableau + self._generer_lignes_detail(variable, None)
+        
+        # Ligne vide
+        tableau.append(["" for x in range(len(tableau[-1]))])
+        
+        # Ajout des axes
+        for axe in axes:
+            tableau = tableau + self._generer_lignes_detail(variable, axe)
+        
+        return(tableau)

+ 2 - 1
thesis_analysis/__init__.py

@@ -1 +1,2 @@
-from .analyseStatistiques import analyseStatistiques
+from .analyseStatistiques import analyseStatistiques
+from .genererTableau import genererTableau

+ 649 - 0
thesis_analysis/genererTableau.py

@@ -0,0 +1,649 @@
+from io import StringIO, BytesIO
+import pyexcel as pe
+import csv 
+import math
+
+class genererTableau ():
+    
+    """
+        ecrire_tableau :
+        Permet de créer un tableau et l'exporter au format excel, ods ou csv à partir de données d'analyses.
+        
+        Input :
+            data : données aggrégées généré par les méthodes de analyseStatistiques
+            precision : nombre de décimales de précision
+            quantitative_string_format : format d'écriture de la moyenne, écart type et taille d'échantillon des variables quantitatives
+            qualitative_string_format : format d'écriture du n et de la proportion des variables qualitatives
+    """
+    
+    def __init__ (self, data, precision = 2, quantitative_string_format = "{mean:.2f} +/- {std:.2f} ({n})", qualitative_string_format = "{n} ({p:.2%})"):
+        
+        """
+            Data : data contenant les données aggrégées, généré par analyseStatistiques.analyse_univarie
+        """
+        
+        # Parametres
+        self.quantitative_string_format = quantitative_string_format
+        self.qualitative_string_format = qualitative_string_format
+        self.absence_test = "Absence de test effectué (CI des tests conventionnels non remplis)"
+        self.precision = precision
+        
+        self.data = data
+        self.liste_variables = list(data.keys()) # Liste des variables analysables
+        self.liste_axes = list(list(data.values())[0]["sous_groupes"].keys()) # Liste des axes d'analyses
+        
+        # On récupère la liste des modalités de chaque axes
+        self.liste_modalites = dict(zip(
+            self.liste_axes,
+            [self._obtenir_modalite_axe(x, True) for x in self.liste_axes]
+        ))
+        
+        # On récupère la liste des modalités de chaque variable
+        self.liste_modalites_variables = dict(zip(
+            self.liste_variables,
+            [self._obtenir_modalite_variable(x, True) for x in self.liste_variables]
+        ))
+        
+    def _generer_texte_propre (self, texte):
+        """
+            Nettoie un texte
+            Si il s'agit d'un flottant : le met en int
+        """
+        
+        if type(texte) != type(str()):
+            texte = str(int(texte))
+            
+        return(texte)
+        
+    def _generer_str_mean_std_n(self, mean, std, n):
+        
+        """
+            Simple fonction qui écrit un string mélangeant moyenne, écart-type et taille de l'effectif
+            Format par défault :
+                {mean:.2f} +/- {std:.2f} ({n})
+                
+            Peut-être personalisé par le paramètre string_format.
+            
+            Input :
+                mean, std et n
+        """
+        
+        texte = self.quantitative_string_format.format(
+            mean = round(mean, self.precision), 
+            std=round(std, self.precision), 
+            n=round(n, self.precision)
+        )
+        
+        return(texte)
+    
+    def _generer_str_n_p(self, n, p):
+        
+        """
+            Simple fonction qui écrit un string mélangeant moyenne, écart-type et taille de l'effectif
+            Format par défault :
+                {n} ({p:.0%})
+                
+            Peut-être personalisé par le paramètre string_format.
+            
+            Input :
+                mean, std et n
+            Sortie :
+                texte
+        """
+        
+        texte = self.qualitative_string_format.format(
+            p = round(p, self.precision), 
+            n = round(n, self.precision)
+        )
+        
+        return(texte)
+    
+    def _generer_str_valeur(self, valeur):
+        
+        """
+            Simple fonction qui écrit un string à partir d'une valeur
+                            
+            Input :
+                valeur
+            Sortie :
+                texte
+        """
+        
+        texte = "{}".format(
+            round(valeur, self.precision)
+        )
+        
+        return(texte)
+    
+    def _generer_str_ci(self, liste_ci):
+        
+        """
+            Simple fonction qui écrit un string à partir d'une liste représentant un intervale de confiance
+                            
+            Input :
+                liste_ci : liste de 2 items décrivant un intervalle de confiance
+            Sortie :
+                texte
+        """
+        
+        texte = "{}-{}".format(
+            self._generer_str_valeur(liste_ci[0]),
+            self._generer_str_valeur(liste_ci[1])
+        )
+        
+        return(texte)
+    
+    def _generer_str_resultat_test(self, test):
+        
+        """
+            Génère une liste avec formatage textuel du résultat d'un test
+            Vérifie la bonne application du test avant.
+            
+            Input :
+                test : liste contenant le résultat du test
+            Sortie :
+                liste de 3 éléments comprenant : Test, Paramètre et p
+        """
+        
+        resultat = []
+        
+        if len(test[1].keys()) > 1:
+            resultat.append(test[0]) # Nom du test
+            resultat.append(self._generer_str_valeur(test[1]["statistic"])) # Paramètre de test
+            resultat.append(self._formater_p_value(test[1]["p_value"])) # Petit p du test
+        else:
+            resultat.append(self.absence_test)
+            resultat = resultat + ["", ""]
+            
+        return(resultat)
+    
+    def _formater_p_value(self, p_value):
+    
+        # Nombre de décimales concernées
+        n_digit = len(str(p_value).split(".")[-1].split("0"))
+
+        borne_basse = 5*math.pow(10, -n_digit)
+        borne_sup = math.pow(10, -n_digit+1)
+
+        estimation_p = borne_basse if p_value <= borne_basse else borne_sup
+
+        # Affichage
+        if p_value > 0.01:
+            formated_p_value = str(round(p_value, 3))
+        else:
+            if p_value > 0.001:
+                formated_p_value = "< "+str(estimation_p)
+            else:
+                formated_p_value = "< 10^"+str(n_digit-1)    
+
+        return(formated_p_value)
+        
+    def _verifier_existence_variables(self, variables):
+        
+        """
+           Vérifie que les variables existent bien dans le jeu de données.
+           Input : variables : liste des variables à tester
+           Output : None
+        """
+        
+        variables_manquantes = [x for x in variables if x not in self.liste_variables]
+            
+        if len(variables_manquantes) > 0:
+            raise Exception("Erreur, les variables {} sont inexistantes des données aggrégées.".format(
+                ", ".join(variables_manquantes)
+            ))
+            
+    def _verifier_existence_axes(self, axes):
+        
+        """
+           Vérifie que les axes existent bien dans le jeu de données.
+           Input : variables : liste des axes à tester
+           Output : None
+        """
+        
+        if axes is not None:
+            axes_manquants = [x for x in axes if x not in self.liste_axes]
+        else:
+            axes_manquants = []
+            
+        if len(axes_manquants) > 0:
+            raise Exception("Erreur, les axes {} sont inexistantes des données aggrégées.".format(
+                ", ".join(axes_manquants)
+            ))
+            
+    def _obtenir_modalite_axe (self, axe, ajouter_nom = True):
+        
+        """
+            Retourne une liste contenant toutes les modalités pour un axe donné
+            Input :
+                axe : nom de l'axe à analyse
+                ajouter_nom : Si True, alors le nom de l'axe est ajouté à la modalité (concaténation)
+        """
+        
+        # Liste des modalités
+        modalites = list(self.data.values())[0]["sous_groupes"][axe].keys()
+        
+        # Post-traitement : on corrige les float et boolean pour les afficher en int
+        modalites = [int(x) if type(x) != type(str()) else x for x in modalites]
+        
+        # On affiche le nom de l'axe avec si nécessaire
+        modalites_avec_str = [axe+" "+str(x) for x in modalites]
+        
+        if ajouter_nom:
+            return(modalites_avec_str)
+        else:
+            return(modalites)
+        
+    def _obtenir_modalite_variable (self, variable, ajouter_nom = True):
+        
+        """
+            Retourne une liste contenant toutes les modalités pour une variable donnée
+            Input :
+                variable : nom de la variable à analyser
+                ajouter_nom : Si True, alors le nom de l'axe est ajouté à la modalité (concaténation)
+        """
+        
+        # Liste des modalités
+        modalites = [x for x in list(self.data[variable]["global"].keys()) if x != "total"]
+        
+        # Post-traitement : on corrige les float et boolean pour les afficher en int
+        modalites = [int(x) if type(x) != type(str()) else x for x in modalites]
+        
+        # On affiche le nom de l'axe avec si nécessaire
+        modalites_avec_str = [axe+" "+str(x) for x in modalites]
+        
+        if ajouter_nom:
+            return(modalites_avec_str)
+        else:
+            return(modalites)
+            
+    def _generer_en_tete_descriptif(self, axes):
+        
+        """
+            Genere l'en-tête d'un tableau Descriptif
+            Input :
+                axes : liste des axes d'analyse
+            Output : None
+        """
+        
+        # Initialisation de l'en-tête
+        en_tete = ["","",""]
+        
+        # Lecture des axes
+        for axe in axes:
+            en_tete = en_tete+self.liste_modalites[axe]+["Test","Paramètre", "p"]
+            
+        return en_tete
+    
+    def _generer_en_tete_detail(self, variable):
+        
+        """
+            Genere l'en-tête d'un tableau Descriptif
+            Input :
+                axes : liste des axes d'analyse
+            Output : None
+        """
+        
+        # Type de variable
+        type_variable = self.data[variable]["type"]
+        
+        # Initialisation de l'en-tête
+        en_tete = []
+        
+        en_tete.append(variable) # Nom de la variable
+        en_tete.append("") # Espace technique
+        
+        ## Pour les variables quantitatives
+        if type_variable == "quantitative":
+            en_tete = en_tete + ["n", "mean", "std", "IC 95%"]
+        elif type_variable == "qualitative":
+            en_tete.append("n")
+            en_tete = en_tete + self.liste_modalites_variables[variable]
+        
+        en_tete = en_tete + ["Test", "Parameter", "p"]
+            
+        return en_tete
+    
+    def _generer_lignes_descriptif (self, variable, axes):
+        
+        """
+            Genere des ligne d'analyse d'un tableau descriptif pour une variable
+            Input :
+                variable : nom de la variable
+                axes : axes sur lesquels générer la ligne
+        """
+        
+        lignes = []
+        
+        if self.data[variable]["type"] == "quantitative":
+            # Ligne d'une variable quantitative : une seule ligne
+            
+            ligne = [] # On instancie la ligne
+            ligne.append(variable+" mean +/- std (n)") # Ajout du nom de variable
+            ligne.append("") # Espace vide, technique
+            ligne.append(self._generer_str_mean_std_n(
+                self.data[variable]["global"]["mean"],
+                self.data[variable]["global"]["std"],
+                self.data[variable]["global"]["n"]
+            )) # Données globales
+            
+            # Données liées à chaque axe
+            for axe in axes:
+                
+                donnees_axe = self.data[variable]["sous_groupes"][axe]
+                # Pour chaque valeur de l'axe
+                for valeur in donnees_axe.keys():
+                    
+                    ligne.append(self._generer_str_mean_std_n(
+                        donnees_axe[valeur]["mean"],
+                        donnees_axe[valeur]["std"],
+                        donnees_axe[valeur]["n"]
+                    )) # Données hors test
+                
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe]) # Données du test
+                
+            lignes.append(ligne)
+        else:
+            # Lignes d'un variable qualitative : une ligne par modalité
+            
+            ## On écrit la première ligne : nom de variable et test
+            ligne = [] # On instancie la ligne
+            ligne.append(variable+" no. (%)") # Nom de variable
+            ligne = ligne + ["", ""] # Variable vides
+            
+            # Données liées à chaque axe
+            for axe in axes:
+                
+                blank_list = ["" for x in range(len(self.liste_modalites[axe]))]
+                
+                ligne = ligne + blank_list # Blanc lié aux tests
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe]) # Données du test
+                
+            lignes.append(ligne)
+            
+            ## On écrit les lignes propres à chaque variable
+            
+            liste_labels = self.data[variable]["global"].keys()
+            
+            for label in liste_labels:
+                                
+                # On ignore le label total
+                if label != "total":
+                    
+                    ligne = []
+                    
+                    ligne.append("") # Première ligne vide
+                    ligne.append(self._generer_texte_propre(label)) # Libelé analysé   
+                    ligne.append(self._generer_str_n_p(
+                        n = self.data[variable]["global"][label]["n"], 
+                        p = self.data[variable]["global"][label]["p"]
+                    )) # Données générales
+                    
+                    # Valeur par axe
+                    for axe in axes:
+                        
+                        liste_valeurs = list(self.data[variable]["sous_groupes"][axe].keys())
+                        
+                        # Pour chaque valeur de l'axe
+                        for valeur in liste_valeurs: # On met 0 si la valeur n'existe pas
+                            
+                            donnees_axe = self.data[variable]["sous_groupes"][axe][valeur]
+                                
+                            n = donnees_axe[label]["n"] if label in donnees_axe.keys() else 0
+                            p = donnees_axe[label]["p"] if label in donnees_axe.keys() else 0
+
+                            ligne.append(self._generer_str_n_p(
+                                n = n, 
+                                p = p
+                            )) # Données spécifique à chaque axe
+                    
+                    ligne = ligne + ["", "", ""] # Espaces vides entre 2 axes
+
+                lignes.append(ligne)
+        
+        return(lignes)
+    
+    def _generer_lignes_detail(self, variable, axe = None):
+        
+        """
+            Genere des ligne d'analyse d'un tableau détaillé pour une variable
+            Input :
+                variable : nom de la variable
+                axe : axe sur lesquels générer les ligne
+        """        
+    
+        lignes = []
+        
+        if self.data[variable]["type"] == "quantitative":
+            # Génération de la ligne simple
+            if axe is None:
+                ligne = [] # On instancie la ligne
+                ligne = ligne + ["", ""] # Blanc technique
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["n"])) # Taille d'échantillon
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["mean"])) # Moyenne
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["std"])) # Ecart type
+                ligne.append(self._generer_str_ci(self.data[variable]["global"]["ci_95"])) # IC à 95%
+                ligne = ligne + ["", "", ""] # Blanc technique
+                
+                lignes.append(ligne)
+            else:
+                # On vérifie l'axe
+                self._verifier_existence_axes([axe])
+                
+                # Données sur l'axe
+                donnees_axe = self.data[variable]["sous_groupes"][axe]
+                
+                # On écrit le nom de l'axe
+                ligne = []
+                ligne.append(axe) # Nom de l'axe
+                ligne = ligne + ["","","","",""]
+                
+                # Résultat du test
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
+                
+                lignes.append(ligne)
+                
+                # On parcours toutes les modalités de l'axe
+                for valeur_axe in donnees_axe.keys():
+                    
+                    ligne = []
+                    
+                    ligne.append("") # Ligne blanche technique
+                    ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["n"])) # Taille d'échantillon
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["mean"])) # Moyenne
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["std"])) # Ecart type
+                    ligne.append(self._generer_str_ci(donnees_axe[valeur_axe]["ci_95"])) # IC à 95%
+                    
+                    ligne = ligne + ["", "", ""] # Blanc technique
+                    lignes.append(ligne)
+        else:
+            # Génération de la ligne simple
+                        
+            if axe is None:
+                ligne = [] # On instancie la ligne
+                ligne = ligne + ["", ""] # Blanc technique
+                ligne.append(self._generer_str_valeur(self.data[variable]["global"]["total"])) # Taille d'échantillon
+                for variable_valeur in self.data[variable]["global"].keys():
+                    if variable_valeur != "total":
+                        ligne.append(self._generer_str_n_p(
+                                n = self.data[variable]["global"][variable_valeur]["n"], 
+                                p = self.data[variable]["global"][variable_valeur]["p"]
+                            ))
+                ligne = ligne + ["", "", ""] # Blanc technique
+                
+                lignes.append(ligne)
+                
+            else:
+                # On vérifie l'axe
+                self._verifier_existence_axes([axe])
+                
+                # Données sur l'axe
+                donnees_axe = self.data[variable]["sous_groupes"][axe]
+                
+                # On écrit le nom de l'axe
+                ligne = []
+                ligne.append(axe) # Nom de l'axe
+                ligne = ligne + ["",""] + ["" for x in range(len(self.liste_modalites_variables[variable]))] # Blanc technique
+                
+                # Résultat du test
+                ligne = ligne + self._generer_str_resultat_test(self.data[variable]["test"][axe])
+                
+                lignes.append(ligne)
+                
+                # On parcours toutes les modalités de l'axe
+                for valeur_axe in donnees_axe.keys():
+                    
+                    ligne = []
+                    
+                    ligne.append("") # Ligne blanche technique
+                    ligne.append(self._generer_texte_propre(valeur_axe)) # Valeur sur l'axe
+                    ligne.append(self._generer_str_valeur(donnees_axe[valeur_axe]["total"])) # Taille d'échantillon
+                    
+                    for variable_valeur in donnees_axe[valeur_axe].keys():
+                        if variable_valeur != "total":
+                            ligne.append(self._generer_str_n_p(
+                                    n = donnees_axe[valeur_axe][variable_valeur]["n"], 
+                                    p = donnees_axe[valeur_axe][variable_valeur]["p"]
+                                )) 
+                    
+                    ligne = ligne + ["", "", ""] # Blanc technique
+                    lignes.append(ligne)
+            
+        return lignes
+    
+    def _generer_sortie (self, chemin, tableau, format_fichier):
+        
+        """
+            Génère le fichier de sortie
+                input :
+                    chemin : chemin du fichier de sortie
+                    tableau : liste contenant les lignes à écrire
+                    format_fichier : format du fichier de sortie
+                output :
+                    si chemin textuel : aucune sortie, écriture du fichier
+                    si chemin du fichier vaut None : retour du stream
+        """
+        
+        # Création du fichier
+        if format_fichier in ["csv", "tsv"]:
+            f = StringIO()
+        elif format_fichier in ["ods", "xlsx"]:
+            f = BytesIO()
+            
+        # Ecriture du fichier en stream
+        if format_fichier == 'csv':
+            csv.writer(f, delimiter = ",").writerows(tableau)
+        elif format_fichier == 'tsv':
+            csv.writer(f, delimiter = "\t").writerows(tableau)
+        elif format_fichier in ['ods','xlsx']:
+            sheet = pe.Sheet(data)
+            f = sheet.save_to_memory(format_fichier, f)
+        elif format_fichier == 'raw':
+            f = tableau
+        else:
+            raise Exception("Format de sortie non supporté.")
+            
+        if type(chemin) == type(str()):
+            # On écrit le fichier
+            if format_fichier in ["csv", "tsv"]:
+                fichier_sortie = open(chemin, "w")
+            else:
+                fichier_sortie = open(chemin, "wb")
+            
+            fichier_sortie.write(file)
+        else:
+            # On renvoie le buffer
+            return (f)
+
+            
+    def tableau_descriptif (self, chemin, variables, axes = None, format_sortie = "csv"):
+        
+        """
+            Décrit le contenu de plusieurs variables selon un axe ou plusieurs axes donnés.
+            L'axe correspond forcément à une variable qualitative.
+            
+            Input :
+                chemin : chemin du fichier de sortie
+                variables : liste des variables à traiter
+                axes : liste des axes d'analyse
+                format_sortie :
+                    'tsv' pour un tableau séparé par une tabulation
+                    'csv' pour un tableau séparé par une virgule
+                    'ods' pour un fichier ods
+                    'xlsx' pour un fichier xlsx
+                    'raw' pour un envoie brut des données (liste python)
+            Output :
+                si chemin textuel : aucune sortie, écriture du fichier
+                si chemin du fichier vaut None : retour du stream
+        """
+        
+        # Vérification que toutes les variables et axes existent
+        self._verifier_existence_variables(variables)
+        self._verifier_existence_axes(axes)
+        
+        # Récupération des donnée
+        
+        # Génération de l'en-tête
+        en_tete = self._generer_en_tete_descriptif(axes)
+        
+        # Création du tableau
+        tableau = []
+        tableau.append(en_tete)
+        
+        # Traitement des variables une à une
+        for variable in variables:
+            
+            lignes = self._generer_lignes_descriptif(variable, axes)
+            tableau = tableau+lignes
+            
+        # Generation de la sortie
+        sortie = self._generer_sortie(chemin, tableau, format_sortie)
+        
+        return(sortie)
+    
+    def tableau_detail_variable (self, chemin, variable, axes = None, format_sortie = "csv"):
+        
+        """
+            Décrit le contenu d'une variable selon un axe ou plusieurs axes donnés.
+            Les axes correspondent forcément à des variable qualitatives.
+            
+            Input :
+                chemin : chemin du fichier de sortie
+                variable : nom de la variable à traiter
+                axes : liste des axes d'analyse
+                format_sortie :
+                    'tsv' pour un tableau séparé par une tabulation
+                    'csv' pour un tableau séparé par une virgule
+                    'ods' pour un fichier ods
+                    'xlsx' pour un fichier xlsx
+                    'raw' pour un envoi brut des données pythons
+            Output :
+                si chemin textuel : aucune sortie, écriture du fichier
+                si chemin du fichier vaut None : retour du stream
+        """
+        
+        # Vérification que toutes les variables et axes existent
+        self._verifier_existence_variables([variable])
+        self._verifier_existence_axes(axes)
+        
+        # Récupération des donnée
+        
+        # Génération de l'en-tête
+        en_tete = self._generer_en_tete_detail(variable)
+                
+        # Création du tableau
+        tableau = []
+        tableau.append(en_tete)
+        
+        # On génère la première ligne d'analyse
+        tableau = tableau + self._generer_lignes_detail(variable, None)
+        
+        # Ligne vide
+        tableau.append(["" for x in range(len(tableau[-1]))])
+        
+        # Ajout des axes
+        for axe in axes:
+            tableau = tableau + self._generer_lignes_detail(variable, axe)
+        
+        return(tableau)