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()

Pour aller plus loin sur les Wordcloud.

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.