IA para Científicos Sociales

Sesión 3.1: Clustering y reducción de dimensionalidad

Danilo Freire

Department of Data and Decision Sciences
Emory University

Día 3: Texto y aprendizaje no supervisado

Repaso del Día 2

  • Regresión logística: interpretable, produce probabilidades
  • Árboles de decisión: intuitivos pero sobreajustan
  • Random Forest: combina muchos árboles para mejor predicción
  • Predicción vs. explicación: dos objetivos diferentes en ciencias sociales
  • Regularización: LASSO selecciona variables, Ridge reduce coeficientes
  • tidymodels permite comparar modelos con la misma sintaxis

Agenda de la sesión

Primera parte: clustering

  • ¿Qué es el aprendizaje no supervisado?
  • K-means: algoritmo y limitaciones
  • Elegir K: método del codo y silueta
  • Ejemplo práctico en R
  • Clustering jerárquico y comparación

Segunda parte: PCA

  • Reducción de dimensionalidad: PCA
  • ¿Cuántos componentes conservar?
  • Interpretación de loadings y biplot
  • Aplicaciones en ciencias sociales

¿Qué es el aprendizaje no supervisado?

Aprender sin etiquetas

  • En aprendizaje supervisado teníamos respuestas correctas (etiquetas). ¡Acá no las hay!
  • El modelo descubre patrones ocultos por sí mismo
  • ¿Por qué usarlo?
    • Las etiquetas son costosas o no existen (“¿cuántos tipos de votantes hay?”)
    • Sirve para explorar los datos antes de modelar
  • Dos técnicas centrales:
    • Clustering: agrupar observaciones similares
    • Reducción de dimensionalidad: simplificar datos con muchas variables

El aprendizaje no supervisado descubre estructura

Fuente: Wikipedia

K-means

¿Qué es K-means?

  • K-means: el algoritmo de clustering más popular
  • Particiona los datos en K grupos (clusters)
  • Los puntos dentro del mismo cluster son similares entre sí
  • Los puntos en clusters diferentes son disimilares
  • ¿Cómo funciona?
    1. Elegir K centros aleatoriamente
    2. Asignar cada punto al centro más cercano
    3. Mover cada centro al promedio de sus puntos asignados
    4. Repetir pasos 2-3 hasta que los centros no se muevan
  • Simple, rápido y funciona bien en muchos casos

K-means en acción

Fuente: Machine Learning CoBan

¿Cómo elegir K?

  • El usuario elige K de antemano. No existe un K “correcto” universal
  • Métodos para orientarse:
    • Método del codo: graficar la suma de distancias internas y buscar dónde se estabiliza la mejora
    • Silueta: qué tan bien cada punto encaja en su cluster (cerca de 1 es bueno)
    • Conocimiento del dominio: ¿cuántos grupos tienen sentido teóricamente?
  • En ciencias sociales, el contexto teórico suele pesar más que las métricas
  • Ejemplo: ¿cuántos “tipos” de regímenes políticos hay? ¿3? ¿5? ¿7? Depende de la teoría

Método del codo y silueta

Codo: Se elige el K donde la curva “se aplana”: agregar más clusters ya no mejora mucho

Silueta: Valores altos (cercanos a 1) indican mejor ajuste

Limitaciones de K-means

Supuestos que hace

  • Los clusters son esféricos y de tamaño similar (bolas alrededor de cada centroide, no alargadas ni curvas)
  • Cada punto pertenece a un solo cluster
  • Sensible a la escala de las variables (siempre normalizar antes)
  • Sensible a los valores iniciales (ejecutar varias veces)

Aplicaciones en ciencias sociales

  • Segmentación de países: agrupar países por indicadores de desarrollo
  • Tipología de votantes: identificar perfiles electorales
  • Clasificación de municipios: agrupar por características socioeconómicas
  • Análisis de encuestas: encontrar grupos de respuesta similares

K-means es un punto de partida, no el final. Sirve para explorar los datos y generar hipótesis, no para confirmar teorías causales

Evaluación de clusters

Coeficiente de silueta

  • El coeficiente de silueta mide qué tan bien cada observación encaja en su cluster
  • Para cada punto \(i\):
    • \(a(i)\) = distancia promedio a otros puntos del mismo cluster
    • \(b(i)\) = distancia promedio al cluster más cercano diferente

\[s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}\]

  • El numerador \(b(i) - a(i)\) es la brecha entre separación (\(b\)) y cohesión (\(a\)): positivo y grande = bien ubicado; negativo = más cerca de otro cluster
  • El denominador \(\max(a, b)\) normaliza esa brecha: mantiene \(s(i)\) en \([-1, +1]\) e independiente de la escala de las distancias
  • Valores de \(s(i)\): cerca de +1 (bien clasificado), 0 (en el borde) o -1 (cluster equivocado)
  • Promediando \(s(i)\) sobre todos los puntos obtenemos una medida de calidad para elegir K: el K con mayor silueta indica clusters mejor definidos

Interpretación del promedio

Valor Interpretación
0.71 - 1.00 Estructura fuerte
0.51 - 0.70 Estructura razonable
0.26 - 0.50 Estructura débil
< 0.25 Sin estructura clara

A diferencia del codo, la silueta mide separación, no solo compacidad. Advertencia: tiende a favorecer K pequeños, así que conviene combinarla con el método del codo y con la teoría

K-means en R: simulando datos

Simulamos 90 “países” organizados en 3 tipos de desarrollo (alto, medio, bajo), con 4 indicadores en escala comparable y correlacionados entre sí

Código
R> Sigma <- matrix(0.3, 4, 4); diag(Sigma) <- 1
R> sim_grupo <- function(n, mu, g) {
+   MASS::mvrnorm(n, mu, Sigma) |>
+     as_tibble(.name_repair = ~ c("pib", "educ", "internet", "desigualdad")) |>
+     mutate(grupo_real = g)
+ }
R> set.seed(2026)
R> datos <- bind_rows(
+   sim_grupo(30, c( 2.5,  2.5,  2.5, -1.0), "alto"),
+   sim_grupo(30, c( 0.0,  0.0,  0.0,  2.5), "medio"),
+   sim_grupo(30, c(-2.5, -2.5, -2.5, -1.0), "bajo")
+ )

Variables (sin unidades; las escalamos antes de agrupar):

  • pib: producto interno bruto per cápita
  • educ: años promedio de escolaridad
  • internet: acceso a internet
  • desigualdad: índice tipo Gini
  • grupo_real: grupo verdadero, para verificar si K-means lo recupera

Desigualdad no es monótona: alta en países de ingreso medio, baja en los extremos. Refleja patrones reales en América Latina

R> # library("tidyverse")
R> # library("tidymodels")
R> # library("cluster")
R> # library("factoextra")
R> 
R> glimpse(datos)
Rows: 90
Columns: 5
$ pib         <dbl> 1.757936, 3.364688, 2.219472, 2.799533, 3.091635, 5.077923…
$ educ        <dbl> 0.9310475, 4.2105916, 2.1411508, 3.1483019, 1.6691161, 3.7…
$ internet    <dbl> 3.0216210, 2.6016175, 2.7453670, 2.3151813, 4.5646070, 4.2…
$ desigualdad <dbl> -0.6457693, -0.7003951, -0.9898428, -1.5293797, -0.9875592…
$ grupo_real  <chr> "alto", "alto", "alto", "alto", "alto", "alto", "alto", "a…

K-means en R: ajuste y evaluación

R> # Escalar (siempre antes de K-means), con la misma receta del Día 2
R> receta_km <- recipe(~ ., data = datos) |>
+   step_rm(grupo_real) |>          # la etiqueta no entra al clustering
+   step_normalize(all_numeric_predictors())
R> 
R> datos_scaled <- receta_km |> prep() |> juice()
R> 
R> # Ajustar K-means con K=3 y 25 inicializaciones aleatorias
R> km <- kmeans(datos_scaled, centers = 3, nstart = 25)
R> 
R> # Silueta promedio
R> sil <- silhouette(km$cluster, dist(datos_scaled))
R> round(mean(sil[, "sil_width"]), 3)
[1] 0.553
R> # ¿Recuperó los grupos reales?
R> table(real = datos$grupo_real, cluster = km$cluster)
       cluster
real     1  2  3
  alto  29  0  1
  bajo   0 30  0
  medio  0  1 29
R> fviz_cluster(km, data = datos_scaled, geom = "point",
+              ellipse.type = "convex") +
+   theme_minimal(base_size = 11)

  • step_normalize() hace lo mismo que scale(): centra en 0 y divide por el desvío. Acá la receta no tiene variable objetivo, por eso la fórmula empieza con ~
  • nstart = 25 protege contra mínimos locales por una mala inicialización aleatoria

K-means en R: eligiendo K

¿Cómo supimos que K=3 era la mejor opción? Aplicando los dos métodos a los datos simulados:

R> fviz_nbclust(datos_scaled, kmeans, method = "wss", nstart = 25) +
+   labs(title = "Método del codo", subtitle = NULL)
R> fviz_nbclust(datos_scaled, kmeans, method = "silhouette", nstart = 25) +
+   labs(title = "Método de la silueta", subtitle = NULL)

  • Izquierda: la suma de distancias internas (within sum of squares) cae hasta K=3 y se aplana. El codo está en K=3
  • Derecha: la silueta promedio se maximiza en K=3, confirmando el diagnóstico
  • Ambos coinciden porque los datos tienen estructura clara. En aplicaciones reales suelen discrepar, y ahí pesa la teoría

Clustering jerárquico

¿Qué es el clustering jerárquico?

  • Clustering jerárquico: crea una jerarquía de clusters anidados
  • No requiere especificar K de antemano
  • Dos enfoques:
    • Aglomerativo (bottom-up): empieza con cada punto como cluster y fusiona los más cercanos
    • Divisivo (top-down): empieza con todos los puntos juntos y los va dividiendo
  • El resultado se visualiza como un dendrograma
  • El usuario “corta” el dendrograma al nivel deseado para leer una agrupación en K clusters (el árbol ya las contiene todas)
  • Tomamos 3 países de cada grupo de los datos simulados para ilustrar:
R> set.seed(2026)
R> muestra <- datos |>
+   group_by(grupo_real) |>
+   slice_sample(n = 3) |>
+   ungroup()
R> 
R> m_scaled <- muestra |>
+   select(where(is.numeric)) |>
+   scale()
R> rownames(m_scaled) <- muestra$grupo_real
R> 
R> hc_demo <- hclust(dist(m_scaled), method = "ward.D2")
R> plot(hc_demo, hang = -1, ylab = "Altura")
R> rect.hclust(hc_demo, k = 3, 
+             border = c("#2d4563", "#b85450", "#6b8e23"))

Métodos de enlace (linkage)

Método Distancia entre clusters Características
Single (mínimo) Puntos más cercanos Puede crear cadenas largas
Complete (máximo) Puntos más lejanos Clusters compactos
Average (promedio) Promedio de todas las distancias Balance entre ambos
Ward Minimiza varianza interna Similar a K-means, muy usado


  • Ward es el más usado en ciencias sociales porque tiende a crear clusters de tamaño similar
  • En R: hclust(dist(datos), method = "ward.D2")

Clustering jerárquico en R

Reutilizamos los mismos datos simulados (datos_scaled) para comparar con K-means:

R> # Matriz de distancias y clustering jerárquico con método Ward
R> d <- dist(datos_scaled, method = "euclidean")
R> hc <- hclust(d, method = "ward.D2")
R> 
R> # Cortar para obtener 3 clusters
R> grupos_hc <- cutree(hc, k = 3)
R> 
R> # ¿Recuperó los grupos reales?
R> table(real = datos$grupo_real, cluster = grupos_hc)
       cluster
real     1  2  3
  alto  29  1  0
  bajo   0  0 30
  medio  0 28  2
R> # Dendrograma con los 3 cortes resaltados.
R> # hang = -1 alinea todas las hojas en la base (altura 0), en vez de dejarlas
R> # colgando desde su punto de unión. Es solo cosmético: no cambia los clusters
R> plot(hc, hang = -1, labels = FALSE, main = "", xlab = "", sub = "", ylab = "Altura")
R> rect.hclust(hc, k = 3, border = c("#2d4563", "#b85450", "#6b8e23"))

  • La tabla confirma que Ward recupera los 3 grupos casi perfectamente. El dendrograma permite explorar otros cortes (2, 4, 5, …) sin reajustar el modelo
  • cutree() no re-ejecuta el clustering: hclust() ya construyó todo el árbol sin fijar K; cutree() y rect.hclust() solo cortan y dibujan el corte en 3

K-means vs. Jerárquico

Criterio K-means Jerárquico
Elegir K Antes de ejecutar Después (cortando)
Forma de clusters Esféricos Cualquier forma
Escalabilidad Muy rápido Lento para n grande
Reproducibilidad Depende de inicialización Determinístico
Visualización Scatter plot Dendrograma
Jerarquía No Sí (anidamiento)


  • Usen K-means para datasets grandes cuando tienen una idea del número de grupos
  • Usen jerárquico para explorar la estructura de los datos y visualizar relaciones entre observaciones

PCA: Reducción de dimensionalidad

¿Qué es PCA?

  • PCA (Principal Component Analysis / Análisis de Componentes Principales)
  • Problema: tenemos muchas variables y queremos simplificar
  • PCA busca las direcciones de máxima variación en los datos
  • Crea nuevas variables (componentes principales) que son combinaciones lineales de las originales
  • Primer componente: la dirección con más varianza
  • Segundo componente: la siguiente dirección con más varianza, perpendicular al primero
  • Y así sucesivamente
  • Permite visualizar datos de alta dimensión en 2D o 3D

PCA: encontrar las direcciones de máxima varianza

Fuente: Vizuara

¿Para qué sirve PCA en ciencias sociales?

  • Visualización: reducir 8 variables a 2 dimensiones para graficar
  • Construcción de índices: combinar muchos indicadores en uno
    • El IDH (esperanza de vida + educación + ingreso) sigue la misma idea, aunque usa una media geométrica
  • Preprocesamiento: simplificar antes de aplicar K-means o regresión
  • Detección de patrones: ver qué variables “van juntas”
    • Si PIB, educación e internet cargan juntas en PC1, lo leemos como “eje de desarrollo”

Fuente: Medium

¿Cuántos componentes conservar?

  • PCA produce tantos componentes como variables originales. Criterios para conservar los primeros: varianza acumulada ~70-80%, método del codo, regla de Kaiser
R> pca <- prcomp(datos_scaled)
R> 
R> fviz_eig(pca, addlabels = TRUE) +
+   labs(title = NULL, y = "Varianza (%)") +
+   theme_minimal(base_size = 10) +
+   coord_cartesian(ylim = c(0, 100))

  • PC1 captura la mayor parte de la varianza (eje de desarrollo: PIB, educación e internet covarían). Con 2 componentes ya explicamos más del 90% del total

Interpretación de los loadings

Idea intuitiva: cada componente es una receta que combina las variables originales. Los loadings son la cantidad de cada ingrediente

  • Grande (cerca de ±1): la variable pesa mucho
  • Cerca de cero: la variable casi no aparece
  • Mismo signo dentro de un PC: las variables suben y bajan juntas
  • Signos opuestos: van en direcciones contrarias
  • El signo global del PC es arbitrario: si lo invertimos, el resultado es equivalente
R> # Loadings (matriz de rotación)
R> round(pca$rotation, 2)
              PC1   PC2   PC3   PC4
pib         -0.58  0.02  0.04  0.81
educ        -0.57 -0.01  0.68 -0.45
internet    -0.57  0.12 -0.72 -0.38
desigualdad -0.07 -0.99 -0.09 -0.02
R> # Varianza explicada
R> summary(pca)$importance |> round(2)
                        PC1  PC2  PC3  PC4
Standard deviation     1.67 1.00 0.37 0.29
Proportion of Variance 0.70 0.25 0.03 0.02
Cumulative Proportion  0.70 0.95 0.98 1.00
  • PC1: PIB, educación e internet pesan casi igual (~-0.57 cada uno); desigualdad casi no pesa. PC1 resume el desarrollo general del país
  • PC2: desigualdad pesa casi todo (-0.99) y las demás casi nada. PC2 captura solo la desigualdad
  • Entre PC1 y PC2 explican el 95% de la varianza
  • PC3 y PC4 son lo que sobra, y con ~5% de la varianza entre ambos, no conviene buscarles nombre. El nombre de un componente lo pone la teoría, no el algoritmo

Biplot: visualización combinada

Un biplot muestra simultáneamente las observaciones (puntos) y las variables (flechas). Flechas cercanas indican variables correlacionadas; las observaciones en la dirección de una flecha tienen valores altos en esa variable.

R> fviz_pca_biplot(pca,
+                 geom.ind = "point",
+                 habillage = datos$grupo_real,
+                 addEllipses = TRUE,
+                 palette = c("#2d4563", "#b85450", "#6b8e23"),
+                 col.var = "black",
+                 repel = TRUE,
+                 legend.title = "Grupo real") +
+   labs(title = NULL) +
+   theme_minimal(base_size = 11)

  • Los tres grupos simulados se separan con claridad en el plano PC1-PC2. Las flechas confirman que PIB, educación e internet apuntan juntas, y desigualdad va en dirección distinta

PCA: lo que hay que recordar

Ventajas

  • Reduce la dimensionalidad preservando la mayor varianza posible
  • Ayuda a visualizar datos complejos
  • Puede revelar estructura latente
  • Los componentes son no correlacionados entre sí (ortogonales, es decir, su correlación es cero)
  • No necesita etiquetas

Limitaciones

  • Solo captura relaciones lineales
  • Los componentes son difíciles de interpretar (son combinaciones de muchas variables)
  • Sensible a la escala: siempre normalizar antes
  • No es un modelo causal: no dice por qué las variables están correlacionadas
  • ¿Cuántos componentes conservar? Regla general: los que explican ~70-80% de la varianza

Aplicaciones en ciencias sociales

Aplicaciones y lecturas clave

Dónde se usan estas técnicas:

  • Tipologías de países por indicadores de desarrollo, gobernanza e institucionalidad
  • Mapas ideológicos de partidos y legisladores
  • Segmentación de votantes a partir de encuestas (Latinobarómetro, LAPOP)
  • Índices compuestos (desarrollo humano, capital social, calidad institucional)
  • Preprocesamiento antes de una regresión con variables correlacionadas
  • Agrupación de respuestas abiertas en encuestas grandes

Lecturas recomendadas:

En el laboratorio (Sesión 3.3) aplicaremos estas técnicas a datos simulados de países latinoamericanos

PCA vs. análisis factorial

Muchos índices que usamos en ciencias sociales (bienestar, confianza institucional, autoritarismo) parecen PCA pero son análisis factorial. Son parientes cercanos que responden a preguntas distintas

PCA

  • No asume un modelo: busca ejes que explican varianza
  • Los componentes son sumas ponderadas de las variables observadas
  • Cada PC intenta captar la máxima varianza posible, sin separar “error”
  • Pregunta: ¿cómo resumo estos datos?

Análisis factorial

  • Asume un modelo: variables observadas = factores latentes + error
  • Los factores son causas hipotéticas que generan las correlaciones
  • Estima por separado la varianza común y la específica de cada variable
  • Pregunta: ¿qué constructos latentes explican estas variables?

Regla práctica: si van a reportar un índice como “medida de X” (un constructo o actitud), quieren análisis factorial. Si solo necesitan reducir dimensiones para visualizar, PCA alcanza

Resumen de la sesión

Clustering:

  • K-means: rápido, asume clusters esféricos, requiere elegir K
  • Jerárquico: produce dendrograma, no requiere K de antemano
  • Silueta: mide calidad de los clusters
  • Escalar siempre antes de aplicar
  • Recomendación: DBSCAN y HDBSCAN manejan clusters no esféricos

PCA:

  • Reduce dimensionalidad preservando varianza
  • Los loadings indican qué variables contribuyen a cada componente
  • Scree plot ayuda a elegir cuántos componentes conservar
  • Biplot visualiza observaciones y variables juntas
  • Recomendación: t-SNE y UMAP son alternativas no lineales para visualización

Son herramientas de exploración: generan hipótesis, no las confirman

Próximos pasos

  • Sesión 3.2: Análisis computacional de texto
    • Tokenización y preprocesamiento
    • TF-IDF y bag-of-words
    • Topic Modeling (LDA)
    • Introducción a embeddings
  • Laboratorios (3.3 y 3.4):
    • Clustering y PCA con datos de países
    • Análisis de textos políticos latinoamericanos

Nos vemos en la próxima sesión

Apéndice

Cómo caracterizar un cluster en la práctica

Después de correr kmeans(), la etiqueta 1/2/3 no significa nada por sí misma. Hay que darle contenido sustantivo:

1. Perfiles por cluster: calcular la media de cada variable dentro de cada grupo

R> datos |>
+   mutate(cluster = km$cluster) |>
+   group_by(cluster) |>
+   summarise(across(where(is.numeric),
+                    \(x) round(mean(x), 2)))
# A tibble: 3 × 5
  cluster   pib  educ internet desigualdad
    <int> <dbl> <dbl>    <dbl>       <dbl>
1       1  2.51  2.48     2.72       -0.84
2       2 -2.52 -2.7     -2.55       -0.97
3       3 -0.08  0       -0.31        2.29

2. Validación externa: cruzar con variables no usadas en el clustering (región, partido, año). Si los grupos separan esas variables, tienen evidencia de estructura real

3. Nombrar los clusters: etiquetas sustantivas (“alto desarrollo”, “ingreso medio”, “bajo desarrollo”) en lugar de “1, 2, 3”

4. Cuidado con la reificación: los clusters no son categorías naturales. Dependen de K y de las variables elegidas

¿Cuándo predecimos un caso nuevo?

En el Día 2 siempre predijimos sobre datos_test, que ya estaba dentro del archivo. Un caso nuevo funciona exactamente igual: cambia lo que pasamos como segundo argumento, no la función

Evaluar vs. predecir

Segundo argumento ¿Trae la respuesta?
Evaluar datos_test Sí (voto)
Predecir una fila nueva No
  • Al evaluar comparamos la predicción con la realidad para calcular métricas
  • Al predecir no hay con qué comparar: la respuesta todavía no ocurrió

Tres funciones, tres salidas

  • predict(ajuste, caso) devuelve .pred_class
  • predict(ajuste, caso, type = "prob") devuelve .pred_no y .pred_si
  • augment(ajuste, caso) devuelve el caso más las tres columnas

.pred_class no es un cálculo aparte: es .pred_si > 0,5 con otro nombre

Un caso nuevo: una votante uruguaya

R> # Una fila con las mismas columnas que los datos
R> # de entrenamiento (sin voto, que es lo que
R> # queremos predecir)
R> nuevo_caso <- tibble(
+   pais = "Uruguay", edad = 42,
+   educacion_anios = 16, ingreso_hogar = 7,
+   zona = "urbana", genero = "mujer",
+   confianza_gobierno = 3, confianza_justicia = 3,
+   satisfaccion_democracia = 4,
+   percepcion_economia = 3,
+   uso_internet = "diario", interes_politica = 3,
+   satisfaccion_vida = 7.5
+ )
R> 
R> augment(ajuste_voto, nuevo_caso) |>
+   select(pais, .pred_class, .pred_no, .pred_si)
# A tibble: 1 × 4
  pais    .pred_class .pred_no .pred_si
  <chr>   <fct>          <dbl>    <dbl>
1 Uruguay si             0.273    0.727
  • El workflow() guarda adentro la receta ya estimada: las medias y desvíos de step_normalize(), las dummies de step_dummy()
  • Si pasamos diez filas en lugar de una, salen diez predicciones. La sintaxis no cambia
  • Es el mismo augment() del Laboratorio 3, con otra tabla adelante

Detalles que hacen fallar la predicción

1. Faltan columnas

La receta valida el caso nuevo contra los datos originales, así que exige todas las columnas, incluso pais y satisfaccion_vida, que step_rm() borra un paso después

Error in `hardhat::forge()`:
! The required column "pais" is missing.

2. Niveles mal escritos

Escribir "Urbana" en lugar de "urbana" no da error: devuelve NA con un warning. Los niveles son rural/urbana, diario/nunca/semanal

3. El umbral es una decisión de ustedes

augment(ajuste_voto, casos) |>
  mutate(clase = if_else(.pred_si > 0.4,
                         "si", "no"))

4. Guardar el modelo para usarlo después

saveRDS(ajuste_voto, "modelo_voto.rds")
modelo <- readRDS("modelo_voto.rds")
predict(modelo, nuevo_caso, type = "prob")

Si ajustaron con last_fit(), primero extraigan el workflow: extract_workflow(ajuste_final)

¡Nos vemos en la sesión de análisis de texto!