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.
El ACM tiene cuatro objetivos principales:
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:
# 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
El ACM trabaja con tres representaciones equivalentes de los mismos datos. Es fundamental entender estas transformaciones.
Es la tabla original de datos: n individuos × s variables.
Características:
# Mostrar extracto de la tabla (primeros 12 registros)
kable(head(Y, 12), caption = "Tabla de Código Condensado (extracto)", row.names = TRUE)| 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 las variables:
## 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
Es una tabla binaria de n individuos × p categorías (donde p es el número total de categorías de todas las variables).
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} \]
# 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)")| 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 de Z: 445 individuos × 12 categorías
## 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
## (Todas las filas suman 4 como debe ser)
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:
## Genero Edad Estrato Origen
## 2 4 3 3
##
## Total p = 12 categorías
##
## 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
Es la tabla de contingencias múltiples, definida como:
\[ B = Z'Z \]
Es una matriz cuadrada y simétrica de orden \(p \times p\).
# Crear tabla de Burt: Z'Z
B <- t(Z) %*% Z
# Mostrar tabla completa
kable(B, caption = "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 |
De la tabla de Burt podemos leer:
## Frecuencias por variable:
## 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:
## 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:
## 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
##
##
## Ejemplos de lectura de la tabla de Burt:
## - Total de mujeres: 139
## - Total de hombres: 306
## - Mujeres de estrato bajo: 54
## - Hombres de estrato alto: 56
El ACM se puede ver como un Análisis de Correspondencias Simple (ACS) aplicado a la tabla disyuntiva completa \(Z\).
La tabla de frecuencias relativas asociada a \(Z\) es:
\[ F = \frac{1}{ns} Z \]
con marginales:
# 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 %
##
## 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 |
Los \(n\) individuos conforman la nube \(N_n\) en el espacio \(\mathbb{R}^p\).
Los perfiles fila son las filas de la tabla \(\frac{1}{s}Z\):
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 :
## Categorías que asume:
## Genero Edad Estrato Origen
## 25 M 17 medio Cund
##
## 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 |
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 %):
| 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 |
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:
# 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 :
## Genero Edad Estrato Origen
## 50 M 17 medio Bogo
##
## Individuo 100 :
## 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
# 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):
| 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 |
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.
## Inercia total de la nube de individuos:
## p/s - 1 = 12 / 4 - 1 = 2
## Esta inercia NO depende de los datos, solo de p y s.
## Por eso NO tiene significado estadístico directo.
En el ACM se pone más atención a las categorías que a los individuos, porque usualmente los individuos son anónimos.
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:
# 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:
| 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 |
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 |
##
## Observación: Las categorías con menor frecuencia están más lejos del centro.
# 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:
## - eig: valores propios
## - cw: pesos de categorías
## - li: coordenadas de individuos
## - co: coordenadas de categorías
## - cr: razones de correlación de variables
# 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")| 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")Criterio principal en ACM: Forma del histograma (NO el porcentaje de inercia)
Razones:
Del histograma:
## Criterio de decisión:
## - Observar el histograma (scree plot)
## - Buscar un 'codo' o cambio de pendiente
## - Los primeros 44 % de inercia NO es malo en ACM
## - En ACM, 40-50% de inercia en 2-3 ejes es típico y aceptable
El rango de la matriz de inercia es \(p - s\) porque por cada variable hay una columna linealmente dependiente.
## Dimensión teórica del espacio:
## p - s = 12 - 4 = 8
## Número de valores propios > 0: 8
## (Coincide con p - s, como debe ser)
# 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:
# 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")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)")| 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 |
# 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 %
## 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 |
##
## ** Interpretación del Eje 1 **
## Este eje opone categorías según nivel socioeconómico y origen geográfico.
## 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 |
##
## ** Interpretación del Eje 2 **
## Este eje opone categorías según edad y género.
Las coordenadas de individuos y categorías están relacionadas:
\[ 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}\).
## Individuo 25 :
## 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
## 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:
## 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
## Coordenada real (ACM): -0.6265
## Diferencia: 0
# 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")Propiedades clave:
Para cada variable, calculamos su correlación con cada eje factorial.
\[ \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")| 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))## ** Interpretación de las razones de correlació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)
##
## Variables cercanas entre sí → contribuyen de manera similar a los ejes.
Las variables ilustrativas no participan en la construcción de los ejes, pero se proyectan para explorar relaciones.
# 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")| 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 |
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%).
## ** Interpretación de valores test **
## 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.
##
## 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.
# 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))Siempre es importante regresar a los datos para verificar las asociaciones encontradas en los planos factoriales.
# 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 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 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
## ** ESTRUCTURA ENCONTRADA EN EL ACM **
## 1. PRIMER EJE (16.1% inercia):
## - Eje de nivel socioeconómico y origen geográfico
## - Opone perfiles según estrato y procedencia
## 2. SEGUNDO EJE (14% inercia):
## - Eje de edad y género
## - Opone perfiles por edad y composición de género
## 3. GRUPOS IDENTIFICADOS (del plano factorial):
## - Perfiles homogéneos en características sociodemográficas
## - Asociaciones entre categorías de diferentes variables
## 4. VARIABLES ILUSTRATIVAS:
## - Algunas carreras muestran perfiles sociodemográficos diferenciados
## - Otras carreras tienen distribución más uniforme
## ** VENTAJAS DEL MÉTODO **
## ✓ Visualización intuitiva de datos categóricos multivariados
## ✓ Detección de patrones no evidentes en tablas bivariadas
## ✓ Síntesis: reduce dimensionalidad (4 variables → 2-3 ejes principales)
## ✓ Flexibilidad: permite variables activas e ilustrativas
## ✓ Representación simultánea de individuos y categorías
## ** LIMITACIONES Y CUIDADOS **
## ⚠ La inercia total NO tiene significado estadístico (depende de p/s)
## ⚠ Aparición de ejes 'parásitos' sin información relevante
## ⚠ El % de inercia explicada NO es un buen criterio (a diferencia del ACP)
## ⚠ Sensible a categorías de muy baja frecuencia
## ⚠ Distancias entre categorías de distintas variables no son comparables
## ** RECOMENDACIONES **
## 1. SELECCIÓN DE EJES:
## → Mirar el HISTOGRAMA (scree plot), no solo los porcentajes
## → Buscar 'codo' o cambio de pendiente
## → 40-50% en 2-3 ejes es TÍPICO y ACEPTABLE en ACM
## 2. INTERPRETACIÓN:
## → Usar ayudas: contribuciones absolutas, cosenos cuadrados
## → Leer VARIOS planos factoriales (1-2, 1-3, 2-3)
## → Identificar categorías con contribución > promedio (100/p %)
## 3. VARIABLES ILUSTRATIVAS:
## → Calcular valores test para evaluar significancia
## → Criterio: |t| > 2 indica coordenada significativa (α=5%)
## 4. VERIFICACIÓN:
## → SIEMPRE regresar a los datos originales
## → Confirmar asociaciones con tablas de contingencia
## → No sobreinterpretar patrones débiles
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
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.
## ** Paquetes de R utilizados **
## ade4: 1.7.23
## ggplot2: 4.0.2
## knitr: 1.50
## Versión de R: R version 4.5.2 (2025-10-31 ucrt)
## Sistema operativo: Windows 10 x64
# =====================================================
# 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!