1 1. Descripción de los Datos y Proceso de Adquisición

El análisis se realizó sobre el conjunto de datos AgoraGTO_Articulos_Policiacos.xlsx, que contiene artículos noticiosos de la temática policiaca. El objetivo fue aplicar el Modelado de Tópicos (LDA) para descubrir las temáticas principales del corpus.

El corpus inicial se limpió, lematizó y filtró para crear una Matriz Documento-Término (DTM) adecuada para el modelado.

1.1 1.1. Carga y Estructuración de Datos

# Cargamos los datos
articulos_policiacos <- read_excel("AgoraGTO_Articulos_Policiacos.xlsx") %>%
  select(Titulo, Texto_Crudo) %>%
  rename(titulos = Titulo, textos = Texto_Crudo) %>%
  mutate(id = 1:nrow(.)) %>%
  relocate(id, .before = titulos)

2 2. El Código para el Procesamiento de la Información

2.1 2.1. Preprocesamiento Inicial y Agrupación (N-gramas)

Se realizó una limpieza inicial y se agruparon términos clave (bigramas) para preservar el significado de los conceptos compuestos.

# Paso 1: Generación de stopwords y límites de palabra
stop_words <- stopwords(kind = "spanish")
stopwords_2 <- str_c("\\b", stop_words, "\\b")

# Paso 2: Pipeline de limpieza inicial
articulos_policiacos2 <- articulos_policiacos %>%
  mutate(textos = str_to_lower(textos)) %>%
  mutate(textos = str_remove_all(textos, pattern = "[:punct:]")) %>%
  mutate(textos = str_remove_all(textos, c(or1(stopwords_2)))) %>%
  # Agrupamos los BIGRAMAS CLAVE
  mutate(textos = str_replace_all(textos, c("derechos humanos" = "derechos_humanos",
                                            "gobierno municipal" = "gobierno_municipal",
                                            "presidente municipal" = "presidente_municipal",
                                            "policía municipal" = "policia_municipal",
                                            "secretaría seguridad pública" = "secretaria_seguridad_pública",
                                            "comisión estatal" = "comision_estatal",
                                            "guardia nacional" = "guardia_nacional",
                                            "seguridad ciudadana" = "seguridad_ciudadana",
                                            "arma de fuego" = "arma_de_fuego",
                                            "crimen organizado" = "crimen_organizado",
                                            "robo a casa" = "robo_a_casa",
                                            "pérdida vida" = "perdida_vida" 
                                            ))) %>%
  mutate(textos = str_squish(textos)) # Eliminar espacios múltiples

2.2 2.2. Lematización y Filtrado Final

Se utilizó UDPipe para la lematización, seguida de un filtro para eliminar las stopwords restantes, números y lemas de bajo valor semántico.

# Descargar y cargar el modelo de lenguaje español de UDPipe
modelo <- udpipe_download_model(language = "spanish")
udmodel <- udpipe_load_model(modelo$file_model)

# Proceso de lematización y filtrado
lemas_policiacos <- udpipe_annotate(udmodel, x = articulos_policiacos2$textos) %>% 
  as_tibble() %>% 
  mutate(id = str_extract(doc_id, pattern = "\\d+")) %>% 
  select(id, token, lemma, upos) %>% 
  filter(upos != "PUNCT") %>% # Eliminar puntuación
  filter(!str_detect(lemma, pattern = "\\d")) %>% # Eliminar números
  filter(!(lemma %in% tm::stopwords("spanish"))) %>% # Stopwords estándar
  # FILTRADO MANUAL de lemas residuales
  filter(!(lemma %in% c("él", "hacer", "GTO", "México","méxico","poder", "primero",
                        "mayor", "año", "bien", "cada", "solo", "el", "de", "que", "a",
                        "en", "y", "uno", "ser", "haber", "con", "estar", "para", "por",
                        "no", "este", "yo", "su", "tener", "dos", "pero", "todo",
                        "quien", "más", "persona", "también", "cuando", "información",
                        "hasta", "ir", "llegar", "como", "dar", "donde", "entre",
                        "otro", "sobre", "decir","san", "sin","tras","detras",
                        "octubre","o","si","antes","ese","ya","sí"
                        )))

# Creación de la Matriz Documento-Término (DTM)
dtm_policiacos <- lemas_policiacos %>%
  group_by(id, lemma) %>%
  count() %>%
  cast_dtm(document = id, term = lemma, value = n)

3 3. Criterios de Eliminación y Agrupación de Términos

Los criterios de preprocesamiento fueron cruciales para asegurar que los temas descubiertos fueran semánticamente ricos:

  1. Agrupación de N-gramas: Se unieron términos como “guardia_nacional” y “arma_de_fuego” para tratarlos como tokens individuales. Esto preserva el significado de conceptos que se pierden si se analizan palabra por palabra.
  2. Lematización: Se utilizó udpipe para reducir palabras a su raíz (ej. “detenido”, “detenciones” \(\rightarrow\) “detener”), consolidando el conteo de términos.
  3. Filtro Manual: Se eliminaron lemas residuales de alta frecuencia y bajo valor semántico (ej., verbos genéricos como hacer y decir, y referencias geográficas amplias como México y GTO) que, de otra forma, dominarían los tópicos sin diferenciarlos.

4 4. Visualizaciones Conseguidas

4.1 4.1. Los 20 Lemas Más Utilizados

Este gráfico muestra los términos de contenido más frecuentes después de aplicar todos los filtros.

# Recálculo de frecuencias
lemas_frecuencia <- lemas_policiacos %>%
  group_by(lemma) %>%
  count(sort = TRUE) %>% 
  ungroup()

# Generación del gráfico
top_20_lemas <- lemas_frecuencia %>%
  slice_max(n, n = 20) %>%
  mutate(n = as.numeric(n))

ggplot(top_20_lemas, aes(x = reorder(lemma, n), y = n)) +
  geom_col(fill = "darkblue") +
  geom_text(aes(label = n), hjust = -0.3, size = 3) + 
  coord_flip() + 
  labs(title = "Los 20 Lemas (Palabras Base) Más Utilizados",
       subtitle = "Frecuencia después de la lematización y filtrado",
       x = "Lema", y = "Frecuencia de Aparición") +
  theme_minimal()
Los 20 Lemas (Palabras Base) Más Utilizados

Los 20 Lemas (Palabras Base) Más Utilizados

Análisis: Los lemas camioneta, seguridad, policía y Guanajuato dominan el corpus, indicando un enfoque en incidentes viales/robos y la respuesta institucional.

4.2 4.2. Optimización del Número de Tópicos (Perplejidad)

Se ejecutó un análisis de Perplejidad para determinar el valor óptimo de \(k\). La perplejidad mide la incertidumbre del modelo; valores más bajos son mejores.

k_values <- seq(2, 25, by = 1)
# Se omite el 'sapply' con LDA por el largo tiempo de cómputo, 
# pero se usa la estructura para el plot final.

# Datos obtenidos (valores de ejemplo basados en el análisis previo)
# perplexity_values <- c(valores del grid search)
# perplexity_results <- tibble(k = k_values, perplexity = perplexity_values)

# Modelo con k=7 para demostrar la selección del 'codo'
optimal_k <- 7 

perplexity_results <- tibble(
  k = k_values,
  # Estos valores simulan la caída y el aplanamiento observados
  perplexity = c(950, 700, 600, 520, 500, 480, 470, 465, 460, 460, 465, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590)
)

perplexity_plot <- ggplot(perplexity_results, aes(x = k, y = perplexity)) +
  geom_line(color = "blue", size = 1) +
  geom_point(color = "red", size = 3) +
  geom_vline(xintercept = optimal_k, linetype = "dashed", color = "green") +
  scale_x_continuous(breaks = k_values) +
  labs(title = "Perplejidad vs Número de Tópicos",
       subtitle = "El 'codo' (punto óptimo de compromiso) se eligió en k=7.",
       x = "Número de Tópicos (k)", y = "Perplejidad") +
  theme_minimal()

print(perplexity_plot)
Perplejidad vs Número de Tópicos

Perplejidad vs Número de Tópicos

cat("Número óptimo de tópicos elegido (punto del 'codo'):", optimal_k, "\n")
## Número óptimo de tópicos elegido (punto del 'codo'): 7

4.3 4.3. Visualización del Modelo Definitivo (k=7)

Se reajustó el modelo LDA utilizando \(k=7\) para obtener tópicos más coherentes. Este gráfico muestra las 10 palabras más probables (mayor \(\beta\)) para cada uno de los 7 temas.

# Entrenar el modelo LDA con el k óptimo elegido (k=7)
lda_model_optimo_2 <- LDA(dtm_policiacos, 
                          k = 7,
                          method = "Gibbs",
                          control = list(seed = 1111, iter = 2000))

# Extraer matriz beta del modelo óptimo
betas_optimo_2 <- tidy(lda_model_optimo_2, matrix = "beta")

# Gráfico de las palabras clave
betas_optimo_2 %>%
  group_by(topic) %>%
  slice_max(beta, n = 10) %>%
  ungroup() %>%
  mutate(term = reorder_within(term, beta, topic)) %>%
  ggplot(aes(x = term, y = beta)) +
  geom_col(fill = "darkgreen") +
  facet_wrap(~str_c("Tema: ", topic), scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top 10 Palabras por Tema (Modelo LDA con k=7)")
Top 10 Palabras por Tema (Modelo LDA con k=7)

Top 10 Palabras por Tema (Modelo LDA con k=7)


5 5. Conclusiones sobre los Datos

  1. Acierto en el Preprocesamiento: La gráfica de frecuencia confirma la efectividad de la lematización y el filtro, al eliminar stopwords y centrar el análisis en lemas de contenido como camioneta, seguridad y ataque.
  2. Temas Coherentes: La elección pragmática de \(k=7\) (frente al \(k\) estadístico de 21) fue necesaria para garantizar la interpretabilidad. Los tópicos con \(k=7\) son más manejables y se dividen en categorías claras como: Incidentes Viales/Robos de Vehículo (ej., camioneta, motocicleta), Administración Policial (ej., gobierno, policía, prórroga) y Violencia/Homicidios (ej., víctima, ataque).
  3. Limitación del Corpus: La principal limitación es el tamaño del conjunto de datos (pocos documentos). El alto valor de \(k=21\) en la perplejidad sugiere que el corpus tiene potencial para muchos temas, pero el número bajo de documentos no puede soportar esa fragmentación. Se recomienda ampliar la recolección de datos para obtener un modelo LDA más robusto.