Sesión 3.4: Laboratorio 6 - Análisis de texto
Lo que vamos a hacer:
Lo que vamos a aprender:
El corpus contiene 60 textos políticos simulados sobre 5 temas y 18 países
| Parte | Contenido | Duración |
|---|---|---|
| 1 | Tokenización y limpieza | ~15 min |
| 2 | TF-IDF + ejercicio | ~17 min |
| 3 | Sentimiento + ejercicio | ~22 min |
| 4 | Síntesis y resumen | ~6 min |
Los textos están etiquetados por tema y país. Vamos a usar dos herramientas complementarias para caracterizarlos. Material extra en los apéndices
R> # Cargar textos políticos
R> textos <- read_csv("datos/textos_politicos.csv")
R> # Lean el archivo directo desde la web:
R> # textos <- read_csv("https://raw.githubusercontent.com/danilofreire/introduccion-ia-ucu/main/clases/dia-03/datos/textos_politicos.csv")
R>
R> # Explorar estructura
R> glimpse(textos)Rows: 60
Columns: 5
$ id <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 1…
$ pais <chr> "México", "Argentina", "Venezuela", "Brasil", "Brasil", "Bolivia…
$ tema <chr> "economia", "economia", "economia", "economia", "economia", "eco…
$ anio <dbl> 2020, 2024, 2021, 2024, 2022, 2019, 2021, 2019, 2018, 2024, 2020…
$ texto <chr> "El crecimiento económico del país ha sido sólido este año graci…
# A tibble: 5 × 2
tema n
<chr> <int>
1 economia 12
2 educacion 12
3 medioambiente 12
4 salud 12
5 seguridad 12
# A tibble: 5 × 2
tema texto
<chr> <chr>
1 economia El crecimiento económico del país ha sido sólido este año graci…
2 educacion La inversión en educación pública ha permitido aumentar la cobe…
3 medioambiente La deforestación en la Amazonia ha alcanzado niveles récord ame…
4 salud El sistema de salud pública ha sido fortalecido con nuevos hosp…
5 seguridad Las políticas de mano dura contra el crimen organizado no han l…
# A tibble: 20 × 5
id pais tema anio palabra
<dbl> <chr> <chr> <dbl> <chr>
1 1 México economia 2020 el
2 1 México economia 2020 crecimiento
3 1 México economia 2020 económico
4 1 México economia 2020 del
5 1 México economia 2020 país
6 1 México economia 2020 ha
7 1 México economia 2020 sido
8 1 México economia 2020 sólido
9 1 México economia 2020 este
10 1 México economia 2020 año
11 1 México economia 2020 gracias
12 1 México economia 2020 a
13 1 México economia 2020 las
14 1 México economia 2020 exportaciones
15 1 México economia 2020 agrícolas
16 1 México economia 2020 y
17 1 México economia 2020 la
18 1 México economia 2020 inversión
19 1 México economia 2020 extranjera
20 1 México economia 2020 directa
unnest_tokens() automáticamente convierte a minúsculas y elimina puntuación
Para bigramas: unnest_tokens(palabra, texto, token = "ngrams", n = 2)
# A tibble: 20 × 1
palabra
<chr>
1 de
2 la
3 que
4 el
5 en
6 y
7 a
8 los
9 del
10 se
11 las
12 por
13 un
14 para
15 con
16 no
17 una
18 su
19 al
20 lo
[1] 308
Tokens antes: 842
Tokens después: 485
Reducción: 42.4 %
R> # Top 20 palabras
R> tokens_limpios |>
+ count(palabra, sort = TRUE) |>
+ head(20) |>
+ mutate(palabra = reorder(palabra, n)) |>
+ ggplot(aes(x = n, y = palabra)) +
+ geom_col(fill = "#2d4563") +
+ labs(title = "20 palabras más frecuentes (sin stopwords)",
+ x = "Frecuencia", y = NULL) +
+ theme_minimal()\[\text{TF-IDF} = \text{TF} \times \log\left(\frac{\text{Total documentos}}{\text{Documentos con el término}}\right)\]
TF-IDF identifica qué palabras caracterizan a cada documento o grupo
# A tibble: 15 × 6
tema palabra n tf idf tf_idf
<chr> <chr> <int> <dbl> <dbl> <dbl>
1 seguridad seguridad 4 0.0421 1.61 0.0678
2 salud salud 7 0.0737 0.916 0.0675
3 educacion educación 4 0.0412 1.61 0.0664
4 seguridad violencia 3 0.0316 1.61 0.0508
5 educacion estudiantes 3 0.0309 1.61 0.0498
6 economia económico 3 0.0286 1.61 0.0460
7 economia país 3 0.0286 1.61 0.0460
8 medioambiente biodiversidad 2 0.0215 1.61 0.0346
9 medioambiente sostenible 2 0.0215 1.61 0.0346
10 salud acceso 2 0.0211 1.61 0.0339
11 salud internacional 2 0.0211 1.61 0.0339
12 salud sistema 2 0.0211 1.61 0.0339
13 seguridad justicia 2 0.0211 1.61 0.0339
14 seguridad social 2 0.0211 1.61 0.0339
15 educacion escolar 2 0.0206 1.61 0.0332
R> # Top 6 palabras por tema
R> tfidf |>
+ group_by(tema) |>
+ slice_max(tf_idf, n = 6, with_ties = FALSE) |>
+ ungroup() |>
+ mutate(palabra = reorder_within(palabra, tf_idf, tema)) |>
+ ggplot(aes(x = tf_idf, y = palabra, fill = tema)) +
+ geom_col(show.legend = FALSE) +
+ facet_wrap(~tema, scales = "free_y", ncol = 3) +
+ scale_y_reordered() +
+ labs(title = "Palabras más distintivas por tema (TF-IDF)",
+ x = "TF-IDF", y = NULL) +
+ theme_minimal()¿Las palabras tienen sentido?
Ojo: “país” aparece alto en economía. Palabras genéricas pueden colarse; por eso a veces conviene ampliar las stopwords
Limitaciones:
TF-IDF es simple pero efectivo para identificar palabras clave
Tarea (5 min): calculen TF-IDF agrupando por pais en lugar de tema. ¿Qué palabras distinguen a cada país?
# Mismo patrón que antes, cambiando tema → pais
tfidf_pais <- tokens_limpios |>
count(___, palabra) |> # TODO: agrupar por
bind_tf_idf(palabra, ___, n) # TODO: columna de agrupamiento
# Top 5 palabras distintivas por país
tfidf_pais |>
group_by(___) |> # TODO
slice_max(tf_idf, n = 5, with_ties = FALSE) |>
arrange(pais, desc(tf_idf))Nota
Pista: cambien las dos referencias a tema por pais. La función bind_tf_idf() toma (palabra, grupo, n), así que el segundo argumento es el grupo
crecimiento, mejora, éxito → positivocrisis, violencia, pobreza → negativoR> # Diccionarios para texto político (versión inicial — ampliable)
R> positivas <- c("crecimiento", "mejora", "mejorado", "oportunidad",
+ "fortalecido", "innovadores", "renovables", "sólido",
+ "beneficio", "avance", "estabilizar", "reducir")
R>
R> negativas <- c("crisis", "inflación", "pobreza", "violencia", "amenaza",
+ "problema", "deuda", "riesgo", "deficiencias", "grave",
+ "crimen", "narcotráfico", "deforestación", "informalidad",
+ "inseguridad", "contaminación", "desempleo", "vulnerables")
R>
R> cat("Positivas:", length(positivas), " Negativas:", length(negativas), "\n")Positivas: 12 Negativas: 18
R> # Score por documento = #positivas − #negativas
R> sentimiento_doc <- tokens_limpios |>
+ mutate(valencia = case_when(
+ palabra %in% positivas ~ 1L,
+ palabra %in% negativas ~ -1L,
+ TRUE ~ 0L
+ )) |>
+ group_by(id, tema, pais) |>
+ summarise(score = sum(valencia), .groups = "drop")
R>
R> # Documentos más positivos y más negativos
R> sentimiento_doc |>
+ arrange(desc(score)) |>
+ head(3)# A tibble: 3 × 4
id tema pais score
<dbl> <chr> <chr> <int>
1 1 economia México 2
2 50 medioambiente Bolivia 2
3 5 economia Brasil 1
# A tibble: 3 × 4
id tema pais score
<dbl> <chr> <chr> <int>
1 2 economia Argentina -4
2 7 economia Panamá -2
3 26 seguridad El Salvador -2
case_when() asigna +1 a positivas, -1 a negativas, 0 a las demásscore es la suma de valencias por documento. Score positivo → tono optimista; negativo → tono críticoR> sentimiento_doc |>
+ group_by(tema) |>
+ summarise(score_promedio = mean(score),
+ .groups = "drop") |>
+ mutate(tema = reorder(tema, score_promedio)) |>
+ ggplot(aes(x = score_promedio, y = tema, fill = score_promedio > 0)) +
+ geom_col(show.legend = FALSE) +
+ scale_fill_manual(values = c("TRUE" = "#27AE60", "FALSE" = "#E74C3C")) +
+ labs(title = "Tono promedio por tema",
+ x = "Score promedio (positivas − negativas)", y = NULL) +
+ theme_minimal()Tarea (8 min): identifiquen el documento con el score de sentimiento más negativo y léanlo. ¿El método captura bien su tono?
Nota
Pista: para ordenar de menor (más negativo) a mayor, arrange() sin desc() ya lo hace. La columna que identifica el documento es la misma en textos y sentimiento_doc
Tip
Checkpoint: van a ver un texto sobre una crisis o un problema grave (por ejemplo, inflación o inseguridad). Pregunta de reflexión: ¿el método le da el “score” correcto, o exagera/subestima el tono real?
TF-IDF: ¿de qué habla el texto?
Sentimiento: ¿cómo lo dice?
Combinadas, las dos herramientas construyen una narrativa: “Los textos sobre seguridad (TF-IDF) tienen un tono marcadamente negativo (sentimiento).” Una herramienta sola rara vez alcanza.
Regla práctica: antes de creerle a un método automático, lean una muestra de textos y validen que el análisis cuadra con su lectura
Lo que practicamos:
unnest_tokens()bind_tf_idf() (por tema y por país)Mañana veremos cómo los LLMs hacen estas dos tareas (caracterizar y medir tono) en una sola llamada. Material extra en los apéndices
# A tibble: 90 × 6
# Groups: pais [18]
pais palabra n tf idf tf_idf
<chr> <chr> <int> <dbl> <dbl> <dbl>
1 Argentina infraestructura 2 0.0513 2.89 0.148
2 Argentina problema 2 0.0513 2.89 0.148
3 Argentina años 1 0.0256 2.89 0.0741
4 Argentina bosque 1 0.0256 2.89 0.0741
5 Argentina comerciantes 1 0.0256 2.89 0.0741
6 Bolivia oportunidad 2 0.0526 2.89 0.152
7 Bolivia representan 2 0.0526 1.79 0.0943
8 Bolivia acuerdos 1 0.0263 2.89 0.0761
9 Bolivia amazonia 1 0.0263 2.89 0.0761
10 Bolivia amenazando 1 0.0263 2.89 0.0761
# ℹ 80 more rows
slice_max() deja cinco por país y las tibbles muestran diez filas por defecto. Saquen el slice_max() y agreguen print(n = Inf): tfidf_pais |> arrange(pais, desc(tf_idf)) |> print(n = Inf). Como la tabla es larga, en RStudio suele ser más cómodo View(tfidf_pais)# A tibble: 1 × 4
id tema pais score
<dbl> <chr> <chr> <int>
1 2 economia Argentina -4
[1] "La inflación sigue siendo un problema grave que afecta a las familias más vulnerables del país."
R> # Contar frecuencias
R> freq <- tokens_limpios |>
+ count(palabra, sort = TRUE) |>
+ head(100)
R>
R> # Nube de palabras
R> ggplot(freq, aes(label = palabra, size = n, color = n)) +
+ geom_text_wordcloud(rm_outside = TRUE) +
+ scale_size_area(max_size = 15) +
+ scale_color_gradient(low = "#457b9d", high = "#e63946") +
+ theme_minimal() +
+ labs(title = "Nube de palabras del corpus")Aprendizaje no supervisado:
Análisis de texto:
Estos métodos son exploratorios. Los LLMs (Día 4) los superan en precisión y flexibilidad