Análisis de Sentimiento

Ir al Capítulo anterior.
Ir al Capítulo siguiente.

La contabilización y visualización de palabras de un documento ya nos ofrece información valiosa sobre su contenido. Sin embargo, mediante el análisis de sentimientos podemos profundizar en las actitudes y el tono emocional de las opiniones expresadas en el texto.

La base del análisis de sentimientos se encuentra en el uso de un léxico o vocabulario de sentimientos. El paquete tidytext contiene cuatro léxicos o diccionarios de sentimientos, a los que podemos acceder mediante la función get_sentiments ().

Cargar librerías

library(dplyr)
library(tidyr)
library(tidytext)
library(forcats)
library(ggplot2)

Diccionario de sentimientos

# Bing dictionary
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
# summary 
get_sentiments("bing") %>% 
  count(sentiment)
## # A tibble: 2 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   4781
## 2 positive   2005
# Afinn dictionary
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
# summary 
get_sentiments("afinn") %>% 
  summarize(min = min(value), 
            max = max(value))
## # A tibble: 1 × 2
##     min   max
##   <dbl> <dbl>
## 1    -5     5
# Loughran dictionary
get_sentiments("loughran")
## # A tibble: 4,150 × 2
##    word         sentiment
##    <chr>        <chr>    
##  1 abandon      negative 
##  2 abandoned    negative 
##  3 abandoning   negative 
##  4 abandonment  negative 
##  5 abandonments negative 
##  6 abandons     negative 
##  7 abdicated    negative 
##  8 abdicates    negative 
##  9 abdicating   negative 
## 10 abdication   negative 
## # ℹ 4,140 more rows
# summary 
get_sentiments("loughran") %>% 
  count(sentiment) %>% 
  arrange(desc(n))
## # A tibble: 6 × 2
##   sentiment        n
##   <chr>        <int>
## 1 negative      2355
## 2 litigious      904
## 3 positive       354
## 4 uncertainty    297
## 5 constraining   184
## 6 superfluous     56
# visualize sentiment counts 
get_sentiments("loughran") %>% 
  count(sentiment) %>% 
  arrange(desc(n)) %>% 
  ggplot(aes(x = fct_reorder(sentiment, n), y = n)) + 
  geom_col(fill = "steelblue") + 
  coord_flip() + 
  labs(title = "Sentiment counts in Loughran", 
       x = "Sentiment", 
       y = "Counts")

# NRC dictionary
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
# summary
get_sentiments("nrc") %>% 
  count(sentiment, sort = TRUE)
## # A tibble: 10 × 2
##    sentiment        n
##    <chr>        <int>
##  1 negative      3316
##  2 positive      2308
##  3 fear          1474
##  4 anger         1245
##  5 trust         1230
##  6 sadness       1187
##  7 disgust       1056
##  8 anticipation   837
##  9 joy            687
## 10 surprise       532

Cada uno de estos diccionarios se ha creado cuidadosamente para usos específicos, por lo que su utilidad puede variar según el tipo de texto en el que se apliquen. De modo muy general, podríamos decir que Bing, que clasifica las palabras como positivas o negativas, puede ser útil para opiniones generales en reseñas, redes sociales o encuestas. Afinn resulta apropiado para textos informales en los que la intensidad del sentimiento es relevante. Por su parte, Loughran se suele emplear en textos financieros, como informes o declaraciones bursátiles. Finalmente, el diccionario NRC solo clasifica palabras como positivas o negativas, sino que también las asigna a emociones específicas.


Ejercicio : visualiza el recuento de sentimientos del diccionario ‘nrc’.

Diagrama de trabajo

1. Combinar el diccionario con el dataset limpio y 'tokenizado'. 
2. Resumir el tono emocional 

La función inner_join ()nos permite combinar dos data frames según una o más columnas clave que tengan en común. Solo devuelve las filas que coinciden en ambos data frames. De esta forma, la dimensión del dataset se reduce significativamente, ya que solo obtenemos el sentimiento de aquellas palabras que están presentes en el diccionario. Por lo tanto, el análisis de sentimiento depende en gran medida del diccionario que utilicemos.

# cargar el objeto limpio y tokenizado 
load("res/tidy_climate.rda")

# obtener el sentimiento de las palabras en nuestro dataset 
tidy_sentiment <- tidy_climate %>% 
  inner_join(get_sentiments("loughran"))

# resumir el tono emocional del documento mediante el análisis de las palabras que contribuyen a cada sentimiento
tidy_sentiment %>% 
  count(sentiment, sort = T)
## # A tibble: 5 × 2
##   sentiment        n
##   <chr>        <int>
## 1 negative       714
## 2 positive       124
## 3 uncertainty     99
## 4 litigious       76
## 5 constraining    31
tidy_sentiment %>% 
  count(word, sentiment, sort = T)
## # A tibble: 361 × 3
##    word     sentiment       n
##    <chr>    <chr>       <int>
##  1 threat   negative       57
##  2 question negative       40
##  3 believes uncertainty    25
##  4 drought  negative       18
##  5 bad      negative       17
##  6 wrong    negative       15
##  7 argument negative       14
##  8 worst    negative       14
##  9 slow     negative       12
## 10 denying  negative       11
## # ℹ 351 more rows
tidy_sentiment %>% 
  count(word, sentiment, sort = T) %>% 
  filter(sentiment %in% c("negative", "positive")) %>% 
  group_by(sentiment) %>% 
  slice(1)
## # A tibble: 2 × 3
## # Groups:   sentiment [2]
##   word    sentiment     n
##   <chr>   <chr>     <int>
## 1 threat  negative     57
## 2 excited positive     11
# visualizar el tono emocional ('negative/positive') del documento
tidy_sentiment %>% 
  filter(sentiment %in% c("negative", "positive")) %>% 
  count(word, sentiment) %>% 
  group_by(sentiment) %>% 
  slice_max(n, n = 10) %>% 
  arrange(sentiment) %>% 
  ungroup() %>% 
  #mutate(word2 = reorder_within(word, n, sentiment)) %>% #ordena dentro de cada grupo
  ggplot(aes(x = fct_reorder(word, n),  y = n, fill = sentiment)) + 
  geom_col(show.legend = FALSE) + 
  facet_wrap(~ sentiment, scales = "free_y") + 
  coord_flip() + 
  #scale_x_reordered() +
  labs(title = "Sentiment Word Counts", 
       subtitle = "loughran lexicon", 
       x = "Words", y ="")


Ejercicio : visualiza el tono emocional (‘constraining’/‘uncertainty/litigious’) usando el diccionario ‘loughran’.

Mejorando el análisis

Mediante la reestructuración o transformación de los datos, las posibilidades que ofrece el análisis, combinado con su posterior visualización, son enormes. Por ejemplo, ¿predominan algunas palabras en el tratamiento que ofrecen determinadas cadenas de televisión de modo que consigan un tono o sentimiento diferente al que ofrecen las otras cadenas? Siguiendo este ejemplo :

  1. ¿en qué cadena predomina el tono de surprise ?
  2. ¿en qué cadena predomina el tono de fear ?
  3. ¿en qué cadena predomina el tono positive ?
## # A tibble: 3 × 11
## # Rowwise: 
##   station  anger anticipation disgust fear  joy   negative positive sadness
##   <chr>    <chr> <chr>        <chr>   <chr> <chr> <chr>    <chr>    <chr>  
## 1 CNN      9.5%  6.5%         4.4%    7.3%  4.0%  16.0%    23.9%    6.4%   
## 2 FOX News 9.4%  8.0%         5.7%    11.6% 5.2%  16.3%    19.8%    7.1%   
## 3 MSNBC    8.8%  8.4%         4.2%    8.1%  4.8%  14.3%    25.5%    5.1%   
## # ℹ 2 more variables: surprise <chr>, trust <chr>

Comentarios :
* rowwise() → asegura que sum() se calcule por fila
* c_across(-station) → selecciona todas las columnas menos station
* across() → convierte cada número en su porcentaje
* scales::percent() → convierte el formato en porcentaje

by_percent %>% 
  select(station, fear, positive, surprise) %>% 
  pivot_longer(cols = -station, names_to = "sentiment", values_to = 'percent') %>% 
  mutate(percent = readr::parse_number(percent) / 100) %>% 
  ggplot(aes(x = station, y = as.numeric(percent), fill = sentiment)) + 
  geom_col(show.legend = FALSE) + 
  facet_wrap(~ sentiment, scales = "free_y") + 
  scale_y_continuous(labels = scales::percent_format()) + 
  scale_fill_brewer(palette = "Set2") +  # paleta de colores suaves
  labs(title = "Emotional Tone of Climate Change Narratives", 
       subtitle = "nrc lexicon", 
       x = "", y ="% of words contributing to tone")

Comentarios :

  • parse_number() → permite dejar la parte numérica de un vector carácter
  • scale_y_continuous() → recupera el formato ‘%’ para la visualización