Revision 50790d370cc6a7d81f80942cc4c5569a3c58efe1 authored by Software Heritage on 20 March 2020, 00:00:00 UTC, committed by Software Heritage on 20 March 2020, 00:00:00 UTC
0 parent
Raw File
arbresetstats.py
# -*- coding: utf-8 -*-
"""
@author: lejocelyn
@licence:GPL3 + CeCill 2.1
"""

from __future__ import print_function
import oursql
import argparse
import sys
import graphviz as gv
import functools
import csv
import re
from collections import OrderedDict

graph = functools.partial(gv.Graph, format='svg')
digraph = functools.partial(gv.Digraph, format='svg')

parser = argparse.ArgumentParser(description='Get the data from a CSV file to input into the graph.')
parser.add_argument("--data", "-d", dest='data', type=str, default=False,
                   help="Spécifie le fichier duquel les données sont extraites.")
parser.add_argument("--fichier", "-f", dest='fichier', type=str, default="arbre_corpus",
                   help="Spécifie le nom du fichier dans lequel est écrit le graphique.")
parser.add_argument("--lettre", "-l", dest='lettre', type=str, default=False,
                   help="Restreint aux formes contenant la ou les lettres spécifiées.")
parser.add_argument("--syllabe", "-s", dest='syllabe', type=str, default=False,
                   help="Faire des stats et arbre pour la syllabe")
parser.add_argument("--contexte", "-c", dest='contexte', type=str, default=False,
                   help="""Définit un contexte pour limiter la recherche. Le formalisme
                   de définition du contexte est celui des expressions régulières.""")
parser.add_argument("--couleurs", "-o", dest='couleurs', type=str, default=False,
                   help="""Active ou non l'usage de couleurs pour marquer les lettres recherchées.""")
#parser.add_argument("--mots", "-m", dest='mots', type=str, default=False,
 #                  help="""Si l'option est précisée, la requête est effectuée sur les unités d'intonations
  #                 et représente les morphèmes à la place des lettres.""")

args = parser.parse_args()

####
####
#### Fonctions récurrentes proposées par la doc de graphviz
####


def add_nodes(graph, nodes):
    for n in nodes:
        if isinstance(n, tuple):
            graph.node(n[0], **n[1])
        else:
            graph.node(n)
    return graph

def add_edges(graph, edges):
    for e in edges:
        if isinstance(e[0], tuple):
            graph.edge(*e[0], **e[1])
        else:
            graph.edge(*e)
    return graph


def log_stats(info):
    """
    append the stats information to the end of the stat file
    """

    with open("stats_" + args.fichier + ".txt", "a") as myfile:
        print(info, file=myfile)
        myfile.write("\n")

def find_lettre_in_list(letter, liste):
    """
    a letter being defined by a form and a position, find if a same label and
    a same position can be already match in the list of node
    """
    for element in liste: # si le caractère et la positon sont identiques
        if element[1]["label"] == letter[1]["label"] and element[1]["position"] == letter[1]["position"] and  element[1]["id_lettre_precedente"] == letter[1]["id_lettre_precedente"]:
            return element[0]
    return False

def get_lettre_in_list(id_lettre, liste_noeuds):
    """
    retourne le nœud/lettre correspondant à l'id
    """
    for lettre in liste_noeuds:
        if lettre[0] == str(id_lettre):
            return lettre
    

def find_liens_in_list(liste_liens, id_A=False, id_B=False):
    """
    chercher un lien dans la liste des liens.
    Si les deux ID, A et B, sont spécifiés, retourne le lien,
    Si seul un ID, A ou B, est spécifié, retourne la liste des liens
    satisfaisants l'ID demandé
    
    """
    if id_A != False and id_B != False:
        for lien in liste_liens:
            # Lorsque l'on trouve le bon lien, on le renvoie
            if lien[0][0] == id_A and lien[0][1] == id_B:
                return lien
        return False
    elif id_A != False:
        liste_id_A = []
        for lien in liste_liens:  
            if lien[0][0] == id_A :
                liste_id_A.append(lien)
        return liste_id_A

    elif id_B != False:
        liste_id_B = []
        for lien in liste_liens:
            if lien[0][1] == id_B:
                liste_id_B.append(lien)
        return liste_id_B
            
            
def incremente_occurrence(id_lettre, liste_node, nom):
    '''
    incremente d'une occurrence la lettre dans la liste 
    et retourne la liste
    '''
    # exemple id_lettre : '1',
    # exemple liste_node : [('1', {'style': 'filled', 'occurrence': '397',
    #  'label': u'v', 'fillcolor': 'grey', 'position': '0', 'id_lettre_precedente': ' '}),
    #  ('2', {'style': 'filled', 'occurrence': '397',
    #  'label': u'a', 'fillcolor': 'grey', 'position': '1',   #  'id_lettre_precedente': '1'}),
    #  ('3', {'style': 'filled', 'occurrence': '397',
    #  'label': u'n', 'fillcolor': 'grey', 'position': '2', 'id_lettre_precedente': '2'})],
    # exemple nom : (u'va', 194L))

    i_list = 0
    for element in liste_node:
        if element[0] == str(id_lettre):
            tmp_occurrence = int(liste_node[i_list][1]["occurrence"])
            tmp_occurrence += nom[1]
            liste_node[i_list][1]["occurrence"] = str(tmp_occurrence)
        i_list += 1
    return liste_node
            
def incremente_lien(id_A, id_B, liste_liens, nom):
    """
    Incrémente d'une occurrence du lien
    """
    i_list = 0
    for element in liste_liens:
        if element[0][0] == str(id_A) and element[0][1] == str(id_B):
            tmp_occurrence = int(liste_liens[i_list][1]["label"])
            tmp_occurrence += nom[1]
            liste_liens[i_list][1]["label"] = str(tmp_occurrence)
        i_list += 1
    #print(liste_liens)
    return liste_liens

def ordonner_node(liste_noeuds):
    """
    ordonne la liste des nœuds par ordre décroissant
    """
    liste_ordonnee = []
    #print(len(liste))
    index = 0
    for element in liste_noeuds:
        i = 0
        if len(liste_ordonnee) == 0:
            liste_ordonnee.append(element)
        else:
            while True:
                if len(liste_ordonnee) - i == 0:
                    liste_ordonnee.insert(i, element)
                    break
                elif int(element[1]["occurrence"]) <= int(liste_ordonnee[i][1]["occurrence"]):
                    i += 1
                else:
                    liste_ordonnee.insert(i, element)
                    break
        index += 1
            
            
    return liste_ordonnee

def get_total(liste_nodes):
    """
    Retourne le nombre total d'occurrences de la requête. Ce nombre correspond à la somme
    des occurrences des lettres en première position
    """
    total = 0
    for element in liste_nodes:
               
        if int(element[1]["position"]) == 0:          
            total += int(element[1]["occurrence"])

    
    return total

def couleur_noeud(label):
    """
    Définit la couleur d'un noeud dans le schema SVG
    """
    ## Colorisation du schéma SVG
    ##

    couleur_lettre = "white"

    if args.couleurs:
       # if label in args.contexte:
        #    couleur_lettre = "green"
        
        if isinstance(args.lettre,str):
            if label in args.lettre:
                couleur_lettre = "red"

    return couleur_lettre

def get_total_position(liste_nodes):
    """
    retourne une liste de totaux suivant la position de la lettre

    À quoi ça sert ?
    """
    liste_total = []
    for element in liste_nodes:
        if len(liste_total) <= int(element[1]["position"]):
            liste_total.append(int(element[1]["occurrence"]))
        else:
            liste_total[int(element[1]["position"])] += int(element[1]["occurrence"])
    return liste_total

def compte_total_occurrence_lettres(liste_nodes):
    """
    retourne un dictionnaire du nombre total d'occurrences de chacune des lettres et en fonction de leur position
    """

    dico_compte_occurrence_lettres = {}
    # Le dictionnaire aura la forme suivante :
    # label : total, "position" : nb_occurrence
    # {"s" : 13}
    
    # pour rappel, un noeud à la forme suivante :
    # str(id_lettre), {"label": label , "position": str(position_node),
    #          "id_lettre_precedente": " ", "occurrence":str(nom[1]), "fillcolor":couleur_noeud(label), "style":"filled"})
    

    for noeud in liste_nodes:

        lettre = noeud[1]["label"]
        label_position = lettre + "_position_" + noeud[1]["position"]
       
        if lettre in dico_compte_occurrence_lettres.keys():
            dico_compte_occurrence_lettres[lettre] += int(noeud[1]["occurrence"])
        else :
            dico_compte_occurrence_lettres[lettre] = 0
            dico_compte_occurrence_lettres[lettre] += int(noeud[1]["occurrence"])

        if label_position in dico_compte_occurrence_lettres.keys():
            dico_compte_occurrence_lettres[label_position] += int(noeud[1]["occurrence"])
        else :
            dico_compte_occurrence_lettres[label_position] = 0
            dico_compte_occurrence_lettres[label_position] += int(noeud[1]["occurrence"])

    dico_compte_occurrence_lettres = OrderedDict(sorted(dico_compte_occurrence_lettres.items(), key=lambda t: t[0]))
    return dico_compte_occurrence_lettres


def comptage_formes(listes_liens, listes_nodes):
    """
    Calcul du nombre de formes différentes dans l'arbre résultant de l'analyse du corpus.
    """
    ## Une lettre égale (str(id unique), dico{"label": "a", "position":"1", id_lettre_precedente = 23, "occurrence":str(nom[1])})
    ## lien = ((id_lettre_A, id_lettre_B), {"label": str(nom[1])})
    nb_formes = 0
    
    # Afin de déterminer le nombre de formes, le principe est de comparer le nombre
    # d'occurrences. Si le nombre d'occurrences d'une lettre est différent du
    # nombre d'occurrences de la lettre précédentes, cela signifie qu'il y a
    # une forme langagière.
    # Une forme est :
    # Si l'on prend la liste des morphèmes, cela correspond à un morphème
    # Si l'on prend la liste des syllables, cela correpond à une syllabe,
    # si l'on prend la liste des squellettes, cela correspond à un squelette
    
    # Initialisation des variables de comparaison à false
    occurrence_lettre_A = False
    occurrence_lettre_B = False
    
    # Calcul du nombre de branches, puis on ajoutera les moments où
    # la somme des occurrences des lettres "branches" n'est pas égale au nombre d'occurrences
    # du nœud précédent
    nb_branches = 0
    nb_arbres = 0
    liste_A_deja_compte = {}

    # la clef, un str, indique le nombre de lettres du morphème, et la valeur son nombre d'occurrences
    dico_forme_nb_lettres = {}
    
    # il faut compter les lettres isolées :
    # si l'id de la lettre n'est pas dans la liste des liens
    # alors l'ajouter comme une lettre isolée
    liste_id_lettre = []
    liste_id_liens = []

    for lettre in listes_nodes:
        liste_id_lettre.append(int(lettre[0]))
    
    for element in listes_liens:
        liste_id_liens.append(int(element[0][0]))
        liste_id_liens.append(int(element[0][1]))
    
    for id_lettre in liste_id_lettre:
        lettre_temp = get_lettre_in_list(id_lettre,listes_nodes)
        dico_forme_nb_lettres[lettre_temp[1]["position"]] = 0
        if id_lettre not in liste_id_liens:
            nb_formes += 1
            # la lettre ne peut pas avoir déjà été ajouté, vu qu'elle n'est pas dans la liste
            dico_forme_nb_lettres[lettre_temp[1]["position"]] += 1

    copie_listes_liens = listes_liens # pas clair la raison de ce hack, une question de globalspace
    
    for lien in listes_liens:
        lettre_A_id = lien[0][0]
        lettre_B_id = lien[0][1]

        lettre_A = get_lettre_in_list(lettre_A_id,listes_nodes)
        lettre_B = get_lettre_in_list(lettre_B_id,listes_nodes)

        # initialisation du comptage de nombre de formes par position :
        if dico_forme_nb_lettres.get(lettre_A[1]["position"]) == None :
            dico_forme_nb_lettres[lettre_A[1]["position"]] = 0
        if dico_forme_nb_lettres.get(lettre_B[1]["position"]) == None :
            dico_forme_nb_lettres[lettre_B[1]["position"]] = 0
      
        ## Si la lettre est la première de la forme, alors il s'agit d'une nouvelle 
        ## forme
        if int(lettre_A[1]["position"]) == 0:
            if lettre_A_id not in liste_A_deja_compte:
                # pas la peine de les compter car cela ferait doublon avec le compte des branches terminales
                #nb_formes += 1
                nb_arbres += 1
                #print("nouvel arbre")
        
        occurrence_lettre_A = int(lettre_A[1]["occurrence"])
        occurrence_lettre_B = int(lettre_B[1]["occurrence"])

        liste_lien_lettre_A = find_liens_in_list(copie_listes_liens, id_A=lettre_A_id)
        liste_lien_lettre_B = find_liens_in_list(copie_listes_liens, id_A=lettre_B_id)
        
        somme_occurrence_lien = 0
        for lien in liste_lien_lettre_A:
            somme_occurrence_lien += int(lien[1]["label"])
        if somme_occurrence_lien != occurrence_lettre_A:
            if lettre_A_id in liste_A_deja_compte:
                pass
            else:
                # Les branches intermédiaires sont en fait des formes
                # existantes mais qui possèdent également des sous-branches
                nb_formes += 1
                dico_forme_nb_lettres[lettre_A[1]["position"]] += 1
                #print("branche intermédiaire")
        if len(liste_lien_lettre_B) == 0:
            nb_formes += 1
            dico_forme_nb_lettres[lettre_B[1]["position"]] += 1
            #print("fin de la branche")
        # On conserve la somme des occurrences des branches pour les prochaines itérations
        liste_A_deja_compte[lettre_A_id] = somme_occurrence_lien
        
    log_stats("Nombre total d'arbres:\n")
    log_stats(nb_arbres)
    log_stats("Nombre de formes existantes en fonction du nombre de lettres :\n")
    tri_dico_forme_nb_lettres = OrderedDict(sorted(dico_forme_nb_lettres.items()))
    log_stats(tri_dico_forme_nb_lettres)

    return nb_formes

def navigation_arbre_lettre(liste_des_noms):
    """à définir """

    # dans ce dico, on stocke les noms correspondants à la requete
    dico_correspond = {}
    
    noms_total = 0
    for element in liste_formes:
        noms_total += int(element[1])
    
    liste_nodes = []
    liste_liens = []

    # Une lettre égale (str(id unique), dico{"label": "a", "position":"1", id_lettre_precedente = 23, "occurrence":str(nom[1])})
    i_lien = 0
    i_node = 0
    #id_lettre est un identifiant unique pour chaque lettre
    id_lettre = 0

    # nombre de morphèmes existants dans le corpus 
    nb_formes = 0

    liste_lettres_premiere_position = []

    # nom est un tuple : (forme, nb_occurrences) 
    for nom in liste_formes:
    
        # Si l'utilisateur a spécifié qu'il souhaité concentré la recherche uniquement 
        # sur les mots contenant une ou plusieurs lettres, les autres mots sont enlevé
        # de la recherche.
        
        if args.lettre != False:
            for lettre in args.lettre:
                if lettre not in nom[0]:
                     cont = True
                else :
                    cont = False
        if args.lettre != True:
            cont = False
        if cont == True:
            continue

        # Si l'utilisateur définit un contexte, il faut voir si le nom
        # contient le contexte rechercher par l'utilisateur, sinon, on passe

        if args.contexte != False:
            match = re.search(args.contexte, nom[0])
            if match is None:
                continue

        # Si l'utilisateur spécifie qu'il étudie la structure syllabique, les lettres
        # sont alors remplacées par leur symbole correspondant S = Demivoyelle; V = voyelle, C = consonne
        if args.syllabe != False:
                nouveau_nom = u""
                i_index_lettre = 0
                for lettre in nom[0]:
                    if lettre in consonnes:
                        nouveau_nom += "C"
                    elif lettre in voyelles:
                        # Gestion des semiconsonnes
                        if len(nom[0]) > 1 and len(nom[0]) == i_index_lettre + 1 and nom[0][i_index_lettre -1] in voyelles:
                            if lettre == "i" or lettre == "o":
                                nouveau_nom += "S"
                            else:
                                nouveau_nom += "V"
                        else:
                            nouveau_nom += "V"
                    else:
                        nouveau_nom += lettre
                    i_index_lettre += 1
                nom = (nouveau_nom, nom[1])

        # dictionnaire des noms correspondant

        dico_correspond[nom[0]] = nom[1]

        # Le premier cas de la liste est particulier, étant donné qu'il ne peut être comparé
        # avec la lettre précédente..
        
        position_node = 0
        label = nom[0][0]

        lettre_A = (str(id_lettre), {"label": label , "position": str(position_node),
        "id_lettre_precedente": " ", "occurrence":str(nom[1]), "fillcolor":couleur_noeud(label), "style":"filled"})
        
        # si la lettre est en première position du mot
        if position_node == 0:
            liste_lettres_premiere_position.append(lettre_A[1]["label"])
            liste_lettres_premiere_position = list(dict.fromkeys(liste_lettres_premiere_position))

        # qu'est qu'on vérifie déjà ?
        if len(nom[0]) == 1:
            id_lettre_A = find_lettre_in_list(lettre_A, liste_nodes)

            if id_lettre_A: # déjà dans la liste des lettres existantes
                liste_nodes = incremente_occurrence(id_lettre_A, liste_nodes, nom)
                lettre_A = get_lettre_in_list(id_lettre_A, liste_nodes)
            else: # ajout de la lettre A dans la liste des nodes
                id_lettre += 1
                lettre_A = (str(id_lettre), {"label": label , "position": str(position_node),
                "id_lettre_precedente": " ", "occurrence":str(nom[1]), "fillcolor":couleur_noeud(label), "style":"filled"})
                liste_nodes.append(lettre_A)
            continue
        
        # On commence par le deuxième élément de la liste, le premier ayant déjà été étudié
        
        for lettre in nom[0][1:]:
            
        # la lettre de référence, lettre A, est la première lettre du mot
            id_lettre_A = find_lettre_in_list(lettre_A, liste_nodes)

            if id_lettre_A: # déjà dans la liste des lettres existantes
                if position_node == 0:
                    liste_nodes = incremente_occurrence(id_lettre_A, liste_nodes, nom)
                lettre_A = get_lettre_in_list(id_lettre_A, liste_nodes)
            else: # ajout de la lettre A dans la liste des nodes
                id_lettre += 1
                lettre_A = (str(id_lettre), {"label": label , "position": str(position_node),
                "id_lettre_precedente": " ", "occurrence":str(nom[1]), "fillcolor":couleur_noeud(label), "style":"filled"})
                liste_nodes.append(lettre_A)
        
            id_lettre_A = lettre_A[0]

            # La première lettre étant A, on incrémente pour la lettre B
            position_node += 1
            # la lettre B est la lettre suivante du mot
            label_b = nom[0][position_node]
            lettre_B = (str(id_lettre), {"label":label_b, "position":str(position_node),
            "id_lettre_precedente":lettre_A[0], "occurrence":str(nom[1]), "fillcolor":couleur_noeud(label_b), "style":"filled"})
            id_lettre_B = find_lettre_in_list(lettre_B, liste_nodes)

            if(id_lettre_B): # déjà dans la liste des lettres existantes
                lettre_B = get_lettre_in_list(id_lettre_B, liste_nodes)
                liste_nodes = incremente_occurrence(id_lettre_B, liste_nodes, nom)
                #print("find B")
                #raw_input()
                if lettre_A[0] == lettre_B[1]["id_lettre_precedente"]:
                    liste_liens = incremente_lien(id_lettre_A, id_lettre_B, liste_liens, nom)
                    
                else: # nouveau lien
                    liste_liens.append(((id_lettre_A, id_lettre_B), {"label": str(nom[1])}))
                
            else: # ajout de la lettre B dans la liste des nodes
                id_lettre += 1                
                lettre_B = (str(id_lettre), {"label":nom[0][position_node], "position":str(position_node),
                "id_lettre_precedente":lettre_A[0], "occurrence":str(nom[1]), "fillcolor":couleur_noeud(label_b), "style":"filled"})
                liste_nodes.append(lettre_B)
                liste_liens.append(((id_lettre_A, lettre_B[0]), {"label": str(nom[1])}))
                
            # Les occurrences ayant déjà été comptabilisé une fois, il ne faut pas les ajouter à nouveau.
            lettre_A = lettre_B # la lettre de référence devient la lettre B, la lettre suivante
            #raw_input()
    
    log_stats("Liste des lettres en première position de mot :")
    log_stats(liste_lettres_premiere_position)

    total = get_total(liste_nodes)
    log_stats("Nombre total d'occurrences des nœuds:")
    log_stats(total)
    
    position_totale = get_total_position(liste_nodes)
    log_stats("Nombre total d'occurrences des nœuds par position:")
    log_stats(position_totale)

    occurrences_lettres = compte_total_occurrence_lettres(liste_nodes)
    log_stats("compte lettres noeuds :")
    log_stats(occurrences_lettres)
    
    log_stats("Liste des mots, ordonnée par taille :\n")
    dico_tri = OrderedDict(sorted(dico_correspond.items(), key=lambda x:len(x[0])))
    
    log_stats(dico_tri)
    
    log_stats("Nombre de formes:\n")
    nb_formes_total = 0
    for element in dico_tri.items() :
        nb_formes_total += element[1]
    log_stats(str(nb_formes_total))
        
    #print(len(liste_nodes))
    liste_nodes = ordonner_node(liste_nodes)
           
    formes = comptage_formes(liste_liens, liste_nodes)
    log_stats("Nombre totales de formes existantes :\n")
    log_stats(formes)

    #print(liste_liens)
    add_edges(
        add_nodes(digraph(), liste_nodes),
        liste_liens
    ).render("img/" + args.fichier)

def navigation_arbre_mots(liste_des_enonces):

    return "hé hé, pas encore"

##### extrait les noms du fichiers CSV pour en faire une liste []
log_stats("args.fichier")
log_stats(args.fichier)


liste_formes = []

if args.data:
    with open(args.data, 'rb') as csvfile:
        spamreader = csv.reader(csvfile, delimiter=',', quotechar='|')
        for row in spamreader:
            liste_formes.append((unicode(row[0]), int(row[1])))
else :

    db = oursql.connect(host="localhost", # your host, usually localhost
                     user="lejocelyn", # your username
                      passwd='Lejocelyn1', # your password
                      db="lexique_nisvai") # name of the data base

    cursor = db.cursor()

    if args.mots:

        cursor.execute("""SELECT nisvais
            FROM Enonces;"""
        )
    else:

    ### COUNT permet d'ordonner les résultats de la recherche
    ### par ordre décroissant de gauche à droite de la forêt
    ### d'arbres

        cursor.execute("""SELECT Termes.termes, COUNT(Termes.id_termes)
            FROM Termes_has_Enonces INNER JOIN Termes ON Termes.id_termes = Termes_has_Enonces.id_termes 
            WHERE Termes.id_categories=1 
            GROUP BY Termes.id_termes
            ORDER BY COUNT(Termes.id_termes) DESC;"""
        )

liste_formes = cursor.fetchall()

#### Les lettres du nisvais

consonnes = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w"]
voyelles = ["a", "e", "u", "i", "o", "y"]


#### Possibilité de définir d'autres classes
####
if args.mots:
    navigation_arbre_mots(liste_formes)

else :
    navigation_arbre_lettre(liste_formes)


########### MOYENNE DURÉE GROUPE DE SOUFFLE
# select Textes.id_textes, SUM(t2-t1)/COUNT(Textes.id_textes) AS moyenne From Enonces INNER JOIN Textes on Enonces.id_textes=Textes.id_textes WHERE Textes.id_corpus="T9" OR Textes.id_corpus="T8";
back to top