1. Carga de librerías

Cargamos todos los paquetes necesarios para la manipulación de datos, web scraping, procesamiento de texto y visualización.

library(tidyverse) # Manipulación de datos (dplyr, ggplot2, etc.)
library(rvest)     # Web Scraping: para leer HTML.
library(polite)    # Web Scraping: verifica el 'robots.txt' para ser cortés con el servidor.
library(lubridate) # Trabajo con fechas (no se usa en este script, pero está cargada).
library(wordcloud) # Generación de Nubes de Palabras estáticas.
library(tidytext)  # Procesamiento de texto (tokenización, anti-join).
library(tm)        # Text Mining: para acceder a listas de StopWords.
library(jcolors)   # Esquemas de colores para gráficos.
library(wordcloud2) # Generación de Nubes de Palabras interactivas (HTML).
library(syuzhet)   # Análisis de Sentimientos.
library(ggplot2)

2. Web Scraping de Reseñas

El objetivo es extraer el texto de las reseñas de la página web especificada.

2.1 Definición de URL y protocolo de cortesía

Definimos la URL y usamos polite::bow() para asegurarnos de que el sitio web permite el web scraping, respetando su archivo robots.txt.

url_tequila <- "https://www.agavematchmaker.com/agave_spirits/2506-don-julio-anejo-70th-anniv"

# Verificar el protocolo de cortesía (Robots.txt)
# 'bow()' verifica si podemos raspar el sitio sin ser bloqueados.
url_tequila %>% bow()
## <polite session> https://www.agavematchmaker.com/agave_spirits/2506-don-julio-anejo-70th-anniv
##     User-agent: polite R package
##     robots.txt: 1 rules are defined for 1 bots
##    Crawl delay: 5 sec
##   The path is scrapable for this user-agent

## 2.2 Extracción del contenido HTML Usamos rvest para leer la página y seleccionar el nodo HTML que contiene las reseñas.

read_html(): Descarga y parsea el contenido HTML.

html_elements(): Selecciona los nodos basados en selectores CSS. En este caso, buscamos el body, y dentro de este, el contenedor de las reseñas #ratings.product-ratings-container.

html_text(): Extrae el texto plano de los nodos seleccionados.

fuente_tequila <-
  url_tequila %>%
  read_html() %>%
  # Selecciona el cuerpo del documento
  html_elements("body") %>%
  # Selecciona el contenedor de todas las reseñas
  html_elements("#ratings.product-ratings-container") %>%
  html_text()

2.3 Creación del Data Frame de Reseñas

El texto extraído se guarda en un Data Frame llamado df_fuente_tequila para facilitar su posterior procesamiento con tidyverse.

df_fuente_tequila <-
  data.frame(
    resena = fuente_tequila # Columna que contiene el texto completo de las reseñas
  )

3. Preparación de StopWords (Palabras no Informativas)

Las StopWords son palabras comunes (como “el”, “la”, “a”, “de”) o palabras específicas del contexto que no añaden valor informativo al análisis de frecuencia. Se crea una lista unificada para eliminarlas del texto.

3.1 Generación de números para StopWords

Se crea una lista de números como texto (vector_caracteres) para eliminarlos, ya que muchos números pueden aparecer como parte de las calificaciones y no son informativos para el análisis de texto.

# 1. Generar la secuencia numérica del 1 al 3000
secuencia_numerica <- 1:3000
# 2. Convertir a vector de caracteres
vector_caracteres <- as.character(secuencia_numerica)
# 3. Convertir a Data Frame
df_caracteres <- data.frame(
  Numero_Texto = vector_caracteres,
  stringsAsFactors = FALSE
)

# Se usará 'vector_caracteres' más adelante para crear 'stop_word_numeros'

3.2 Combinación de Listas de StopWords

Se combinan las listas de StopWords en español e inglés (tm::stopwords()), una lista personalizada (palabras) y la lista de números.

stopwords(“es”) / stopwords(“en”): Obtiene listas predefinidas del paquete tm.

bind_rows(): Combina todos los Data Frames de StopWords en un único stop_words_total.

# StopWords predefinidas
stop_words_spanish <- data.frame(word = stopwords("es"))
stop_words_english <- data.frame(word = stopwords("en"))

# StopWords personalizadas y específicas del contexto (ej. "tequila", "calificación")
palabras<-c("tequila","agave","Calificación","calificacion","it's","1","4","3",
            "calificación","it's","i´m", "i'm","hace","5", "años",
            "calificaciones","casi","one", "mas", "más","badger","wizard",
            "Phenom", "Badger", "Tequila Honey Badger")
stop_word_personal<-data.frame(word=palabras)

# StopWords de números
stop_word_numeros<-data.frame(word=vector_caracteres)

# Unión de todas las listas de StopWords
stop_words_total <- bind_rows(
  stop_words_spanish,
  stop_words_english,
  stop_word_personal,
  stop_word_numeros
)

4. Análisis de Frecuencia de Palabras

(N-grama)Se utiliza el concepto de N-grama (donde \(n=1\) es una sola palabra) para tokenizar el texto, eliminar las StopWords y contar la frecuencia de cada palabra.unnest_tokens(): Función clave de tidytext. Desanida el texto completo (resena) en palabras individuales (word). token = “ngrams”, n = 1 especifica que queremos tokens de una sola palabra.anti_join(): Elimina todas las filas (palabras) del ngrama que coincidan con las palabras en el stop_words_total.count(word): Agrupa y cuenta la frecuencia de cada palabra restante.

ngrama <-
  df_fuente_tequila %>%
  select(resena) %>%
  # Desanida el texto en palabras individuales (1-grama)
  unnest_tokens(output = "word", resena, token = "ngrams", n = 1) %>%
  # Elimina las stopwords (limpieza del texto)
  anti_join(stop_words_total, by = "word") %>%
  # Conteo de la frecuencia de cada palabra
  count(word) %>%
  # Ordenar por frecuencia
  arrange(desc(n))

# Mostrar las 10 palabras más frecuentes
head(ngrama, 10)
##          word  n
## 1  recomiendo 25
## 2     vanilla 21
## 3        like 14
## 4      finish 12
## 5        nose 10
## 6       taste 10
## 7  artificial  9
## 8       sweet  9
## 9      flavor  8
## 10       it’s  8

5. Visualización: Nubes de Palabras

Una Nube de Palabras (Word Cloud) es una representación visual donde el tamaño de cada palabra es proporcional a su frecuencia en el texto.

5.1 Nube de Palabras Estática (wordcloud)

wordcloud(
  words = ngrama$word,      # Palabras a mostrar
  freq = ngrama$n,          # Frecuencia para determinar el tamaño
  max.words = 740,          # Número máximo de palabras a incluir
  random.order = FALSE,     # Palabras más frecuentes en el centro
  colors = jcolors("pal3")  # Esquema de colores del paquete 'jcolors'
)

## 5.2 Nube de Palabras Interactiva (wordcloud2) Esta versión genera un widget HTML que permite interactuar con la nube (útil para RPubs). Se filtra para incluir solo palabras con frecuencia mayor a 1.

wordcloud2(
  data=ngrama[ngrama$n>1,],  # Datos, filtrando palabras que aparecen más de una vez
  color = 'random-light',   # Colores de las palabras
  backgroundColor = 'Black' # Fondo de la nube
)

6. Análisis de Sentimientos y Emociones (NRC)

Se usa la librería syuzhet para realizar un análisis de sentimientos basado en léxicos predefinidos. El análisis de Emociones NRC categoriza palabras en 8 emociones básicas (alegría, tristeza, etc.) más sentimientos positivos y negativos.

6.1 Preparación de Datos y Análisis NRC

El análisis se realiza sobre el vector de palabras únicas que resultó del análisis de frecuencia (ngrama$word).

get_nrc_sentiment(): Aplica el léxico NRC (derivado del National Research Council Canada) para contar la ocurrencia de palabras asociadas a cada emoción en el vector de texto.

language = “english”: Es importante notar que el léxico de NRC en syuzhet está optimizado para inglés, lo cual puede introducir ruido en textos en español o spanglish, pero se utiliza para una aproximación rápida.

# Asegurar la carga de la librería
library(syuzhet)
library(dplyr)
library(tidyr)

# El vector de texto a analizar son las palabras filtradas del N-grama
vector_resenas <- ngrama$word

# Obtener las puntuaciones de sentimiento para cada emoción de NRC
sentimientos_nrc <- get_nrc_sentiment(vector_resenas, language = "english")

# Convertir y resumir el resultado del análisis NRC
df_nrc <- as.data.frame(sentimientos_nrc) %>%
  # Sumar el total de detecciones por cada columna (emoción)
  summarise_all(sum) %>%
  # Pivotar la tabla de formato ancho a formato largo para el gráfico
  tidyr::pivot_longer(cols = everything(), names_to = "emocion", values_to = "conteo") %>%
  # Filtrar emociones con conteo > 0 para simplificar el gráfico
  filter(conteo > 0) %>%
  arrange(desc(conteo))

# Mostrar el resumen de emociones
head(df_nrc)
## # A tibble: 6 × 2
##   emocion      conteo
##   <chr>         <dbl>
## 1 positive         41
## 2 trust            22
## 3 negative         20
## 4 joy              16
## 5 anticipation     15
## 6 sadness          11

6.2 Visualización de Emociones (NRC)

Se crea un gráfico de barras para visualizar la distribución de las emociones detectadas.

ggplot(): Inicia la creación del gráfico.

reorder(emocion, conteo): Ordena las emociones en el eje X de menor a mayor conteo.

geom_col(): Crea las barras de conteo.

coord_flip(): Voltea el gráfico (barras horizontales) para mejorar la legibilidad de las etiquetas largas de las emociones.

grafico_emociones <-
  df_nrc %>%
  ggplot(aes(x = reorder(emocion, conteo), y = conteo, fill = emocion)) +
  geom_col() +
  coord_flip() + # Barras horizontales
  labs(
    title = "Distribución de Emociones (Léxico NRC)",
    x = "Emoción",
    y = "Total de Palabras Detectadas"
  ) +
  theme_minimal() +
  theme(legend.position = "none") # Se quita la leyenda

print(grafico_emociones)