Text mining
en langage python
L'essentiel de cette page
Dans cet exemple, on va ouvrir des commentaires qui ont été laissés sur ce site internet et mon autre site internet "Aide à l'utilisation de R". On va tenter d'en extraire du sens et d'avoir un regard d'ensemble sur ces commentaires.
Le textmining se fait avec gensim, le module python dédié au text mining (l'équivalent de quanteda sous R).
Wordcloud
Graphique de relations sémantiques
Modules de base nécessaires :
os (gestion des fichiers)
easygui (ouverture simplifiée des fichiers avec une fenêtre d'exploration)
pandas (gestion des tableaux)
numpy (gestion des matrices et nombres)
string (gestion des caractères)
unicodedata (gestion des caractères spéciaux)
stop_words (dictionnaires des mots banaux du français "vides de sens"
gensim (dédié au text-mining)
matplotlib (graphique)
WordCloud (pour les nuages de mots)
networkx (pour réaliser des graphiques en réseaux, théorie des graphs, igraph)
Exemple à taper dans la console windows pour installer gensim et stopwords
python -m pip install --user gensim stop-words WordCloud
1- Ouvrir un corpus
1.1- Charger un corpus
Le fichier qui me sert d'exemple est téléchargeable ici.
import os as os
os.chdir("C:/Users/XXXXXXXXX/Text_Mining") # Répertoire source : remplacer \ par /
print("Current Working Directory " , os.getcwd())
# Ouverture des commentaires au format excel
import easygui
import pandas as pd
df1 = pd.read_excel(easygui.fileopenbox()) # On peut remplacer easygui.fileopenbox() par le nom du fichier.
print(df1.head(3)) # en-tête du tableau
colnames = list(df1.columns) # Noms des colonnes
ncol = len(df1.columns) # Nombre de colonnes
nrow = len(df1.index) # Nombre de lignes
for i in range(ncol) : # Afficher proprement les titres des colonnes
print(i,colnames[i])
colnames[1] = "Commentaires"; df1.columns = colnames # Renommer la 2ème colonne
1.2- Nettoyer le corpus par la suppression des caractères non-souhaités
import string # pour charger une librairie de ponctuation (string.punctuation)
import unicodedata # pour remplacer tous les caractères accentués
corpus = df1.iloc[:,1]
for i in range(len(corpus)) :
tmp = str(corpus[i])
tmp = tmp.lower() # Supprimer les majuscules
tmp = tmp.replace("'"," ") # Remplacer apostrophe par une espace
tmp = tmp.replace("\n"," ") # Supprimer les retours chariot
tmp = tmp.translate(str.maketrans("","", string.punctuation)) # Supprimer la ponctuation
tmp = tmp.encode('utf-8').decode('utf-8') # Éliminations des caractères spéciaux et accentués
tmp = unicodedata.normalize('NFD', tmp).encode('ascii', 'ignore')
tmp = str(tmp)[2:-1] # Supprimer le préfixe b' qui apparaît lors de la conversion
corpus[i] = tmp
print(corpus)
2- Réaliser des listes de mots à ne pas garder qui serviront à filtrer le corpus (optionnel)
2.1- Récupérer la fréquence relative de chaque mot
Ce code s'inspire de l'étude de mails de Sarah Palin. Code à copier-coller.
import re
expr = re.compile("\W+",re.U) # Expression régulière de segmentation sur les espaces
# 1) Identifier la fréquence relative de chaque mot
words_dico=dict()
for text in corpus: # Pour chaque commentaire du corpus
text = str(text)
text = expr.split(text)
for word in set(text): # Récupération de chaque nouveau mot
if word not in words_dico:
words_dico[word]=1
else: # Pour chaque mot déjà listé : ajouter 1 si on le retrouve
words_dico[word]=words_dico[word]+1
#print(words_dico)
# 2) Conversion du dictionnaire words_dico en list pour tri
words_freq = list()
for key, val in words_dico.items():
words_freq.append( (key, val) )
words_freq.sort(key=lambda tup: tup[1] ,reverse=True)
print("Le mot le plus fréquent : ",words_freq[0])
2.2- Utiliser la fréquence des mots pour définir une liste de ceux trop rares ou trop courants
1) Les mots trop banaux partagés entre commentaires
Supprimer les mots trop rares partagés entre moins de 5 (filtre1) commentaires
filtre1 = 5
words_to_delete1= [t[0] for t in words_freq if t[1]<filtre1]
print(words_to_delete1) # Mots qui seront éliminés
2) Les mots à très haute fréquence tous mails confondus
Supprimer les mots à une fréquence de 20 ou plus (filtre2).
filtre2 = 20
words_to_delete2=[t[0] for t in words_freq[:filtre2]]
print(words_to_delete2)
3) Fusion des listes
words_to_delete=words_to_delete1+words_to_delete2
2.3- Charger une liste de stopwords (mots banaux de la langue française)
# https://pypi.org/project/stop-words/
from stop_words import get_stop_words
stopwords = get_stop_words('fr')
print(stopwords)
Remarque, on peut récupérer d'autres listes de stopwords dans le module ntlk.
Il peut être pertinent d'aller chercher plusieurs listes de stopwords et de les fusionner pour avoir un dictionnaire plus complet.
2.4- Créer manuellement son propre dictionnaire de mots blancs à éliminer
On notera dans cet exemple que je charge manuellement des mots déjà nettoyés de leurs accents et majuscules.
Je pourrais aussi bien charger un dictionnaire mais il faudrait le nettoyer comme j'ai nettoyé le corpus.
# Dictionnaire perso
stopwords1 = set(["ai","avais","a","as","avons","est","etait","ete","la","j","d","l",
"peu","en","ce","au","vu","faire","pour","une","nan","de","et","nous","que","si","le","il","ma","vous","y","c","des","on","un","les","je","ne","pas","ces","m","qu","fallut","ou","sur","du","fais","me","fait","fur","mais","cela","pr","avait","mis","plus","tous","part","sinon","tout","sont","sans","an","qui","cest","cas","par","memes","meme",
"sous","aurais","malgre","etaient","vraiment","donc","votre","plutot","passe",
"n","avoir","aussi","chose","assez","trop","moins","mieux","beaucoup","grace","cette","vrai","voir","choses","trouve","journee","appris","pense","bien","bonjour"])
2.5- Regrouper les listes de son choix pour définir sa liste perso de stopwords
On peut fusionner toutes ces listes (ou pas !)
stopwords = set(stopwords)|set(words_to_delete)|set(stopwords1)
print(stopwords)
2.6- Créer un dictionnaire des synonymes pour regrouper tous les termes proches selon un même terme
On peut fusionner toutes ces listes (ou pas !)
Dico = {'manipulations':"manipulation", 'manipulation':"manipulation",
'manip':'manipulation', 'manipe':'manipulation',
'manipule':'manipulation','bonne':'compliment','bravo':'compliment','merci':'compliment','graphe':'graphique'}
Voir la partie 4 - ci-dessous sur l'application de ce dictionnaire.
3- Nuages de mots
from wordcloud import WordCloud
On initialise le wordcloud et on définit la liste qui va servir à faire le nettoyage (on aurait pu utiliser d'autres listes de la partie 2 ou toutes fusionnées)
wordcloud = WordCloud(stopwords=stopwords, background_color="white")
Fusionner toutes les cellules du corpus, cette étape ne serait pas nécessaire si on ne travaillait que sur un commentaire.
text = " ".join(corpus)
Tracer le wordcloud :
wordcloud.generate(text)
import matplotlib.pyplot as plt
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
4- Hacher le corpus mot par mot pour mettre en place un token
Si je veux tokeniser (faire un token) sur un seul document
[tok for tok in txt.split()] # token sur un seul doc
Si je veux tokeniser un ensemble de documents (comme plusieurs commentaires)
toks = [[tok for tok in txt.split()] for txt in precorpus] # token sur doc multiples
Si je veux tokeniser mon ensemble en éliminant les mots de mes stopwords (partie 2)
toks = [[tok for tok in txt.split() if tok not in stopwords] for txt in precorpus]
print(toks)
Nettoyer le token avec le dictionnaire des synonymes (cf. partie 2.6- ci-dessus pour la construction du dictionnaire Dico) : exemple pour que les déclinaison d'un même mot au pluriel et au singulier soient reconnus comme un même :
for i in range(len(toks)) : # pour chaque commentaire
if len(toks[i])>0 : # et si le commentaire n'est pas vide
for k in range(len(toks[i])) : # pour chaque mot du commentaire
for j in range(len(Dico)) :
temp = list(Dico.keys())[j]
temp2 = toks[i][k]
if temp2 == temp :
toks[i][k] = list(Dico.values())[j]
5- Etudier la fréquence de chaque mot en faisant une DTM
DTM (Document-term Matrix) :
from gensim import corpora
dic = corpora.Dictionary(toks) # dictionnaire de tous les mots restant dans le token
# Equivalent (ou presque) de la DTM : DFM, Document Feature Matrix
dfm = [dic.doc2bow(tok) for tok in toks]
print(dfm)
Afficher les mots contenus dans le dictionnaire :
mes_labels = [k for k, v in dic.token2id.items()]
print(mes_labels)
Ou encore :
print(dic.token2id) # Les mots
print(dic.cfs) # Les fréquences
dic.cfs.values()
6- Matrice adjacente des cooccurrences dans les commentaires
6.1- Matrice de cooccurrences
from gensim.matutils import corpus2csc
term_matrice = corpus2csc(dfm)
print(term_matrice)
# Transposée de la matrice pour établir les cooccurrences
import numpy as np
term_matrice = np.dot(term_matrice, term_matrice.T)
6.2- Représentation en réseau
Pour la représentation, il existe deux modules "igraph" et "networkx".
igraph dysfonctionne très facilement à cause de son besoin d'utiliser cairo. Aussi, mieux vaut utiliser networkx qui marche avec matplotlib.
Compiler le igraph
import networkx as nx
print(term_matrice.todense())
G = nx.from_scipy_sparse_matrix(term_matrice)
G.add_nodes = dic
pos=nx.spring_layout(G) # position des nodes
print(G[1][1])
Tracer
nx.draw_networkx_nodes(G,pos, dic,
node_color='r',
node_size=500,
alpha=0.8)
nx.draw_networkx_edges(G,pos,width=1.0,alpha=0.5)
nx.draw_networkx_labels(G,pos,dic,font_size=8)
#draw_networkx_nodes(G, pos[, nodelist, …])
import matplotlib.pyplot as plt
plt.show()
Seulement 8 mots ont été conservés lors du nettoyage.
Ils permettent de voir que les pages d'aide sont les éléments centraux de mes deux sites.
Les gens qui me contactent s'intéressent globalement aux graphiques, données, fonctions et viennent à cause d'erreur et pour chercher une aide utile.
Une vision d'ensemble sur 78 commentaires.
En projet :
Explorer le module très performant de text mining : the Natural Language Toolkit (ntlk)
Trouver une alternative python de textstat_keyness de quanteda sous R pour identifier les mots clefs d'un document par comparaison avec d'autre documents.