Text Mining in R

Librairies utilisées

library(tidyverse)
library(tidytext)
library(janeaustenr)
library(lsa)
library(gutenbergr)
library(scales)
library(textdata)
library(reshape2)

Introduction à la manipulation de texte avec tibble et tidyverse

text <- c("Because I could not stop for Death -", 
      "He kindly stopped for me -", 
      "The Carriage held but just Ourselves -", 
      "and Immortality")
text_df <- text |>
  as_tibble() |>
  mutate(line = 1:4, text = value) |>
  select(line, text)

Les lignes utilisées ici sont extraites du poème “Because I could not stop for Death” d’Emily Dickinson.

Tokenisation unnest_tokens()

text_df |> unnest_tokens(word, text)
# A tibble: 20 × 2
    line word       
   <int> <chr>      
 1     1 because    
 2     1 i          
 3     1 could      
 4     1 not        
 5     1 stop       
 6     1 for        
 7     1 death      
 8     2 he         
 9     2 kindly     
10     2 stopped    
11     2 for        
12     2 me         
13     3 the        
14     3 carriage   
15     3 held       
16     3 but        
17     3 just       
18     3 ourselves  
19     4 and        
20     4 immortality

Mise en forme tidy des romans de Jane Austen

original_books <- austen_books() %>%
  group_by(book)%>%
  mutate(linenumber = row_number(), chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]", ignore_case = TRUE)))) %>% ungroup()
original_books
# A tibble: 73,422 × 4
   text                    book                linenumber chapter
   <chr>                   <fct>                    <int>   <int>
 1 "SENSE AND SENSIBILITY" Sense & Sensibility          1       0
 2 ""                      Sense & Sensibility          2       0
 3 "by Jane Austen"        Sense & Sensibility          3       0
 4 ""                      Sense & Sensibility          4       0
 5 "(1811)"                Sense & Sensibility          5       0
 6 ""                      Sense & Sensibility          6       0
 7 ""                      Sense & Sensibility          7       0
 8 ""                      Sense & Sensibility          8       0
 9 ""                      Sense & Sensibility          9       0
10 "CHAPTER 1"             Sense & Sensibility         10       1
# ℹ 73,412 more rows
text # Exemple simple 
[1] "Because I could not stop for Death -"  
[2] "He kindly stopped for me -"            
[3] "The Carriage held but just Ourselves -"
[4] "and Immortality"                       
str_detect(text,regex("Kindly",ignore_case = TRUE))
[1] FALSE  TRUE FALSE FALSE

Commentaire du code

  • On commence par charger les romans de Jane Austen sous forme de tableau (tibble).
  • Chaque ligne correspond à une ligne de texte dans un roman, avec le nom du livre.
  • On groupe par roman pour pouvoir numéroter les lignes dans chaque livre.
  • On ajoute une colonne linenumber qui donne le numéro de ligne dans le roman.
  • On crée aussi une colonne chapter en détectant les débuts de chapitres (“Chapter I”, “Chapter II”, etc.) à l’aide d’une expression régulière.
  • cumsum() permet de numéroter les chapitres de manière cumulative.
  • On enlève ensuite le regroupement pour revenir à une table simple et exploitable.

Exemple de détection d’un mot dans un texte

  • On utilise str_detect() pour repérer la présence d’un mot précis dans le texte (par exemple “Kindly”).
  • L’option ignore_case = TRUE permet de ne pas tenir compte de la casse (majuscule/minuscule).
  • Le résultat est un vecteur logique (TRUE ou FALSE) indiquant si le mot est présent dans chaque ligne.
library(tidytext)
tidy_books <- original_books %>% unnest_tokens(word, text)
tidy_books
# A tibble: 725,064 × 4
   book                linenumber chapter word       
   <fct>                    <int>   <int> <chr>      
 1 Sense & Sensibility          1       0 sense      
 2 Sense & Sensibility          1       0 and        
 3 Sense & Sensibility          1       0 sensibility
 4 Sense & Sensibility          3       0 by         
 5 Sense & Sensibility          3       0 jane       
 6 Sense & Sensibility          3       0 austen     
 7 Sense & Sensibility          5       0 1811       
 8 Sense & Sensibility         10       1 chapter    
 9 Sense & Sensibility         10       1 1          
10 Sense & Sensibility         13       1 the        
# ℹ 725,054 more rows

La fonction unnest_tokens() du package tidytext sert à transformer une colonne de texte (par exemple une phrase ou un paragraphe) en plusieurs lignes, chacune contenant un token (généralement un mot, mais ça peut aussi être un n-gramme, une phrase, etc.).

Un n-gramme est une séquence de n mots consécutifs dans un texte. Par exemple, si n = 2, on parle de bigrammes (comme “data science”) ; si n = 3, ce sont des trigrammes (comme “I love data”). Les n-grammes permettent d’analyser des expressions fréquentes et de capturer les relations entre mots proches, utiles notamment en traitement automatique du langage ou en analyse textuelle.

Suppression des mots vides (stop words)

data(stop_words) ## J'appelle le df stop_words 
stop_words       ## Je visualise le df
# A tibble: 1,149 × 2
   word        lexicon
   <chr>       <chr>  
 1 a           SMART  
 2 a's         SMART  
 3 able        SMART  
 4 about       SMART  
 5 above       SMART  
 6 according   SMART  
 7 accordingly SMART  
 8 across      SMART  
 9 actually    SMART  
10 after       SMART  
# ℹ 1,139 more rows
tidy_books <- tidy_books %>% 
  anti_join(stop_words,by="word")
tidy_books
# A tibble: 217,602 × 4
   book                linenumber chapter word       
   <fct>                    <int>   <int> <chr>      
 1 Sense & Sensibility          1       0 sense      
 2 Sense & Sensibility          1       0 sensibility
 3 Sense & Sensibility          3       0 jane       
 4 Sense & Sensibility          3       0 austen     
 5 Sense & Sensibility          5       0 1811       
 6 Sense & Sensibility         10       1 chapter    
 7 Sense & Sensibility         10       1 1          
 8 Sense & Sensibility         13       1 family     
 9 Sense & Sensibility         13       1 dashwood   
10 Sense & Sensibility         13       1 settled    
# ℹ 217,592 more rows
names(tidy_books)
[1] "book"       "linenumber" "chapter"    "word"      
names(stop_words)
[1] "word"    "lexicon"

On utilise le jeu de données stop_words (fourni par le package tidytext) pour supprimer les mots très fréquents mais peu informatifs (comme “the”, “and”, “of”, etc.).

La fonction anti_join() permet d’éliminer ces mots de notre corpus tidy, en gardant uniquement les mots porteurs de sens.

Liste des mots vides en français (stopwords_fr)

data(stopwords_fr)
head(stopwords_fr,20)
 [1] "$"        "£"        "a"        "a"        "à"        "â"       
 [7] "abord"    "afin"     "ah"       "ai"       "aie"      "aient"   
[13] "aies"     "ailleurs" "ainsi"    "ait"      "alentour" "alias"   
[19] "allais"   "allaient"

Si vous avez un texte en français, vous pouvez utiliser la liste stopwords_fr fournie par le package lsa pour supprimer les mots vides courants comme “le”, “la”, “et”, etc.

Fréquence des mots dans le corpus

tidy_books %>%
count(word, sort = TRUE)
# A tibble: 13,910 × 2
   word       n
   <chr>  <int>
 1 miss    1855
 2 time    1337
 3 fanny    862
 4 dear     822
 5 lady     817
 6 sir      806
 7 day      797
 8 emma     787
 9 sister   727
10 house    699
# ℹ 13,900 more rows

On calcule la fréquence d’apparition de chaque mot dans le corpus. Le résultat est trié par ordre décroissant pour identifier les mots les plus fréquents.

Visualisation des mots les plus fréquents

tidy_books %>% 
  count(word, sort = TRUE) %>% 
  filter(n > 600) %>% 
  mutate(word = reorder(word, n)) %>% 
  ggplot(aes(word, n)) + 
    geom_col(fill="red") + 
    labs(title = "Nombre d’occurrences des mots les plus communs dans les textes de Jane Austen", x = "Mots", y = "“Nombre d’occurrences") +
    theme(plot.title = element_text(hjust = 0.5))+ coord_flip()

Ce graphique ggplot présente les mots les plus fréquents (plus de 600 occurrences) dans les textes de Jane Austen.
On utilise count() pour calculer la fréquence, reorder() pour trier les mots, et geom_col() pour représenter les barres.
Les fonctions labs() et theme() permettent d’ajouter un titre centré et des étiquettes claires aux axes.
Le graphique est retourné horizontalement avec coord_flip() pour une meilleure lisibilité.

Téléchargement et traitement des textes de H. G. Wells

Herbert George Wells (1866–1946), plus connu sous le nom de H. G. Wells, est un écrivain britannique célèbre pour ses romans de science-fiction tels que La Guerre des mondes, La Machine à explorer le temps ou L’Homme invisible. Ses œuvres ont souvent été adaptées au cinéma.

Récupération d’un corpus de 4 œuvres

my_mirror <- "http://mirrors.xmission.com/gutenberg/"
hgwells <- gutenberg_download(c(35, 36, 5230,159),mirror = my_mirror)
Warning: ! Could not download a book at
  http://mirrors.xmission.com/gutenberg//1/5/159/159.zip.
ℹ The book may have been archived.
ℹ Alternatively, You may need to select a different mirror.
→ See https://www.gutenberg.org/MIRRORS.ALL for options.
war_text <- readLines("https://mirror.csclub.uwaterloo.ca/gutenberg/1/5/159/159-0.txt", encoding = "UTF-8")

war_df <- tibble(
  gutenberg_id = 159,
  text = war_text
)
hgwells_full <- bind_rows(hgwells, war_df)

hgwells_full %>% count(gutenberg_id)
# A tibble: 4 × 2
  gutenberg_id     n
         <dbl> <int>
1           35  3174
2           36  6372
3          159  5094
4         5230  5757

Lors de la tentative de téléchargement des œuvres de H. G. Wells à l’aide du package gutenbergr, une difficulté est survenue : l’une des œuvres — The War of the Worlds (ID 159) — n’était plus disponible sur le miroir utilisé (mirrors.xmission.com).
Pour compléter le corpus, le texte a été récupéré manuellement à partir d’un autre miroir fonctionnel (csclub.uwaterloo.ca) à l’aide de readLines().
Une fois converti en tibble, le texte a été intégré au corpus principal avec bind_rows().
Ce processus permet d’obtenir un corpus homogène de quatre œuvres, prêt pour l’analyse textuelle.

tidy_hgwells <- hgwells_full %>% unnest_tokens(word, text) %>% 
anti_join(stop_words)
Joining with `by = join_by(word)`
head(hgwells,100)
# A tibble: 100 × 2
   gutenberg_id text              
          <int> <chr>             
 1           35 "The Time Machine"
 2           35 ""                
 3           35 "An Invention"    
 4           35 ""                
 5           35 "by H. G. Wells"  
 6           35 ""                
 7           35 ""                
 8           35 "CONTENTS"        
 9           35 ""                
10           35 " I Introduction" 
# ℹ 90 more rows
dim(hgwells)
[1] 15303     2
names(hgwells)
[1] "gutenberg_id" "text"        

Le corpus complet, stocké dans hgwells_full, contient les quatre œuvres, y compris celle ajoutée manuellement.
On applique unnest_tokens() pour transformer chaque ligne de texte en une ligne par mot (format tidy),
puis anti_join(stop_words) pour supprimer les mots vides.
Ce traitement permet d’obtenir un corpus lexical nettoyé, prêt pour les analyses textuelles.

Fréquence des mots dans le corpus

tidy_hgwells %>% count(word, sort = TRUE)
# A tibble: 12,040 × 2
   word       n
   <chr>  <int>
 1 time     461
 2 people   305
 3 door     260
 4 heard    249
 5 black    232
 6 stood    229
 7 white    224
 8 hand     218
 9 kemp     213
10 eyes     210
# ℹ 12,030 more rows

Récupération partielle d’un corpus des sœurs Brontë

bronte <- gutenberg_download(c(1260, 768, 969, 9182, 767),mirror = my_mirror)
Warning: ! Could not download a book at
  http://mirrors.xmission.com/gutenberg//1/2/6/1260/1260.zip.
ℹ The book may have been archived.
ℹ Alternatively, You may need to select a different mirror.
→ See https://www.gutenberg.org/MIRRORS.ALL for options.
tidy_bronte <- bronte %>% unnest_tokens(word, text) %>%
anti_join(stop_words)
Joining with `by = join_by(word)`
tidy_bronte %>% 
count(word, sort = TRUE)
# A tibble: 20,686 × 2
   word      n
   <chr> <int>
 1 time    821
 2 don’t   625
 3 day     593
 4 hand    585
 5 miss    544
 6 eyes    527
 7 till    472
 8 heart   469
 9 half    449
10 night   430
# ℹ 20,676 more rows

Le corpus rassemble plusieurs œuvres des sœurs Brontë, mais l’œuvre associée à l’identifiant 1260 n’a pas pu être téléchargée depuis le miroir utilisé.
Le reste des textes a été transformé au format tidy, puis nettoyé des mots vides en vue d’une analyse lexicale.

Comparaison des fréquences lexicales entre auteurs

frequency <- bind_rows(mutate(tidy_bronte, author = "Brontë Sisters"), mutate(tidy_hgwells, author = "H.G. Wells"), mutate(tidy_books, author = "Jane Austen")) %>%
mutate(word = str_extract(word, "[a-z']+")) %>% count(author, word) %>% group_by(author) %>% mutate(proportion = n / sum(n)) 

frequency<-frequency[,-c(3)] %>%  
spread(author, proportion) %>% 
gather(author, proportion, `Brontë Sisters`:`H.G. Wells`)

which(frequency$word=="a")
[1]     1 26823
frequency
# A tibble: 53,644 × 4
   word        `Jane Austen` author          proportion
   <chr>               <dbl> <chr>                <dbl>
 1 a              0.00000919 Brontë Sisters  0.0000262 
 2 a'n't          0.00000460 Brontë Sisters NA         
 3 aback         NA          Brontë Sisters  0.00000524
 4 abaht         NA          Brontë Sisters  0.00000524
 5 abandon       NA          Brontë Sisters  0.0000262 
 6 abandoned      0.00000460 Brontë Sisters  0.0000785 
 7 abandoning    NA          Brontë Sisters  0.00000524
 8 abandonment   NA          Brontë Sisters  0.0000157 
 9 abart         NA          Brontë Sisters NA         
10 abase         NA          Brontë Sisters  0.00000524
# ℹ 53,634 more rows

On fusionne ici les trois corpus (Brontë Sisters, H.G. Wells, Jane Austen) pour calculer la proportion d’apparition de chaque mot dans le vocabulaire de chaque auteur.
Après avoir extrait les mots valides et supprimé les doublons, on restructure les données pour pouvoir comparer les fréquences entre auteurs.
La fonction gather() permet de retrouver un format long (mot, auteur, proportion) utile pour les visualisations ou comparaisons croisées.

Comparaison lexicale entre Jane Austen et les autres auteurs

frequency%>% ggplot(aes(x = proportion, y = `Jane Austen`, color = abs(`Jane Austen` - proportion))) +
geom_abline(color = "gray40", lty = 2) + geom_jitter(alpha = 0.1, size = 2.5, width = 0.3, height = 0.3) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) + 
  scale_x_log10(labels = percent_format()) + scale_y_log10(labels = percent_format()) + scale_color_gradient(limits = c(0, 0.001), low = "darkslategray4", high = "gray75") +
facet_wrap(~author, ncol = 2) + theme(legend.position="none") + labs(y = "Jane Austen", x = NULL)
Warning: Removed 37770 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 37772 rows containing missing values or values outside the scale range
(`geom_text()`).

Ce graphique compare, pour chaque mot, la fréquence relative d’utilisation entre Jane Austen (axe vertical) et deux autres auteurs (axe horizontal), dans deux panneaux séparés.
Les mots situés près de la diagonale sont utilisés de manière similaire par les deux auteurs, tandis que ceux éloignés signalent une préférence lexicale marquée.
La couleur reflète l’écart absolu entre les fréquences. Les échelles logarithmiques sont utilisées pour mieux visualiser les différences, y compris pour les mots rares.
Les axes sont exprimés en pourcentage grâce à la fonction percent_format() de la librairie scales, ce qui rend la lecture des proportions plus intuitive.

Remarque : les proportions affichées ne tiennent compte que des mots communs aux deux auteurs comparés, sans correction explicite des valeurs manquantes (NA).
Cela peut biaiser la visualisation, car les mots spécifiques à un seul auteur sont ignorés, et les proportions ne sont pas recalculées en conséquence.

Corrélation entre les fréquences lexicales

cor.test(data = frequency[frequency$author == "Brontë Sisters",], ~ proportion + `Jane Austen`)

    Pearson's product-moment correlation

data:  proportion and Jane Austen
t = 102.06, df = 9786, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.7083097 0.7275071
sample estimates:
      cor 
0.7180449 
cor.test(data = frequency[frequency$author == "H.G. Wells",], ~ proportion + `Jane Austen`)

    Pearson's product-moment correlation

data:  proportion and Jane Austen
t = 35.703, df = 6084, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.3952067 0.4367561
sample estimates:
      cor 
0.4161986 

Corrélation entre les fréquences lexicales

Ce code évalue la corrélation entre les proportions d’utilisation des mots chez Jane Austen et celles des sœurs Brontë d’une part, et de H.G. Wells d’autre part.
L’objectif est de déterminer dans quelle mesure la fréquence des mots employés par Jane Austen est associée à celle de ces autres auteurs.

  • Première commande : teste la corrélation entre Jane Austen et les sœurs Brontë.
  • Deuxième commande : teste la corrélation entre Jane Austen et H.G. Wells.

Chaque test produit un coefficient de corrélation accompagné d’une valeur p, indiquant si l’association observée est statistiquement significative.

Les résultats montrent :

  • Une forte corrélation positive entre Jane Austen et les sœurs Brontë, suggérant une proximité lexicale ou stylistique dans les mots les plus fréquents.
  • Une corrélation plus faible entre Jane Austen et H.G. Wells, ce qui est cohérent avec des différences de genre et de style littéraire (romans sentimentaux vs science-fiction).

La p-value associée à chaque test est très faible (souvent < 0.001), ce qui signifie que la corrélation observée est hautement significative statistiquement, et qu’elle n’est pas due au hasard.

Remarque : Les valeurs manquantes (NA) dans les données peuvent affecter les résultats.
Il est donc recommandé de les gérer (par exemple, en les supprimant ou en les imputant) avant d’effectuer ces tests pour garantir des résultats fiables.

Sentiment Analysis

get_sentiments("afinn")
# A tibble: 2,477 × 2
   word       value
   <chr>      <dbl>
 1 abandon       -2
 2 abandoned     -2
 3 abandons      -2
 4 abducted      -2
 5 abduction     -2
 6 abductions    -2
 7 abhor         -3
 8 abhorred      -3
 9 abhorrent     -3
10 abhors        -3
# ℹ 2,467 more rows
get_sentiments("bing")
# A tibble: 6,786 × 2
   word        sentiment
   <chr>       <chr>    
 1 2-faces     negative 
 2 abnormal    negative 
 3 abolish     negative 
 4 abominable  negative 
 5 abominably  negative 
 6 abominate   negative 
 7 abomination negative 
 8 abort       negative 
 9 aborted     negative 
10 aborts      negative 
# ℹ 6,776 more rows
get_sentiments("nrc")
# A tibble: 13,872 × 2
   word        sentiment
   <chr>       <chr>    
 1 abacus      trust    
 2 abandon     fear     
 3 abandon     negative 
 4 abandon     sadness  
 5 abandoned   anger    
 6 abandoned   fear     
 7 abandoned   negative 
 8 abandoned   sadness  
 9 abandonment anger    
10 abandonment fear     
# ℹ 13,862 more rows

Analyse des sentiments

On charge ici trois lexiques de sentiments différents disponibles dans le package tidytext :

  • afinn : attribue à chaque mot un score numérique de sentiment allant de -5 (très négatif) à +5 (très positif).
  • bing : classe les mots en deux catégories : positif ou négatif.
  • nrc : associe les mots à plusieurs émotions (joie, colère, peur, tristesse, etc.) ainsi qu’à une polarité (positif ou négatif).

Ces lexiques permettent d’associer une valence émotionnelle aux mots d’un texte, et ainsi de réaliser des analyses de sentiment sur les corpus.

Préparation du corpus de Jane Austen

tidy_books <- austen_books() %>% group_by(book) %>% mutate(linenumber = row_number(), chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]", ignore_case = TRUE)))) %>%
ungroup() %>% unnest_tokens(word, text)

On extrait les textes avec austen_books() puis on : - numérote les lignes dans chaque livre (linenumber), - identifie les débuts de chapitres (chapter) à l’aide de str_detect() et cumsum(), - convertit enfin le texte au format tidy avec unnest_tokens() (une ligne par mot).

Analyse des mots associés à la joie dans Emma

nrcjoy <- get_sentiments("nrc") %>% filter(sentiment == "joy")
tidy_books %>% filter(book == "Emma") %>% inner_join(nrcjoy) %>% count(word, sort = TRUE)
Joining with `by = join_by(word)`
# A tibble: 301 × 2
   word          n
   <chr>     <int>
 1 good        359
 2 friend      166
 3 hope        143
 4 happy       125
 5 love        117
 6 deal         92
 7 found        92
 8 present      89
 9 kind         82
10 happiness    76
# ℹ 291 more rows

On extrait ici les mots associés au sentiment joy dans le lexique nrc, puis on filtre le corpus pour ne conserver que le livre Emma.
Grâce à inner_join(), on identifie les mots du texte qui sont présents dans la liste des mots associés à la joie.
Enfin, count() permet de classer ces mots joyeux selon leur fréquence d’apparition dans le roman.

Évolution du sentiment dans les romans de Jane Austen

janeaustensentiment <- tidy_books %>% inner_join(get_sentiments("bing")) %>% 
count(book, index = linenumber %/% 80, sentiment) %>% spread(sentiment, n, fill = 0) %>%
mutate(sentiment = positive - negative)
Joining with `by = join_by(word)`
Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 435443 of `x` matches multiple rows in `y`.
ℹ Row 5051 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.

On associe ici chaque mot du corpus à une polarité (positive ou négative) à l’aide du lexique bing.
Les mots sont regroupés par tranches de 80 lignes (linenumber %/% 80) afin de visualiser l’évolution du sentiment au fil du texte.
On utilise spread() pour séparer les sentiments, puis on calcule un score global (positive - negative) indiquant la valence émotionnelle nette dans chaque segment du texte.

Visualisation de l’évolution des sentiments dans les romans de Jane Austen

janeaustensentiment%>%ggplot(aes(index, sentiment, fill = book)) + 
  geom_col(show.legend = FALSE) + 
  facet_wrap(~book, ncol = 2, scales = "free_x")

Ce graphique représente la variation du score de sentiment au fil du texte pour chaque roman.
Chaque barre correspond à une tranche de 80 lignes, et sa hauteur reflète la valence émotionnelle nette (positif – négatif) calculée précédemment.
Le facettage par livre (facet_wrap) permet de comparer l’évolution émotionnelle à l’intérieur de chaque œuvre, indépendamment des autres.

Comparaison des méthodes d’analyse des sentiments dans Pride & Prejudice

pride_prejudice <- tidy_books %>% filter(book == "Pride & Prejudice")
pride_prejudice
# A tibble: 122,204 × 4
   book              linenumber chapter word     
   <fct>                  <int>   <int> <chr>    
 1 Pride & Prejudice          1       0 pride    
 2 Pride & Prejudice          1       0 and      
 3 Pride & Prejudice          1       0 prejudice
 4 Pride & Prejudice          3       0 by       
 5 Pride & Prejudice          3       0 jane     
 6 Pride & Prejudice          3       0 austen   
 7 Pride & Prejudice          7       1 chapter  
 8 Pride & Prejudice          7       1 1        
 9 Pride & Prejudice         10       1 it       
10 Pride & Prejudice         10       1 is       
# ℹ 122,194 more rows
afinn <- pride_prejudice %>% inner_join(get_sentiments("afinn")) %>%
group_by(index = linenumber %/% 80) %>% 
summarise(sentiment = sum(value)) %>% mutate(method = "AFINN")
Joining with `by = join_by(word)`
bing_and_nrc <- bind_rows( pride_prejudice %>% inner_join(get_sentiments("bing")) %>% 
mutate(method = "Bing et al."), pride_prejudice %>% inner_join(get_sentiments("nrc") %>% filter(sentiment %in% c("positive", "negative"))) %>%
mutate(method = "NRC")) %>%
count(method, index = linenumber %/% 80, sentiment) %>% 
spread(sentiment, n, fill = 0) %>% mutate(sentiment = positive - negative)
Joining with `by = join_by(word)`
Joining with `by = join_by(word)`
Warning in inner_join(., get_sentiments("nrc") %>% filter(sentiment %in% : Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 215 of `x` matches multiple rows in `y`.
ℹ Row 5178 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.
bind_rows(afinn, bing_and_nrc) %>%
ggplot(aes(index, sentiment, fill = method)) + geom_col(show.legend = FALSE) + 
facet_wrap(~method, ncol = 1, scales = "free_y")

Ce graphique compare trois méthodes de calcul de sentiment appliquées au roman Pride & Prejudice :

  • AFINN : attribue des scores numériques aux mots (de -5 à +5) et calcule une somme par tranche de 80 lignes.
  • Bing et NRC : classent les mots comme positifs ou négatifs, et calculent un score net (positifs – négatifs) pour chaque segment.

Les résultats sont visualisés avec geom_col() et séparés par méthode (facet_wrap). Cela permet d’observer les différences d’interprétation émotionnelle selon le lexique utilisé.

Mots les plus contributifs aux sentiments positifs et négatifs

get_sentiments("nrc") %>% 
filter(sentiment %in% c("positive", "negative")) %>%
count(sentiment)
# A tibble: 2 × 2
  sentiment     n
  <chr>     <int>
1 negative   3316
2 positive   2308
get_sentiments("bing") %>% 
count(sentiment)
# A tibble: 2 × 2
  sentiment     n
  <chr>     <int>
1 negative   4781
2 positive   2005
bing_word_counts <- tidy_books %>%
inner_join(get_sentiments("bing")) %>% 
count(word, sentiment, sort = TRUE) %>% ungroup()
Joining with `by = join_by(word)`
Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 435443 of `x` matches multiple rows in `y`.
ℹ Row 5051 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.
bing_word_counts %>% group_by(sentiment) %>%
top_n(10) %>% ungroup() %>% mutate(word = reorder(word, n)) %>% 
ggplot(aes(word, n, fill = sentiment)) + geom_col(show.legend = FALSE) + facet_wrap(~sentiment, scales = "free_y") + labs(y = "Contribution to sentiment", x = NULL) +
coord_flip()
Selecting by n

On commence par comparer le nombre total de mots associés à une polarité (positive ou négative) dans les lexiques nrc et bing.
Ensuite, on extrait les mots du corpus de Jane Austen présents dans le lexique bing, et on calcule leur fréquence en fonction du sentiment associé.

Le graphique présente les 10 mots les plus fréquents pour chaque polarité.
On observe ainsi les mots qui contribuent le plus au sentiment global des textes, en les classant par fréquence décroissante et en les visualisant avec un graphique à barres horizontal.

Extension personnalisée des mots vides

custom_stop_words <- bind_rows(data_frame(word = c("miss"), lexicon = c("custom")),stop_words)
Warning: `data_frame()` was deprecated in tibble 1.1.0.
ℹ Please use `tibble()` instead.
custom_stop_words
# A tibble: 1,150 × 2
   word        lexicon
   <chr>       <chr>  
 1 miss        custom 
 2 a           SMART  
 3 a's         SMART  
 4 able        SMART  
 5 about       SMART  
 6 above       SMART  
 7 according   SMART  
 8 accordingly SMART  
 9 across      SMART  
10 actually    SMART  
# ℹ 1,140 more rows

On crée ici une liste personnalisée de mots à exclure en ajoutant manuellement le mot "miss" (fréquent mais peu informatif dans le corpus de Jane Austen).
Ce mot est ajouté sous un lexique nommé "custom", puis fusionné (bind_rows) avec la liste standard stop_words.
Le nouvel objet custom_stop_words contient donc les mots vides classiques et les ajouts spécifiques à ce corpus.

Nuages de mots (Wordcloud)

Les nuages de mots permettent de visualiser les mots les plus fréquents d’un corpus de manière intuitive.
La taille des mots dans le nuage reflète leur fréquence dans le texte.

Nuage de mots global (sans mots vides)

library(wordcloud)
Loading required package: RColorBrewer
tidy_books %>% anti_join(stop_words) %>% count(word) %>% 
with(wordcloud(word, n, max.words = 100))
Joining with `by = join_by(word)`

Le premier graphique affiche les 100 mots les plus fréquents dans le corpus de Jane Austen, après suppression des mots vides (stop_words).
On utilise la fonction wordcloud() pour générer ce nuage.

Nuage de mots comparatif selon le sentiment

Le second graphique distingue les mots associés à un sentiment positif ou négatif selon le lexique bing.
On transforme la table avec acast() (grâce au package reshape2), puis on utilise comparison.cloud() pour afficher les deux catégories dans un nuage de mots contrasté en deux couleurs.

library(reshape2)
tidy_books %>% inner_join(get_sentiments("bing")) %>% count(word, sentiment, sort = TRUE) %>% 
acast(word ~ sentiment, value.var = "n", fill = 0) %>% comparison.cloud(colors = c("gray20", "gray80"), max.words = 100)
Joining with `by = join_by(word)`
Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 435443 of `x` matches multiple rows in `y`.
ℹ Row 5051 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.