1 Introducción

En esta actividad se realizará un análisis de minería de texto sobre la obra “Don Quijote de la Mancha”. El propósito es examinar las relaciones entre bigramas y descubrir patrones significativos en el texto.

2 Preparación del entorno

3 Cargue de librerias

Se cargan las librerias necesarias para la ejecucón.

library(pdftools)
library(dplyr)
library(tidytext)
library(stringr)
library(ggplot2)
library(tidyr)
library(widyr)
library(ggraph)
library(igraph)
library(stopwords)
library(wordcloud)
library(RColorBrewer)

4 Carga de datos

  • Se cargan los PDF con la funcion pdf_text del paquete pdftools y se convierte a texto plano.
  • Se unen los dos textos.
  • Se limpia el texto con las siguientes accioens:
    • Eliminar saltos de línea y retornos de carro.
    • Se borra el pie de pagina.
    • Se detectan espacios multiples y se remplaza por un espacio.
# === 1. Lectura de los PDF ===
texto01 <- pdf_text("DONQUIJOTE_PARTE1.pdf")
texto02 <- pdf_text("DONQUIJOTE_PARTE2.pdf")

# Unir páginas dentro de cada parte
texto01 <- paste(texto01, collapse = " ")
texto02 <- paste(texto02, collapse = " ")
library(pdftools)

# === 2. Definir función de limpieza ===
limpiar_texto <- function(texto) {
  # Eliminar frases de origen
  texto <- gsub("http://www\\.educa\\.jcyl\\.es", "", texto)
  texto <- gsub("Portal Educativo EducaCYL", "", texto)

  # Saltos y caracteres de control
  texto <- gsub("\f", " ", texto)
  texto <- gsub("\\r|\\n", " ", texto)

  # Sustitución y normalización
  texto <- gsub("—|–", " ", texto)
  texto <- gsub("\\.{2,}", ".", texto)
  texto <- gsub("\\d+", "", texto)

  # Comillas, apóstrofos y espacios
  texto <- gsub("[‘’´`]", "'", texto)
  texto <- gsub("[“”]", "\"", texto)
  texto <- gsub("\\(\\s+", "(", texto)
  texto <- gsub("\\s+\\)", ")", texto)
  texto <- gsub("\"\\s+", "\"", texto)
  texto <- gsub("\\s+\"", "\"", texto)

  # Encabezados y títulos
  texto <- gsub("Miguel de Cervantes Saavedra", "", texto)
  #texto <- gsub("El Ingenioso Hidalgo Don Quijote de la Mancha", "", texto)
  texto <- gsub("PRIMERA PARTE|SEGUNDA PARTE", "\n", texto)
  texto <- gsub("CAP[IÍ]TULO\\s*\\d+:?", "\n\nCAPÍTULO ", texto)

  # Paréntesis y puntuación
  texto <- gsub("\\(\\)", "", texto)
  texto <- gsub("\\(.*?\\)", "", texto)
  texto <- gsub("\\s+([,;:.!?])", "\\1", texto)

  # Limpieza de frases editoriales
  texto <- gsub("Cuenta Cide Hamete Benengeli.*?historia", "", texto)

  # Espacios múltiples y formato final
  texto <- gsub("\\s{2,}", " ", texto)
  texto <- trimws(texto)

  # Saltos de línea entre oraciones
  texto <- gsub("([.!?])\\s+", "\\1\n", texto)

  return(texto)
}

# === 3. Aplicar limpieza a cada parte ===
texto01_limpio <- limpiar_texto(texto01)
texto02_limpio <- limpiar_texto(texto02)

# === 4. Unir las partes limpias ===
texto <- paste(texto01_limpio, texto02_limpio, collapse = "\n\n")

# === 5. Exportar resultado ===
writeLines(texto, "don_quijote_limpio.txt", useBytes = TRUE)

5 Estructuración y limpieza del texto

  • La estructura hace el siguiente proceso
    • Separa el texto por frases donde encuentre un punto con “\.”
    • El resultado de frases lo convierte a un data frame llamado df_texto
    • Se eliminan espacios al principio y al final de las frases en el data fraem, por medio de str_trim()
# Separar por oraciones y convertir a dataframe
frases <- unlist(strsplit(texto, "\\."))   # ← corrección aquí
df_texto <- data.frame(frase = frases, stringsAsFactors = FALSE)

# Quitar encabezados y espacios sobrantes
df_texto$frase <- stringr::str_trim(df_texto$frase)

6 Tokenización y eliminación de stopwords

  • Definición de stopwords: las stopswords son palabras comunes en cualquier texto, intepenidiente del tema y que en general no dan un contexto, ejemplos de estas palabras son, los articuos, las proposiciones, conjunciones y pronombres.Son eliminadas ya que aprotan poco significado al valor semantico.
    • Se defien una lista de las stopwords en español.
    • Se Tokeniza el data frame “df_texto, esto quiere decir que se separan las palabras y se guardan en el data frame df_tokens con una columna”word”
    • Se garantiza que la palabras del data frame df_tokens solo contenga palabras sin caracteres especiales.
# Stopwords en español
stopwords_es <- stopwords("es")

# Tokenización
df_tokens <- df_texto %>%
unnest_tokens(word, frase) %>%
filter(!word %in% stopwords_es,
str_detect(word, "[a-záéíóúñ]"))

7 Análisis de frecuencia de palabras

  • Se realiza un grafico de barras horizontal con las palabras mas frecuentes despues de la tokenización.
word_counts <- df_tokens %>%
  count(word, sort = TRUE)

ggplot(head(word_counts, 20), aes(x = reorder(word, n), y = n)) +
  geom_col(fill = "steelblue") +
  geom_text(aes(label = n), hjust = -0.1, size = 3) +  # Etiquetas
  coord_flip() +
  labs(
    title = "Palabras más frecuentes (sin stopwords)",
    x = "Palabra", 
    y = "Frecuencia"
  ) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5)) +      # Centrar título
  expand_limits(y = max(head(word_counts$n, 20)) * 1.1)  # Deja espacio para etiquetas

# Nube de palabras * Se realiza un grafico de nube de palabras, que permite representar de practica y estetica las palabras mas frecuentes, que son representadas por la palabras con el tamaño mas grande y las palabras menos frecuentes representadas por el texto con tamaño mas pequeño.

set.seed(123)
wordcloud(words = word_counts$word,
freq = word_counts$n,
min.freq = 20,
max.words = 200,
random.order = FALSE,
rot.per = 0.3,
colors = brewer.pal(8, "Dark2"))

8 Generación y análisis de bigramas

  • En este codigo se inicia el analisis de los bigramas, buscando las palabras que mas aparecen en el texto en forma conjunta, ejemplo el bigrama “don quijote”.
    • Se procesa el data frame df_texto y este procesamiento se almacena en el data frame bigrams.
    • Se divide el texto en bigramas (conjuntos de dos palabras), ejemplo, la frase “de nuestro hidalgo” se divide en (de : nuestro) y (nuestro : hidalgo).
    • Se divide el bigrams en dos columnas “word1” y “word2”
    • Se eliminan los bigramas que contengan stop words, en el ejemplo antes citado (de : nuestro) se elimina ya que (de) es un stopwords.
    • Finalmente se cuenta la frecuencia de cada bigrama y se organiza de mayor a menor
bigrams <- df_texto %>%
unnest_tokens(bigram, frase, token = "ngrams", n = 2) %>%
separate(bigram, into = c("word1", "word2"), sep = " ") %>%
filter(!word1 %in% stopwords_es,
!word2 %in% stopwords_es) %>%
count(word1, word2, sort = TRUE)

head(bigrams, 15)
##         word1    word2    n
## 1         don  quijote 2186
## 2        dijo      don  310
## 3   respondió   sancho  310
## 4      sancho    panza  279
## 5   respondió      don  267
## 6        dijo   sancho  246
## 7       vuesa   merced  245
## 8       señor      don  183
## 9  caballeros andantes  131
## 10  caballero  andante  116
## 11        don fernando  114
## 12     sancho     dijo  106
## 13     merced    señor  100
## 14     señora dulcinea   96
## 15     muchas    veces   81

9 Visualización de red de bigramas

  • Se genera una visualizacion tipo grafo de bigramas, donde de forma grafica se busca representar los conjuntos de palabras (bigramas) con mayor repetición en el texto
    • Los gurpos de palabras se unen con una linea, donde el grosor de la linea (mas gruesa) y color mas oscuro (verde oscuro) representan una mayor frecuencia de aparicion en el texto, por el contrario un color mas claro y una linea mas delgada representan menor frecuencia.
    • En este caso el grosor más grande y color verde mas oscuro se representa una frecuencia de aparición de los bigramas mayor o igual a 2000.
# Filtrar bigramas más frecuentes

bigrams_grafo <- bigrams %>%
filter(n > 100) %>%
graph_from_data_frame()

ggraph(bigrams_grafo, layout = "fr") +
geom_edge_link(aes(edge_alpha = n, edge_width = n), color = "darkcyan") +
geom_node_point(size = 5, color = "lightblue") +
geom_node_text(aes(label = name), repel = TRUE) +
theme_void() +
labs(title = "Red de bigramas más frecuentes")

10 Coocurrencia entre palabras

  • Este código busca analizar coocurrencias de palabras dentro de grupos de frases del corpus. Específicamente:
    • Divide el texto tokenizado en 10 grupos
    • Se crean dos columnas “grupo” donde esta la segmentación de los grupos y “word” donde esta cada palabra tokenizada
    • Se eliminan las stopswords y se filtra solo por caracteres alfabeticos.
    • pairwise_count() (del paquete widyr) calcula cuántas veces dos palabras aparecen juntas dentro del mismo grupo (como un documento o bloque de texto), aunque no estén una al lado de la otra en el texto original. Es decir, no forman necesariamente binomios o n-gramas, sino que coocurren dentro de la misma unidad de análisis.
    • Se generan tres columnas Item1 donde esta la primera palabra, Item2 donde esta la segunda palabra y “n” que es el número de concurrencias.
# Crear grupos de 10 frases para mejorar las coocurrencias

df_tokens_coocurencia_grupos <- df_texto %>%
mutate(grupo = rep(1:ceiling(nrow(df_texto)/10), each = 10, length.out = nrow(df_texto))) %>%
unnest_tokens(word, frase) %>%
filter(!word %in% stopwords_es,
str_detect(word, "[a-záéíóúñ]"))

#write.csv(df_tokens_coocurencia_grupos, "df_tokens_coocurencia_grupos.csv", row.names = FALSE)

#head(df_tokens_coocurencia_grupos, 1000)
#print(n_distinct(df_tokens_coocurencia_grupos$grupo))

# Calcular coocurrencias por grupo

word_pairs <- df_tokens_coocurencia_grupos %>%
pairwise_count(word, grupo, sort = TRUE)

# Mostrar los pares con más de 100 coocurrencias

head(word_pairs[word_pairs$n > 100, ], 15)
## # A tibble: 15 × 3
##    item1   item2       n
##    <chr>   <chr>   <dbl>
##  1 dijo    si        610
##  2 si      dijo      610
##  3 quijote don       572
##  4 don     quijote   572
##  5 dijo    don       553
##  6 don     dijo      553
##  7 si      don       549
##  8 don     si        549
##  9 dijo    quijote   518
## 10 quijote dijo      518
## 11 si      quijote   516
## 12 quijote si        516
## 13 si      así       515
## 14 así     si        515
## 15 dijo    así       507

11 Correlaciones entre palabras (con gráfico)

# ============================================================
# 11. Correlaciones entre palabras (agrupadas y graficadas)
# ============================================================

# Crear grupos de 10 frases para analizar coocurrencias en segmentos más largos
df_tokens_grupos <- df_texto %>%
  mutate(grupo = rep(1:ceiling(nrow(df_texto)/10), each = 10, length.out = nrow(df_texto))) %>%
  unnest_tokens(word, frase) %>%
  filter(!word %in% stopwords_es,
         str_detect(word, "[a-záéíóúñ]"))

# Calcular correlación entre palabras (phi de coocurrencia)
word_cors <- df_tokens_grupos %>%
  group_by(word) %>%
  filter(n() >= 20) %>%                 # solo palabras con al menos 20 apariciones
  pairwise_cor(word, grupo, sort = TRUE)

# Mostrar las correlaciones más fuertes
head(word_cors, 15)
## # A tibble: 15 × 3
##    item1      item2      correlation
##    <chr>      <chr>            <dbl>
##  1 hamete     cide             0.983
##  2 cide       hamete           0.983
##  3 camila     lotario          0.965
##  4 lotario    camila           0.965
##  5 marcela    grisóstomo       0.953
##  6 grisóstomo marcela          0.953
##  7 camila     anselmo          0.923
##  8 anselmo    camila           0.923
##  9 tosilos    lacayo           0.903
## 10 lacayo     tosilos          0.903
## 11 lotario    anselmo          0.890
## 12 anselmo    lotario          0.890
## 13 quijote    don              0.844
## 14 don        quijote          0.844
## 15 sansón     carrasco         0.791

12 Gráfico de Correlaciones entre palabras

# ============================================================
# Graficar red de correlaciones más fuertes (Top 100)
# ============================================================

library(dplyr)
library(igraph)
library(ggraph)
library(tidygraph)

# =========================================
# Función auxiliar para graficar correlaciones
# =========================================
graficar_red <- function(data, titulo, subtitulo) {
  set.seed(123)
  data %>%
    graph_from_data_frame() %>%
    ggraph(layout = "fr") +
    geom_edge_link(aes(edge_alpha = correlation, edge_width = correlation), color = "darkgreen") +
    geom_node_point(size = 4, color = "lightgreen") +
    geom_node_text(aes(label = name), repel = TRUE, size = 3) +
    theme_void() +
    labs(title = titulo,
         subtitle = subtitulo,
         caption = "Fuente: Análisis de texto del Don Quijote de la Mancha") +
    theme(plot.title = element_text(size = 14, face = "bold"),
          plot.subtitle = element_text(size = 11))
}

# =========================================
# Filtrar y graficar TOP 15 correlaciones
# =========================================
word_cors_top15 <- word_cors %>%
  filter(correlation > 0.25) %>%
  arrange(desc(correlation)) %>%
  slice_head(n = 15)

grafico_top15 <- graficar_red(
  word_cors_top15,
  titulo = "Red de correlaciones entre palabras",
  subtitulo = "Top 15 correlaciones > 0.25"
)

# Mostrar gráfico
print(grafico_top15)

# =========================================
# Filtrar y graficar TOP 200 correlaciones
# =========================================
word_cors_top200 <- word_cors %>%
  filter(correlation > 0.25) %>%
  arrange(desc(correlation)) %>%
  slice_head(n = 200)

grafico_top200 <- graficar_red(
  word_cors_top200,
  titulo = "Red de correlaciones entre palabras",
  subtitulo = "Top 200 correlaciones > 0.25"
)

# Mostrar gráfico
print(grafico_top200)

# ============================================================
# 11A. Gráfico de barras - correlaciones más fuertes por palabra
# ============================================================

# Selecciona una palabra base para analizar correlaciones
palabra_base <- "quijote"   # 👈 puedes cambiarla por otra: "sancho", "don", etc.

# Filtra las correlaciones relacionadas con esa palabra
cor_palabra <- word_cors %>%
  filter(item1 == palabra_base) %>%
  arrange(desc(correlation)) %>%
  head(10)

# Gráfico de barras de correlaciones
ggplot(cor_palabra, aes(x = reorder(item2, correlation), y = correlation)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(title = paste("Palabras más correlacionadas con:", palabra_base),
       x = "Palabra asociada",
       y = "Correlación (phi)") +
  theme_minimal(base_size = 13)

# ============================================================
# 11B. Heatmap - correlaciones entre las palabras más comunes
# ============================================================

# Seleccionar las 15 palabras más frecuentes
top_palabras <- df_tokens_grupos %>%
  count(word, sort = TRUE) %>%
  top_n(15) %>%
  pull(word)

# Filtrar solo correlaciones entre esas palabras
cor_top <- word_cors %>%
  filter(item1 %in% top_palabras,
         item2 %in% top_palabras)

# Crear matriz de correlación
library(reshape2)
matriz_cor <- acast(cor_top, item1 ~ item2, value.var = "correlation", fill = 0)

# Graficar heatmap
library(ggplot2)
ggplot(melt(matriz_cor), aes(Var1, Var2, fill = value)) +
  geom_tile(color = "white") +
  scale_fill_gradient2(low = "lightblue", high = "darkblue", mid = "white",
                       midpoint = 0.2, limit = c(0, 1), space = "Lab") +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "Mapa de calor de correlaciones entre palabras frecuentes",
       x = "Palabra 1", y = "Palabra 2", fill = "Correlación")

# ============================================================
# 11C. Scatterplot - pares de palabras más correlacionadas
# ============================================================

# Seleccionamos los pares con mayor correlación
word_cors_top <- word_cors %>%
  filter(correlation > 0.25) %>%
  arrange(desc(correlation)) %>%
  head(20)

# Graficar correlaciones como puntos
ggplot(word_cors_top, aes(x = reorder(paste(item1, item2, sep = " - "), correlation),
                          y = correlation)) +
  geom_point(color = "darkgreen", size = 3) +
  coord_flip() +
  labs(title = "Pares de palabras con mayor correlación",
       x = "Par de palabras", y = "Correlación (phi)") +
  theme_minimal(base_size = 13)

13 Conclusiones

En esta sección redacta tus conclusiones personales, por ejemplo:

Las palabras más frecuentes reflejan los temas centrales del texto, destacando términos como don, quijote, sancho, y caballero.

Los bigramas con mayor frecuencia muestran relaciones semánticas clave, como don quijote, sancho panza, mi señor, lo que revela la interacción entre personajes.