Modelado de tópicos

¿Qué es?

Tipos de modelados de tópicos

Latent Dirichlet Allocation (LDA)

¿Cómo lo usamos en R? {topicmodels}

LDA

Se rige por dos principios

  • Cada documento es una mezcla de temas
  • Cada tema es una mezcla de palabras

Cuentos

Vamos a utilizar un corpus con cuentos de 57 autores en español creado por Karen Palacios

library(tidyverse)
library(janitor)
cuentos <- read.csv("https://raw.githubusercontent.com/karen-pal/borges/refs/heads/master/datasets/full_corpus.csv") |> 
  clean_names() |> 
  rename(id = x)

Observamos

library(highcharter)

cuentos |> 
  count(author, sort= T)|> 
  arrange(n) |>  # Para que coord_flip de ggplot se vea igual en highcharter
  hchart(
    type = "bar",  # bar = horizontal; column = vertical
    hcaes(x = author, y = n)
  ) |>
  hc_chart(backgroundColor = "transparent") |>
  hc_colors("#112446") |>
  hc_xAxis(title = list(text = "")) |>
  hc_yAxis(title = list(text = "Cantidad")) |>
  hc_add_theme(hc_theme_flat())  # Similar a theme_minimal

Observamos

Recortamos

Nos vamos a quedar solo con los cuentos de Borges

borges <- cuentos |> 
  filter(str_detect(author, regex("borges", T)))

Preprocesamiento


library(udpipe)
modelo_sp <- udpipe::udpipe_download_model('spanish')
modelo_sp$file_model
modelo_sp <- udpipe_load_model(file = modelo_sp$file_model)

cuentos_anotados <- udpipe_annotate(
  object = modelo_sp,
  x = borges$text,
  doc_id = borges$id,
  trace = 20
  ) %>% as.data.frame(.)  

Preprocesamiento

Convertimos a matriz

library(tidytext)
cuentos_anotados_limpios <- cuentos_anotados  %>% 
  filter(upos=="ADJ"| upos=="VERB"| upos=="NOUN") %>% 
  select( doc_id, lemma ) %>%
  filter(!lemma %in% stopwords::stopwords(language = "es"))%>%
  count(doc_id, lemma, sort = TRUE) %>% # Armamos una freq por cada documento
  cast_dtm(doc_id, lemma, n) # Convertimos a vector

Topic Models en R

Podemos utilizar la LDA() función del paquete topicmodels, configurando k = 5, para crear un modelo LDA de dos temas.

library(topicmodels)

cuentos_lda <- LDA(cuentos_anotados_limpios, k = 6, control = list(seed = 1234))
cuentos_lda
A LDA_VEM topic model with 6 topics.

Beta y Gamma

  • beta: probabilidad topico x palabra;
  • gamma: probabilidad topico x documento;
cuentos_lda_beta <- tidy(cuentos_lda, matrix = "beta")
cuentos_lda_gamma <- tidy(cuentos_lda, matrix = "gamma")

Beta

Podemos usar dplyr slice_max() para encontrar los 10 términos más comunes en cada tema

cuentos_lda_beta %>% # principales términos en cada tópico
  group_by(topic) %>%
  top_n(15) %>%
  ungroup() %>%
  arrange(topic, -beta) %>% # vamos a mostrarlo como grafico
  ggplot(aes(x=reorder(term, (beta)),y=beta)) + 
    geom_col() +
    facet_wrap(~topic, scales = "free_y") +
  coord_flip() +
  theme_minimal()

Eliminar palabras comunes

library(tidyr)

beta_wide <- cuentos_lda_beta |> 
  mutate(topic = paste0("topic", topic)) |> 
  pivot_wider(names_from = topic, values_from = beta)

# Filtrar términos con beta > 0.001 en al menos un tema
beta_wide_filtrado <- beta_wide |> 
  filter(if_any(starts_with("topic"), ~ .x > 0.001))

cols_topics <- beta_wide_filtrado |> 
  select(starts_with("topic")) |> 
  colnames()

# Luego, hacer el mutate con max.col correctamente
beta_topico_max <- beta_wide_filtrado |> 
  mutate(topico_max = cols_topics[max.col(across(all_of(cols_topics)), ties.method = "first")])

Filtramos

beta_larga <- beta_topico_max |> 
  pivot_longer(cols = all_of(cols_topics), names_to = "topic", values_to = "beta")

beta_filtrada <- beta_larga |> 
  filter(topic == topico_max) |> 
  group_by(topic) |> 
  slice_max(order_by = beta, n = 20, with_ties = FALSE) |> 
  ungroup()

ggplot(beta_filtrada, aes(x = reorder(term, beta), y = beta, fill = topic)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ topic, scales = "free_y") +
  coord_flip() +
  theme_minimal()

Nombres de los tópicos

topicos_nombres <- rbind( 
  c(topico_max = "topic1" , nombre = "accion"),
  c(topico_max = "topic2" , nombre = "suenio"),
  c(topico_max = "topic3" , nombre = "onirico_horizonte"),
  c(topico_max = "topic4" , nombre = "tragedia"),
  c(topico_max = "topic5" , nombre = "existencialismo"),
  c(topico_max = "topic6" , nombre = "ficcion_artistico")
) %>% as_tibble()

# Agregar nombres al gráfico
beta_filtrada <- beta_filtrada |> 
  left_join(topicos_nombres, by = "topico_max")|> 
  group_by(topic) |> 
  slice_max(order_by = beta, n = 20, with_ties = FALSE) |> 
  ungroup()

Observamos

ggplot(beta_filtrada, aes(x = reorder(term, beta), y = beta, fill = nombre)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ nombre, scales = "free_y") +
  coord_flip() +
  theme_minimal()

¿Qué topico tiene cada cuento?

topicos_nombres <- tibble(
  topic = 1:5,
  nombre = c("Acciones", "Tiempo y espacio", "Existencia", "Conocimiento", "Reflexiones")
)

gamma_dominante <- cuentos_lda_gamma |>
  group_by(document) |>
  slice_max(gamma, n = 1)|> 
  left_join(topicos_nombres, by = "topic")

Cantidad de cuentos por tópico

gamma_dominante |> 
  count(nombre) |> 
  ggplot(aes(x = reorder(nombre, n), y = n)) +
  geom_col(fill = "#112446") +
  coord_flip() +
  theme_minimal() +
  labs(x = "", y = "Cantidad de documentos", title = "Distribución de cuentos por tópico dominante")

Unimos todo

cuentos_clasificados <- gamma_dominante |> 
  select(document, topic_final = topic, nombre_topico = nombre, probabilidad = gamma) |> 
  mutate(document = as.numeric(document)) |> 
  left_join(borges |> select(id, author, text), by = c("document" = "id"))

head(cuentos_clasificados)
# A tibble: 6 × 6
# Groups:   document [6]
  document topic_final nombre_topico probabilidad author            text        
     <dbl>       <int> <chr>                <dbl> <chr>             <chr>       
1      144           1 Acciones             0.542 Jorge Luis Borges Abel y Caín…
2      145           5 Reflexiones          0.994 Jorge Luis Borges Era muy lin…
3      146           3 Existencia           0.999 Jorge Luis Borges ¿Qué soñará…
4      147           3 Existencia           0.999 Jorge Luis Borges Los años le…
5      148           3 Existencia           0.998 Jorge Luis Borges Cierro los …
6      149           5 Reflexiones          1.00  Jorge Luis Borges El seis de …

Recursos