Module produit par des chercheurs néerlandais pour faire du web mining.
Forces:
Faiblesses:
Python 2.7 fait souvent des difficultés avec l'encodage (le format sous lequel le texte est encodé). Quelques fonctions pour faire la conversion:
chaine_unicode = u"Cette chaîne est en unicode"
print type(chaine_unicode)
<type 'unicode'>
chaine_ordinaire = "Cette chaîne est en UTF-8 (je crois)"
print type(chaine_ordinaire)
<type 'str'>
chaine_dechaine = chaine_ordinaire.decode("utf-8")
print type(chaine_dechaine)
<type 'unicode'>
unicode_enchaine = chaine_unicode.encode("utf-8", errors='ignore')
print type(unicode_enchaine)
<type 'str'>
decode et encode ont une option errors pour gérer les erreurs de conversion:
import codecs
fichier = codecs.open("nom_du_fichier.txt", "r", encoding = "utf-8")
Très utile si on entre des caractère non-ASCII, comme des lettres accentuées
# -*- encoding: utf-8 -*-
phrase = "Longtemps, je me suis couché de bonne heure."
On peut la découper en mots:
phrase.split()
['Longtemps,', 'je', 'me', 'suis', 'couch\xc3\xa9', 'de', 'bonne', 'heure.']
... notez qu'ici split() coupe aux espaces, mais on peut mettre n'importe quel caractère ou chaîne de caractère:
print phrase.split(",")
print phrase.split("couché")
['Longtemps', ' je me suis couch\xc3\xa9 de bonne heure.'] ['Longtemps, je me suis ', ' de bonne heure.']
Pour raboutter tout ça:
mots_de_phrase = phrase.split()
print "_".join(mots_de_phrase)
Longtemps,_je_me_suis_couché_de_bonne_heure.
On peut aussi tout convertir en minuscules:
phrase.lower()
'longtemps, je me suis couch\xc3\xa9 de bonne heure.'
ou en majuscules:
print phrase.upper()
LONGTEMPS, JE ME SUIS COUCHé DE BONNE HEURE.
... sauf que ça ne marche pas avec les accents.
print phrase.decode('utf-8').upper()
LONGTEMPS, JE ME SUIS COUCHÉ DE BONNE HEURE.
Remplace toutes les occurences d'une expression par une autre
print phrase.replace("o", "☺")
print phrase.lower().replace(",","").replace(".","")
L☺ngtemps, je me suis c☺uché de b☺nne heure. longtemps je me suis couché de bonne heure
Peuvent être beaucoup plus pratiques pour faire des modifications au texte
import re
print re.sub("[.,:;]","",phrase)
Longtemps je me suis couché de bonne heure
Le module re possède aussi sa propre fonction split().
*Attention :* les expressions régulières ne comptent jamais les caractères accentués comme des lettres.
Mettons qu'on prend Du côté de chez Swann, de Proust
On importe les libraires utiles... (ici, on va prendre des textes en ligne, donc on emploie urllib)
import urllib
Ouvrons un fichier texte, qui est hébergé sur Gutenberg:
f = urllib.urlopen("http://www.gutenberg.org/files/2650/2650-0.txt")
texte = f.read()
texte = texte.replace("\r","") # les fins de ligne de Gutenberg sont en CRLF. On enlève les CR.
print texte[:1000]
The Project Gutenberg EBook of Du Côté de Chez Swann, by Marcel Proust This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online at www.gutenberg.net Title: Du Côté de Chez Swann Author: Marcel Proust Posting Date: April 13, 2010 [EBook #2650] Release Date: May, 2001 [Last updated: June 2, 2012] Language: French Character set encoding: UTF-8 *** START OF THIS PROJECT GUTENBERG EBOOK DU CÔTÉ DE CHEZ SWANN *** Produced by Sue Asscher MARCEL PROUST À la RECHERCHE DU TEMPS PERDU TOME I DU COTÉ DE CHEZ SWANN À Monsieur Gaston Calmette Comme un témoignage de profonde et affectueuse reconnaissance, Marcel Proust. PREMIÈRE PARTIE COMBRAY I. Longtemps, je me suis couché de bonne heure. Parfois, à peine ma bougie éteinte, mes yeux se fermaient si vite que
À la fin, ç'a l'aire de ça:
print texte[-20000:-18000] + "\n\n[...]\n\n" + texte[-2000:]
e Jardin élyséen de la Femme; au-dessus du moulin factice le vrai ciel était gris; le vent ridait le Grand Lac de petites vaguelettes, comme un lac; de gros oiseaux parcouraient rapidement le Bois, comme un bois, et poussant des cris aigus se posaient l'un après l'autre sur les grands chênes qui sous leur couronne druidique et avec une majesté dodonéenne semblaient proclamer le vide inhumain de la forêt désaffectée, et m'aidaient à mieux comprendre la contradiction que c'est de chercher dans la réalité les tableaux de la mémoire, auxquels manquerait toujours le charme qui leur vient de la mémoire même et de n'être pas perçus par les sens. La réalité que j'avais connue n'existait plus. Il suffisait que Mme Swann n'arrivât pas toute pareille au même moment, pour que l'Avenue fût autre. Les lieux que nous avons connus n'appartiennent pas qu'au monde de l'espace où nous les situons pour plus de facilité. Ils n'étaient qu'une mince tranche au milieu d'impressions contiguës qui formaient notre vie d'alors; le souvenir d'une certaine image n'est que le regret d'un certain instant; et les maisons, les routes, les avenues, sont fugitives, hélas, comme les années. End of the Project Gutenberg EBook of Du Côté de Chez Swann, by Marcel Proust *** END OF THIS PROJECT GUTENBERG EBOOK DU CÔTÉ DE CHEZ SWANN *** ***** This file should be named 2650-0.txt or 2650-0.zip ***** This and all associated files of various formats will be found in: http://www.gutenberg.org/2/6/5/2650/ Produced by Sue Asscher Updated editions will replace the previous one--the old editions will be renamed. Creating the works from public domain print editions means that no one owns a United States copyright in these works, so the Foundation (and you!) can copy and distribute it in the United States without permission and without paying copyright royalties. Special rules, set forth in the General Terms of Use part of this license, apply to copying and dis [...] considerable effort, much paperwork and many fees to meet and keep up with these requirements. We do not solicit donations in locations where we have not received written confirmation of compliance. To SEND DONATIONS or determine the status of compliance for any particular state visit http://pglaf.org While we cannot and do not solicit contributions from states where we have not met the solicitation requirements, we know of no prohibition against accepting unsolicited donations from donors in such states who approach us with offers to donate. International donations are gratefully accepted, but we cannot make any statements concerning tax treatment of donations received from outside the United States. U.S. laws alone swamp our small staff. Please check the Project Gutenberg Web pages for current donation methods and addresses. Donations are accepted in a number of other ways including including checks, online payments and credit card donations. To donate, please visit: http://pglaf.org/donate Section 5. General Information About Project Gutenberg-tm electronic works. Professor Michael S. Hart is the originator of the Project Gutenberg-tm concept of a library of electronic works that could be freely shared with anyone. For thirty years, he produced and distributed Project Gutenberg-tm eBooks with only a loose network of volunteer support. Project Gutenberg-tm eBooks are often created from several printed editions, all of which are confirmed as Public Domain in the U.S. unless a copyright notice is included. Thus, we do not necessarily keep eBooks in compliance with any particular paper edition. Most people start at our Web site which has the main PG search facility: http://www.gutenberg.net This Web site includes information about Project Gutenberg-tm, including how to make donations to the Project Gutenberg Literary Archive Foundation, how to help produce our new eBooks, and how to subscribe to our email newsletter to hear about new eBooks.
Le plus souvent, on veut que le prétraitement puisse fonctionner pour d'autres textes – ici d'autres textes de Gutenberg. Le problème est de trouver un séparateur qui marche non seulement pour ce texte, mais probablement aussi pour les autres.
Pour ce faire, on peut employer les expressions régulières. Ici:
\* = *
(?: ) =
bloc. "?:
" signifie que le bloc ne sert pas à recouvrir le texte correspondant à l'expression qu'il contient.| =
ou[^*]
matche tout caractère qui n'est pas *
.
= wildcard. Matche n'importe quel caractère.*
matche le caractère ou bloc précédent de 0 à ∞ foistexte = re.split("\*\*\* (?:START|END) OF THIS PROJECT GUTENBERG EBOOK [^*]*\*\*\*.*",texte)[1]
texte = texte.strip()
print texte[:100]
print "[...]"
print texte[-150:]
Produced by Sue Asscher MARCEL PROUST À la RECHERCHE DU TEMPS PERDU TOME I DU COT [...] tes, les avenues, sont fugitives, hélas, comme les années. End of the Project Gutenberg EBook of Du Côté de Chez Swann, by Marcel Proust
Enlever la première et la dernière ligne
texte = re.sub("^.*\n","",texte)
texte = re.sub("\n.*$","", texte)
texte = texte.strip()
print texte[:100]
print "[...]"
print texte[-100:]
MARCEL PROUST À la RECHERCHE DU TEMPS PERDU TOME I DU COTÉ DE CHEZ SWANN À Monsieur [...] certain instant; et les maisons, les routes, les avenues, sont fugitives, hélas, comme les années.
Faisons-en une fonction, pour l'appliquer à tous les textes
def degutenberger(txt):
t = txt.replace("\r","")
t = re.split("\*\*\* (?:START|END) OF [^*]* PROJECT GUTENBERG [^*]*\*\*\*.*",t)[1]
t = t.strip()
t = re.sub("^.*\n\n","",t)
t = re.sub("\n\n.*$","", t)
return t.strip()
Le reste du prétraitement est pris en charge par pattern:
import pattern.vector as vec
doc_Swann = vec.Document(texte.replace("'"," ").decode('utf-8'),
# Remplace les ' par des espaces, respectant le fait qu'en français,
# les ' séparent des mots. Pour éviter des erreurs, je convertis aussi
# le texte en unicode
filter = lambda w: w.isalpha(),
# Filtre les mots qui ne sont pas exclusivement composés de lettres
punctuation = u'.,;:!?()[]{}\'`"@#$*+-|=~_«»',
top = None, # Filtre les mots qui ne sont pas parmi les plus fréquents.
threshold = 0, # Filtre les mots dont la fréquence est inférieure à un seuil.
exclude = [], # Filtre les mots dans la liste noire.
stemmer = None, # vec.STEMMER | vec.LEMMA | fonction | None
stopwords = False, # Inclure les mots fonctionnels ?
name = None, # Nom, type et description
type = None,
description = None,
language = 'fr') # Langue du texte (fr | en | es | de | it | nl)
Cette fonction vient de la documentation pattern (http://www.clips.ua.ac.be/pages/pattern-vector). Mais il faut parfois se poser des questions sur le traitement de la langue. En général, les outils de forage de texte ont été conçus pour l'anglais. E.g. Est-ce que la fonction isalpha() prend en compte les accents?
print "couché".isalpha()
print u"couché".isalpha() # "couché" est en format unicode, plutôt que chaîne UTF-8
print "couche".isalpha()
False True True
À ce point-ci, on peut faire un corpus entier (À la recherche du temps perdu, mettons):
textes_proust = []
i=0
for ln in liste_proust:
f = urllib.urlopen("http://louis.philotech.org/confs/MtlPythonFr2014/corpus/" + ln)
t = f.read()
t = degutenberger(t)
l = ln.split("_")
if i == int(l[1]):
textes_proust[-1] += t
else:
textes_proust.append(t)
i=int(l[1])
documents_proust = []
for t in textes_proust:
d = vec.Document(t.replace("'"," ").decode('utf-8'), # Remplace les ' par des espaces, respectant le fait qu'en français, les ' séparent des mots
filter = lambda w: w.isalpha(),
# Filtre les mots qui ne sont pas exclusivement composés de lettres
punctuation = u'.,;:!?()[]{}\'`"@#$*+-|=~_«»',
top = None, # Filtre les mots qui ne sont pas parmi les plus fréquents.
threshold = 0, # Filtre les mots dont la fréquence est inférieure à un seuil.
exclude = [], # Filtre les mots dans la liste noire.
stemmer = None, # vec.STEMMER | vec.LEMMA | fonction | None
stopwords = False, # Inclure les mots fonctionnels ?
name = None, # Nom, type et description
type = None,
description = None,
language = 'fr') # Langue du texte (fr | en | es | de | it | nl)
documents_proust.append(d)
corpus_proust = vec.Model(documents_proust, weight=vec.TFIDF)
À vrai dire, tout ça marche bien si on veut comparer les œuvre entre elles. Mais souvent, on préfère prendre une œuvre, la découper en morceaux, et analyser ceux-ci.
Prenons le Discours de la méthode pour cette fin:
f = urllib.urlopen("http://www.gutenberg.org/cache/epub/13846/pg13846.txt")
texte_detude = f.read()
texte_detude = degutenberger(texte_detude)
print texte_detude[:500]
Produced by Miranda van de Heijning, Renald Levesque and the Online Distributed Proofreading Team. This file was produced from images generously made available by the Bibliothèque nationale de France (BnF/Gallica) Descartes, René _Oeuvres de Descartes, précédées de l'éloge de René Descartes par Thomas_ OEUVRES DE DESCARTES. TOME PREMIER PUBLIÉES PAR VICTOR COUSIN. A M. ROYER-COLLARD, Professeur de l'histoire de la philosophie morale à la Faculté des Lettres de l'Acad�
On peut découper le texte de plusieurs façons.
Commençont par séparer en mots.
On veut éviter de garder les mots fonctionnels, car ils augmentent le temps de traitement et compromettent les résultats. On va donc d'abord télécharger une liste de mots fonctionnels:
f = urllib.urlopen("http://louis.philotech.org/confs/MtlPythonFr2014/corpus/stoplist.fr.utf-8.txt")
stoplist = f.read().decode('utf-8').split("\r\n")
mots_etude = vec.words(texte_detude.lower().replace("'"," ").decode('utf-8'),
filter = lambda w: w.isalpha() and len(w)>1 and w not in stoplist,
# On filtre les mots d'un seul caractère et les mots fonctionnels
punctuation = u'.,;:!?()[]{}\'`"@#$*+-|=~_«»')
On sépare fait des tranches de 50 mots, qu'on raboutte avec "join()"
t = 50
segments_etude = [ " ".join(mots_etude[i:i+t]) for i in xrange(0, len(mots_etude), t) ]
Maintenant, faisons un corpus avec ça:
documents_etude = []
for t in segments_etude:
d = vec.Document(t, language = 'fr')
documents_etude.append(d)
corpus_etude = vec.Model(documents_etude, weight=None)
Comme on a dit précédemment, pattern ne produit pas de matrice. Donc on doit la produire soi-même.
Par matrice, on entend celle du modèle vectoriel:
mot 1 | mot 2 | ... | mot n | |
segment 1 | 0 | 1 | ... | 4 |
segment 2 | 3 | 0 | ... | 1 |
... | ... | ... | ... | ... |
segment n | 5 | 2 | ... | 0 |
On fait la matrice sous forme d'une array de numpy
import numpy as np
La raison est simple: dans ce format, on peut faire toutes sortes de manipulations qui prendraient beaucoup de computation et de mémoire autrement.
Par exemple:
m = np.arange(12) # = np.array(range(12))
m = m.reshape((4,3)) # réarrange une matrice 1×12 en matrice 4×3
print m
[[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]]
On peut facilement prendre des lignes se la base d'une liste ou d'un critère:
m[[1,2]]
array([[3, 4, 5], [6, 7, 8]])
m[m.sum(1)<15]
array([[0, 1, 2], [3, 4, 5]])
En passant, sum(0), c'est la somme sur l'axe 0 – la somme de chaque ligne:
m.sum(0)
array([18, 22, 26])
Autres trucs pratiques:
m.T
array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]])
np.zeros((4,5))
array([[ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.]])
On commence par faire une matrice vide:
matrice_etude = np.zeros((len(segments_etude),len(corpus_etude.features)), dtype=int)
# Par défaut, numpy fait une matrice remplie de floats. J'utilise dtype pour spécifier que ce sont des entiers
Puis on la remplit
attributs_etude = corpus_etude.features # Liste des mots dans le corpus
ln = 0
for doc in corpus_etude.documents:
for mot in doc.words.keys():
matrice_etude[ln][attributs_etude.index(mot)]=doc.words[mot] # words() rend le nombre d'occurences du mot
ln += 1
Qu'est-ce que ça donne comme matrice ?
print matrice_etude.shape # Les dimensions de la matrice
print matrice_etude.sum() # Somme de toutes les cellules = nombre total de mots dans l'œuvre (excluant les mots fonctionnels)
(930, 7419) 41037
On veut cependant enlever les mots qui n'apparaissent qu'une fois.
On détermine quels sont les index des mots qu'on veut garder:
ids = matrice_etude.sum(0)>1
print ids
[ True True False ..., True True True]
Puis on met la matruce et la liste des attributs (mots) à jour:
matrice_etude = matrice_etude.T[ids].T
attributs_etude = np.array(attributs_etude)
attributs_etude = attributs_etude[ids]
Ça nous donne une matrice rétrécie:
print matrice_etude.shape
(930, 3815)
On peut savoir quels mots sont les plus fréquents:
for ln in doc_Swann.keywords(top=20): print "%0.08f\t%s" % ln
0.01038255 swann 0.00671635 odette 0.00631067 faire 0.00468792 mme 0.00419208 dit 0.00398173 voir 0.00387655 verdurin 0.00375635 temps 0.00356102 vie 0.00333564 dire 0.00330559 air 0.00321543 grand 0.00288488 faisait 0.00280975 moment 0.00279472 chose 0.00279472 tante 0.00277970 jour 0.00271960 mère 0.00265949 disait 0.00253929 françoise
Le nombre à droite est un indice de fréquence $$x = \frac{N_{mot}}{N_{tot}}$$ où $N_{mot}$ est la fréquence du mot, et $N_{tot}$ est le nombre total de mots dans l'œuvre.
Faisons pareil avec le livre à l'étude
doc_etude = vec.Document(" ".join(mots_etude),
filter = None,
stopwords = False,
language = 'fr')
for ln in doc_etude.keywords(top=20): print "%0.08f\t%s" % ln
0.01320759 chose 0.01267149 point 0.01245218 choses 0.01194044 dieu 0.00857763 esprit 0.00804152 idée 0.00777347 nature 0.00728611 descartes 0.00687185 corps 0.00531228 étoit 0.00528791 cause 0.00458123 avoit 0.00458123 vérité 0.00453250 homme 0.00404513 raison 0.00397203 idées 0.00392329 existence 0.00363087 fort 0.00363087 âme 0.00353340 clairement
Ou si on veut l'avoir en fréquence absolue:
for ln in sorted(zip(matrice_etude.sum(0),attributs_etude), reverse=True)[:20]: print "%d\t%s" % ln
542 chose 520 point 511 choses 490 dieu 352 esprit 330 idée 319 nature 299 descartes 282 corps 218 étoit 217 cause 188 vérité 188 avoit 186 homme 166 raison 163 idées 161 existence 149 âme 149 fort 145 clairement
Déjà, on a une petite idée des thèmes qui parcourent le Discours de la méthode et Du côté de chez Swann.
Pour avoir une meilleure idée, on peut regrouper les segments semblables, et prendre les mots qui leur sont le plus associés.
classes = corpus_etude.cluster(k=10, iterations = 100)
[len(i) for i in classes ]
[45, 113, 132, 29, 69, 98, 104, 120, 128, 92]
Mais pour une raison impénétrable, pattern ne semble me donner que des clases égales, peu importe ce que je fais...
On va donc employer l'algorithme k-means de scipy:
from scipy.cluster.vq import whiten, vq, kmeans
Ça marche en deux étapes:
center, dist = kmeans(matrice_etude, 10, iter=100)
codes, distance = vq(matrice_etude, center)
La distribution:
codes_uniq = np.unique(codes) # Ne garde qu'une instance de chaque item dans la liste
print [ sum(codes==i) for i in codes_uniq ]
[142, 225, 39, 53, 45, 74, 152, 87, 44, 69]
On va faire une liste de listes – de documents, puis d'index de documents – pour faciliter les opérations qui vont suivre.
rg_idx = np.arange(len(codes))
array_docs = np.array(corpus_etude.documents)
classes_docs = [ array_docs[codes==i].tolist() for i in codes_uniq ]
classes = [ rg_idx[codes==i].tolist() for i in codes_uniq ]
Prenons la première classe... quels sont les mots qui y sont fréquents ?
freq_attributes_classe1 = matrice_etude[codes==codes_uniq[0]].sum(0)
for ln in sorted(zip(freq_attributes_classe1, attributs_etude), reverse = True)[:10]:
print "%g\t%s" % ln
315 dieu 84 choses 72 chose 69 nature 65 existence 63 point 57 idée 42 existe 40 raison 36 vérité
On peut voir ce que ça donne avec les autres...
def mots_frequents(doclist):
lsids = [ corpus_etude.documents.index(d) for d in doclist ]
return sorted(zip(matrice_etude[lsids].sum(0), attributs_etude), reverse = True)
from ipy_table import * # Pour faire un beau tableau!
tableau = []
for c in classes_docs:
ln = []
for freq, mot in mots_frequents(c)[:8]:
ln.append(("%d %s" % (freq, mot)).encode('utf-8'))
tableau.append(ln)
# Renverser le tableau pour que 1 colonne = 1 classe
tableau =np.array(tableau).T.tolist()
make_table(tableau)
315 dieu | 59 esprit | 93 étoit | 169 idée | 103 cause | 157 corps | 239 choses | 139 descartes | 74 homme | 214 chose |
84 choses | 49 point | 35 descartes | 51 dieu | 69 chose | 100 esprit | 188 point | 102 avoit | 49 descartes | 43 pense |
72 chose | 48 fort | 19 avoit | 38 chose | 58 point | 47 nature | 56 chose | 29 étoit | 18 grand | 41 nature |
69 nature | 45 choses | 14 point | 35 point | 26 dieu | 43 chose | 51 nature | 25 point | 17 esprit | 40 point |
65 existence | 43 nature | 11 nature | 31 réalité | 23 idée | 41 choses | 51 clairement | 25 hommes | 15 génie | 39 choses |
63 point | 37 vérité | 11 homme | 29 choses | 20 effet | 38 point | 47 vérité | 21 nature | 14 nature | 32 esprit |
57 idée | 37 chose | 11 choses | 25 esprit | 18 esprit | 30 âme | 47 esprit | 21 ans | 14 hommes | 29 idée |
42 existe | 34 hommes | 11 ans | 23 cause | 15 besoin | 28 dieu | 42 dieu | 19 note | 13 humain | 28 entendement |
0.0621499 dieu | 0.0115606 coeur | 0.0926475 étoit | 0.120696 idée | 0.101607 cause | 0.0787255 corps | 0.036491 choses | 0.052417 descartes | 0.0672167 homme | 0.0766394 chose |
0.0225363 existence | 0.0108457 sang | 0.0298512 descartes | 0.0435556 réalité | 0.0387848 chose | 0.0394028 esprit | 0.0260611 point | 0.04909 avoit | 0.0362153 descartes | 0.0323339 pense |
0.0160841 existe | 0.00986613 fort | 0.0206815 avoit | 0.0302075 objective | 0.0286571 efficiente | 0.0202768 parties | 0.0166163 clairement | 0.0149053 ans | 0.022368 génie | 0.0230575 entendement |
0.0155706 idée | 0.00870746 opinions | 0.0176583 ans | 0.0280861 objectivement | 0.027755 point | 0.0198746 âme | 0.0145252 distinctement | 0.0140122 philosophe | 0.0211528 grand | 0.0185284 nature |
0.0150871 nature | 0.00758576 esprit | 0.0159495 étoient | 0.0263074 dieu | 0.0239287 effet | 0.0193853 nature | 0.0124045 vérité | 0.01368 nouvelle | 0.0173099 humain | 0.0170047 pensée |
0.0136481 choses | 0.00758027 mieux | 0.0129421 problème | 0.0211714 artifice | 0.0204428 besoin | 0.0143456 distinction | 0.0123067 raisons | 0.0136583 hommes | 0.0153248 socrate | 0.0167833 dit |
0.0124759 chose | 0.00744646 hommes | 0.0129421 frère | 0.0208336 entendement | 0.0203812 idée | 0.0140549 chose | 0.010479 nature | 0.0127736 étoit | 0.0149908 hommes | 0.0163729 idée |
0.0123017 raison | 0.00743181 objets | 0.0127823 reine | 0.019942 existence | 0.0191765 réalité | 0.0131133 pied | 0.0104455 semble | 0.0123592 géométrie | 0.0132921 éloge | 0.0161207 cire |
$m$ présent | $m$ absent | |
$\in c$ | $N_{11}$ | $N_{10}$ |
$\notin c$ | $N_{01}$ | $N_{00}$ |
Le calcul du $\chi^2$ se fait ainsi: $$\chi^2(m, c, D) = N \times \frac{(N_{11}N_{00}-N_{10}N_{01})^2}{(N_{11}+N_{00})(N_{11}+N_{10})(N_{00}+N_{01})(N_{00}+N_{10})}$$ où $N$ est le nombre total de documents/segment dans $D$.
from time import time
def chi2(mot, classe, matrice):
pasclasse = np.ones(len(matrice_etude), dtype=bool)
pasclasse[classe]=False
motidx = attributs_etude.tolist().index(mot)
mat = matrice.clip(0,1)
invmat = abs(mat-1)
n11 = mat[classe].T[motidx].sum()
n10 = invmat[classe].T[motidx].sum()
n01 = mat[pasclasse].T[motidx].sum()
n00 = invmat[pasclasse].T[motidx].sum()
N = n11 + n10 + n01 + n00
r= N * ( n11 * n00 - n10 * n01)**2 / float( (n11+n00) * (n11+n10) * (n00+n01) * (n00+n10) )
return r
def liste_chi2(classe, matrice):
codes_classe = np.zeros(matrice.shape[0], dtype=int)
codes_classe[classe] += 1
codes_pasclasse = np.ones(matrice.shape[0], dtype=int)
codes_pasclasse[classe] -= 1
mat = matrice.clip(0,1)
invmat = abs(mat-1)
n11 = (codes_classe * mat.T).T.sum(0)
n10 = (codes_classe * invmat.T).T.sum(0)
n01 = (codes_pasclasse * mat.T).T.sum(0)
n00 = (codes_pasclasse * invmat.T).T.sum(0)
N = n11 + n10 + n01 + n00
r= N * ( n11 * n00 - n10 * n01)**2 / np.array( (n11+n00) * (n11+n10) * (n00+n01) * (n00+n10), dtype=float )
return sorted(zip(r, attributs_etude), reverse=True)
tableau = []
for c in classes:
ln = []
for freq, mot in liste_chi2(c,matrice_etude)[:8]:
ln.append(("%g %s" % (freq, mot)).encode('utf-8'))
tableau.append(ln)
# Renverser le tableau pour que 1 colonne = 1 classe
tableau =np.array(tableau).T.tolist()
make_table(tableau)
137.692 dieu | 80.586 dieu | 38.7471 étoit | 52.5255 idée | 42.5143 cause | 68.6737 corps | 84.7574 choses | 70.1279 descartes | 43.7338 homme | 66.9559 chose |
8.46968 existence | 37.1664 point | 4.77272 descartes | 8.15269 dieu | 26.1874 chose | 20.6065 esprit | 59.711 point | 40.4161 avoit | 24.2584 descartes | 6.25413 pense |
6.08514 descartes | 29.5879 chose | 4.19009 dieu | 3.22636 réalité | 7.54916 point | 6.68858 nature | 7.98626 descartes | 24.5662 choses | 11.9061 chose | 3.53408 descartes |
3.71882 corps | 27.1175 choses | 3.76914 choses | 2.98476 objective | 3.17051 effet | 4.0287 descartes | 4.4949 étoit | 20.974 chose | 11.084 choses | 2.39268 entendement |
3.32446 avoit | 9.281 idée | 2.67303 chose | 2.74022 idées | 1.94429 descartes | 1.87218 parties | 4.27673 idée | 12.5975 dieu | 6.28311 dieu | 2.23615 dit |
2.29922 esprit | 7.66803 nature | 2.58239 avoit | 2.70216 perfection | 1.87155 choses | 1.82339 exemple | 2.56816 avoit | 6.60385 point | 5.37068 point | 1.6332 idée |
2.08301 existe | 3.83996 descartes | 1.78268 esprit | 2.5669 objectivement | 1.78295 efficiente | 1.48287 chose | 2.55006 distinctement | 3.82534 étoit | 3.5636 grand | 1.46004 nature |
2.05172 choses | 3.50631 existence | 1.16948 idée | 1.58666 existence | 1.72687 raison | 1.41902 âme | 2.55001 clairement | 2.97891 hommes | 3.19829 génie | 1.31212 raison |