Avant-propos
Le traitement du langage naturel (mieux connu sous l’acronyme anglais de NLP) permet d’analyser des discours, des résolutions, des procès-verbaux, des éditoriaux, des courriels, des textos, etc. Il permet aussi d’analyser des verbatims (les résultats de transcriptions d’émissions de radio ou de comparutions par exemple).
Préparation
Installons les fonctions pour le forage de textes (forer un texte consiste à en extraire des termes d’un texte comme on extrait du minerai de la roche).
library(tm)
library(RCurl)
library(RColorBrewer)
library(wordcloud)
library(XML)
library(xml2)Chargement
Récupérons les textes qui constitueront le corpus.
Il s’agit de deux discours du trône de l’Assemblée Nationale du Québec, donc de deux premiers ministres. Le premier texte, celui de Philippe Couillard, provient du site La Société du patrimoine politique du Québec, alors que le second texte, celui de Pauline Marois, provient du site du gouvernement du Québec.
# Récupérer des texte du Web
doc1 <- getURL("http://dimension.usherbrooke.ca/voute/discours/2014Couillard.html")
doc2 <- getURL('http://dimension.usherbrooke.ca/voute/discours/2012Marois.html')Prétraitement
Il y a un problème.
Comme le minerai, il faut raffiner l’extraction brute, car le texte html comporte des déchets. Bien qu’il existe plusieurs degrés de raffinage, nous n’allons qu’utiliser un raffinage superficiel.
Il existe une fonction personnalisée que l’on peut récupérer d’un dépositaire GitHub.
# Nettoyer le html avec une fonction
htmlToText = function(input, ...) {
require(RCurl)
require(XML)
evaluate_input <- function(input) {
if(file.exists(input)) {
char.vec <- readLines(input, warn = FALSE)
return(paste(char.vec, collapse = ""))
}
if(grepl("", input, fixed = TRUE)) return(input)
if(!grepl(" ", input)) {
#if(!file.exists("cacert.perm")) download.file(url="http://curl.haxx.se/ca/cacert.pem", destfile="cacert.perm")
#return(getURL(input, followlocation = TRUE, cainfo = "cacert.perm"))
}
return(NULL)
}
convert_html_to_text = function(html) {
doc <- htmlParse(html, asText = TRUE, encoding="Latin-1")
text <- xpathSApply(doc, "//text()[not(ancestor::script)][not(ancestor::style)][not(ancestor::noscript)][not(ancestor::form)]", xmlValue)
return(text)
}
collapse_text = function(txt) {
return(paste(txt, collapse = " "))
}
html.list <- lapply(input, evaluate_input)
text.list <- lapply(html.list, convert_html_to_text)
text.vector <- sapply(text.list, collapse_text)
return(text.vector)
} Exécutons le raffinage simple sur les textes bruts.
# Nettoyer
txtdoc1 <- htmlToText(doc1)
txtdoc2 <- htmlToText(doc2)Idéalement, on sauvegarde les textes nettoyés à l’aide de la fonction write. On archive tous les fichiers du dossier, txt et Rmd, ensemble (nous avons préalablement précisé le répertoire de travail avec setwd).
# Copier dans sur l'ordi, dans un sous-répertoire du répertoire de travail (créer ce sous-répertoire avant d'exécuter les commandes)
write(txtdoc1, file = "textes/2014Couillard.txt")
write(txtdoc2, file = "textes/2012Marois.txt")Ensuite, on peut recharger les fichiers txt avec scan ou read.ftable (fonctions idéales pour des fichiers txt ou plats (flat files)).
scan("textes/2014Couillard.txt", skip = 1, nlines = 10, what = "raw")## [1] "Monsieur" "le" "Président,"
## [4] "Monsieur" "le" "Chef"
## [7] "de" "l’opposition" "officielle,"
## [10] "Monsieur" "le" "Chef"
## [13] "du" "deuxième" "groupe"
## [16] "de" "l’opposition," "Mesdames,"
## [19] "Monsieur," "représentants" "de"
## [22] "Québec" "solidaire," "Chers"
## [25] "collègues" "parlementaires," "Distingués"
## [28] "invités" "dans" "les"
## [31] "tribunes," "Chères" "Québécoises,"
## [34] "chers" "Québécois," "Monsieur"
## [37] "le" "Président,"
Les deux textes ont été filtrés et les termes ont été segmentés. Le travail d’analyse peut commencer.
qdapCette section 2 est générée sur Windows, car Linux ne permet pas le chargement du package qdap.
library(qdap)
library(qdapTools)
texte <- scan("textes/2014Couillard.txt", skip = 1, nlines = 10, what = "raw", encoding = "UTF-8")
# Print and plot text
frequent_terms <- freq_terms(texte, top = 20, stopwords = tm::stopwords("french"))
plot(frequent_terms)Le package qdap est très sommaire. Il permet un aperçu, une exploration. Il se limite à des analyses de fréquences simples sur un texte ou des documents.
La prochaine approche permet d’analyser un corpus de documents et de pousser plus loin l’exploration de texte et la visualisation des résultats.
tmCréation de la matrice du corpus
Qu’il s’agisse de deux longs documents (des livres par exemple) ou de milliers de courriels ou de millions de textos, la procédure de création est la même.
Sur Windows.
# Garder les parenthèses
# Garder paramètre encoding
( base <- Corpus(DirSource("textes/", encoding = "UTF-8"), readerControl = list(language = "french", load = TRUE)) )
dtm <- DocumentTermMatrix(base)
dtmSur Linux.
# Enlever paramètre encoding
( base <- Corpus(DirSource("textes/"), readerControl = list(language = "french", load = TRUE)) )
dtm <- DocumentTermMatrix(base)
dtm## <<SimpleCorpus>>
## Metadata: corpus specific: 1, document level (indexed): 0
## Content: documents: 2
## <<DocumentTermMatrix (documents: 2, terms: 3493)>>
## Non-/sparse entries: 4405/2581
## Sparsity : 37%
## Maximal term length: 21
## Weighting : term frequency (tf)
À noter qu’un corpus peut avoir deux formes:
Voice la taille de la matrice de corpus (ligne x colonne).
dim(dtm)## [1] 2 3493
Analysons le contenu de la matrice du corpus. Voici quelques fonctions.
# document 1
inspect(dtm[1,])## <<DocumentTermMatrix (documents: 1, terms: 3493)>>
## Non-/sparse entries: 2009/1484
## Sparsity : 42%
## Maximal term length: 21
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs dans des gouvernement les notre nous pour que sur une
## 2012Marois.txt 74 174 91 187 75 184 104 84 62 70
# document 2
inspect(dtm[2,])## <<DocumentTermMatrix (documents: 1, terms: 3493)>>
## Non-/sparse entries: 2396/1097
## Sparsity : 31%
## Maximal term length: 21
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs dans des les nos notre nous pour que qui une
## 2014Couillard.txt 79 192 221 71 114 218 81 99 106 57
# document 1, 10 premiers termes
inspect(dtm[1,1:10])## <<DocumentTermMatrix (documents: 1, terms: 10)>>
## Non-/sparse entries: 10/0
## Sparsity : 0%
## Maximal term length: 4
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs 000 170 1812 1990 1er 2000 2001 2002 2011 2012
## 2012Marois.txt 1 1 1 1 1 1 1 1 1 1
# 500e terme
inspect(dtm[,500])## <<DocumentTermMatrix (documents: 2, terms: 1)>>
## Non-/sparse entries: 1/1
## Sparsity : 50%
## Maximal term length: 11
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs déclaration
## 2012Marois.txt 1
## 2014Couillard.txt 0
# Éviter
#inspect(base[2]) # document 2
# les termes les moins fréquents
findFreqTerms(dtm, lowfreq = 200)## [1] "des" "les" "nous"
# idem, dans le document 1
findFreqTerms(dtm[1,], lowfreq = 100)## [1] "des" "les" "nous" "pour"
Transformons la matrice du corpus en une matrice simple (une matrice comme en algèbre, à l’exception que le contenu n’est pas numérique). Examinons cette matrice simple.
# Imprimer la classe
class(dtm)## [1] "DocumentTermMatrix" "simple_triplet_matrix"
# Dimensions
dim(dtm)## [1] 2 3493
# Transformer
dtm2 <- inspect(dtm)## <<DocumentTermMatrix (documents: 2, terms: 3493)>>
## Non-/sparse entries: 4405/2581
## Sparsity : 37%
## Maximal term length: 21
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs dans des gouvernement les notre nous pour que qui une
## 2012Marois.txt 74 174 91 187 75 184 104 84 60 70
## 2014Couillard.txt 79 192 38 221 114 218 81 99 106 57
# Imprimer la classe
class(dtm2)## [1] "matrix"
# Dimensions
dim(dtm2)## [1] 2 10
# Calculer le nombre de mots (la somme des lignes sur chaque colonne)
nbmots <- rowSums(dtm2)
nbmots## 2012Marois.txt 2014Couillard.txt
## 1103 1205
Traçons les résultats sur un graphique.
# Changer les paramètres
par(mfrow = c(1,1), oma = c(0,.5,.5,.5))
# Tracer
dotchart(nbmots, row.names(dtm), main = "Nombre de mots dans les discours")
abline(v = median(nbmots), lty = 1, lwd = 2, col = 'blue3')
abline(v = mean(nbmots), lty = 2, lwd = 2, col = 'red3')
legend("bottomright", inset = 0.02, c("Médiane", "Moyenne"), lty = c(1,2), lwd = 2, col = c('blue3', 'red3'))# Rechanger les paramètres
par(mfrow = c(1,1), oma = c(0,0,0,0))Évidemment, avec plus de textes (pas seulement deux), ce graphique devient plus intéressant.
Filtres
Nous pouvons raffiner le corpus:
Opération de filtrage
En partant du corpus base, nous reprenons la procédure de création de la matrice du corpus (juste avant l’étape dtm <- DocumentTermMatrix(base)), mais avec des étapes supplémentaires de raffinage.
# Transformation complète
base2 <- tm_map(base, content_transformer(tolower)) # minuscule
base2 <- tm_map(base2,content_transformer(removePunctuation)) # ponctuation
base2 <- tm_map(base2,content_transformer(removeNumbers)) # nombres
base2 <- tm_map(base2,content_transformer(stripWhitespace)) # espaces
base2 <- tm_map(base2,content_transformer(removeWords), stopwords("french")) # stopwords
motssupprimes <- c("président", "qui", "est", "par", "sont", "pour", "cette", "que", "des", "comme", "les", "cest", "avec", "aussi", "allons", "bien", "plus", "tous", "ailleurs", "elles", "ils", "parce", "encore", "être", "entre", "faut", "très", "ainsi", "certains", "certaines", "the", "autres", "autre", "hui", "ans", "celles", "celle", "quelque", "quelques", "chaque", "depuis", "beaucoup", "celui", "lorsqu", "fois", "donc", "chez", "rien", "mal", "bien", "prendre", "dès", "face", "dit", "fois", "pourtant", "face", "dire", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", "dix", "plusieurs", "chacun", "chacune", "car", "sous", "sur", "vers", "quoi", "grâce", "non", "oui", "cependant", "avoir", "ceux", "surtout", "toute", "toutes", "tout", "uvre", "sorte", "premier", "dernier", "moins", "plus", "dont", "monsieur")
base2 <- tm_map(base2,content_transformer(removeWords), motssupprimes) # mots choisisCréation de la deuxième matrice du corpus
Au lieu de base, nous travaillons avec base2.
Créons et inspectons la matrice du corpus (assurance-qualité).
# Créer une matrice de corpus
dtm2 <- DocumentTermMatrix(base2)
dtm2## <<DocumentTermMatrix (documents: 2, terms: 3284)>>
## Non-/sparse entries: 4073/2495
## Sparsity : 38%
## Maximal term length: 25
## Weighting : term frequency (tf)
dim(dtm2)## [1] 2 3284
# A/Q
findFreqTerms(dtm2, lowfreq = 100)## [1] "gouvernement" "québec"
# Créer une matrice simple
dtm2_2 <- inspect(dtm2)## <<DocumentTermMatrix (documents: 2, terms: 3284)>>
## Non-/sparse entries: 4073/2495
## Sparsity : 38%
## Maximal term length: 25
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs développement dune faire gouvernement létat loi
## 2012Marois.txt 21 18 24 91 9 15
## 2014Couillard.txt 16 15 23 38 18 13
## Terms
## Docs ministre québec québécois services
## 2012Marois.txt 9 48 28 9
## 2014Couillard.txt 25 52 21 21
dtm2_2## Terms
## Docs développement dune faire gouvernement létat loi
## 2012Marois.txt 21 18 24 91 9 15
## 2014Couillard.txt 16 15 23 38 18 13
## Terms
## Docs ministre québec québécois services
## 2012Marois.txt 9 48 28 9
## 2014Couillard.txt 25 52 21 21
dim(dtm2_2)## [1] 2 10
# Calculer le nombre de mots
nbmots2_2 <- rowSums(dtm2_2)
nbmots2_2## 2012Marois.txt 2014Couillard.txt
## 272 242
Nous obtenons dtm2_2; plus épurée que dtm2.
Créons des nuages de mots. Les mots en gros caractères sont les plus fréquents.
Cette fois, nous allons extraires les documents de base2 pour en faire deux matrices de corpus indépendantes: dtmUnText1 et dtmUnText2. Les nuages de mots sont tirées des ces deux matrices indépendantes.
par(mfrow = c(1,2), mar = c(5,2,3,2), oma = c(1,0,1,0))
# Créer une matrice de corpus
dtmUnTexte1 <- DocumentTermMatrix(base2[1])
# Transformer en matrice simple, filtrer les mots le plus fréquents
freq1 <- sort(colSums(as.matrix(dtmUnTexte1)), decreasing = T)
# Dessiner le nuage
wordcloud(names(freq1), freq1, max.words = 20)
title("Marois \n2012", "sur 272 mots")
# ---
dtmUnTexte2 <- DocumentTermMatrix(base2[2])
freq2 <- sort(colSums(as.matrix(dtmUnTexte2)), decreasing = T)
wordcloud(names(freq2), freq2, max.words = 20)
title("Couillard \n2014", "sur 242 mots")par(mfrow = c(1,1))
par(mfrow = c(1,2), mar = c(4,2,4,2), oma = c(0,0,0,0))Lorsqu’un nuage apparait plus gros qu’un autre, il démontre non seulement la fréquence des mots les plus usités, mais la concentration du vocabulaire. Le texte de gauche est plus riche en vocabulaire et les fréquences de mots récurrents sont moins élevées. Il faut aussi relever que le texte de gauche est plus long et que les mots les plus usités sont plus dilués dans l’ensemble (pourcentage plus bas).
En couleur
Ajoutons de la couleur et quelques paramètres.
En passant, nous sauvegardons nos résultats préliminaires dans des fichers CSV. Ils nous serons utiles plus loin.
par(mfrow = c(1,2), mar = c(5,2,3,2), oma = c(1,0,1,0))
dtmUnTexte1 <- DocumentTermMatrix(base2[1])
freq1 <- sort(colSums(as.matrix(dtmUnTexte1)), decreasing = T)
wordcloud(names(freq1), freq1, max.words = 30, random.order = F, colors = brewer.pal(8, "Set1"))
# Autres thèmes de couleur...
# Dark2 8, Paired 12, Set1 9, Set2 8, Set3 12, etc.
title("Marois \n2012", "sur 272 mots")
# Sauvegarder
freq_1 <- as.matrix(freq1)
write.table(freq_1, file = "freq1.csv", sep = ";", row.names = T, append = F, col.names = F)
# ---
dtmUnTexte2 <- DocumentTermMatrix(base2[2])
freq2 <- sort(colSums(as.matrix(dtmUnTexte2)), decreasing = T)
wordcloud(names(freq2), freq2, max.words = 30, random.order = F, colors = brewer.pal(8, "Dark2"))
title("Couillard \n2014", "sur 242 mots")# Sauvegarder
freq_2 <- as.matrix(freq2)
write.table(freq_2, file = "freq2.csv", sep = ";", row.names = T, append = T, col.names = F)
par(mfrow = c(1,2), mar = c(4,2,4,2), oma = c(0,0,0,0))Les paramètres ont permis de concentrer les mots plus usités au milieu des nuages. La fréquence se reflète non seulement sur la taille des caractères, mais aussi sur la couleur des caractères.
Voici un aperçu des fichiers sauvegardés. Il s’agit d’un classement décroissant des mots et de leur fréquence.
# Charger le fichier
freq1 <- read.csv("freq1.csv", header = FALSE, sep = ";")
# Lire la tête du fichier
head(freq1)## V1 V2
## 1 gouvernement 91
## 2 québec 48
## 3 québécois 28
## 4 faire 24
## 5 développement 21
## 6 dune 18
Nous avons sauvegardé les résultats préliminaires pour les utiliser avec d’autres programmes.
Pour générer de meilleurs nuages de mots, il existe bien d’autres options comme ce site.
On peut ensuite télécharger resultat, le nuage, en format SVG et l’appliquer dans un rapport ou un site web.
Au lieu de sauvegarder les résultats en fichier CSV, il est possible de les copier-coller dans une feuille de calcul ou un traitement de texte quelconque.
# Exporter dans un objet
V1 <- as.vector(names(freq1))
V2 <- as.numeric(freq1)
freq_1 <- cbind(V1, V2)
# Ne pas sauvegarder
#write.table(freq_1, file = "freq1.csv", sep = ";", row.names = T, append = F, col.names = F)Sur Windows.
# Copier
library(questionr)
clipcopy(freq_1, digits = 3)Il ne reste qu’à coller la capture dans un document externe et formater le résultat brut.
Sur Linux.
Il faut fignoler un peu:
xclip dans un terminal: sudo apt-get install xclip.# Rouler cette fonction
clipboard <- function(x, sep = "\t", row.names = FALSE, col.names = TRUE){ con <- pipe("xclip -selection clipboard -i", open = "w")
write.table(x, con, sep=sep, row.names = row.names, col.names = col.names)
close(con)}
# Utiliser cette fonction pour copier
clipboard(freq_1)Il ne reste qu’à coller à l’extérieur et formater le tout.
Un nuage de mot c’est bien, mais nous aurions pu tracer un simple graphique à barres à propos des 10 mots les plus fréquents.
# Reprendre la matrice simple filtrant les mots le plus fréquents
# Transformer
freq1_df <- as.data.frame(freq1)
# Lire les 10 mots le plus fréquents
head(freq1_df, 10)## V1 V2
## 1 gouvernement 91
## 2 québec 48
## 3 québécois 28
## 4 faire 24
## 5 développement 21
## 6 dune 18
## 7 politique 18
## 8 également 16
## 9 loi 15
## 10 québécoise 14
par(mar = c(5,2,4,2), oma = c(4,1,0,0))
# Tracer un graphique à barres
barplot(freq1_df[1:10,2], names.arg = freq1_df[1:10,1], col = "tan", las = 2, ylim = c(0, 100))
grid()# las = 0, parallèle
# las = 2, perpendiculaire
par(mar = c(4,2,4,2), oma = c(0,0,0,0))Cette solution est simple, mais elle révèle beaucoup plus en ordonnant les résultats.
Répertoire
Répertorions les mots selon certaines catégories. Il faut d’abord construire un dictionnaire thématique.
dictionnaire <- c("québec", "québécois", "québécoises", "canada", "canadien", "canadiens", "canadiennes")
dtmDictionnaire <- DocumentTermMatrix(base2,list(dictionary = dictionnaire))
inspect(dtmDictionnaire)## <<DocumentTermMatrix (documents: 2, terms: 7)>>
## Non-/sparse entries: 9/5
## Sparsity : 36%
## Maximal term length: 11
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs canada canadien canadiennes canadiens québec québécois
## 2012Marois.txt 3 0 0 0 2 48
## 2014Couillard.txt 10 4 0 1 0 52
## Terms
## Docs québécoises
## 2012Marois.txt 28
## 2014Couillard.txt 21
dtmDictionnaireP <- DocumentTermMatrix(base2,list(dictionary = dictionnaire)) / nbmots2_2
inspect(dtmDictionnaireP)## <<DocumentTermMatrix (documents: 2, terms: 7)>>
## Non-/sparse entries: 9/5
## Sparsity : 36%
## Maximal term length: 11
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs canada canadien canadiennes canadiens
## 2012Marois.txt 0.01102941 0.00000000 0 0.000000000
## 2014Couillard.txt 0.04132231 0.01652893 0 0.004132231
## Terms
## Docs québec québécois québécoises
## 2012Marois.txt 0.007352941 0.1764706 0.10294118
## 2014Couillard.txt 0.000000000 0.2148760 0.08677686
dtmDictionnairePDF <- data.frame(inspect(dtmDictionnaireP))## <<DocumentTermMatrix (documents: 2, terms: 7)>>
## Non-/sparse entries: 9/5
## Sparsity : 36%
## Maximal term length: 11
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs canada canadien canadiennes canadiens
## 2012Marois.txt 0.01102941 0.00000000 0 0.000000000
## 2014Couillard.txt 0.04132231 0.01652893 0 0.004132231
## Terms
## Docs québec québécois québécoises
## 2012Marois.txt 0.007352941 0.1764706 0.10294118
## 2014Couillard.txt 0.000000000 0.2148760 0.08677686
dtmDictionnaireDF = data.frame(inspect(dtmDictionnaire))## <<DocumentTermMatrix (documents: 2, terms: 7)>>
## Non-/sparse entries: 9/5
## Sparsity : 36%
## Maximal term length: 11
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs canada canadien canadiennes canadiens québec québécois
## 2012Marois.txt 3 0 0 0 2 48
## 2014Couillard.txt 10 4 0 1 0 52
## Terms
## Docs québécoises
## 2012Marois.txt 28
## 2014Couillard.txt 21
Tout ce code est bien lourd et ne nous dit rien…
Visualisation
En fonction de mots clés, nous obtenons un décompte…
par(mfrow = c(2, 1), mar = c(4,5,4,2), oma = c(0,4,1,0), las = 2)
# 1
qc <- dtmDictionnaireDF$québécoise + dtmDictionnaireDF$québécoises + dtmDictionnaireDF$québécois + dtmDictionnaireDF$québec
barplot((qc), horiz = T, names.arg = (row.names(dtmDictionnaireDF)), xlim = c(0,200))
title(main = list("Fréquences absolues\n du mot Québec et ses déclinaisons", cex = 1))
# 2
qc <- dtmDictionnaireDF$canadienne + dtmDictionnaireDF$canadiennes + dtmDictionnaireDF$canadiens + dtmDictionnaireDF$canadien + dtmDictionnaireDF$canada
barplot((qc), horiz = T, names.arg = (row.names(dtmDictionnaireDF)), xlim = c(0,20))
title(main = list("Fréquences absolues\n du mot Canada et ses déclinaisons", cex = 1))…ou un ratio.
par(mfrow = c(2, 1), mar = c(4,5,4,2), oma = c(0,4,1,0), las = 2)
qc <- dtmDictionnairePDF$québécoise + dtmDictionnairePDF$québécoises + dtmDictionnairePDF$québécois + dtmDictionnairePDF$québec
barplot((qc), horiz = T, names.arg = (row.names(dtmDictionnairePDF)), xlim = c(0,0.5))
title(main = list("Fréquences pondérées\n du mot Québec et ses déclinaisons", cex = 1))
qc <- dtmDictionnairePDF$canadienne + dtmDictionnairePDF$canadiennes + dtmDictionnairePDF$canadiens + dtmDictionnairePDF$canadien + dtmDictionnairePDF$canada
barplot((qc), horiz = T, names.arg = (row.names(dtmDictionnairePDF)), xlim = c(0,0.5))
title(main = list("Fréquences pondérées\n du mot Canada et ses déclinaisons", cex = 1))par(mfrow = c(1, 1), mar = c(4,2,4,2), oma = c(0,0,0,0), las = 0)Croisons les documents pour retrouver des mots communs et différents: commonality.cloud et comparison.cloud. Ces fonctions sont graphiques.
D’abord, il faut générer une matrice de corpus inversée (TDM plutôt que DTM).
tdm3 <- TermDocumentMatrix(base2)Ensuite nous transformons cette matrice de corpus en matrice simple et nous trançons les graphiques.
par(mfrow = c(1,2), mar = c(4,2,3,2), oma = c(0,0,0,0))
tdm3_m <- as.matrix(tdm3)
# Changer le nom des colonnes (facultatif)
#colnames(tdm3_m) <- c("A", "B")
par(mfrow = c(1, 2))
# Commun
commonality.cloud(tdm3_m, colors = "steelblue1", max.words = 10, random.order = FALSE)
title("Mots les plus communs \ndans les documents")
# ---
# Différent
comparison.cloud(tdm3_m, colors = "steelblue1", max.words = 10, random.order = FALSE, scale = c(1,2), title.size = 1.5)
title("Mots différents \ndans les documents")par(mfrow = c(1,1), mar = c(4,2,4,2), oma = c(0,0,0,0))La visualisation par nuage donne une idée d’ensemble, mais c’est une visualisation floue.
Si l’on veut mieux comparer (les mots communs ou différents), il faut un graphique qui ressemble au graphique à barres.
par(mfrow = c(2,1), mar = c(4,5,4,2), oma = c(0,2,0,0))
barplot(freq1_df[1:10,2], names.arg = freq1_df[1:10,1], col = "tan", las = 2, ylim = c(0, 100))
grid()
barplot(freq1_df[1:10,2], names.arg = freq1_df[1:10,1], col = "tan", las = 2, xlim = c(0, 100), horiz = TRUE, las = 1)
grid()par(mfrow = c(1,1), mar = c(4,2,4,2), oma = c(0,0,0,0))Le pyramid.plot() du package plotrix permet de comparer du graphiques à barres horizontales.
La matrice simple (dérivée de la matrice du corpus) comporte des cellules vides. Extrayons uniquement les cellules non nulles communes aux deux textes (car un texte peut comporter un mot qu’un autre texte ne possède pas). Nous appliquons la double contrainte (non nul, commun) avec le & (AND).
meme_mots <- subset(tdm3_m, tdm3_m[, 1] > 0 & tdm3_m[, 2] > 0)
head(meme_mots)## Docs
## Terms 2012Marois.txt 2014Couillard.txt
## accommodements 1 1
## accès 5 2
## accélérer 5 2
## action 1 1
## adopter 1 1
## affaires 1 3
Il faut ensuite ordonner les mots en ordre décroissant. Créons d’abord une nouvelle colonne: la moyenne de fréquence par ligne des deux colonnes.
meme_mots_ <- cbind(meme_mots, apply(meme_mots[,1:2], 1, mean))
head(meme_mots_)## 2012Marois.txt 2014Couillard.txt
## accommodements 1 1 1.0
## accès 5 2 3.5
## accélérer 5 2 3.5
## action 1 1 1.0
## adopter 1 1 1.0
## affaires 1 3 2.0
meme_mots_ <- meme_mots_[order(meme_mots_[, 3], decreasing = TRUE), ]
head(meme_mots_)## 2012Marois.txt 2014Couillard.txt
## gouvernement 91 38 64.5
## québec 48 52 50.0
## québécois 28 21 24.5
## faire 24 23 23.5
## développement 21 16 18.5
## ministre 9 25 17.0
Créons un palmarès des 20 premiers mots.
top_mots <- data.frame(
x = meme_mots_[1:20, 1],
y = meme_mots_[1:20, 2],
labels = rownames(meme_mots_[1:20, ])
)
head(top_mots)## x y labels
## gouvernement 91 38 gouvernement
## québec 48 52 québec
## québécois 28 21 québécois
## faire 24 23 faire
## développement 21 16 développement
## ministre 9 25 ministre
Traçons le graphique pyramide.
library(plotrix)
pyramid.plot(top_mots$x, top_mots$y, labels = top_mots$labels, gap = 30, top.labels = c("2012Marois", "terme", "2014Couillard"), main = "Mots en commun", laxlab = NULL, raxlab = NULL, unit = NULL)## [1] 5.1 4.1 4.1 2.1
Un dendrogramme permet d’observer les associations de mots.
D’abord, il faut nettoyer la matrice du corpus. Créons un corpus plus dense.
Nous l’ordonnons.
Puis, nous calculons une distance ‘euclidienne’. C’est un principe de géométrie.
Dans une immense matrice (un plan donc), on cherche à connaitre la ‘distance’ entre chaque mot: mesurée avec la fréquence de chaque paire de mots ou bigrammes. Une paire de mots représente deux mots qui se suivent dans les textes. Au lieu d’analyser des mots uniques, on analyse des bigrammes. Les paires de mots les plus fréquentes sont les bigrammes les plus fréquents.
On pourrait répéter l’exercice pour des trigrammes et plus.
# Créer
dense_tdm3 <- removeSparseTerms(tdm3, sparse = 0.9) # sparsity remains high
dense_tdm3 <- removeSparseTerms(tdm3, sparse = 0.1) # picky, sparsity decreases
# Simplifions
dense_tdm3_m <- as.matrix(dense_tdm3)
# Fréquences non nulles et mots communs dans les documents
meme_mots <- subset(dense_tdm3_m, dense_tdm3_m[, 1] > 0 & dense_tdm3_m[, 2] > 0)
head(meme_mots)## Docs
## Terms 2012Marois.txt 2014Couillard.txt
## accommodements 1 1
## accès 5 2
## accélérer 5 2
## action 1 1
## adopter 1 1
## affaires 1 3
# Ordonner en ordre décroissant
meme_mots_ <- cbind(meme_mots, apply(meme_mots[,1:2], 1, mean))
head(meme_mots_)## 2012Marois.txt 2014Couillard.txt
## accommodements 1 1 1.0
## accès 5 2 3.5
## accélérer 5 2 3.5
## action 1 1 1.0
## adopter 1 1 1.0
## affaires 1 3 2.0
meme_mots_ <- meme_mots[order(meme_mots_[, 3], decreasing = TRUE), ]
head(meme_mots_)## Docs
## Terms 2012Marois.txt 2014Couillard.txt
## gouvernement 91 38
## québec 48 52
## québécois 28 21
## faire 24 23
## développement 21 16
## ministre 9 25
top_mots <- data.frame(
x1 = meme_mots_[1:20, 1],
x2 = meme_mots_[1:20, 2])
head(top_mots)## x1 x2
## gouvernement 91 38
## québec 48 52
## québécois 28 21
## faire 24 23
## développement 21 16
## ministre 9 25
# Créer un d.f
top_mots_df <- as.data.frame(top_mots)
# Calculer la distance euclidienne
top_mots_dist <- dist(top_mots_df)
# Créer des grappes
hc <- hclust(top_mots_dist)
# Transformer en dendrogramme
hcd <- as.dendrogram(hc)
# Ajouter des étiquettes de champs
labels(hcd)## [1] "ministre" "services" "létat" "projet"
## [5] "santé" "travail" "loi" "doit"
## [9] "léconomie" "place" "projets" "québécois"
## [13] "faire" "développement" "dune" "politique"
## [17] "également" "société" "gouvernement" "québec"
par(mar = c(8,2,4,2), oma = c(0,1,0,0))
# Tracer le dendrogramme
plot(hcd, main = "Dendrogramme 1")par(mar = c(4,2,4,2), oma = c(0,0,0,0))Il faut lire que la paire ‘gouvernement-québec’, à droite du graphique, revient plus de 40 fois.
Ensuite, nous avons beaucoup de bigrammes peu fréquents qui reviennent moins de 20 fois. Mais on peut les regrouper en familles et remonter dans l’arbre.
On doit comprendre que le bigramme ‘ministre-services’ partage une plus grande parenté (frère-soeur) dans les textes avec le bigramme ‘santé-travail’ qu’avec le bigramme ‘québecois-faire’ (cousin-cousine).
Il est possible de constituer des grappes de bigrammes.
En couleur
Ajoutons de la couleur à un bigramme donné (“gouvernement-québec”) et des boites de division (allons-y pour 5 grappes).
library(dendextend)
hcd2 <- branches_attr_by_labels(hcd, c("gouvernement", "québec"), col = 'red3')
par(mar = c(8,2,4,2), oma = c(0,1,0,0))
plot(hcd2, main = "Dendrogramme 2")
# Ajouter 4 grappes
rect.dendrogram(hcd2, k = 5, border = 'grey50')par(mar = c(4,2,4,2), oma = c(0,0,0,0))Nous nous arrêtons ici.
Le sujet de l’analyse du langage naturel est tellement vaste qu’il est comme le contenu d’une bibliothèque: on lit les classiques, mais il est impossible de tout étudier.
en typographie et en imprimerie, le mot ‘espace’ est féminin.↩