1 Introducción al Análisis de Correspondencias Simples

1.1 ¿Qué es el ACS?

El Análisis de Correspondencias Simples (ACS) es un método estadístico para describir tablas de contingencia mediante la representación geométrica de los perfiles fila y columna derivados de ellas.

1.1.1 Aplicaciones típicas

  • Estudios de mercado: producto × región
  • Encuestas: pregunta A × pregunta B
  • Educación: carrera × estrato social
  • Salud: enfermedad × grupo etario

1.1.2 Tabla de contingencia

Una tabla de contingencia (TC) clasifica a \(n\) individuos según dos variables cualitativas:

  • Variable fila: \(n\) categorías
  • Variable columna: \(p\) categorías
  • Celda \((i,j)\): número de individuos con categoría \(i\) de fila Y categoría \(j\) de columna

1.2 Objetivos del ACS

El ACS tiene tres objetivos principales:

  1. Comparar los perfiles fila entre sí
  2. Comparar los perfiles columna entre sí
  3. Estudiar las correspondencias entre perfiles fila y columna

1.3 Ejemplo de aplicación

Vamos a analizar 445 estudiantes admitidos a la Facultad de Ciencias clasificados según:

  • Carrera (7 categorías): Biología, Estadística, Farmacia, Física, Geología, Matemáticas, Química
  • Estrato socioeconómico (3 categorías): Bajo, Medio, Alto

Pregunta de investigación: ¿Existe asociación entre la carrera elegida y el estrato socioeconómico del estudiante?

# Cargar paquetes necesarios
library(ade4)        # Para ejecutar ACS
library(ggplot2)     # Para gráficas
library(knitr)       # Para tablas

cat("Paquetes cargados correctamente.\n")
## Paquetes cargados correctamente.

2 Tabla de Contingencia y Notación

2.1 Tabla de frecuencias absolutas (K)

Es la tabla original que cuenta individuos en cada combinación de categorías.

Notación:

  • \(K\): tabla de contingencia
  • \(k_{ij}\): término general (celda \(i,j\))
  • \(k_{i\cdot} = \sum_j k_{ij}\): suma fila \(i\) (marginal fila)
  • \(k_{\cdot j} = \sum_i k_{ij}\): suma columna \(j\) (marginal columna)
  • \(k = \sum_{i,j} k_{ij}\): total de la tabla
# Crear tabla de contingencia (frecuencias del libro)
K <- matrix(c(
  23, 26, 14,  # Biología
  29, 29,  8,  # Estadística
  30, 36,  7,  # Farmacia
  27, 36, 19,  # Física
  18,  9, 18,  # Geología
  21, 25,  7,  # Matemáticas
  31, 24,  8   # Química
), nrow = 7, byrow = TRUE)

rownames(K) <- c("Biol", "Esta", "Farm", "Fisi", "Geol", "Mate", "Quim")
colnames(K) <- c("Bajo", "Medio", "Alto")

# Mostrar tabla con marginales
K_ext <- addmargins(K)
kable(K_ext, caption = "Tabla de Contingencia K: Carreras × Estratos")
Tabla de Contingencia K: Carreras × Estratos
Bajo Medio Alto Sum
Biol 23 26 14 63
Esta 29 29 8 66
Farm 30 36 7 73
Fisi 27 36 19 82
Geol 18 9 18 45
Mate 21 25 7 53
Quim 31 24 8 63
Sum 179 185 81 445
# Verificar dimensiones
n_carreras <- nrow(K)
n_estratos <- ncol(K)
n_total <- sum(K)

cat("\nDimensiones:\n")
## 
## Dimensiones:
cat("- Carreras (filas):", n_carreras, "\n")
## - Carreras (filas): 7
cat("- Estratos (columnas):", n_estratos, "\n")
## - Estratos (columnas): 3
cat("- Total de estudiantes:", n_total, "\n")
## - Total de estudiantes: 445

2.1.1 Ejemplo de lectura

De la tabla anterior:

  • \(k_{11} = 23\): 23 estudiantes de Biología y estrato bajo
  • \(k_{1\cdot} = 63\): 63 estudiantes admitidos a Biología
  • \(k_{\cdot 1} = 179\): 179 estudiantes de estrato bajo
  • \(k = 445\): Total de admitidos

2.2 Tabla de frecuencias relativas (F)

Se obtiene dividiendo cada celda por el total:

\[ F = \frac{K}{k} \times 100 \]

con término general \(f_{ij} = \frac{k_{ij}}{k} \times 100\) (en porcentaje).

# Calcular tabla de frecuencias relativas
F <- prop.table(K) * 100

# Marginales
marginal_fila <- rowSums(F)
marginal_col <- colSums(F)

# Tabla con marginales
F_ext <- addmargins(F)
kable(F_ext, digits = 1, 
      caption = "Tabla de Frecuencias Relativas F (%)")
Tabla de Frecuencias Relativas F (%)
Bajo Medio Alto Sum
Biol 5.2 5.8 3.1 14.2
Esta 6.5 6.5 1.8 14.8
Farm 6.7 8.1 1.6 16.4
Fisi 6.1 8.1 4.3 18.4
Geol 4.0 2.0 4.0 10.1
Mate 4.7 5.6 1.6 11.9
Quim 7.0 5.4 1.8 14.2
Sum 40.2 41.6 18.2 100.0
# Matrices diagonales (útiles para cálculos)
Dn <- diag(marginal_fila / 100)  # Pesos filas
Dp <- diag(marginal_col / 100)   # Pesos columnas

cat("\nInterpretaciones:\n")
## 
## Interpretaciones:
cat("- f₁₁ = 5.2%: El 5.2% de admitidos son de Biología Y estrato bajo\n")
## - f₁₁ = 5.2%: El 5.2% de admitidos son de Biología Y estrato bajo
cat("- f₁· = 14.2%: El 14.2% de admitidos son de Biología (marginal fila)\n")
## - f₁· = 14.2%: El 14.2% de admitidos son de Biología (marginal fila)
cat("- f·₁ = 40.2%: El 40.2% de admitidos son de estrato bajo (marginal columna)\n")
## - f·₁ = 40.2%: El 40.2% de admitidos son de estrato bajo (marginal columna)

3 Perfiles Fila y Columna

3.1 Perfiles fila (condicionales por carrera)

Cada perfil fila es la distribución de estratos dentro de una carrera específica.

\[ \text{Perfil fila } i = \left\{ \frac{f_{ij}}{f_{i\cdot}} ; j = 1, \ldots, p \right\} \]

Propiedades:

  • Suma de cada perfil = 100%
  • Compara distribuciones de estratos entre carreras
  • La marginal columna es el perfil promedio
# Calcular perfiles fila
perfiles_fila <- prop.table(K, margin = 1) * 100

# Añadir marginal como referencia
perfiles_fila_ext <- rbind(perfiles_fila, 
                            "Marginal" = marginal_col)

kable(perfiles_fila_ext, digits = 1,
      caption = "Perfiles fila: Distribución de estratos por carrera (%)")
Perfiles fila: Distribución de estratos por carrera (%)
Bajo Medio Alto
Biol 36.5 41.3 22.2
Esta 43.9 43.9 12.1
Farm 41.1 49.3 9.6
Fisi 32.9 43.9 23.2
Geol 40.0 20.0 40.0
Mate 39.6 47.2 13.2
Quim 49.2 38.1 12.7
Marginal 40.2 41.6 18.2

3.1.1 Interpretación de perfiles fila

cat("** Comparación con el promedio (marginal) **\n\n")
## ** Comparación con el promedio (marginal) **
# Comparar Geología con marginal
cat("GEOLOGÍA:\n")
## GEOLOGÍA:
cat("- Bajo: 40.0% (promedio: 40.2%) → similar\n")
## - Bajo: 40.0% (promedio: 40.2%) → similar
cat("- Medio: 20.0% (promedio: 41.6%) → MUCHO MENOS\n")
## - Medio: 20.0% (promedio: 41.6%) → MUCHO MENOS
cat("- Alto: 40.0% (promedio: 18.2%) → MÁS DEL DOBLE\n")
## - Alto: 40.0% (promedio: 18.2%) → MÁS DEL DOBLE
cat("→ Geología tiene perfil MUY diferente: más estrato alto\n\n")
## → Geología tiene perfil MUY diferente: más estrato alto
cat("QUÍMICA:\n")
## QUÍMICA:
cat("- Bajo: 49.2% (promedio: 40.2%) → más\n")
## - Bajo: 49.2% (promedio: 40.2%) → más
cat("- Medio: 38.1% (promedio: 41.6%) → similar\n")
## - Medio: 38.1% (promedio: 41.6%) → similar
cat("- Alto: 12.7% (promedio: 18.2%) → menos\n")
## - Alto: 12.7% (promedio: 18.2%) → menos
cat("→ Química tiene más estudiantes de estrato bajo\n\n")
## → Química tiene más estudiantes de estrato bajo
cat("BIOLOGÍA:\n")
## BIOLOGÍA:
cat("- Bajo: 36.5%, Medio: 41.3%, Alto: 22.2%\n")
## - Bajo: 36.5%, Medio: 41.3%, Alto: 22.2%
cat("→ Perfil cercano al promedio (carrera 'típica')\n")
## → Perfil cercano al promedio (carrera 'típica')

3.1.2 Gráfica de perfiles fila

# Gráfica de barras apiladas
par(mar = c(5, 6, 4, 2))
barplot(t(perfiles_fila), 
        beside = FALSE, 
        col = c("gray20", "gray50", "gray85"),
        legend.text = colnames(K),
        args.legend = list(x = "topright", bty = "n", cex = 0.9),
        main = "Perfiles fila: Distribución de estratos por carrera",
        xlab = "Carrera", ylab = "Porcentaje",
        las = 1, cex.names = 0.9, horiz = FALSE)

# Añadir líneas de referencia (marginales)
abline(h = c(40.2, 81.8), lty = 2, col = c("red", "blue"), lwd = 1.5)
text(0.5, 20, "40.2%", col = "red", cex = 0.8)
text(0.5, 60, "81.8%", col = "blue", cex = 0.8)


3.2 Perfiles columna (condicionales por estrato)

Cada perfil columna es la distribución de carreras dentro de un estrato específico.

\[ \text{Perfil columna } j = \left\{ \frac{f_{ij}}{f_{\cdot j}} ; i = 1, \ldots, n \right\} \]

Propiedades:

  • Suma de cada perfil = 100%
  • Compara distribuciones de carreras entre estratos
  • La marginal fila es el perfil promedio
# Calcular perfiles columna
perfiles_col <- prop.table(K, margin = 2) * 100

# Añadir marginal como referencia
perfiles_col_ext <- cbind(perfiles_col, 
                          "Marginal" = marginal_fila)

kable(perfiles_col_ext, digits = 1,
      caption = "Perfiles columna: Distribución de carreras por estrato (%)")
Perfiles columna: Distribución de carreras por estrato (%)
Bajo Medio Alto Marginal
Biol 12.8 14.1 17.3 14.2
Esta 16.2 15.7 9.9 14.8
Farm 16.8 19.5 8.6 16.4
Fisi 15.1 19.5 23.5 18.4
Geol 10.1 4.9 22.2 10.1
Mate 11.7 13.5 8.6 11.9
Quim 17.3 13.0 9.9 14.2

3.2.1 Interpretación de perfiles columna

cat("** Comparación de estratos **\n\n")
## ** Comparación de estratos **
cat("ESTRATO ALTO:\n")
## ESTRATO ALTO:
cat("- Geología: 22.2% (promedio: 10.1%) → MÁS DEL DOBLE\n")
## - Geología: 22.2% (promedio: 10.1%) → MÁS DEL DOBLE
cat("- Física: 23.5% (promedio: 18.4%) → más\n")
## - Física: 23.5% (promedio: 18.4%) → más
cat("- Farmacia: 8.6% (promedio: 16.4%) → MENOS DE LA MITAD\n")
## - Farmacia: 8.6% (promedio: 16.4%) → MENOS DE LA MITAD
cat("→ Estrato alto tiene más Geología y Física, menos Farmacia\n\n")
## → Estrato alto tiene más Geología y Física, menos Farmacia
cat("ESTRATO BAJO:\n")
## ESTRATO BAJO:
cat("- Distribución más uniforme entre carreras\n")
## - Distribución más uniforme entre carreras
cat("- Química ligeramente mayor: 17.3% (promedio: 14.2%)\n")
## - Química ligeramente mayor: 17.3% (promedio: 14.2%)

3.2.2 Gráfica de perfiles columna

# Gráfica de barras apiladas
barplot(perfiles_col, 
        beside = FALSE,
        col = gray.colors(7, start = 0.2, end = 0.9),
        legend.text = rownames(K),
        args.legend = list(x = "topright", bty = "n", cex = 0.8),
        main = "Perfiles columna: Distribución de carreras por estrato",
        xlab = "Estrato", ylab = "Porcentaje",
        las = 1)


4 Modelo de Independencia

4.1 ¿Qué es independencia estadística?

Si NO hubiera asociación entre carreras y estratos, las frecuencias esperadas serían:

\[ a_{ij} = f_{i\cdot} \times f_{\cdot j} \]

Es decir, el producto de las marginales.

Bajo independencia: todos los perfiles fila serían iguales a la marginal columna (y viceversa).

# Calcular tabla de independencia A
A <- outer(marginal_fila / 100, marginal_col / 100) * 100

# Mostrar tabla
kable(A, digits = 1,
      caption = "Tabla de Independencia A: Frecuencias esperadas bajo H₀")
Tabla de Independencia A: Frecuencias esperadas bajo H₀
Bajo Medio Alto
Biol 5.7 5.9 2.6
Esta 6.0 6.2 2.7
Farm 6.6 6.8 3.0
Fisi 7.4 7.7 3.4
Geol 4.1 4.2 1.8
Mate 4.8 5.0 2.2
Quim 5.7 5.9 2.6
cat("\nBajo independencia:\n")
## 
## Bajo independencia:
cat("- Todos los perfiles fila serían iguales: [40.2, 41.6, 18.2]\n")
## - Todos los perfiles fila serían iguales: [40.2, 41.6, 18.2]
cat("- Todos los perfiles columna serían iguales: marginales fila\n")
## - Todos los perfiles columna serían iguales: marginales fila

4.2 Desviaciones del modelo

Las desviaciones \(F - A\) muestran las asociaciones:

  • Valor positivo: más frecuente de lo esperado (asociación positiva)
  • Valor negativo: menos frecuente de lo esperado (asociación negativa)
# Calcular desviaciones
Desviaciones <- F - A

kable(Desviaciones, digits = 2,
      caption = "Desviaciones F - A: Diferencia con modelo de independencia")
Desviaciones F - A: Diferencia con modelo de independencia
Bajo Medio Alto
Biol -0.53 -0.04 0.57
Esta 0.55 0.35 -0.90
Farm 0.14 1.27 -1.41
Fisi -1.34 0.43 0.92
Geol -0.02 -2.18 2.20
Mate -0.07 0.67 -0.59
Quim 1.27 -0.49 -0.78
cat("\n** Desviaciones destacadas **\n\n")
## 
## ** Desviaciones destacadas **
cat("Geología - Alto: +2.2%\n")
## Geología - Alto: +2.2%
cat("→ Hay MÁS geólogos de estrato alto de lo que se esperaría por azar\n\n")
## → Hay MÁS geólogos de estrato alto de lo que se esperaría por azar
cat("Geología - Medio: -2.2%\n")
## Geología - Medio: -2.2%
cat("→ Hay MENOS geólogos de estrato medio de lo esperado\n\n")
## → Hay MENOS geólogos de estrato medio de lo esperado
cat("Farmacia - Alto: -1.4%\n")
## Farmacia - Alto: -1.4%
cat("→ Hay MENOS farmacéuticos de estrato alto de lo esperado\n")
## → Hay MENOS farmacéuticos de estrato alto de lo esperado

5 Prueba de Independencia χ²

5.1 Hipótesis estadística

H₀ (Hipótesis nula): No hay asociación entre carreras y estratos

\[ H_0: f_{ij} = f_{i\cdot} \times f_{\cdot j} \quad \forall i,j \]

H₁ (Hipótesis alternativa): Sí hay asociación

5.2 Estadístico χ²

\[ \chi^2 = k \times \text{Inercia} = k \sum_{i,j} \frac{(f_{ij} - a_{ij})^2}{a_{ij}} \]

con \((n-1)(p-1)\) grados de libertad.

Criterio de decisión: Si \(\chi^2_{\text{calc}} > \chi^2_{\text{crítico}}\) → Rechazar H₀

# Calcular inercia
inercia <- sum((F - A)^2 / A)

# Calcular chi-cuadrado
chi2_calc <- n_total * inercia
gl <- (n_carreras - 1) * (n_estratos - 1)

cat("** Prueba de independencia χ² **\n\n")
## ** Prueba de independencia χ² **
cat("Inercia (φ²):", round(inercia, 4), "\n")
## Inercia (φ²): 6.5596
cat("χ² calculado:", round(chi2_calc, 2), "\n")
## χ² calculado: 2919
cat("Grados de libertad:", gl, "\n")
## Grados de libertad: 12
# Valor p
p_valor <- pchisq(chi2_calc, gl, lower.tail = FALSE)
cat("Valor p:", format.pval(p_valor, digits = 4), "\n\n")
## Valor p: < 2.2e-16
# Valor crítico (α = 5%)
chi2_critico <- qchisq(0.95, gl)
cat("χ² crítico (α=5%):", round(chi2_critico, 2), "\n\n")
## χ² crítico (α=5%): 21.03
# Decisión
if(p_valor < 0.05) {
  cat("** DECISIÓN: RECHAZAR H₀ **\n")
  cat("Conclusión: Hay asociación SIGNIFICATIVA entre carreras y estratos (α=5%)\n")
} else {
  cat("DECISIÓN: No rechazar H₀\n")
}
## ** DECISIÓN: RECHAZAR H₀ **
## Conclusión: Hay asociación SIGNIFICATIVA entre carreras y estratos (α=5%)

5.2.1 Verificación con R

# Prueba automática
cat("\nVerificación con chisq.test():\n\n")
## 
## Verificación con chisq.test():
test_result <- chisq.test(K)
print(test_result)
## 
##  Pearson's Chi-squared test
## 
## data:  K
## X-squared = 29.19, df = 12, p-value = 0.003692
cat("\n✓ Los resultados coinciden.\n")
## 
## ✓ Los resultados coinciden.

6 Distancia χ² entre Perfiles

6.1 Definición

La distancia χ² (ji-cuadrado o de Benzécri) entre dos perfiles fila \(i\) y \(l\) es:

\[ d^2(i, l) = \sum_{j=1}^p \frac{1}{f_{\cdot j}} \left( \frac{f_{ij}}{f_{i\cdot}} - \frac{f_{lj}}{f_{l\cdot}} \right)^2 \]

Propiedades clave:

  1. Amplifica diferencias en categorías de baja frecuencia marginal
  2. Invariante ante reagrupaciones (equivalencia distribucional)
  3. Permite comparar perfiles de manera ponderada
# Función para calcular distancia χ² entre perfiles fila
dist_chi2_filas <- function(i, l, K, marginal_col) {
  # Calcular perfiles
  perfil_i <- K[i, ] / sum(K[i, ])
  perfil_l <- K[l, ] / sum(K[l, ])
  
  # Diferencia
  diff <- perfil_i - perfil_l
  
  # Pesos (inverso de marginales columna)
  pesos <- 1 / (marginal_col / 100)
  
  # Distancia al cuadrado
  d2 <- sum(pesos * diff^2)
  
  return(sqrt(d2))
}

# Calcular matriz de distancias
n_carr <- nrow(K)
D_chi2 <- matrix(0, n_carr, n_carr)

for(i in 1:n_carr) {
  for(j in 1:n_carr) {
    if(i < j) {
      D_chi2[i,j] <- dist_chi2_filas(i, j, K, marginal_col)
      D_chi2[j,i] <- D_chi2[i,j]
    }
  }
}

rownames(D_chi2) <- rownames(K)
colnames(D_chi2) <- rownames(K)

cat("Matriz de distancias χ² entre carreras:\n\n")
## Matriz de distancias χ² entre carreras:
kable(round(D_chi2, 3))
Biol Esta Farm Fisi Geol Mate Quim
Biol 0.000 0.267 0.329 0.073 0.534 0.235 0.304
Esta 0.267 0.000 0.112 0.312 0.754 0.088 0.124
Farm 0.329 0.112 0.000 0.354 0.846 0.094 0.228
Fisi 0.073 0.312 0.354 0.000 0.553 0.261 0.366
Geol 0.534 0.754 0.846 0.553 0.000 0.756 0.714
Mate 0.235 0.088 0.094 0.261 0.756 0.000 0.207
Quim 0.304 0.124 0.228 0.366 0.714 0.207 0.000

6.1.1 Interpretación de distancias

cat("\n** Interpretación de distancias **\n\n")
## 
## ** Interpretación de distancias **
# Identificar pares más cercanos y lejanos
D_vec <- D_chi2[upper.tri(D_chi2)]
nombres_pares <- outer(rownames(K), rownames(K), paste, sep = " - ")
nombres_vec <- nombres_pares[upper.tri(nombres_pares)]

# Más cercanos (perfiles similares)
idx_min <- order(D_vec)[1:3]
cat("Carreras con perfiles MÁS SIMILARES:\n")
## Carreras con perfiles MÁS SIMILARES:
for(i in idx_min) {
  cat("-", nombres_vec[i], "→ d² =", round(D_vec[i], 3), "\n")
}
## - Biol - Fisi → d² = 0.073 
## - Esta - Mate → d² = 0.088 
## - Farm - Mate → d² = 0.094
# Más lejanos (perfiles diferentes)
idx_max <- order(D_vec, decreasing = TRUE)[1:3]
cat("\nCarreras con perfiles MÁS DIFERENTES:\n")
## 
## Carreras con perfiles MÁS DIFERENTES:
for(i in idx_max) {
  cat("-", nombres_vec[i], "→ d² =", round(D_vec[i], 3), "\n")
}
## - Farm - Geol → d² = 0.846 
## - Geol - Mate → d² = 0.756 
## - Esta - Geol → d² = 0.754
cat("\nGeología es la carrera más alejada de las demás (perfil atípico).\n")
## 
## Geología es la carrera más alejada de las demás (perfil atípico).

7 Ejecución del ACS

# Ejecutar ACS con ade4
acs <- dudi.coa(K, scannf = FALSE, nf = 2)

cat("** ACS ejecutado correctamente **\n\n")
## ** ACS ejecutado correctamente **
cat("Componentes del objeto 'acs':\n")
## Componentes del objeto 'acs':
cat("- $eig: valores propios\n")
## - $eig: valores propios
cat("- $li: coordenadas de filas (carreras)\n")
## - $li: coordenadas de filas (carreras)
cat("- $co: coordenadas de columnas (estratos)\n")
## - $co: coordenadas de columnas (estratos)
cat("- $lw: pesos de filas\n")
## - $lw: pesos de filas
cat("- $cw: pesos de columnas\n")
## - $cw: pesos de columnas

7.1 Valores propios e inercia

# Valores propios
valores_propios <- acs$eig
n_ejes <- length(valores_propios)

# Inercia total
inercia_total <- sum(valores_propios)

# Porcentaje de inercia
porc_inercia <- valores_propios / inercia_total * 100
porc_acum <- cumsum(porc_inercia)

# Tabla resumen
vp_tabla <- data.frame(
  Eje = 1:n_ejes,
  Valor_propio = round(valores_propios, 4),
  Inercia_pct = round(porc_inercia, 1),
  Acumulado_pct = round(porc_acum, 1)
)

kable(vp_tabla, caption = "Valores propios y porcentaje de inercia explicada")
Valores propios y porcentaje de inercia explicada
Eje Valor_propio Inercia_pct Acumulado_pct
1 0.0562 85.7 85.7
2 0.0094 14.3 100.0
cat("\n** Observaciones **\n")
## 
## ** Observaciones **
cat("- Inercia total:", round(inercia_total, 4), "\n")
## - Inercia total: 0.0656
cat("- Coincide con φ² calculado antes:", round(inercia, 4), "\n")
## - Coincide con φ² calculado antes: 6.5596
cat("- Dimensión máxima:", min(n_carreras, n_estratos) - 1, "=", n_ejes, "\n")
## - Dimensión máxima: 2 = 2
cat("- Eje 1 retiene el", round(porc_inercia[1], 1), "% de la inercia\n")
## - Eje 1 retiene el 85.7 % de la inercia
cat("- Eje 2 retiene el", round(porc_inercia[2], 1), "% de la inercia\n")
## - Eje 2 retiene el 14.3 % de la inercia

7.1.1 Histograma de valores propios (Scree plot)

barplot(valores_propios, 
        names.arg = 1:n_ejes,
        col = "steelblue",
        border = "white",
        main = "Histograma de valores propios (Scree plot)",
        xlab = "Eje", ylab = "Valor propio",
        las = 1)
abline(h = mean(valores_propios), col = "red", lty = 2, lwd = 2)
legend("topright", legend = "Promedio", col = "red", lty = 2, lwd = 2, bty = "n")

Interpretación: El primer eje concentra 85.7% de la inercia → casi toda la información está en una dimensión.


8 Planos Factoriales

8.1 Plano de carreras (perfiles fila)

# Coordenadas de carreras
coord_carreras <- acs$li

# Gráfica con ggplot2
ggplot(data.frame(coord_carreras, Carrera = rownames(K)),
       aes(x = Axis1, y = Axis2, label = Carrera)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(size = 5, color = "darkred", alpha = 0.7) +
  geom_text(vjust = -1.2, hjust = 0.5, size = 4.5, fontface = "bold") +
  labs(title = "Primer plano factorial: Carreras",
       subtitle = "Perfiles según estratos socioeconómicos",
       x = paste0("Eje 1 (", round(porc_inercia[1], 1), "%)"),
       y = paste0("Eje 2 (", round(porc_inercia[2], 1), "%)")) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
        plot.subtitle = element_text(hjust = 0.5, size = 11))

8.1.1 Interpretación del plano de carreras

cat("** Lectura del plano factorial de carreras **\n\n")
## ** Lectura del plano factorial de carreras **
cat("EJE 1 (horizontal, 85.7% inercia):\n")
## EJE 1 (horizontal, 85.7% inercia):
cat("- Izquierda: Geología (coord = -0.60)\n")
## - Izquierda: Geología (coord = -0.60)
cat("- Derecha: Farmacia, Química, Estadística, Matemáticas\n")
## - Derecha: Farmacia, Química, Estadística, Matemáticas
cat("- Centro: Biología (perfil promedio)\n\n")
## - Centro: Biología (perfil promedio)
cat("INTERPRETACIÓN EJE 1:\n")
## INTERPRETACIÓN EJE 1:
cat("→ Opone Geología (más estrato alto) vs las demás carreras\n\n")
## → Opone Geología (más estrato alto) vs las demás carreras
cat("EJE 2 (vertical, 14.3% inercia):\n")
## EJE 2 (vertical, 14.3% inercia):
cat("- Arriba: Geología\n")
## - Arriba: Geología
cat("- Abajo: Biología, Física\n")
## - Abajo: Biología, Física
cat("→ Diferencias más sutiles entre carreras\n\n")
## → Diferencias más sutiles entre carreras
cat("DISTANCIA AL ORIGEN:\n")
## DISTANCIA AL ORIGEN:
cat("- Geología: MÁS ALEJADA → perfil más diferente del promedio\n")
## - Geología: MÁS ALEJADA → perfil más diferente del promedio
cat("- Biología: MÁS CERCANA → perfil más parecido al promedio\n")
## - Biología: MÁS CERCANA → perfil más parecido al promedio

8.2 Plano de estratos (perfiles columna)

# Coordenadas de estratos
coord_estratos <- acs$co

# Gráfica
ggplot(data.frame(coord_estratos, Estrato = colnames(K)),
       aes(x = Comp1, y = Comp2, label = Estrato)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(size = 6, color = "darkblue", alpha = 0.7) +
  geom_text(vjust = -1.3, hjust = 0.5, size = 5, fontface = "bold") +
  labs(title = "Primer plano factorial: Estratos",
       subtitle = "Perfiles según carreras",
       x = paste0("Eje 1 (", round(porc_inercia[1], 1), "%)"),
       y = paste0("Eje 2 (", round(porc_inercia[2], 1), "%)")) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
        plot.subtitle = element_text(hjust = 0.5, size = 11))

8.2.1 Interpretación del plano de estratos

cat("** Lectura del plano factorial de estratos **\n\n")
## ** Lectura del plano factorial de estratos **
cat("EJE 1:\n")
## EJE 1:
cat("- Izquierda: Bajo y Medio (negativos)\n")
## - Izquierda: Bajo y Medio (negativos)
cat("- Derecha: Alto (positivo, coord = -0.49)\n")
## - Derecha: Alto (positivo, coord = -0.49)
cat("→ Opone estrato alto vs bajo/medio\n\n")
## → Opone estrato alto vs bajo/medio
cat("EJE 2:\n")
## EJE 2:
cat("- Arriba: Bajo (coord = 0.12)\n")
## - Arriba: Bajo (coord = 0.12)
cat("- Abajo: Medio y Alto (negativos)\n")
## - Abajo: Medio y Alto (negativos)
cat("→ Separa bajo de los otros dos\n\n")
## → Separa bajo de los otros dos
cat("OBSERVACIÓN:\n")
## OBSERVACIÓN:
cat("- Estrato Alto es el MÁS ALEJADO del origen\n")
## - Estrato Alto es el MÁS ALEJADO del origen
cat("→ Tiene distribución de carreras más diferente del promedio\n")
## → Tiene distribución de carreras más diferente del promedio

8.3 Biplot: Representación simultánea

Las relaciones cuasibaricéntricas permiten representar filas y columnas en el mismo gráfico:

\[ F_s(i) = \frac{1}{\sqrt{\lambda_s}} \sum_{j=1}^p \frac{f_{ij}}{f_{i\cdot}} G_s(j) \]

Interpretación física: La coordenada de una fila es el promedio ponderado dilatado de las coordenadas de las columnas (ponderado según su perfil).

# Combinar coordenadas de filas y columnas
df_biplot <- rbind(
  data.frame(x = coord_carreras[,1], y = coord_carreras[,2],
             Tipo = "Carrera", Etiqueta = rownames(K)),
  data.frame(x = coord_estratos[,1], y = coord_estratos[,2],
             Tipo = "Estrato", Etiqueta = colnames(K))
)

# Colores
colores_biplot <- c("Carrera" = "darkred", "Estrato" = "darkblue")

# Gráfica
ggplot(df_biplot, aes(x = x, y = y, color = Tipo, label = Etiqueta)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(size = 5, alpha = 0.7) +
  geom_text(vjust = -1.2, size = 4, fontface = "bold", show.legend = FALSE) +
  scale_color_manual(values = colores_biplot) +
  labs(title = "Biplot del ACS: Carreras y Estratos simultáneamente",
       subtitle = "Proximidad en el plano indica asociación",
       x = paste0("Eje 1 (", round(porc_inercia[1], 1), "%)"),
       y = paste0("Eje 2 (", round(porc_inercia[2], 1), "%)"),
       color = "") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
        plot.subtitle = element_text(hjust = 0.5, size = 11),
        legend.position = "top")

8.3.1 Lectura del biplot

cat("** Cómo leer el biplot **\n\n")
## ** Cómo leer el biplot **
cat("1. PROXIMIDAD = ASOCIACIÓN\n")
## 1. PROXIMIDAD = ASOCIACIÓN
cat("   - Geología está CERCA de Alto → asociación positiva\n")
##    - Geología está CERCA de Alto → asociación positiva
cat("   - Química está más cerca de Bajo → más estudiantes de estrato bajo\n\n")
##    - Química está más cerca de Bajo → más estudiantes de estrato bajo
cat("2. DIRECCIÓN\n")
## 2. DIRECCIÓN
cat("   - Geología y Alto están en la MISMA dirección (ambos izquierda)\n")
##    - Geología y Alto están en la MISMA dirección (ambos izquierda)
cat("   - Se atraen mutuamente en el análisis\n\n")
##    - Se atraen mutuamente en el análisis
cat("3. DISTANCIA AL ORIGEN\n")
## 3. DISTANCIA AL ORIGEN
cat("   - Geología (más lejos) → perfil más atípico\n")
##    - Geología (más lejos) → perfil más atípico
cat("   - Biología (más cerca) → perfil más típico\n\n")
##    - Biología (más cerca) → perfil más típico
cat("4. OPOSICIONES\n")
## 4. OPOSICIONES
cat("   - Eje 1 opone: Alto (izquierda) vs Bajo/Medio (derecha)\n")
##    - Eje 1 opone: Alto (izquierda) vs Bajo/Medio (derecha)
cat("   - Geología queda del lado de Alto\n")
##    - Geología queda del lado de Alto

9 Ayudas para la Interpretación

9.1 Contribuciones absolutas

Miden cuánto contribuye cada categoría a la inercia del eje.

\[ CA_s(i) = \frac{f_{i\cdot} F_s^2(i)}{\lambda_s} \times 100 \]

Interpretación: Categorías con contribución > promedio (100/n) son las que más definen el eje.

# Calcular ayudas completas
inercia_completa <- inertia.dudi(acs, row.inertia = TRUE, col.inertia = TRUE)

# Contribuciones absolutas de carreras
contrib_carreras <- inercia_completa$row.abs * 100

cat("** Contribuciones absolutas de carreras (%) **\n\n")
## ** Contribuciones absolutas de carreras (%) **
contrib_df <- data.frame(
  Carrera = rownames(K),
  Eje1 = round(contrib_carreras[,1], 1),
  Eje2 = round(contrib_carreras[,2], 1)
)
contrib_df <- contrib_df[order(-contrib_df$Eje1), ]
kable(contrib_df, row.names = FALSE)
Carrera Eje1 Eje2
Geol 6558.1 915.9
Farm 1604.7 67.3
Esta 588.7 439.5
Mate 425.8 156.9
Fisi 326.5 3560.7
Quim 282.3 4280.7
Biol 213.9 579.0
promedio <- 100 / n_carreras
cat("\nContribución promedio:", round(promedio, 1), "%\n")
## 
## Contribución promedio: 14.3 %
cat("\nCarreras con contribución > promedio en Eje 1:\n")
## 
## Carreras con contribución > promedio en Eje 1:
cat("- Geología: 65.6% (¡domina el eje!)\n")
## - Geología: 65.6% (¡domina el eje!)
cat("- Farmacia: 16.1%\n")
## - Farmacia: 16.1%
# Contribuciones de estratos
contrib_estratos <- inercia_completa$col.abs * 100
cat("\n\n** Contribuciones absolutas de estratos (%) **\n\n")
## 
## 
## ** Contribuciones absolutas de estratos (%) **
contrib_est_df <- data.frame(
  Estrato = colnames(K),
  Eje1 = round(contrib_estratos[,1], 1),
  Eje2 = round(contrib_estratos[,2], 1)
)
kable(contrib_est_df, row.names = FALSE)
Estrato Eje1 Eje2
Bajo 150.1 5827.5
Medio 2125.7 3717.0
Alto 7724.2 455.5
cat("\nEn Eje 1:\n")
## 
## En Eje 1:
cat("- Alto: 77.2% (define el eje)\n")
## - Alto: 77.2% (define el eje)
cat("- Medio: 21.3%\n")
## - Medio: 21.3%

9.2 Cosenos cuadrados (calidad de representación)

Miden qué tan bien representada está una categoría en un eje o plano.

\[ \cos^2_s(i) = \frac{F_s^2(i)}{d^2(i, g)} \]

Valor entre 0 y 1:

  • Cercano a 1: muy bien representada
  • Cercano a 0: mal representada (proyección engañosa)
# Cosenos cuadrados de carreras
cos2_carreras <- inercia_completa$row.rel

cat("** Cosenos cuadrados de carreras **\n\n")
## ** Cosenos cuadrados de carreras **
cos2_df <- data.frame(
  Carrera = rownames(K),
  Eje1 = round(cos2_carreras[,1], 3),
  Eje2 = round(cos2_carreras[,2], 3),
  Plano12 = round(cos2_carreras[,1] + cos2_carreras[,2], 3)
)
kable(cos2_df, row.names = FALSE)
Carrera Eje1 Eje2 Plano12
Biol -68.856 -31.144 -100.000
Esta 88.908 11.092 100.000
Farm 99.304 -0.696 98.608
Fisi -35.428 -64.572 -100.000
Geol -97.719 2.281 -95.438
Mate 94.197 -5.803 88.395
Quim 28.293 71.707 100.000
cat("\nInterpretación:\n")
## 
## Interpretación:
cat("- Geología: cos²=0.978 en plano 1-2 → EXCELENTE representación\n")
## - Geología: cos²=0.978 en plano 1-2 → EXCELENTE representación
cat("- Farmacia: cos²=0.994 → también excelente\n")
## - Farmacia: cos²=0.994 → también excelente
cat("- Todas las carreras: cos² > 0.80 → buen ajuste\n\n")
## - Todas las carreras: cos² > 0.80 → buen ajuste
cat("Conclusión: El primer plano factorial representa MUY BIEN todas las carreras.\n")
## Conclusión: El primer plano factorial representa MUY BIEN todas las carreras.

10 Verificación: Fórmulas Cuasibaricéntricas

Vamos a verificar numéricamente que la coordenada de Geología es el promedio ponderado de las coordenadas de los estratos.

cat("** Verificación de fórmula cuasibaricéntrica **\n\n")
## ** Verificación de fórmula cuasibaricéntrica **
# Perfil de Geología (fila 5)
perfil_geol <- perfiles_fila[5, ] / 100
cat("Perfil de Geología:\n")
## Perfil de Geología:
print(perfil_geol)
##  Bajo Medio  Alto 
##   0.4   0.2   0.4
# Coordenadas de estratos en Eje 1
coord_estratos_eje1 <- coord_estratos[,1]
cat("\nCoordenadas de estratos en Eje 1:\n")
## 
## Coordenadas de estratos en Eje 1:
print(coord_estratos_eje1)
## [1]  0.04578864  0.16952099 -0.48836481
# Promedio ponderado
promedio_ponderado <- sum(perfil_geol * coord_estratos_eje1)
cat("\nPromedio ponderado:", round(promedio_ponderado, 4), "\n")
## 
## Promedio ponderado: -0.1431
# Factor de dilatación
lambda1 <- acs$eig[1]
factor_dilatacion <- 1 / sqrt(lambda1)
cat("Factor de dilatación (1/√λ₁):", round(factor_dilatacion, 4), "\n")
## Factor de dilatación (1/√λ₁): 4.2181
# Coordenada calculada
coord_calculada <- promedio_ponderado * factor_dilatacion
cat("\nCoordenada calculada:", round(coord_calculada, 4), "\n")
## 
## Coordenada calculada: -0.6037
# Coordenada real del ACS
coord_real <- coord_carreras[5, 1]
cat("Coordenada real (del ACS):", round(coord_real, 4), "\n")
## Coordenada real (del ACS): -0.6037
# Diferencia
diferencia <- abs(coord_calculada - coord_real)
cat("\nDiferencia:", format(diferencia, scientific = TRUE), "\n")
## 
## Diferencia: 2.220446e-16
if(diferencia < 0.001) {
  cat("\n✓ ¡Verificación exitosa! La fórmula cuasibaricéntrica funciona.\n")
} else {
  cat("\n✗ Hay discrepancia (posiblemente errores de redondeo).\n")
}
## 
## ✓ ¡Verificación exitosa! La fórmula cuasibaricéntrica funciona.

Interpretación: Geología está donde está porque tiene 40% de estrato alto (que tira hacia la izquierda), 40% de estrato bajo y solo 20% de medio.


11 Resumen y Conclusiones

11.1 Resumen del análisis

cat("** RESUMEN DEL ANÁLISIS DE CORRESPONDENCIAS SIMPLES **\n\n")
## ** RESUMEN DEL ANÁLISIS DE CORRESPONDENCIAS SIMPLES **
cat("1. ASOCIACIÓN SIGNIFICATIVA\n")
## 1. ASOCIACIÓN SIGNIFICATIVA
cat("   - χ² = 29.19, p = 0.004 < 0.05\n")
##    - χ² = 29.19, p = 0.004 < 0.05
cat("   - Hay asociación entre carreras y estratos\n\n")
##    - Hay asociación entre carreras y estratos
cat("2. ESTRUCTURA PRINCIPAL (Eje 1, 85.7% inercia)\n")
## 2. ESTRUCTURA PRINCIPAL (Eje 1, 85.7% inercia)
cat("   - Opone Geología vs demás carreras\n")
##    - Opone Geología vs demás carreras
cat("   - Geología tiene 40% estrato alto (vs 18.2% promedio)\n")
##    - Geología tiene 40% estrato alto (vs 18.2% promedio)
cat("   - Es el doble del esperado → asociación fuerte\n\n")
##    - Es el doble del esperado → asociación fuerte
cat("3. PERFILES DESTACADOS\n")
## 3. PERFILES DESTACADOS
cat("   - Geología: perfil más atípico (más estrato alto)\n")
##    - Geología: perfil más atípico (más estrato alto)
cat("   - Química: más estrato bajo que promedio\n")
##    - Química: más estrato bajo que promedio
cat("   - Biología: perfil más cercano al promedio\n\n")
##    - Biología: perfil más cercano al promedio
cat("4. REPRESENTACIÓN\n")
## 4. REPRESENTACIÓN
cat("   - Primer plano retiene 100% de información útil\n")
##    - Primer plano retiene 100% de información útil
cat("   - Todas las categorías bien representadas (cos² > 0.8)\n")
##    - Todas las categorías bien representadas (cos² > 0.8)
cat("   - Biplot permite lectura directa de asociaciones\n")
##    - Biplot permite lectura directa de asociaciones

11.2 Ventajas del ACS

cat("** VENTAJAS DEL MÉTODO **\n\n")
## ** VENTAJAS DEL MÉTODO **
cat("✓ Visualización intuitiva de tablas de contingencia\n")
## ✓ Visualización intuitiva de tablas de contingencia
cat("✓ Detecta asociaciones no evidentes en tablas\n")
## ✓ Detecta asociaciones no evidentes en tablas
cat("✓ Representación simultánea de filas y columnas\n")
## ✓ Representación simultánea de filas y columnas
cat("✓ Distancia χ² pondera por frecuencias marginales\n")
## ✓ Distancia χ² pondera por frecuencias marginales
cat("✓ Invariante ante reagrupaciones (equivalencia distribucional)\n")
## ✓ Invariante ante reagrupaciones (equivalencia distribucional)
cat("✓ Conexión directa con prueba χ² de independencia\n")
## ✓ Conexión directa con prueba χ² de independencia

11.3 Limitaciones

cat("** LIMITACIONES Y CUIDADOS **\n\n")
## ** LIMITACIONES Y CUIDADOS **
cat("⚠ Solo para variables CUALITATIVAS (categorías)\n")
## ⚠ Solo para variables CUALITATIVAS (categorías)
cat("⚠ Solo DOS variables a la vez (para más → ACM)\n")
## ⚠ Solo DOS variables a la vez (para más → ACM)
cat("⚠ Sensible a categorías de muy baja frecuencia\n")
## ⚠ Sensible a categorías de muy baja frecuencia
cat("⚠ Interpretación requiere entender distancia χ²\n")
## ⚠ Interpretación requiere entender distancia χ²
cat("⚠ No mide causalidad, solo asociación\n")
## ⚠ No mide causalidad, solo asociación

11.4 Conceptos clave para recordar

  1. Perfil fila/columna: Distribución condicional (suma 100%)
  2. Distancia χ²: Amplifica diferencias en categorías raras
  3. Inercia: φ² = medida de asociación global
  4. Relaciones cuasibaricéntricas: Promedio ponderado dilatado
  5. Biplot: Proximidad = asociación

11.5 Próximos pasos

El ACS es la base para métodos más avanzados:

  • Análisis de Correspondencias Múltiples (ACM): Para más de 2 variables cualitativas
  • Análisis Factorial Múltiple (AFM): Mezcla variables cuanti y cuali
  • Análisis de Co-inercia: Relacionar dos tablas

12 Referencias

12.1 Bibliografía

  • Lebart, L., Morineau, A., & Piron, M. (2006). Statistique exploratoire multidimensionnelle. Dunod.
  • Greenacre, M. (2007). Correspondence Analysis in Practice (2nd ed.). Chapman & Hall/CRC.
  • Benzécri, J.-P. (1973). L’Analyse des Données. Dunod.

12.2 Software

cat("** Software utilizado **\n\n")
## ** Software utilizado **
cat("R version:", R.version.string, "\n")
## R version: R version 4.5.2 (2025-10-31 ucrt)
cat("ade4 version:", as.character(packageVersion("ade4")), "\n")
## ade4 version: 1.7.23
cat("ggplot2 version:", as.character(packageVersion("ggplot2")), "\n")
## ggplot2 version: 4.0.2

¡Fin del notebook de ACS!

12.3 Preguntas de discusión

  1. ¿Por qué la distancia χ² amplifica diferencias en categorías de baja frecuencia?
  2. ¿Qué significa que el primer eje retenga 85.7% de la inercia?
  3. ¿Cómo se interpreta la proximidad entre una carrera y un estrato en el biplot?
  4. ¿Cuál es la diferencia entre contribución absoluta y coseno cuadrado?
  5. ¿Cuándo usarías ACS vs ACM vs regresión logística?