IA para Científicos Sociales

Sesión 3.2: Análisis computacional de texto

Danilo Freire

Department of Data and Decision Sciences
Emory University

Texto como datos

Agenda de la sesión

Primera parte

  • El texto como datos
  • Tokenización y preprocesamiento
  • Bag-of-words
  • TF-IDF
  • Análisis de sentimiento

Segunda parte

  • Topic Modeling (LDA)
  • Introducción a embeddings
  • Word2Vec y representaciones densas
  • Aplicaciones en política y medios
  • Limitaciones y próximos pasos

El texto como datos

¿Por qué analizar texto con computadoras?

  • Las ciencias sociales producen y estudian enormes cantidades de texto:
    • Discursos parlamentarios
    • Artículos periodísticos
    • Respuestas a encuestas abiertas
    • Publicaciones en redes sociales
    • Documentos legales y de política pública
  • Un investigador puede leer cientos de textos. Una computadora puede analizar millones
  • El análisis computacional de texto permite:
    • Medir temas y opiniones a gran escala
    • Descubrir patrones invisibles a la lectura manual
    • Comparar textos entre países, períodos o actores

El desafío central

Las computadoras trabajan con números, pero el texto son palabras

¿Cómo convertirlas en números preservando el significado?

"La democracia es
 importante para el
 desarrollo"

        ↓ ???

[0.23, 0.87, 0.12,
 0.45, 0.91, 0.33]

Esta es la pregunta central del análisis de texto

El pipeline de análisis de texto

Pipeline de análisis de texto
  • Paso 1: Recoger el corpus (conjunto de textos)
  • Paso 2: Limpiar: quitar puntuación, convertir a minúsculas, eliminar palabras vacías (stopwords)
  • Paso 3: Representar el texto como números (bag-of-words, TF-IDF, embeddings)
  • Paso 4: Aplicar un modelo (sentimiento, tópicos, clasificación)
  • Paso 5: Interpretar los resultados en contexto
  • Hoy nos enfocaremos en los pasos 2 a 4 con métodos clásicos (no deep learning)
  • Mañana veremos cómo los LLMs cambian este panorama

Preprocesamiento

Nuestro corpus de trabajo

Un corpus simulado de textos políticos

  • 60 textos cortos en español sobre política pública
  • 5 temas (12 textos cada uno):
    • economía, educación, medio ambiente, salud, seguridad
  • Varios países de América Latina, entre 2018 y 2024
  • Los textos son simulados: no provienen de discursos reales, pero imitan el vocabulario típico de cada tema
  • Usaremos el mismo corpus en todos los ejemplos de hoy: tokenización, TF-IDF, sentimiento, LDA y embeddings
  • Paquetes principales: tidytext (tokenización y TF-IDF), tm (stopwords), topicmodels (LDA), además de tidyverse

Cargar y explorar el corpus

R> # Cargar el corpus simulado (60 textos x 5 temas)
R> corpus <- read_csv("datos/textos_politicos.csv")
R> 
R> # Estructura del corpus
R> head(corpus)
# A tibble: 6 × 5
     id pais      tema      anio texto                                          
  <dbl> <chr>     <chr>    <dbl> <chr>                                          
1     1 México    economia  2020 El crecimiento económico del país ha sido sóli…
2     2 Argentina economia  2024 La inflación sigue siendo un problema grave qu…
3     3 Venezuela economia  2021 El gobierno anunció un nuevo plan de estímulo …
4     4 Brasil    economia  2024 Las tasas de desempleo han alcanzado niveles h…
5     5 Brasil    economia  2022 La política monetaria del banco central ha log…
6     6 Bolivia   economia  2019 Los acuerdos comerciales con Asia representan …
R> # Distribución por tema
R> corpus |> count(tema)
# A tibble: 5 × 2
  tema              n
  <chr>         <int>
1 economia         12
2 educacion        12
3 medioambiente    12
4 salud            12
5 seguridad        12
R> # Primer texto del corpus
R> corpus$texto[1]
[1] "El crecimiento económico del país ha sido sólido este año gracias a las exportaciones agrícolas y la inversión extranjera directa."

Tokenización

  • Tokenización: dividir el texto en unidades más pequeñas (tokens)
  • Los tokens pueden ser:
    • Palabras: “la democracia es importante” → [“la”, “democracia”, “es”, “importante”]
    • N-gramas: secuencias de N palabras
      • Bigramas: [“la democracia”, “democracia es”, “es importante”]
    • Subpalabras: para modelos de lenguaje (lo veremos mañana)
  • La tokenización es el primer paso de casi todo análisis de texto
  • En R, usamos el paquete tidytext con la función unnest_tokens()
  • tidytext sigue la filosofía de tidyverse: una fila por token

Tokenización de nuestro corpus

R> # Tokenizar los 60 textos del corpus
R> tokens <- corpus |>
+   select(id, tema, texto) |>
+   unnest_tokens(palabra, texto)
R> 
R> head(tokens, 8)
# A tibble: 8 × 3
     id tema     palabra    
  <dbl> <chr>    <chr>      
1     1 economia el         
2     1 economia crecimiento
3     1 economia económico  
4     1 economia del        
5     1 economia país       
6     1 economia ha         
7     1 economia sido       
8     1 economia sólido     

tidytext convierte a minúsculas y genera una fila por token

Stopwords y limpieza

  • Stopwords (palabras vacías): palabras muy frecuentes que aportan poco significado
    • En español: “el”, “la”, “de”, “en”, “que”, “y”, “a”, “los”…
  • Las eliminamos para centrarnos en las palabras con contenido
  • Otros pasos de limpieza:
    • Quitar números y puntuación; convertir a minúsculas
    • Stemming: reducir a la raíz (“democracia/democrático” → “democra-”)
    • Lematización: reducir al lema (“gobierna/gobernó” → “gobernar”)
R> # Stopwords en español (paquete tm) ampliadas con algunos
R> # verbos y palabras muy frecuentes en nuestro corpus
R> extra_stop <- c("sigue", "siendo", "sido", "ser", "hacer",
+   "ha", "han", "hay", "más", "país", "países", "región",
+   "regiones", "gobierno", "toda", "todos", "todas")
R> stop_es <- tibble(
+   palabra = unique(c(tm::stopwords("spanish"), extra_stop))
+ )
R> 
R> # Eliminar números y stopwords
R> # !str_detect(palabra, "^[0-9]+$") filtra tokens 
R> # que son solo números
R> # anti_join() elimina las stopwords
R> tokens_limpios <- tokens |>
+   filter(!str_detect(palabra, "^[0-9]+$")) |>
+   anti_join(stop_es, by = "palabra")
R> 
R> tibble(
+   etapa = c("con stopwords", "sin stopwords"),
+   tokens = c(nrow(tokens), nrow(tokens_limpios))
+ )
# A tibble: 2 × 2
  etapa         tokens
  <chr>          <int>
1 con stopwords    842
2 sin stopwords    460

Eliminar stopwords reduce el corpus en un 45% y deja solo palabras con contenido

Representaciones numéricas del texto

Bag-of-words

  • Bag-of-words (bolsa de palabras): representar un texto como un vector de frecuencias
  • Contar cuántas veces aparece cada palabra
  • Ignora el orden de las palabras (por eso se llama “bolsa”)
  • “El gato come pescado” y “El pescado come gato” tienen la misma representación
  • Simple pero sorprendentemente efectivo para muchos problemas
  • Limitaciones: no captura orden ni significado, y las palabras frecuentes dominan
R> # Vocabulario de muestra (palabras representativas)
R> terminos <- c("económico", "educación", "violencia",
+               "salud", "pandemia", "estudiantes")
R> 
R> # Matriz término-documento: pivot_wider transforma
R> # (id, palabra, n) en una columna por palabra.
R> # values_fill = 0 pone cero donde la palabra no aparece.
R> bow <- tokens_limpios |>
+   filter(id %in% c(1, 13, 25),
+          palabra %in% terminos) |>
+   count(id, palabra) |>
+   pivot_wider(names_from = palabra,
+               values_from = n,
+               values_fill = 0)
R> bow
# A tibble: 3 × 4
     id económico educación violencia
  <dbl>     <int>     <int>     <int>
1     1         1         0         0
2    13         0         1         0
3    25         0         0         1

Docs 1 (economía, México), 13 (educación, Paraguay) y 25 (seguridad, Panamá)

Cada fila es el vector que representa al documento

TF-IDF

  • TF-IDF: Term Frequency - Inverse Document Frequency (el concepto de IDF viene de Sparck Jones, 1972)
  • Problema con bag-of-words: las palabras más frecuentes no siempre son las más informativas
  • TF-IDF pondera la frecuencia de una palabra por su rareza en el corpus:

\[\text{TF-IDF}(t, d) = \text{TF}(t, d) \times \log\left(\frac{N}{\text{DF}(t)}\right)\]

  • TF (Term Frequency): frecuencia del término en el documento
  • IDF (Inverse Document Frequency): \(\log(N / \text{documentos que contienen el término})\)
  • Una palabra que aparece mucho en un documento pero poco en el corpus tiene TF-IDF alto
  • Resultado: identifica las palabras distintivas de cada documento

Palabras más distintivas por tema (TF-IDF real)

R> # Calcular TF-IDF de cada palabra en cada tema
R> palabras_puntuadas <- tokens_limpios |>
+   count(tema, palabra) |>
+   bind_tf_idf(palabra, tema, n)
R> 
R> # Top 3 palabras distintivas por tema
R> palabras_puntuadas |>
+   group_by(tema) |>
+   slice_max(tf_idf, n = 3) |>
+   select(tema, palabra, tf_idf) |>
+   mutate(tf_idf = round(tf_idf, 3))
# A tibble: 80 × 3
# Groups:   tema [5]
   tema          palabra       tf_idf
   <chr>         <chr>          <dbl>
 1 economia      económico      0.05 
 2 economia      crecimiento    0.033
 3 economia      exportaciones  0.033
 4 economia      fiscal         0.033
 5 economia      laboral        0.033
 6 educacion     educación      0.068
 7 educacion     estudiantes    0.051
 8 educacion     escolar        0.034
 9 medioambiente biodiversidad  0.037
10 medioambiente sostenible     0.037
# ℹ 70 more rows

TF-IDF identifica las palabras características de cada tema: económico, educación, biodiversidad, etc

Análisis de sentimiento

¿Qué es el análisis de sentimiento?

  • Análisis de sentimiento: clasificar un texto como positivo, negativo o neutro
  • Método más simple: diccionarios con listas de palabras valoradas
    • “crecimiento”, “mejora” → positivo; “crisis”, “pobreza” → negativo
  • Se cuentan palabras positivas y negativas y se comparan
  • Limitaciones: no captura negaciones (“no es bueno” cuenta como positivo) ni ironía; los diccionarios son dependientes del dominio
  • Aun así, funciona razonablemente bien a gran escala

Sentimiento por tema en el corpus

R> # Diccionario mínimo en español
R> pos <- c("crecimiento", "sólido", "mejora",
+   "mejorado", "oportunidad", "beneficiará",
+   "innovadores", "fortalecido", "efectivos",
+   "estabilizar", "reducir", "renovables")
R> neg <- c("inflación", "problema", "vulnerables",
+   "desempleo", "deuda", "riesgo", "crisis",
+   "violencia", "amenaza", "deficiencias",
+   "grave", "graves", "pobreza", "crimen",
+   "narcotráfico", "inseguridad", "deforestación",
+   "contaminación", "informalidad")
R> 
R> sentimiento <- tokens_limpios |>
+   mutate(valencia = case_when(
+     palabra %in% pos ~  1L, # L para entero (integer)
+     palabra %in% neg ~ -1L,
+     TRUE ~ 0L
+   )) |>
+   group_by(id, tema) |>
+   summarise(puntaje = sum(valencia),
+             .groups = "drop") # desagrupar
R> 
R> sentimiento |>
+   group_by(tema) |>
+   summarise(promedio = round(mean(puntaje), 2),
+             n = n())
# A tibble: 5 × 3
  tema          promedio     n
  <chr>            <dbl> <int>
1 economia         -0.17    12
2 educacion         0.25    12
3 medioambiente     0       12
4 salud            -0.25    12
5 seguridad        -0.5     12

Seguridad y salud son los temas más negativos; educación, el más positivo

Topic Modeling

LDA: Latent Dirichlet Allocation

  • LDA (Latent Dirichlet Allocation, Blei, Ng y Jordan, 2003): el método más usado para descubrir temas en un corpus
  • Idea: cada documento es una mezcla de temas, y cada tema es una mezcla de palabras
  • LDA descubre ambas mezclas simultáneamente
  • Ejemplo:
    • Tema 1 (economía): “crecimiento”, “inflación”, “PIB”, “empleo”
    • Tema 2 (salud): “hospital”, “vacunación”, “pandemia”, “médicos”
    • Documento X: 70% tema 1 + 30% tema 2
  • El usuario elige el número de temas (K)
  • LDA es un modelo generativo: asume que los documentos fueron “generados” por este proceso de mezcla

Ajustar LDA con K=5 en nuestro corpus

R> # Construir matriz documento-término
R> dtm <- tokens_limpios |>
+   count(id, palabra) |>
+   cast_dtm(id, palabra, n)
R> 
R> # Ajustar LDA con 5 tópicos
R> set.seed(123)
R> modelo_lda <- LDA(
+   dtm,
+   k = 5,
+   control = list(seed = 123)
+ )
R> 
R> modelo_lda
A LDA_VEM topic model with 5 topics.

El modelo fue ajustado sobre 60 documentos y 352 términos únicos. Veremos las palabras más probables de cada tópico en la próxima diapositiva

Interpretar temas de LDA

Lo que LDA nos da

  • Las palabras más probables de cada tema
  • La proporción de cada tema en cada documento
  • Podemos etiquetar los temas según las palabras más frecuentes

Palabras principales de cada tópico (LDA real)

R> tidy(modelo_lda, matrix = "beta") |>
+   group_by(topic) |>
+   slice_max(beta, n = 5, with_ties = FALSE) |>
+   summarise(palabras = paste(term, collapse = ", "))
# A tibble: 5 × 2
  topic palabras                                                    
  <int> <chr>                                                       
1     1 comunidades, salud, laboral, central, logrado               
2     2 programas, miles, educación, estudiantes, públicas          
3     3 salud, familias, fiscal, pandemia, significativamente       
4     4 representan, sistemas, oportunidad, mejorar, mejorado       
5     5 inversión, económico, gracias, infraestructura, insuficiente

→ Interpretación tentativa: tópico 2 ≈ educación, tópico 3 ≈ salud y políticas sociales, tópico 5 ≈ economía e inversión. Los tópicos 1 y 4 son ambiguos: LDA no siempre separa bien los temas en corpora pequeños

Limitaciones de LDA

  • Elegir K es difícil y los temas pueden resultar incoherentes
  • No captura relaciones entre palabras (eso lo hacen los embeddings)
  • Los temas no tienen nombre: los pone el investigador
  • Funciona mal en textos cortos (tweets, respuestas breves)
  • Los LLMs modernos lo hacen mucho mejor (Día 4)

Introducción a embeddings

De representaciones dispersas a densas

  • Bag-of-words y TF-IDF son representaciones dispersas (sparse): cada palabra es una dimensión independiente y no capturan significado
    • “perro” y “can” son tan distintos como “perro” y “democracia”
    • No manejan sinónimos, polisemia ni orden
  • Un embedding es un vector denso que representa una palabra: las que tienen significados similares quedan cercanas
  • Idea central: “una palabra se conoce por la compañía que frecuenta” (Firth, 1957). Si dos palabras aparecen en contextos similares, sus embeddings se parecen
  • Métodos clásicos: Word2Vec (2013), GloVe (2014); modernos: BERT, GPT (mañana)

Disperso vs. denso

Bag-of-words (disperso):
"perro" = [0,0,0,1,0,0,0,...]
  (10.000 dims, casi todo cero)

Embedding (denso):
"perro" = [0.23, -0.45, 0.12, ...]
  (300 dims, todas con valores)

El embedding codifica
SIGNIFICADO en cada dimensión

Word2Vec: la idea

  • Word2Vec (Mikolov et al., 2013): entrenar una red neuronal para predecir palabras a partir de su contexto
  • Dos arquitecturas:
    • CBOW (Continuous Bag of Words): predecir la palabra central dado el contexto
    • Skip-gram: predecir el contexto dada la palabra central
  • El “subproducto” del entrenamiento son los embeddings
  • Propiedades emergentes:
    • Aritmética de vectores: rey - hombre + mujer ≈ reina
    • Analogías: París:Francia :: Madrid:España

King - Man + Woman = Queen

Los embeddings capturan relaciones semánticas como operaciones vectoriales

Embeddings en la práctica

Embeddings preentrenados

De palabras a documentos

  • Para representar documentos completos:
    • Promedio de los embeddings de sus palabras (con o sin ponderar por TF-IDF)
    • Doc2Vec: extensión de Word2Vec
    • Sentence-BERT: embeddings de oraciones con transformers
  • Permiten medir similitud semántica entre textos

En el apéndice construimos mini-embeddings con LSA (SVD sobre TF-IDF) y buscamos palabras y documentos similares en nuestro corpus   Ver demo (LSA)

Aplicaciones

Aplicaciones en política y medios

Líneas de investigación activas

  • Discursos políticos: posicionamiento ideológico, agenda del gobierno, polarización entre bloques
  • Cobertura mediática: sesgo editorial, framing, tono sobre temas económicos o electorales
  • Manifiestos de partidos: clasificación automática de posiciones en escalas izquierda-derecha
  • Detección de eventos: conflicto, protestas y corrupción a partir de noticias

Método dominante: pipeline clásico (tokenización → TF-IDF / LDA / sentimiento) sobre corpus grandes, con validación humana en una muestra

Lecturas fundacionales

  • Grimmer & Stewart (2013) “Text as Data”, Political Analysis (manifiesto metodológico del campo)
  • Laver, Benoit & Garry (2003) “Extracting Policy Positions from Political Texts Using Words as Data”, APSR (Wordscores sobre manifiestos)
  • Roberts, Stewart & Tingley (2014) “Structural Topic Models”, AJPS (LDA con covariables)

Fuentes de datos

¿Qué método para qué problema?

Problema de investigación Método recomendado Por qué
Contar palabras y frases comunes Bag-of-words Simple, interpretable, rápido
Identificar términos distintivos de un grupo TF-IDF Pondera por rareza en el corpus
Descubrir temas latentes sin etiquetas LDA No supervisado, útil si no saben qué temas buscan
Medir el tono de un texto Diccionarios de sentimiento Transparente y validable
Medir similitud semántica entre textos Embeddings (Word2Vec, LSA) Capturan significado, no solo palabras
Extraer entidades (personas, lugares, partidos) NER (paquetes spacyr, cleanNLP) Identifica menciones específicas en el texto
Clasificar, resumir, extraer información compleja LLMs (Día 4) Estado del arte en casi todas las tareas

Regla práctica: empezar simple, validar contra lectura manual y escalar a métodos complejos solo si el simple falla

Limitaciones de los métodos clásicos

Bag-of-words / TF-IDF:

  • No capturan orden de palabras
  • No capturan significado
  • Dependen de stopwords predefinidas
  • Sensibles a errores ortográficos

LDA:

  • Elegir K es arbitrario
  • Temas pueden ser incoherentes
  • Malo para textos cortos
  • No captura relaciones entre temas


Los LLMs (transformers) superan estas limitaciones

Mañana veremos cómo BERT, GPT y otros modelos revolucionan el análisis de texto

Resumen y próximos pasos

Lo que vimos hoy

  • Tokenización y stopwords
  • Bag-of-words y TF-IDF
  • Sentimiento con diccionarios
  • LDA: temas latentes
  • Embeddings: representaciones densas
  • Caso integrado sobre encuesta abierta (apéndice)

Los métodos clásicos son el fundamento del análisis de texto. Entenderlos es la base para usar bien los LLMs

Qué viene

  • Laboratorios 3.3 y 3.4:
    • Clustering y PCA con datos de países latinoamericanos
    • Análisis de textos políticos con TF-IDF y LDA
  • Día 4: LLMs y aplicaciones avanzadas
    • Transformers, BERT, GPT
    • APIs y casos de investigación
    • Cuándo un LLM supera al pipeline clásico (y cuándo no)

Apéndice

Mini-embeddings de palabras con LSA

Aplicamos SVD sobre la matriz TF-IDF (palabra x documento) para obtener mini-embeddings: cada palabra y cada documento queda representado en 10 dimensiones. Es la idea detrás de LSA (Latent Semantic Analysis)

R> # Matriz palabra x documento con TF-IDF
R> matriz <- tokens_limpios |>
+   count(id, palabra) |>
+   bind_tf_idf(palabra, id, n) |>
+   cast_sparse(palabra, id, tf_idf) |>
+   as.matrix()
R> 
R> # SVD: cada palabra en 10 dimensiones (nu = 10)
R> # y cada documento en 10 dimensiones (nv = 10)
R> # $u = embeddings de palabras; $v = los de documentos
R> reduccion <- svd(matriz, nu = 10, nv = 10)
R> embeddings <- reduccion$u
R> rownames(embeddings) <- rownames(matriz)
R> 
R> # Similitud coseno entre dos vectores
R> coseno <- function(a, b) {
+   sum(a * b) / (sqrt(sum(a^2)) * sqrt(sum(b^2)))
+ }
R> 
R> # Vecinos más cercanos de "salud"
R> objetivo <- embeddings["salud", ]
R> sims <- apply(embeddings, 1, coseno, b = objetivo)
R> head(sort(sims, decreasing = TRUE), 6)
     salud   infantil    materno mortalidad   neonatal   reducido 
  1.000000   0.926154   0.926154   0.926154   0.926154   0.926154 

Los vecinos más cercanos de “salud” son palabras del mismo campo semántico (mortalidad, infantil, materno), aprendidas sin etiquetas

Búsqueda semántica de documentos

Con los embeddings de documentos (la matriz \(v\) del SVD) buscamos los textos más parecidos a uno dado, midiendo similitud coseno

R> # Embeddings de documentos desde el SVD:
R> # cada fila es un documento (10 dims)
R> doc_emb <- reduccion$v
R> rownames(doc_emb) <- colnames(matriz)
R> 
R> # Vector de consulta: la "huella semántica" del
R> # doc 1 (economía, México)
R> consulta <- doc_emb["1", ]
R> 
R> # Top 4 documentos más similares
R> top <- apply(doc_emb, 1, coseno, b = consulta) |>
+   sort(decreasing = TRUE) |>
+   head(4)
R> 
R> # Mostrar id, similitud y tema desde el corpus
R> tibble(id = as.integer(names(top)),
+        sim = round(top, 3)) |>
+   left_join(corpus |> select(id, tema), by = "id")
# A tibble: 4 × 3
     id   sim tema         
  <dbl> <dbl> <chr>        
1     1 1     economia     
2    12 0.963 economia     
3    50 0.952 medioambiente
4     6 0.945 economia     

Los documentos más cercanos al primero (tema economía) son otros del mismo tema y uno de medio ambiente, que comparte vocabulario económico. LSA aprende la similitud sin conocer las etiquetas

Caso integrado (1/2): explorar las respuestas

Pregunta de investigación

¿Cuál es el principal problema del país?

  • 18 respuestas cortas de una encuesta abierta simulada
  • Queremos clasificar cada respuesta en un tema sin leerlas todas
  • Pipeline simple en dos pasos:
    • tokenizar y quitar stopwords; ver las palabras más frecuentes
    • clasificar por tema con un diccionario de palabras clave y medir el tono
  • Todo el análisis usa funciones que ya conocemos

Palabras más frecuentes en las respuestas

R> respuestas <- tibble::tibble(
+   id = 1:18,
+   texto = c(
+     "La economía está muy mal y no hay trabajo",
+     "La inflación y los precios de la economía suben cada mes",
+     "No hay trabajo ni empleo para los jóvenes",
+     "Los precios altos de la economía son un problema",
+     "La economía y el empleo son el mayor problema",
+     "Hay mucha inseguridad y violencia en las calles",
+     "Los robos y la inseguridad aumentan cada día",
+     "La violencia en los barrios es muy grave",
+     "La delincuencia y los robos son el principal problema",
+     "Los políticos son corruptos y no cumplen nada",
+     "Los políticos corruptos arruinan el gobierno",
+     "Los políticos siempre protegen a los corruptos",
+     "El gobierno permite a los políticos corruptos",
+     "Faltan docentes y recursos en las escuelas",
+     "La salud pública está mal, faltan médicos",
+     "La educación en las escuelas es deficiente",
+     "Los hospitales no tienen médicos ni medicinas",
+     "La educación y la salud pública están abandonadas"
+   )
+ )
R> 
R> # Tokenizar y quitar stopwords
R> tokens_resp <- respuestas |>
+   unnest_tokens(palabra, texto) |>
+   anti_join(stop_es, by = "palabra")
R> 
R> # Palabras más frecuentes en el conjunto
R> tokens_resp |>
+   count(palabra, sort = TRUE) |>
+   head(8)
# A tibble: 8 × 2
  palabra       n
  <chr>     <int>
1 corruptos     4
2 economía      4
3 políticos     4
4 problema      3
5 cada          2
6 educación     2
7 empleo        2
8 escuelas      2

Caso integrado (2/2): clasificar e interpretar

Lo que aprendemos

  • Un diccionario de palabras clave asigna cada respuesta al tema que más aparece en ella
  • Contar palabras negativas mide el tono promedio de cada tema
  • Con 18 respuestas surgen 4 grupos claros: economía, seguridad, corrupción y servicios públicos
  • El pipeline escala a miles de respuestas con el mismo código
  • Regla práctica: un humano siempre revisa una muestra para validar las etiquetas automáticas

Respuestas y tono por tema

R> # Asignar un tema a cada palabra del corpus
R> tokens_etiq <- tokens_resp |>
+   mutate(tema = case_when(
+     palabra %in% c("economía", "inflación", "precios", "trabajo", "empleo") ~ "economía",
+     palabra %in% c("inseguridad", "violencia", "robos", "delincuencia") ~ "seguridad",
+     palabra %in% c("corruptos", "políticos", "gobierno") ~ "corrupción",
+     palabra %in% c("educación", "salud", "escuelas", "hospitales", "médicos", "docentes") ~ "servicios"
+   ))
R> 
R> # Tema dominante de cada respuesta
R> clasificacion <- tokens_etiq |>
+   filter(!is.na(tema)) |>
+   count(id, tema) |>
+   slice_max(n, by = id, n = 1)
R> 
R> # Diccionario breve de palabras negativas
R> neg <- c("mal", "inflación", "corruptos",
+          "inseguridad", "robos", "violencia",
+          "delincuencia", "grave", "deficiente",
+          "abandonadas", "faltan", "arruinan")
R> 
R> # Palabras negativas por respuesta
R> tono <- tokens_resp |>
+   summarise(n_neg = sum(palabra %in% neg), .by = id)
R> 
R> # Resumen: cantidad de respuestas y tono por tema
R> clasificacion |>
+   left_join(tono, by = "id") |>
+   summarise(respuestas = n(),
+             negativas = round(mean(n_neg), 2),
+             .by = tema) |>
+   arrange(desc(negativas))
# A tibble: 4 × 3
  tema       respuestas negativas
  <chr>           <int>     <dbl>
1 seguridad           4      2   
2 corrupción          4      1.25
3 servicios           5      1   
4 economía            5      0.4 

¡Nos vemos en el laboratorio! 🤓