1 Introducción al Análisis de Correspondencias Múltiples

1.1 ¿Qué es el ACM?

El Análisis de Correspondencias Múltiples (ACM) es una técnica de análisis exploratorio multivariado que se utiliza para analizar tablas de individuos descritos por variables cualitativas.

1.1.1 Aplicaciones típicas

  • Encuestas sociales y cuestionarios estructurados
  • Estudios de mercado con variables categóricas
  • Análisis de perfiles sociodemográficos
  • Exploración de patrones en datos categóricos

1.1.2 Diferencia con otros métodos

  • ACP (Análisis de Componentes Principales): para variables cuantitativas
  • ACS (Análisis de Correspondencias Simple): para dos variables cualitativas
  • ACM: para múltiples variables cualitativas simultáneamente

1.2 Objetivos del ACM

El ACM tiene cuatro objetivos principales:

  1. Describir las asociaciones entre las categorías de las variables activas
  2. Descubrir patrones de individuos que pueden dar origen a grupos
  3. Sintetizar en planos factoriales las asociaciones entre categorías y posibles grupos
  4. Explorar la explicación de la estructura con variables ilustrativas

1.3 Ejemplo de aplicación

Vamos a utilizar un ejemplo de 445 admitidos a una Facultad de Ciencias, siguiendo la estructura del libro pero con datos sintéticos realistas.

Variables sociodemográficas activas:

  • Género: Femenino, Masculino
  • Edad: 16 o menos, 17, 18, 19 o más
  • Estrato: Bajo, Medio, Alto
  • Procedencia: Bogotá, Cundinamarca, Otro departamento
# Cargar paquetes necesarios
library(ade4)        # Análisis de datos y ACM
library(ggplot2)     # Gráficas elegantes
library(knitr)       # Tablas bonitas

# Crear datos sintéticos realistas (basados en las frecuencias del libro)
set.seed(2013)
n <- 445

# Crear variables con frecuencias aproximadas del ejemplo original
Genero <- factor(sample(c("F", "M"), n, replace = TRUE, prob = c(128/445, 317/445)),
                 levels = c("F", "M"))

Edad <- factor(sample(c("16om", "17", "18", "19oM"), n, replace = TRUE,
                      prob = c(118/445, 171/445, 56/445, 100/445)),
               levels = c("16om", "17", "18", "19oM"))

Estrato <- factor(sample(c("bajo", "medio", "alto"), n, replace = TRUE,
                         prob = c(179/445, 185/445, 81/445)),
                  levels = c("bajo", "medio", "alto"))

Origen <- factor(sample(c("Bogo", "Cund", "Otro"), n, replace = TRUE,
                        prob = c(311/445, 38/445, 96/445)),
                 levels = c("Bogo", "Cund", "Otro"))

# Introducir asociaciones realistas (como en datos reales)
# Mujeres tienden a ser más jóvenes
idx_mujeres <- which(Genero == "F")
Edad[sample(idx_mujeres, 30)] <- "16om"

# Estrato alto tiende a ser de Bogotá
idx_alto <- which(Estrato == "alto")
Origen[sample(idx_alto, 50)] <- "Bogo"

# Crear data frame
Y <- data.frame(Genero, Edad, Estrato, Origen)

# Añadir variable ilustrativa: Carrera
Carrera <- factor(sample(c("Biol", "Esta", "Farm", "Fisi", "Geol", "Mate", "Quim"),
                         n, replace = TRUE,
                         prob = c(63, 66, 73, 82, 45, 53, 63)/445),
                  levels = c("Biol", "Esta", "Farm", "Fisi", "Geol", "Mate", "Quim"))

cat("Datos creados:", nrow(Y), "admitidos con", ncol(Y), "variables activas\n")
## Datos creados: 445 admitidos con 4 variables activas

2 Transformaciones de la Tabla de Datos

El ACM trabaja con tres representaciones equivalentes de los mismos datos. Es fundamental entender estas transformaciones.

2.1 Tabla de Código Condensado (Y)

Es la tabla original de datos: n individuos × s variables.

Características:

  • Cada fila representa un individuo
  • Cada columna representa una variable cualitativa (factor en R)
  • No tiene significado numérico directo
# Mostrar extracto de la tabla (primeros 12 registros)
kable(head(Y, 12), caption = "Tabla de Código Condensado (extracto)", row.names = TRUE)
Tabla de Código Condensado (extracto)
Genero Edad Estrato Origen
1 M 17 alto Bogo
2 F 16om medio Bogo
3 F 17 bajo Bogo
4 F 16om medio Cund
5 M 18 medio Cund
6 F 19oM medio Bogo
7 F 17 bajo Bogo
8 F 17 medio Bogo
9 F 16om medio Bogo
10 F 16om bajo Bogo
11 M 18 medio Bogo
12 M 17 bajo Cund
# Dimensiones
n <- nrow(Y)
s <- ncol(Y)
cat("\nDimensiones de Y:", n, "individuos ×", s, "variables\n")
## 
## Dimensiones de Y: 445 individuos × 4 variables
# Resumen de frecuencias
cat("\nResumen de las variables:\n")
## 
## Resumen de las variables:
summary(Y)
##  Genero    Edad      Estrato     Origen   
##  F:139   16om:142   bajo :169   Bogo:318  
##  M:306   17  :179   medio:201   Cund: 54  
##          18  : 46   alto : 75   Otro: 73  
##          19oM: 78

2.2 Tabla Disyuntiva Completa (TDC o Z)

Es una tabla binaria de n individuos × p categorías (donde p es el número total de categorías de todas las variables).

2.2.1 Definición matemática

El término general de la TDC es:

\[ z_{ij} = \begin{cases} 1 & \text{si el individuo } i \text{ asume la categoría } j \\ 0 & \text{si no la asume} \end{cases} \]

2.2.2 Propiedades importantes

  1. Disyunción completa: En cada fila, hay exactamente un 1 por cada variable (las categorías de cada variable son mutuamente excluyentes)
  2. Suma fila: La suma de cada fila es igual a \(s\) (número de variables)
  3. Suma columna: La suma de cada columna \(j\) es \(n_j\) (número de individuos con esa categoría)
  4. Total de la tabla: \(n \times s\)
# Función para crear tabla disyuntiva completa
acm_disjonctif <- function(df) {
  result <- NULL
  for(i in 1:ncol(df)) {
    dummy <- model.matrix(~ df[,i] - 1)
    colnames(dummy) <- paste0(names(df)[i], ".", levels(df[,i]))
    result <- cbind(result, dummy)
  }
  return(result)
}

# Crear tabla disyuntiva completa
Z <- acm_disjonctif(Y)

# Mostrar extracto (primeros 10 individuos)
extracto_indices <- c(1:5, 25, 50, 75, 100, 125)
kable(head(cbind(Y, Z)[extracto_indices, ], 10), 
      caption = "Tabla Condensada Y y Tabla Disyuntiva Completa Z (extracto)")
Tabla Condensada Y y Tabla Disyuntiva Completa Z (extracto)
Genero Edad Estrato Origen Genero.F Genero.M Edad.16om Edad.17 Edad.18 Edad.19oM Estrato.bajo Estrato.medio Estrato.alto Origen.Bogo Origen.Cund Origen.Otro
1 M 17 alto Bogo 0 1 0 1 0 0 0 0 1 1 0 0
2 F 16om medio Bogo 1 0 1 0 0 0 0 1 0 1 0 0
3 F 17 bajo Bogo 1 0 0 1 0 0 1 0 0 1 0 0
4 F 16om medio Cund 1 0 1 0 0 0 0 1 0 0 1 0
5 M 18 medio Cund 0 1 0 0 1 0 0 1 0 0 1 0
25 M 17 medio Cund 0 1 0 1 0 0 0 1 0 0 1 0
50 M 17 medio Bogo 0 1 0 1 0 0 0 1 0 1 0 0
75 M 18 alto Bogo 0 1 0 0 1 0 0 0 1 1 0 0
100 M 19oM medio Bogo 0 1 0 0 0 1 0 1 0 1 0 0
125 M 16om bajo Bogo 0 1 1 0 0 0 1 0 0 1 0 0
# Dimensiones
p <- ncol(Z)
cat("\nDimensiones de Z:", n, "individuos ×", p, "categorías\n")
## 
## Dimensiones de Z: 445 individuos × 12 categorías
cat("Total de la tabla Z:", sum(Z), "=", n, "×", s, "\n")
## Total de la tabla Z: 1780 = 445 × 4
# Verificar propiedad: suma de cada fila = número de variables
cat("\nSuma de fila (primeros 8):", rowSums(Z)[1:8], "\n")
## 
## Suma de fila (primeros 8): 4 4 4 4 4 4 4 4
cat("(Todas las filas suman", s, "como debe ser)\n")
## (Todas las filas suman 4 como debe ser)

2.2.3 Estructura de Z

La TDC \(Z\) es una yuxtaposición de \(s\) matrices indicadoras:

\[ Z = [Z_1 \quad Z_2 \quad \cdots \quad Z_q \quad \cdots \quad Z_s] \]

donde cada \(Z_q\) es la matriz indicadora de la partición originada por la variable \(q\).

# Número de categorías por variable
n_categorias <- sapply(Y, function(x) length(levels(x)))
cat("Número de categorías por variable:\n")
## Número de categorías por variable:
print(n_categorias)
##  Genero    Edad Estrato  Origen 
##       2       4       3       3
cat("\nTotal p =", sum(n_categorias), "categorías\n")
## 
## Total p = 12 categorías
# Mostrar nombres de columnas de Z agrupadas
cat("\nEstructura de columnas de Z:\n")
## 
## Estructura de columnas de Z:
for(i in 1:s) {
  cat(names(Y)[i], ":", colnames(Z)[sum(n_categorias[0:(i-1)]) + 1:n_categorias[i]], "\n")
}
## Genero : Genero.F Genero.M 
## Edad : Edad.16om Edad.17 Edad.18 Edad.19oM 
## Estrato : Estrato.bajo Estrato.medio Estrato.alto 
## Origen : Origen.Bogo Origen.Cund Origen.Otro

2.3 Tabla de Burt (B)

Es la tabla de contingencias múltiples, definida como:

\[ B = Z'Z \]

Es una matriz cuadrada y simétrica de orden \(p \times p\).

2.3.1 Estructura de B

  • Diagonal: Matrices diagonales con frecuencias \(n_j\) de cada categoría
  • Fuera de diagonal: Tablas de contingencia cruzando todas las variables por parejas (\(Z'_q Z_{q'}\))
# Crear tabla de Burt: Z'Z
B <- t(Z) %*% Z

# Mostrar tabla completa
kable(B, caption = "Tabla de Burt: Contingencias Múltiples")
Tabla de Burt: Contingencias Múltiples
Genero.F Genero.M Edad.16om Edad.17 Edad.18 Edad.19oM Estrato.bajo Estrato.medio Estrato.alto Origen.Bogo Origen.Cund Origen.Otro
Genero.F 139 0 55 56 11 17 54 66 19 88 25 26
Genero.M 0 306 87 123 35 61 115 135 56 230 29 47
Edad.16om 55 87 142 0 0 0 57 63 22 102 20 20
Edad.17 56 123 0 179 0 0 69 76 34 126 19 34
Edad.18 11 35 0 0 46 0 14 26 6 30 10 6
Edad.19oM 17 61 0 0 0 78 29 36 13 60 5 13
Estrato.bajo 54 115 57 69 14 29 169 0 0 119 19 31
Estrato.medio 66 135 63 76 26 36 0 201 0 132 30 39
Estrato.alto 19 56 22 34 6 13 0 0 75 67 5 3
Origen.Bogo 88 230 102 126 30 60 119 132 67 318 0 0
Origen.Cund 25 29 20 19 10 5 19 30 5 0 54 0
Origen.Otro 26 47 20 34 6 13 31 39 3 0 0 73
# Matriz diagonal Dp (frecuencias marginales)
nj <- diag(B)  # Frecuencias de cada categoría
Dp <- diag(nj)

cat("\nFrecuencias marginales (diagonal de B):\n")
## 
## Frecuencias marginales (diagonal de B):
nombres_cat <- colnames(Z)
freq_df <- data.frame(Categoria = nombres_cat, Frecuencia = nj)
kable(freq_df)
Categoria Frecuencia
Genero.F Genero.F 139
Genero.M Genero.M 306
Edad.16om Edad.16om 142
Edad.17 Edad.17 179
Edad.18 Edad.18 46
Edad.19oM Edad.19oM 78
Estrato.bajo Estrato.bajo 169
Estrato.medio Estrato.medio 201
Estrato.alto Estrato.alto 75
Origen.Bogo Origen.Bogo 318
Origen.Cund Origen.Cund 54
Origen.Otro Origen.Otro 73

2.3.2 Ejemplo de lectura

De la tabla de Burt podemos leer:

# Resumen de frecuencias por variable
cat("Frecuencias por variable:\n\n")
## Frecuencias por variable:
for(i in 1:s) {
  cat(names(Y)[i], ":\n")
  print(table(Y[, i]))
  cat("\n")
}
## Genero :
## 
##   F   M 
## 139 306 
## 
## Edad :
## 
## 16om   17   18 19oM 
##  142  179   46   78 
## 
## Estrato :
## 
##  bajo medio  alto 
##   169   201    75 
## 
## Origen :
## 
## Bogo Cund Otro 
##  318   54   73
# Extraer subtablas de interés de B
# Género vs Estrato
idx_genero <- 1:2
idx_estrato <- 7:9
cat("Tabla de contingencia Género × Estrato:\n")
## Tabla de contingencia Género × Estrato:
print(B[idx_genero, idx_estrato])
##          Estrato.bajo Estrato.medio Estrato.alto
## Genero.F           54            66           19
## Genero.M          115           135           56
# Edad vs Origen
idx_edad <- 3:6
idx_origen <- 10:12
cat("\n\nTabla de contingencia Edad × Origen:\n")
## 
## 
## Tabla de contingencia Edad × Origen:
print(B[idx_edad, idx_origen])
##           Origen.Bogo Origen.Cund Origen.Otro
## Edad.16om         102          20          20
## Edad.17           126          19          34
## Edad.18            30          10           6
## Edad.19oM          60           5          13
# Interpretación directa
cat("\n\nEjemplos de lectura de la tabla de Burt:\n")
## 
## 
## Ejemplos de lectura de la tabla de Burt:
cat("- Total de mujeres:", B[1,1], "\n")
## - Total de mujeres: 139
cat("- Total de hombres:", B[2,2], "\n")
## - Total de hombres: 306
cat("- Mujeres de estrato bajo:", B[1,7], "\n")
## - Mujeres de estrato bajo: 54
cat("- Hombres de estrato alto:", B[2,9], "\n")
## - Hombres de estrato alto: 56

3 El ACM como Análisis de Correspondencias Simple

El ACM se puede ver como un Análisis de Correspondencias Simple (ACS) aplicado a la tabla disyuntiva completa \(Z\).

3.1 Tabla de frecuencias relativas

La tabla de frecuencias relativas asociada a \(Z\) es:

\[ F = \frac{1}{ns} Z \]

con marginales:

  • Fila: \(f_{i\cdot} = \frac{1}{n}\) para todo \(i\) (todos los individuos tienen el mismo peso)
  • Columna: \(f_{\cdot j} = \frac{n_j}{ns}\) (depende de la frecuencia de la categoría \(j\))
# Tabla de frecuencias relativas
F <- Z / (n * s)

# Marginales
marginal_fila <- 1/n
marginal_col <- colSums(Z) / (n * s)

cat("Marginal fila (constante):", round(marginal_fila * 100, 4), "%\n")
## Marginal fila (constante): 0.2247 %
cat("\nMarginales columna (en %):\n")
## 
## Marginales columna (en %):
marg_df <- data.frame(
  Categoria = nombres_cat,
  Porcentaje = round(marginal_col * 100, 2)
)
kable(marg_df)
Categoria Porcentaje
Genero.F Genero.F 7.81
Genero.M Genero.M 17.19
Edad.16om Edad.16om 7.98
Edad.17 Edad.17 10.06
Edad.18 Edad.18 2.58
Edad.19oM Edad.19oM 4.38
Estrato.bajo Estrato.bajo 9.49
Estrato.medio Estrato.medio 11.29
Estrato.alto Estrato.alto 4.21
Origen.Bogo Origen.Bogo 17.87
Origen.Cund Origen.Cund 3.03
Origen.Otro Origen.Otro 4.10

4 Nube de Individuos

Los \(n\) individuos conforman la nube \(N_n\) en el espacio \(\mathbb{R}^p\).

4.1 Coordenadas y perfiles

Los perfiles fila son las filas de la tabla \(\frac{1}{s}Z\):

  • Son barras de altura \(\frac{1}{s}\) cuando el individuo asume la categoría \(j\)
  • Son 0 cuando no la asume

Peso de cada individuo: \(\frac{1}{n}\) (todos tienen el mismo peso)

Métrica: \(M = ns D_p^{-1}\), donde \(D_p\) es la matriz diagonal de frecuencias de categorías.

# Perfil del primer individuo
i_ejemplo <- 25
perfil_i <- Z[i_ejemplo, ] / s

cat("Perfil del individuo", i_ejemplo, ":\n")
## Perfil del individuo 25 :
cat("Categorías que asume:\n")
## Categorías que asume:
print(Y[i_ejemplo, ])
##    Genero Edad Estrato Origen
## 25      M   17   medio   Cund
cat("\nPerfil numérico (altura 0.25 en categorías asumidas):\n")
## 
## Perfil numérico (altura 0.25 en categorías asumidas):
perfil_df <- data.frame(
  Categoria = nombres_cat,
  Valor = perfil_i
)
kable(perfil_df[perfil_i > 0, ])
Categoria Valor
Genero.M Genero.M 0.25
Edad.17 Edad.17 0.25
Estrato.medio Estrato.medio 0.25
Origen.Cund Origen.Cund 0.25

4.2 Centro de gravedad

La coordenada \(j\) del centro de gravedad \(g_p\) es:

\[ g_{p,j} = \frac{1}{n} \sum_{i=1}^n \frac{1}{s} z_{ij} = \frac{n_j}{ns} \]

Es decir, el centro de gravedad es la marginal columna de \(F\).

# Centro de gravedad de la nube de individuos
gp <- colSums(Z) / (n * s)

cat("Centro de gravedad (coordenadas en %):\n")
## Centro de gravedad (coordenadas en %):
cg_df <- data.frame(
  Categoria = nombres_cat,
  Coordenada = round(gp * 100, 2)
)
kable(cg_df)
Categoria Coordenada
Genero.F Genero.F 7.81
Genero.M Genero.M 17.19
Edad.16om Edad.16om 7.98
Edad.17 Edad.17 10.06
Edad.18 Edad.18 2.58
Edad.19oM Edad.19oM 4.38
Estrato.bajo Estrato.bajo 9.49
Estrato.medio Estrato.medio 11.29
Estrato.alto Estrato.alto 4.21
Origen.Bogo Origen.Bogo 17.87
Origen.Cund Origen.Cund 3.03
Origen.Otro Origen.Otro 4.10

4.3 Distancia entre individuos

La distancia al cuadrado entre dos individuos \(i\) y \(l\) es:

\[ d^2(i, l) = \frac{n}{s} \sum_{j=1}^p \frac{1}{n_j} (z_{ij} - z_{lj})^2 \]

Interpretación:

  • Dos individuos se parecen cuando asumen más o menos las mismas categorías
  • La distancia se amplifica más cuando solo uno de los dos asume una categoría de baja frecuencia

4.3.1 Ejemplo de cálculo

# Función para calcular distancia entre dos individuos
dist_acm <- function(i1, i2, Z, nj, n, s) {
  diff <- Z[i1, ] - Z[i2, ]
  d2 <- (n/s) * sum(diff^2 / nj)
  return(sqrt(d2))
}

# Comparar individuos 50 y 100
i1 <- 50
i2 <- 100

cat("Individuo", i1, ":\n")
## Individuo 50 :
print(Y[i1, ])
##    Genero Edad Estrato Origen
## 50      M   17   medio   Bogo
cat("\nIndividuo", i2, ":\n")
## 
## Individuo 100 :
print(Y[i2, ])
##     Genero Edad Estrato Origen
## 100      M 19oM   medio   Bogo
# Calcular distancia
d_50_100 <- dist_acm(i1, i2, Z, nj, n, s)
cat("\nDistancia entre individuos", i1, "y", i2, ":", round(d_50_100, 2), "\n")
## 
## Distancia entre individuos 50 y 100 : 1.43
# Contar diferencias
diferencias <- sum(Z[i1, ] != Z[i2, ])
cat("Número de categorías diferentes:", diferencias, "\n")
## Número de categorías diferentes: 2

4.3.2 Matriz de distancias (extracto)

# Calcular matriz de distancias para una muestra de individuos
indices_muestra <- seq(25, 200, by = 25)

# Crear matriz de distancias
n_muestra <- length(indices_muestra)
D_muestra <- matrix(0, n_muestra, n_muestra)

for(i in 1:n_muestra) {
  for(j in 1:n_muestra) {
    if(i < j) {
      D_muestra[i,j] <- dist_acm(indices_muestra[i], indices_muestra[j], Z, nj, n, s)
      D_muestra[j,i] <- D_muestra[i,j]
    }
  }
}

rownames(D_muestra) <- indices_muestra
colnames(D_muestra) <- indices_muestra

cat("Matriz de distancias (muestra de individuos):\n")
## Matriz de distancias (muestra de individuos):
kable(round(D_muestra, 2))
25 50 75 100 125 150 175 200
25 0.00 1.55 2.74 2.11 2.24 2.11 1.55 2.49
50 1.55 0.00 2.25 1.43 1.62 1.43 0.00 1.94
75 2.74 2.25 0.00 2.43 2.31 1.74 2.25 2.55
100 2.11 1.43 2.43 0.00 1.85 2.02 1.43 2.14
125 2.24 1.62 2.31 1.85 0.00 1.88 1.62 1.08
150 2.11 1.43 1.74 2.02 1.88 0.00 1.43 2.17
175 1.55 0.00 2.25 1.43 1.62 1.43 0.00 1.94
200 2.49 1.94 2.55 2.14 1.08 2.17 1.94 0.00

4.4 Inercia de la nube de individuos

La inercia total de la nube de individuos es:

\[ I(N_n) = \frac{1}{n} \sum_{i=1}^n d^2(i, g_p) = \frac{p}{s} - 1 \]

Importante: La inercia depende solo del cociente entre número de categorías y número de variables, NO de los valores internos de la tabla. Por lo tanto, no tiene significado estadístico directo.

# Cálculo de inercia
inercia_total <- p/s - 1

cat("Inercia total de la nube de individuos:\n")
## Inercia total de la nube de individuos:
cat("p/s - 1 =", p, "/", s, "- 1 =", inercia_total, "\n\n")
## p/s - 1 = 12 / 4 - 1 = 2
cat("Esta inercia NO depende de los datos, solo de p y s.\n")
## Esta inercia NO depende de los datos, solo de p y s.
cat("Por eso NO tiene significado estadístico directo.\n")
## Por eso NO tiene significado estadístico directo.

5 Nube de Categorías

En el ACM se pone más atención a las categorías que a los individuos, porque usualmente los individuos son anónimos.

5.1 Distancia entre categorías

La distancia al cuadrado entre dos categorías \(j\) y \(k\) es:

\[ d^2(j, k) = n \sum_{i=1}^n \left( \frac{z_{ij}}{n_j} - \frac{z_{ik}}{n_k} \right)^2 = \frac{n}{n_j n_k}(b + c) \]

donde \(b\) = individuos que asumen \(j\) pero no \(k\), y \(c\) = individuos que asumen \(k\) pero no \(j\).

Interpretación clave:

  • Solo suman los individuos que asumen una y solo una de las dos categorías
  • Categorías de baja frecuencia se alejan más de las otras
# Calcular matriz de distancias entre categorías
dist_cat_acm <- function(j1, j2, Z, nj, n) {
  diff <- Z[, j1]/nj[j1] - Z[, j2]/nj[j2]
  d2 <- n * sum(diff^2)
  return(sqrt(d2))
}

# Matriz de distancias entre todas las categorías
D_cat <- matrix(0, p, p)
for(i in 1:p) {
  for(j in 1:p) {
    if(i < j) {
      D_cat[i,j] <- dist_cat_acm(i, j, Z, nj, n)
      D_cat[j,i] <- D_cat[i,j]
    }
  }
}

rownames(D_cat) <- nombres_cat
colnames(D_cat) <- nombres_cat

cat("Matriz de distancias entre categorías:\n")
## Matriz de distancias entre categorías:
kable(round(D_cat, 2))
Genero.F Genero.M Edad.16om Edad.17 Edad.18 Edad.19oM Estrato.bajo Estrato.medio Estrato.alto Origen.Bogo Origen.Cund Origen.Otro
Genero.F 0.00 2.16 1.96 1.92 3.37 2.74 1.95 1.82 2.74 1.68 2.91 2.65
Genero.M 2.16 0.00 1.68 1.39 2.99 2.21 1.45 1.31 2.28 0.87 2.85 2.38
Edad.16om 1.96 1.68 0.00 2.37 3.58 2.97 1.91 1.84 2.69 1.59 3.01 2.74
Edad.17 1.92 1.39 2.37 0.00 3.49 2.86 1.76 1.68 2.48 1.38 3.00 2.50
Edad.18 3.37 2.99 3.58 3.49 0.00 3.92 3.27 3.06 3.75 3.04 3.79 3.77
Edad.19oM 2.74 2.21 2.97 2.86 3.92 0.00 2.53 2.42 3.11 2.23 3.59 3.13
Estrato.bajo 1.95 1.45 1.91 1.76 3.27 2.53 0.00 2.20 2.93 1.44 3.00 2.55
Estrato.medio 1.82 1.31 1.84 1.68 3.06 2.42 2.20 0.00 2.85 1.33 2.83 2.44
Estrato.alto 2.74 2.28 2.69 2.48 3.75 3.11 2.93 2.85 0.00 2.20 3.62 3.40
Origen.Bogo 1.68 0.87 1.59 1.38 3.04 2.23 1.44 1.33 2.20 0.00 3.10 2.74
Origen.Cund 2.91 2.85 3.01 3.00 3.79 3.59 3.00 2.83 3.62 3.10 0.00 3.79
Origen.Otro 2.65 2.38 2.74 2.50 3.77 3.13 2.55 2.44 3.40 2.74 3.79 0.00

5.2 Distancia al centro de gravedad

La distancia de una categoría \(j\) al centro de gravedad es:

\[ d^2(j, g_n) = \frac{n}{n_j} - 1 \]

Importante: Las categorías de menores frecuencias son las más alejadas del origen.

# Distancias al centro de gravedad
dist_centro <- sqrt(n/nj - 1)

cat("Distancias de categorías al centro de gravedad:\n")
## Distancias de categorías al centro de gravedad:
dist_df <- data.frame(
  Categoria = nombres_cat,
  Frecuencia = nj,
  Dist_centro = round(dist_centro, 3)
)
dist_df <- dist_df[order(-dist_df$Dist_centro), ]
kable(dist_df, row.names = FALSE)
Categoria Frecuencia Dist_centro
Edad.18 46 2.945
Origen.Cund 54 2.691
Origen.Otro 73 2.257
Estrato.alto 75 2.221
Edad.19oM 78 2.169
Genero.F 139 1.484
Edad.16om 142 1.461
Estrato.bajo 169 1.278
Edad.17 179 1.219
Estrato.medio 201 1.102
Genero.M 306 0.674
Origen.Bogo 318 0.632
cat("\nObservación: Las categorías con menor frecuencia están más lejos del centro.\n")
## 
## Observación: Las categorías con menor frecuencia están más lejos del centro.

6 Ejecución del ACM

6.1 Análisis con ade4

# Ejecutar ACM con ade4
acm <- dudi.acm(Y, scannf = FALSE, nf = 3)

# Ver estructura básica
cat("Estructura del objeto ACM:\n")
## Estructura del objeto ACM:
cat("- eig: valores propios\n")
## - eig: valores propios
cat("- cw: pesos de categorías\n")
## - cw: pesos de categorías
cat("- li: coordenadas de individuos\n")
## - li: coordenadas de individuos
cat("- co: coordenadas de categorías\n")
## - co: coordenadas de categorías
cat("- cr: razones de correlación de variables\n")
## - cr: razones de correlación de variables

6.2 Valores propios y porcentaje de inercia

# Tabla de valores propios
n_ejes <- length(acm$eig)
vp_tabla <- data.frame(
  Eje = 1:n_ejes,
  Valor_propio = round(acm$eig, 3),
  Porcentaje = round(acm$eig * 100 / sum(acm$eig), 1),
  Acumulado = round(cumsum(acm$eig) * 100 / sum(acm$eig), 1)
)

kable(vp_tabla, caption = "Valores propios y porcentaje de inercia explicada")
Valores propios y porcentaje de inercia explicada
Eje Valor_propio Porcentaje Acumulado
1 0.323 16.1 16.1
2 0.281 14.0 30.2
3 0.279 13.9 44.1
4 0.254 12.7 56.8
5 0.241 12.0 68.9
6 0.222 11.1 80.0
7 0.205 10.2 90.2
8 0.196 9.8 100.0
# Gráfico de sedimentación
par(mar = c(5, 4, 4, 2))
barplot(acm$eig, names.arg = 1:n_ejes,
        main = "Histograma de valores propios (Scree plot)",
        xlab = "Eje", ylab = "Valor propio",
        col = "steelblue", border = "white", las = 1)
abline(h = mean(acm$eig), col = "red", lty = 2, lwd = 2)
legend("topright", legend = "Promedio", col = "red", lty = 2, lwd = 2, bty = "n")

6.2.1 Decisión sobre número de ejes

Criterio principal en ACM: Forma del histograma (NO el porcentaje de inercia)

Razones:

  1. El ACM tiene ejes “parásitos” (aparecen pero no contienen información relevante)
  2. La inercia total \(\frac{p}{s}-1\) no tiene significado estadístico
  3. El % de inercia explicada es artificialmente bajo

Del histograma:

  • Se observa un “codo” o cambio de pendiente
  • Los primeros ejes se destacan claramente sobre los demás
  • Decisión: Retener los primeros 3 ejes para interpretación
cat("Criterio de decisión:\n")
## Criterio de decisión:
cat("- Observar el histograma (scree plot)\n")
## - Observar el histograma (scree plot)
cat("- Buscar un 'codo' o cambio de pendiente\n")
## - Buscar un 'codo' o cambio de pendiente
cat("- Los primeros", sum(vp_tabla$Porcentaje[1:3]), "% de inercia NO es malo en ACM\n")
## - Los primeros 44 % de inercia NO es malo en ACM
cat("- En ACM, 40-50% de inercia en 2-3 ejes es típico y aceptable\n")
## - En ACM, 40-50% de inercia en 2-3 ejes es típico y aceptable

6.3 Dimensión del espacio

El rango de la matriz de inercia es \(p - s\) porque por cada variable hay una columna linealmente dependiente.

cat("Dimensión teórica del espacio:\n")
## Dimensión teórica del espacio:
cat("p - s =", p, "-", s, "=", p - s, "\n\n")
## p - s = 12 - 4 = 8
cat("Número de valores propios > 0:", sum(acm$eig > 1e-10), "\n")
## Número de valores propios > 0: 8
cat("(Coincide con p - s, como debe ser)\n")
## (Coincide con p - s, como debe ser)

7 Planos Factoriales e Interpretación

7.1 Plano de individuos

# Extraer coordenadas
coord_ind <- acm$li[, 1:2]
colnames(coord_ind) <- c("Eje1", "Eje2")

# Gráfica con ggplot2
ggplot(data.frame(coord_ind), aes(x = Eje1, y = Eje2)) +
  geom_point(alpha = 0.4, color = "steelblue", size = 2) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40", size = 0.5) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40", size = 0.5) +
  labs(title = "Primer plano factorial: Nube de individuos",
       subtitle = paste0("n = ", n, " admitidos"),
       x = paste0("Eje 1 (", round(vp_tabla$Porcentaje[1], 1), "%)"),
       y = paste0("Eje 2 (", round(vp_tabla$Porcentaje[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))

Lectura del plano:

  • La nube de individuos muestra la dispersión en el espacio sociodemográfico
  • Concentraciones de puntos indican perfiles similares
  • Los individuos son anónimos; se interpretan a través de sus categorías

7.2 Plano de categorías

# Coordenadas de categorías
coord_cat <- acm$co[, 1:2]
pesos_cat <- acm$cw

# Crear etiquetas limpias
etiquetas_cat <- rownames(coord_cat)
etiquetas_cat <- gsub("Genero\\.", "", etiquetas_cat)
etiquetas_cat <- gsub("Edad\\.", "", etiquetas_cat)
etiquetas_cat <- gsub("Estrato\\.", "", etiquetas_cat)
etiquetas_cat <- gsub("Origen\\.", "", etiquetas_cat)

# Asignar colores por variable
variable_cat <- c(rep("Genero", 2), rep("Edad", 4), rep("Estrato", 3), rep("Origen", 3))
colores <- c("Genero" = "#E41A1C", "Edad" = "#377EB8", 
             "Estrato" = "#4DAF4A", "Origen" = "#984EA3")

# Gráfica
df_cat <- data.frame(coord_cat, 
                     Categoria = etiquetas_cat, 
                     Variable = variable_cat,
                     Peso = pesos_cat)

ggplot(df_cat, aes(x = Comp1, y = Comp2, label = Categoria, color = Variable)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(aes(size = Peso), alpha = 0.7) +
  geom_text(vjust = -0.8, size = 3.5, fontface = "bold", show.legend = FALSE) +
  scale_color_manual(values = colores) +
  scale_size_continuous(range = c(3, 8), guide = "none") +
  labs(title = "Primer plano factorial: Categorías",
       x = paste0("Eje 1 (", round(vp_tabla$Porcentaje[1], 1), "%)"),
       y = paste0("Eje 2 (", round(vp_tabla$Porcentaje[2], 1), "%)"),
       color = "Variable") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
        legend.position = "right")

7.2.1 Interpretación de los ejes

Para interpretar cada eje, identificamos las categorías con mayor contribución absoluta (> promedio = 100/p %).

# Calcular ayudas para interpretación
inertia_acm <- inertia.dudi(acm, row.inertia = FALSE, col.inertia = TRUE)

# Contribuciones absolutas
contrib_abs <- inertia_acm$col.abs * 100
colnames(contrib_abs) <- paste0("Eje", 1:ncol(contrib_abs))

# Cosenos cuadrados
cos2 <- inertia_acm$col.rel
colnames(cos2) <- paste0("Eje", 1:ncol(cos2))

# Tabla de ayudas para los 3 primeros ejes
ayudas_df <- data.frame(
  Categoria = etiquetas_cat,
  Coord1 = round(coord_cat[,1], 3),
  Contrib1 = round(contrib_abs[,1], 1),
  Cos2_1 = round(cos2[,1], 3),
  Coord2 = round(coord_cat[,2], 3),
  Contrib2 = round(contrib_abs[,2], 1),
  Cos2_2 = round(cos2[,2], 3)
)

kable(ayudas_df, caption = "Ayudas para interpretación de categorías (Ejes 1 y 2)")
Ayudas para interpretación de categorías (Ejes 1 y 2)
Categoria Coord1 Contrib1 Cos2_1 Coord2 Contrib2 Cos2_2
F -0.838 1699.9 -31.918 0.134 49.6 0.811
M 0.381 772.2 31.918 -0.061 22.5 -0.811
16om -0.403 401.2 -7.608 -0.134 50.6 -0.836
17 0.125 48.4 1.044 0.398 567.9 10.678
18 -0.508 206.3 -2.971 -1.638 2468.1 -30.937
19oM 0.747 757.5 11.858 0.295 135.7 1.850
bajo -0.040 4.7 -0.098 0.591 1181.3 21.406
medio -0.404 572.1 -13.471 -0.220 195.1 -3.998
alto 1.174 1799.2 27.938 -0.742 825.3 -11.156
Bogo 0.416 958.1 43.344 -0.140 123.8 -4.876
Cund -1.525 2185.1 -32.108 -1.123 1360.6 -17.404
Otro -0.685 595.3 -9.195 1.438 3019.4 40.596

7.2.2 Interpretación Eje 1

# Categorías con alta contribución al eje 1
promedio_contrib <- 100/p
cat("Contribución promedio:", round(promedio_contrib, 1), "%\n\n")
## Contribución promedio: 8.3 %
cat("Categorías destacadas en el Eje 1 (contrib >", round(promedio_contrib, 1), "%):\n")
## Categorías destacadas en el Eje 1 (contrib > 8.3 %):
destacadas_eje1 <- ayudas_df[ayudas_df$Contrib1 > promedio_contrib, 
                             c("Categoria", "Coord1", "Contrib1")]
destacadas_eje1 <- destacadas_eje1[order(-abs(destacadas_eje1$Coord1)), ]
kable(destacadas_eje1, row.names = FALSE)
Categoria Coord1 Contrib1
Cund -1.525 2185.1
alto 1.174 1799.2
F -0.838 1699.9
19oM 0.747 757.5
Otro -0.685 595.3
18 -0.508 206.3
Bogo 0.416 958.1
medio -0.404 572.1
16om -0.403 401.2
M 0.381 772.2
17 0.125 48.4
cat("\n** Interpretación del Eje 1 **\n")
## 
## ** Interpretación del Eje 1 **
cat("Este eje opone categorías según nivel socioeconómico y origen geográfico.\n")
## Este eje opone categorías según nivel socioeconómico y origen geográfico.

7.2.3 Interpretación Eje 2

cat("Categorías destacadas en el Eje 2 (contrib >", round(promedio_contrib, 1), "%):\n")
## Categorías destacadas en el Eje 2 (contrib > 8.3 %):
destacadas_eje2 <- ayudas_df[ayudas_df$Contrib2 > promedio_contrib, 
                             c("Categoria", "Coord2", "Contrib2")]
destacadas_eje2 <- destacadas_eje2[order(-abs(destacadas_eje2$Coord2)), ]
kable(destacadas_eje2, row.names = FALSE)
Categoria Coord2 Contrib2
18 -1.638 2468.1
Otro 1.438 3019.4
Cund -1.123 1360.6
alto -0.742 825.3
bajo 0.591 1181.3
17 0.398 567.9
19oM 0.295 135.7
medio -0.220 195.1
Bogo -0.140 123.8
F 0.134 49.6
16om -0.134 50.6
M -0.061 22.5
cat("\n** Interpretación del Eje 2 **\n")
## 
## ** Interpretación del Eje 2 **
cat("Este eje opone categorías según edad y género.\n")
## Este eje opone categorías según edad y género.

8 Representación Simultánea

8.1 Fórmulas cuasibaricéntricas

Las coordenadas de individuos y categorías están relacionadas:

8.1.1 Individuo = promedio dilatado de sus categorías

\[ F_s(i) = \frac{1}{\sqrt{\lambda_s}} \frac{1}{s} \sum_{j \in J_i} G_s(j) \]

Interpretación: La coordenada del individuo es el promedio aritmético de las coordenadas de sus categorías, dilatado por \(1/\sqrt{\lambda_s}\).

8.1.2 Verificación numérica

# Verificar para el individuo 25
i_verif <- 25

cat("Individuo", i_verif, ":\n")
## Individuo 25 :
print(Y[i_verif, ])
##    Genero Edad Estrato Origen
## 25      M   17   medio   Cund
# Identificar categorías que asume
cat_asumidas <- which(Z[i_verif, ] == 1)
cat("\nÍndices de categorías asumidas:", cat_asumidas, "\n")
## 
## Índices de categorías asumidas: 2 4 8 11
cat("Nombres:", etiquetas_cat[cat_asumidas], "\n")
## Nombres: M 17 medio Cund
# Coordenadas de esas categorías (eje 1)
coord_sus_cat <- coord_cat[cat_asumidas, 1]
names(coord_sus_cat) <- etiquetas_cat[cat_asumidas]

cat("\nCoordenadas sobre Eje 1:\n")
## 
## Coordenadas sobre Eje 1:
print(round(coord_sus_cat, 3))
##      M     17  medio   Cund 
##  0.381  0.125 -0.404 -1.525
# Promedio aritmético
promedio <- mean(coord_sus_cat)
cat("\nPromedio aritmético:", round(promedio, 4), "\n")
## 
## Promedio aritmético: -0.3559
# Factor de dilatación
lambda1 <- acm$eig[1]
factor_dilat <- 1 / sqrt(lambda1)
cat("Factor de dilatación (1/√λ₁):", round(factor_dilat, 3), "\n")
## Factor de dilatación (1/√λ₁): 1.76
# Coordenada calculada vs real
coord_calc <- promedio * factor_dilat
coord_real <- coord_ind[i_verif, 1]

cat("\nCoordenada calculada:", round(coord_calc, 4), "\n")
## 
## Coordenada calculada: -0.6265
cat("Coordenada real (ACM):", round(coord_real, 4), "\n")
## Coordenada real (ACM): -0.6265
cat("Diferencia:", round(abs(coord_calc - coord_real), 6), "\n")
## Diferencia: 0

8.2 Plano factorial simultáneo

# Combinar datos para gráfica
df_simultaneo <- rbind(
  data.frame(x = coord_ind[,1], y = coord_ind[,2], 
             Tipo = "Individuo", Etiqueta = "", Variable = "Individuo"),
  data.frame(x = coord_cat[,1], y = coord_cat[,2],
             Tipo = "Categoria", Etiqueta = etiquetas_cat, 
             Variable = variable_cat)
)

# Colores
colores_simul <- c("Individuo" = "gray50", colores)

# Gráfica
ggplot(df_simultaneo, aes(x = x, y = y, color = Variable)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(data = subset(df_simultaneo, Tipo == "Individuo"), 
             alpha = 0.2, size = 1.5, show.legend = TRUE) +
  geom_point(data = subset(df_simultaneo, Tipo == "Categoria"), 
             size = 5, alpha = 0.8) +
  geom_text(data = subset(df_simultaneo, Tipo == "Categoria"),
            aes(label = Etiqueta), vjust = -1.2, size = 3.5, 
            fontface = "bold", show.legend = FALSE) +
  scale_color_manual(values = colores_simul) +
  labs(title = "Plano factorial simultáneo: Individuos y Categorías",
       subtitle = "Las categorías son cuasibaricentros de sus individuos",
       x = paste0("Eje 1 (", round(vp_tabla$Porcentaje[1], 1), "%)"),
       y = paste0("Eje 2 (", round(vp_tabla$Porcentaje[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 = 10),
        legend.position = "right")

8.2.1 Lectura del plano simultáneo

Propiedades clave:

  1. Categorías centradas por variable: Las categorías de cada variable están centradas en el origen
  2. Frecuencia y distancia: Categorías más cerca del origen → mayor frecuencia
  3. Cuasibaricentros: Cada categoría es el baricentro dilatado de sus individuos
  4. Asociaciones: Categorías cercanas tienden a aparecer juntas en los mismos individuos

9 Razones de Correlación

Para cada variable, calculamos su correlación con cada eje factorial.

9.1 Definición

\[ \eta_s^2(q) = \lambda_s \cdot s \cdot \sum_{j \in J_q} CA_s(j) \]

donde \(CA_s(j)\) es la contribución absoluta de la categoría \(j\) al eje \(s\).

# Razones de correlación (están en acm$cr)
razones_cor <- acm$cr

# Tabla
kable(round(razones_cor[, 1:3], 3), 
      caption = "Razones de correlación: Variables × Ejes")
Razones de correlación: Variables × Ejes
RS1 RS2 RS3
Genero 0.319 0.008 0.189
Edad 0.182 0.362 0.517
Estrato 0.307 0.247 0.272
Origen 0.483 0.506 0.137
# Gráfica del plano 1-2
df_variables <- data.frame(razones_cor[, 1:2], 
                           Variable = rownames(razones_cor))

ggplot(df_variables, aes(x = RS1, y = RS2, label = Variable)) +
  geom_segment(aes(xend = 0, yend = 0), 
               arrow = arrow(length = unit(0.3, "cm"), type = "closed"),
               color = "darkblue", size = 1.2) +
  geom_point(size = 5, color = "darkred") +
  geom_text(vjust = -1.2, hjust = 0.5, size = 4.5, fontface = "bold") +
  xlim(0, max(razones_cor[,1]) * 1.1) + 
  ylim(0, max(razones_cor[,2]) * 1.1) +
  labs(title = "Razones de correlación de las variables",
       subtitle = "Relación de cada variable con los ejes factoriales",
       x = "Eje 1", y = "Eje 2") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"),
        plot.subtitle = element_text(hjust = 0.5))

9.1.1 Interpretación

cat("** Interpretación de las razones de correlación **\n\n")
## ** Interpretación de las razones de correlación **
cat("Eje 1:\n")
## Eje 1:
for(i in 1:nrow(razones_cor)) {
  if(razones_cor[i,1] > 0.5) {
    cat("- ", rownames(razones_cor)[i], ": alta relación (", 
        round(razones_cor[i,1], 2), ")\n", sep = "")
  }
}

cat("\nEje 2:\n")
## 
## Eje 2:
for(i in 1:nrow(razones_cor)) {
  if(razones_cor[i,2] > 0.5) {
    cat("- ", rownames(razones_cor)[i], ": alta relación (", 
        round(razones_cor[i,2], 2), ")\n", sep = "")
  }
}
## - Origen: alta relación (0.51)
cat("\nVariables cercanas entre sí → contribuyen de manera similar a los ejes.\n")
## 
## Variables cercanas entre sí → contribuyen de manera similar a los ejes.

10 Variables Ilustrativas

Las variables ilustrativas no participan en la construcción de los ejes, pero se proyectan para explorar relaciones.

10.1 Proyección de Carrera

# Función para proyectar variable cualitativa suplementaria
proyectar_var_sup <- function(acm, var_sup) {
  # Coordenadas: promedio de individuos que asumen cada categoría
  coord_sup <- matrix(0, nlevels(var_sup), acm$nf)
  ncat <- table(var_sup)
  
  for(k in 1:nlevels(var_sup)) {
    idx <- which(var_sup == levels(var_sup)[k])
    for(eje in 1:acm$nf) {
      coord_sup[k, eje] <- mean(acm$li[idx, eje]) / sqrt(acm$eig[eje])
    }
  }
  
  rownames(coord_sup) <- levels(var_sup)
  colnames(coord_sup) <- paste0("Eje", 1:acm$nf)
  
  # Valores test
  vtest <- matrix(0, nlevels(var_sup), acm$nf)
  for(k in 1:nlevels(var_sup)) {
    nk <- ncat[k]
    for(eje in 1:acm$nf) {
      vtest[k, eje] <- sqrt((nk * (n-1)) / (n - nk)) * coord_sup[k, eje]
    }
  }
  rownames(vtest) <- levels(var_sup)
  colnames(vtest) <- paste0("Eje", 1:acm$nf)
  
  return(list(coord = coord_sup, vtest = vtest, ncat = as.numeric(ncat)))
}

# Proyectar Carrera
sup_carrera <- proyectar_var_sup(acm, Carrera)

# Tabla de resultados
sup_tabla <- data.frame(
  Carrera = levels(Carrera),
  N = sup_carrera$ncat,
  Coord_Eje1 = round(sup_carrera$coord[, 1], 3),
  Coord_Eje2 = round(sup_carrera$coord[, 2], 3),
  Vtest_Eje1 = round(sup_carrera$vtest[, 1], 2),
  Vtest_Eje2 = round(sup_carrera$vtest[, 2], 2)
)

kable(sup_tabla, caption = "Proyección de Carrera como variable ilustrativa")
Proyección de Carrera como variable ilustrativa
Carrera N Coord_Eje1 Coord_Eje2 Vtest_Eje1 Vtest_Eje2
Biol Biol 66 -0.050 -0.193 -0.44 -1.70
Esta Esta 66 -0.090 0.112 -0.79 0.99
Farm Farm 81 0.067 0.042 0.67 0.42
Fisi Fisi 72 -0.059 0.165 -0.55 1.52
Geol Geol 52 0.060 0.240 0.46 1.84
Mate Mate 47 0.072 -0.195 0.52 -1.41
Quim Quim 61 0.025 -0.217 0.21 -1.83

10.1.1 Valores test: interpretación

El valor test indica si la coordenada es significativamente diferente de cero:

\[ t_s(j) = \sqrt{\frac{n_j(n-1)}{n - n_j}} G_s(j) \]

Criterio: Si \(|t_s(j)| > 2\), la coordenada es significativa (nivel 5%).

cat("** Interpretación de valores test **\n\n")
## ** Interpretación de valores test **
cat("Valores test significativos en Eje 1 (|t| > 2):\n")
## Valores test significativos en Eje 1 (|t| > 2):
sig_eje1 <- sup_tabla[abs(sup_tabla$Vtest_Eje1) > 2, c("Carrera", "Vtest_Eje1")]
if(nrow(sig_eje1) > 0) {
  kable(sig_eje1, row.names = FALSE)
} else {
  cat("Ninguna carrera tiene asociación significativa con el Eje 1.\n")
}
## Ninguna carrera tiene asociación significativa con el Eje 1.
cat("\nValores test significativos en Eje 2 (|t| > 2):\n")
## 
## Valores test significativos en Eje 2 (|t| > 2):
sig_eje2 <- sup_tabla[abs(sup_tabla$Vtest_Eje2) > 2, c("Carrera", "Vtest_Eje2")]
if(nrow(sig_eje2) > 0) {
  kable(sig_eje2, row.names = FALSE)
} else {
  cat("Ninguna carrera tiene asociación significativa con el Eje 2.\n")
}
## Ninguna carrera tiene asociación significativa con el Eje 2.

10.2 Gráfica con carreras ilustrativas

# Preparar datos
df_carreras <- data.frame(
  x = sup_carrera$coord[, 1],
  y = sup_carrera$coord[, 2],
  Carrera = levels(Carrera)
)

# Combinar con categorías activas
df_combinado <- rbind(
  data.frame(x = coord_cat[,1], y = coord_cat[,2],
             Tipo = "Activa", Etiqueta = etiquetas_cat),
  data.frame(x = df_carreras$x, y = df_carreras$y,
             Tipo = "Carrera", Etiqueta = df_carreras$Carrera)
)

# Gráfica
ggplot() +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(data = subset(df_combinado, Tipo == "Activa"),
             aes(x = x, y = y), size = 4, color = "darkred", alpha = 0.6) +
  geom_text(data = subset(df_combinado, Tipo == "Activa"),
            aes(x = x, y = y, label = Etiqueta), 
            vjust = -0.8, size = 3, color = "darkred") +
  geom_point(data = subset(df_combinado, Tipo == "Carrera"),
             aes(x = x, y = y), size = 5, color = "darkgreen", shape = 17) +
  geom_text(data = subset(df_combinado, Tipo == "Carrera"),
            aes(x = x, y = y, label = Etiqueta),
            vjust = 1.5, size = 3.5, fontface = "italic", color = "darkgreen") +
  labs(title = "Plano factorial con Carreras ilustrativas",
       subtitle = "Triángulos verdes = Carreras (no participan en construcción de ejes)",
       x = paste0("Eje 1 (", round(vp_tabla$Porcentaje[1], 1), "%)"),
       y = paste0("Eje 2 (", round(vp_tabla$Porcentaje[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 = 10))


11 Verificación con Tablas de Contingencia

Siempre es importante regresar a los datos para verificar las asociaciones encontradas en los planos factoriales.

11.1 Tablas bivariadas

# Función para mostrar tabla con porcentajes
tabla_con_pct <- function(var1, var2, nombre1, nombre2) {
  cat("\n** Tabla de contingencia:", nombre1, "×", nombre2, "**\n\n")
  
  tb <- table(var1, var2)
  print(tb)
  
  cat("\nPorcentajes por fila:\n")
  print(round(prop.table(tb, 1) * 100, 1))
  
  cat("\nPorcentajes por columna:\n")
  print(round(prop.table(tb, 2) * 100, 1))
}

# Verificar asociaciones sugeridas por el ACM
tabla_con_pct(Y$Genero, Y$Edad, "Género", "Edad")
## 
## ** Tabla de contingencia: Género × Edad **
## 
##     var2
## var1 16om  17  18 19oM
##    F   55  56  11   17
##    M   87 123  35   61
## 
## Porcentajes por fila:
##     var2
## var1 16om   17   18 19oM
##    F 39.6 40.3  7.9 12.2
##    M 28.4 40.2 11.4 19.9
## 
## Porcentajes por columna:
##     var2
## var1 16om   17   18 19oM
##    F 38.7 31.3 23.9 21.8
##    M 61.3 68.7 76.1 78.2
tabla_con_pct(Y$Estrato, Y$Origen, "Estrato", "Origen")
## 
## ** Tabla de contingencia: Estrato × Origen **
## 
##        var2
## var1    Bogo Cund Otro
##   bajo   119   19   31
##   medio  132   30   39
##   alto    67    5    3
## 
## Porcentajes por fila:
##        var2
## var1    Bogo Cund Otro
##   bajo  70.4 11.2 18.3
##   medio 65.7 14.9 19.4
##   alto  89.3  6.7  4.0
## 
## Porcentajes por columna:
##        var2
## var1    Bogo Cund Otro
##   bajo  37.4 35.2 42.5
##   medio 41.5 55.6 53.4
##   alto  21.1  9.3  4.1
tabla_con_pct(Y$Edad, Y$Estrato, "Edad", "Estrato")
## 
## ** Tabla de contingencia: Edad × Estrato **
## 
##       var2
## var1   bajo medio alto
##   16om   57    63   22
##   17     69    76   34
##   18     14    26    6
##   19oM   29    36   13
## 
## Porcentajes por fila:
##       var2
## var1   bajo medio alto
##   16om 40.1  44.4 15.5
##   17   38.5  42.5 19.0
##   18   30.4  56.5 13.0
##   19oM 37.2  46.2 16.7
## 
## Porcentajes por columna:
##       var2
## var1   bajo medio alto
##   16om 33.7  31.3 29.3
##   17   40.8  37.8 45.3
##   18    8.3  12.9  8.0
##   19oM 17.2  17.9 17.3

12 Resumen y Conclusiones

12.1 Principales hallazgos

cat("** ESTRUCTURA ENCONTRADA EN EL ACM **\n\n")
## ** ESTRUCTURA ENCONTRADA EN EL ACM **
cat("1. PRIMER EJE (", round(vp_tabla$Porcentaje[1], 1), "% inercia):\n", sep = "")
## 1. PRIMER EJE (16.1% inercia):
cat("   - Eje de nivel socioeconómico y origen geográfico\n")
##    - Eje de nivel socioeconómico y origen geográfico
cat("   - Opone perfiles según estrato y procedencia\n\n")
##    - Opone perfiles según estrato y procedencia
cat("2. SEGUNDO EJE (", round(vp_tabla$Porcentaje[2], 1), "% inercia):\n", sep = "")
## 2. SEGUNDO EJE (14% inercia):
cat("   - Eje de edad y género\n")
##    - Eje de edad y género
cat("   - Opone perfiles por edad y composición de género\n\n")
##    - Opone perfiles por edad y composición de género
cat("3. GRUPOS IDENTIFICADOS (del plano factorial):\n")
## 3. GRUPOS IDENTIFICADOS (del plano factorial):
cat("   - Perfiles homogéneos en características sociodemográficas\n")
##    - Perfiles homogéneos en características sociodemográficas
cat("   - Asociaciones entre categorías de diferentes variables\n\n")
##    - Asociaciones entre categorías de diferentes variables
cat("4. VARIABLES ILUSTRATIVAS:\n")
## 4. VARIABLES ILUSTRATIVAS:
cat("   - Algunas carreras muestran perfiles sociodemográficos diferenciados\n")
##    - Algunas carreras muestran perfiles sociodemográficos diferenciados
cat("   - Otras carreras tienen distribución más uniforme\n")
##    - Otras carreras tienen distribución más uniforme

12.2 Ventajas del ACM

cat("** VENTAJAS DEL MÉTODO **\n\n")
## ** VENTAJAS DEL MÉTODO **
cat("✓ Visualización intuitiva de datos categóricos multivariados\n")
## ✓ Visualización intuitiva de datos categóricos multivariados
cat("✓ Detección de patrones no evidentes en tablas bivariadas\n")
## ✓ Detección de patrones no evidentes en tablas bivariadas
cat("✓ Síntesis: reduce dimensionalidad (", s, " variables → 2-3 ejes principales)\n", sep = "")
## ✓ Síntesis: reduce dimensionalidad (4 variables → 2-3 ejes principales)
cat("✓ Flexibilidad: permite variables activas e ilustrativas\n")
## ✓ Flexibilidad: permite variables activas e ilustrativas
cat("✓ Representación simultánea de individuos y categorías\n")
## ✓ Representación simultánea de individuos y categorías

12.3 Limitaciones y precauciones

cat("** LIMITACIONES Y CUIDADOS **\n\n")
## ** LIMITACIONES Y CUIDADOS **
cat("⚠ La inercia total NO tiene significado estadístico (depende de p/s)\n")
## ⚠ La inercia total NO tiene significado estadístico (depende de p/s)
cat("⚠ Aparición de ejes 'parásitos' sin información relevante\n")
## ⚠ Aparición de ejes 'parásitos' sin información relevante
cat("⚠ El % de inercia explicada NO es un buen criterio (a diferencia del ACP)\n")
## ⚠ El % de inercia explicada NO es un buen criterio (a diferencia del ACP)
cat("⚠ Sensible a categorías de muy baja frecuencia\n")
## ⚠ Sensible a categorías de muy baja frecuencia
cat("⚠ Distancias entre categorías de distintas variables no son comparables\n")
## ⚠ Distancias entre categorías de distintas variables no son comparables

12.4 Recomendaciones prácticas

cat("** RECOMENDACIONES **\n\n")
## ** RECOMENDACIONES **
cat("1. SELECCIÓN DE EJES:\n")
## 1. SELECCIÓN DE EJES:
cat("   → Mirar el HISTOGRAMA (scree plot), no solo los porcentajes\n")
##    → Mirar el HISTOGRAMA (scree plot), no solo los porcentajes
cat("   → Buscar 'codo' o cambio de pendiente\n")
##    → Buscar 'codo' o cambio de pendiente
cat("   → 40-50% en 2-3 ejes es TÍPICO y ACEPTABLE en ACM\n\n")
##    → 40-50% en 2-3 ejes es TÍPICO y ACEPTABLE en ACM
cat("2. INTERPRETACIÓN:\n")
## 2. INTERPRETACIÓN:
cat("   → Usar ayudas: contribuciones absolutas, cosenos cuadrados\n")
##    → Usar ayudas: contribuciones absolutas, cosenos cuadrados
cat("   → Leer VARIOS planos factoriales (1-2, 1-3, 2-3)\n")
##    → Leer VARIOS planos factoriales (1-2, 1-3, 2-3)
cat("   → Identificar categorías con contribución > promedio (100/p %)\n\n")
##    → Identificar categorías con contribución > promedio (100/p %)
cat("3. VARIABLES ILUSTRATIVAS:\n")
## 3. VARIABLES ILUSTRATIVAS:
cat("   → Calcular valores test para evaluar significancia\n")
##    → Calcular valores test para evaluar significancia
cat("   → Criterio: |t| > 2 indica coordenada significativa (α=5%)\n\n")
##    → Criterio: |t| > 2 indica coordenada significativa (α=5%)
cat("4. VERIFICACIÓN:\n")
## 4. VERIFICACIÓN:
cat("   → SIEMPRE regresar a los datos originales\n")
##    → SIEMPRE regresar a los datos originales
cat("   → Confirmar asociaciones con tablas de contingencia\n")
##    → Confirmar asociaciones con tablas de contingencia
cat("   → No sobreinterpretar patrones débiles\n")
##    → No sobreinterpretar patrones débiles

13 Extensiones del ACM

13.1 Métodos relacionados

El ACM es parte de una familia de métodos factoriales:

1. Análisis Factorial Múltiple (AFM) - Generaliza el ACM para mezclar variables cuantitativas y cualitativas - Permite ponderar grupos de variables de manera equilibrada

2. ACM con variables instrumentales - Análisis de co-inercia entre bloques de variables - Estudia asociaciones entre tablas de individuos

3. Clasificación post-ACM - Clustering sobre coordenadas factoriales - Identifica grupos de individuos con perfiles similares

4. ACM de la tabla de Burt - Alternativa teórica al ACM de la TDC - Mismas conclusiones pero sin información directa sobre individuos


14 Referencias

14.1 Bibliografía fundamental

  • Lebart, L., Morineau, A., & Piron, M. (2006). Statistique exploratoire multidimensionnelle: Visualisation et inférence en fouilles de données. Dunod. (4ème édition)

  • Escofier, B., & Pagès, J. (1992). Análisis factoriales simples y múltiples: Objetivos, métodos e interpretación. Servicio Editorial Universidad del País Vasco.

  • Greenacre, M. (2007). Correspondence Analysis in Practice (2nd ed.). Chapman & Hall/CRC.

  • Le Roux, B., & Rouanet, H. (2010). Multiple Correspondence Analysis. SAGE Publications.

14.2 Software

cat("** Paquetes de R utilizados **\n\n")
## ** Paquetes de R utilizados **
cat("ade4:    ", as.character(packageVersion("ade4")), "\n")
## ade4:     1.7.23
cat("ggplot2: ", as.character(packageVersion("ggplot2")), "\n")
## ggplot2:  4.0.2
cat("knitr:   ", as.character(packageVersion("knitr")), "\n\n")
## knitr:    1.50
cat("Versión de R:", R.version.string, "\n")
## Versión de R: R version 4.5.2 (2025-10-31 ucrt)
cat("Sistema operativo:", Sys.info()["sysname"], Sys.info()["release"], "\n")
## Sistema operativo: Windows 10 x64

15 Código para reproducir el análisis

# =====================================================
# ANÁLISIS DE CORRESPONDENCIAS MÚLTIPLES
# Código completo y reproducible
# =====================================================

# 1. Cargar paquetes
library(ade4)
library(ggplot2)
library(knitr)

# 2. Preparar datos (usar sus propios datos aquí)
# Y debe ser un data.frame con variables tipo factor
# Y <- data.frame(Var1 = factor(...), Var2 = factor(...), ...)

# 3. Ejecutar ACM
acm <- dudi.acm(Y, scannf = FALSE, nf = 3)

# 4. Valores propios
barplot(acm$eig, main = "Valores propios")

# 5. Plano factorial de categorías
plot(acm, Trow = FALSE)

# 6. Plano factorial de individuos
plot(acm, Tcol = FALSE)

# 7. Plano simultáneo
plot(acm)

# 8. Ayudas para interpretación
inertia_acm <- inertia.dudi(acm, row.inertia = TRUE, col.inertia = TRUE)

# Contribuciones absolutas de categorías
print(inertia_acm$col.abs * 100)

# Cosenos cuadrados de categorías
print(inertia_acm$col.rel)

# 9. Razones de correlación de variables
print(acm$cr)

# 10. Variables ilustrativas (ejemplo)
# var_sup <- factor(...)
# Proyectar y analizar valores test

¡Fin de la presentación!

15.1 Preguntas para discusión

  1. ¿En qué se diferencia el ACM del ACP y cuándo usaría cada uno?
  2. ¿Por qué la inercia total del ACM no tiene significado estadístico?
  3. ¿Cómo se interpreta la cercanía entre una categoría y un individuo en el plano factorial?
  4. ¿Qué papel juegan los valores test en la interpretación de variables ilustrativas?
  5. ¿Cuáles son las ventajas de usar el ACM sobre análisis bivariados simples?