El Análisis de Componentes Principales (ACP) es una técnica de reducción de dimensionalidad que transforma un conjunto de variables correlacionadas en un nuevo conjunto de variables no correlacionadas denominadas componentes principales, ordenadas de mayor a menor varianza explicada.
Se analiza el conjunto de datos decathlon2 (paquete
FactoMineR), que contiene el desempeño de 27
atletas en 10 pruebas del decatlón. El
objetivo es identificar la estructura latente del rendimiento atlético y
determinar qué pruebas concentran la mayor variabilidad entre
competidores.
library(FactoMineR) # PCA y métodos multivariados
library(factoextra) # Visualización de resultados ACP
library(ggplot2) # Gráficos avanzados
library(corrplot) # Mapas de correlación
library(MVN) # Tests de normalidad multivariada
library(knitr) # Tablas formateadas
# Cargar datos y seleccionar las 10 variables numéricas del decatlón
data(decathlon2)
datos <- decathlon2[1:23, 1:10] # Solo atletas de la primera competición
cat("Dimensiones del conjunto de datos:", nrow(datos), "atletas ×", ncol(datos), "variables\n")## Dimensiones del conjunto de datos: 23 atletas × 10 variables
Nota: Se retienen únicamente los 23 atletas del primer torneo (
Decastar) para mantener homogeneidad en las condiciones de competición.
| Variable | Descripción | Unidad |
|---|---|---|
| X100m | Tiempo en 100 metros | segundos |
| Long.jump | Salto largo | metros |
| Shot.put | Lanzamiento de bala | metros |
| High.jump | Salto alto | metros |
| X400m | Tiempo en 400 metros | segundos |
| X110m.hurdle | 110 metros vallas | segundos |
| Discus | Lanzamiento de disco | metros |
| Pole.vault | Salto con pértiga | metros |
| Javeline | Lanzamiento de jabalina | metros |
| X1500m | Carrera 1500 metros | segundos |
desc <- data.frame(
Media = round(colMeans(datos), 3),
SD = round(apply(datos, 2, sd), 3),
Min = round(apply(datos, 2, min), 3),
Max = round(apply(datos, 2, max), 3)
)
kable(desc,
caption = "Tabla 1. Estadísticas descriptivas de las variables del decatlón",
align = "r")| Media | SD | Min | Max | |
|---|---|---|---|---|
| X100m | 11.000 | 0.301 | 10.44 | 11.64 |
| Long.jump | 7.350 | 0.313 | 6.80 | 7.96 |
| Shot.put | 14.620 | 0.845 | 12.68 | 16.36 |
| High.jump | 2.007 | 0.097 | 1.86 | 2.15 |
| X400m | 49.433 | 1.007 | 46.81 | 51.16 |
| X110m.hurdle | 14.534 | 0.484 | 13.97 | 15.67 |
| Discus | 45.160 | 3.326 | 37.92 | 51.65 |
| Pole.vault | 4.797 | 0.250 | 4.40 | 5.32 |
| Javeline | 59.115 | 4.930 | 52.33 | 70.52 |
| X1500m | 277.878 | 10.011 | 262.10 | 301.50 |
Las variables presentan escalas muy distintas (e.g.,
100men segundos vs.Shot.puten metros), lo que hace imprescindible estandarizar antes del ACP.
par(mar = c(7, 4, 4, 2))
boxplot(datos,
main = "Distribución de variables — Detección de atípicos univariados",
col = "#AED6F1",
las = 2,
cex.axis = 0.85)# Identificar valores fuera del rango intercuartílico
outs <- lapply(datos, function(x) {
s <- boxplot.stats(x)$out
if (length(s) == 0) "Ninguno" else paste(round(s, 2), collapse = ", ")
})
kable(data.frame(Variable = names(outs), Atípicos = unlist(outs)),
caption = "Tabla 2. Valores atípicos univariados por variable",
row.names = FALSE)| Variable | Atípicos |
|---|---|
| X100m | Ninguno |
| Long.jump | Ninguno |
| Shot.put | 12.68 |
| High.jump | Ninguno |
| X400m | 46.81 |
| X110m.hurdle | Ninguno |
| Discus | 37.92 |
| Pole.vault | Ninguno |
| Javeline | Ninguno |
| X1500m | Ninguno |
# Estandarización Z (media = 0, desviación estándar = 1)
datosest <- as.data.frame(scale(datos, center = TRUE, scale = TRUE))El ACP es sensible a las diferencias de escala. Al estandarizar, cada variable contribuye equitativamente al análisis, independientemente de su unidad de medida.
La distancia de Mahalanobis \(D^2\) mide cuánto se aleja cada observación del centroide multivariado, teniendo en cuenta la correlación entre variables. Bajo normalidad multivariada, \(D^2 \sim \chi^2_p\).
p <- ncol(datosest)
d2 <- mahalanobis(datosest,
center = colMeans(datosest),
cov = cov(datosest))
cutoff <- qchisq(0.975, df = p) # Umbral al 97.5% con p gl
plot(d2,
pch = 19, col = ifelse(d2 > cutoff, "#E74C3C", "#2E86C1"),
main = expression("Distancias de Mahalanobis" * D^2),
ylab = expression(D^2),
xlab = "Índice del atleta",
ylim = c(0, 22))
abline(h = cutoff,
col = "#E74C3C", lwd = 2, lty = 2)
legend("topright",
legend = c("Normal", paste0("Atípico (D^2 > ", round(cutoff,2), ")")),
col = c("#2E86C1", "#E74C3C"), pch = 19, bty = "n")# Identificar atípicos
idx_out <- which(d2 > cutoff)
if (length(idx_out) > 0) {
cat("Atletas con distancia de Mahalanobis elevada:\n")
print(data.frame(Atleta = rownames(datos)[idx_out],
D2 = round(d2[idx_out], 3)))
} else {
cat("No se detectan atípicos multivariados con el umbral chi^2(0.975, p).\n")
}## No se detectan atípicos multivariados con el umbral chi^2(0.975, p).
result <- mvn(data = datosest,
mvn_test = "hz",
multivariate_outlier_method = "adj",
)
# Resultado del test HZ
result$multivariate_normality## Test Statistic p.value Method MVN
## 1 Henze-Zirkler 0.964 0.276 asymptotic ✓ Normal
Interpretación: El test de Henze-Zirkler (HZ) evalúa \(H_0\): los datos siguen una distribución normal multivariada. Si \(p > 0.05\), no se rechaza la normalidad, lo que valida los supuestos del ACP clásico y el uso de la distribución \(\chi^2\) para los umbrales de Mahalanobis.
R <- cor(datosest)
corrplot(R,
method = "color",
type = "upper",
addCoef.col = "black",
number.cex = 0.7,
tl.col = "black",
tl.srt = 45,
col = colorRampPalette(c("#2980B9", "white", "#C0392B"))(200),
title = "Mapa de correlaciones - Variables del decatlón",
mar = c(0, 0, 2, 0))Interpretación: Correlaciones positivas fuertes entre pruebas de velocidad (
100m,400m,110m.hurdle) y entre pruebas de campo (Long.jump,Shot.put,High.jump) sugieren la existencia de al menos dos dimensiones latentes. La estandarización previa garantiza que el mapa refleja la correlación real y no efectos de escala.
eig.val <- get_eigenvalue(acp)
kable(round(eig.val, 4),
caption = "Tabla 4. Valores propios y varianza explicada acumulada",
col.names = c("Valor propio", "% Varianza", "% Acumulado"))| Valor propio | % Varianza | % Acumulado | |
|---|---|---|---|
| Dim.1 | 4.1242 | 41.2421 | 41.2421 |
| Dim.2 | 1.8385 | 18.3853 | 59.6274 |
| Dim.3 | 1.2391 | 12.3914 | 72.0188 |
| Dim.4 | 0.8194 | 8.1944 | 80.2132 |
| Dim.5 | 0.7016 | 7.0155 | 87.2288 |
| Dim.6 | 0.4229 | 4.2288 | 91.4576 |
| Dim.7 | 0.3026 | 3.0258 | 94.4834 |
| Dim.8 | 0.2745 | 2.7447 | 97.2281 |
| Dim.9 | 0.1552 | 1.5522 | 98.7803 |
| Dim.10 | 0.1220 | 1.2197 | 100.0000 |
fviz_eig(acp,
addlabels = TRUE,
ylim = c(0, 45),
barfill = "#2E86C1",
barcolor = "#1A5276",
linecolor = "#C0392B",
ggtheme = theme_minimal()) +
labs(title = "Scree Plot - Varianza explicada por componente",
x = "Componente principal",
y = "% de varianza explicada") +
geom_hline(yintercept = 10, linetype = "dashed", color = "gray50")Criterio de selección: Se retienen los componentes con valor propio \(\lambda > 1\) (criterio de Kaiser) y con varianza individual superior al promedio (10% para 10 variables). Las dos primeras componentes explican conjuntamente más del 50% de la varianza total, lo que es adecuado para datos deportivos con alta variabilidad individual.
El \(\cos^2\) indica qué proporción de la varianza de cada variable queda capturada por las componentes seleccionadas. Valores cercanos a 1 indican una representación excelente.
var <- get_pca_var(acp)
corrplot(var$cos2,
is.corr = FALSE,
col = colorRampPalette(c("white", "#2E86C1", "#1A5276"))(200),
title = "Cos^2 de variables en cada componente",
mar = c(0, 0, 2, 0))fviz_cos2(acp,
choice = "var",
axes = 1:2,
fill = "#2E86C1",
color = "#1A5276",
ggtheme = theme_minimal()) +
labs(title = "Cos^2 acumulado en CP1 y CP2",
x = "Variable",
y = expression(cos^2)) +
geom_hline(yintercept = 0.6, linetype = "dashed", color = "#E74C3C") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))La contribución mide cuánto aportó cada variable a la construcción de cada componente. Una variable con contribución superior a \(100/p = 10\%\) tiene una influencia superior al promedio.
corrplot(var$contrib,
is.corr = FALSE,
col = colorRampPalette(c("white", "#F39C12", "#E74C3C"))(200),
title = "Contribución (%) de variables por componente",
mar = c(0, 0, 2, 0))fviz_contrib(acp, choice = "var", axes = 1,
fill = "#2E86C1", color = "#1A5276",
ggtheme = theme_minimal()) +
labs(title = "Contribución de variables - Componente 1",
x = "Variable", y = "Contribución (%)") +
geom_hline(yintercept = 10, linetype = "dashed", color = "#E74C3C") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))fviz_contrib(acp, choice = "var", axes = 2,
fill = "#E67E22", color = "#CA6F1E",
ggtheme = theme_minimal()) +
labs(title = "Contribución de variables - Componente 2",
x = "Variable", y = "Contribución (%)") +
geom_hline(yintercept = 10, linetype = "dashed", color = "#E74C3C") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))fviz_pca_var(acp,
col.var = "cos2",
gradient.cols = c("#95A5A6", "#F39C12", "#E74C3C"),
repel = TRUE,
ggtheme = theme_minimal()) +
labs(title = "Círculo de correlaciones - ACP",
color = expression(cos^2),
subtitle = "Color según calidad de representación")Interpretación del círculo: - Variables con vectores largos (cercanos al círculo unitario) están bien representadas en el plano CP1–CP2. - Vectores en la misma dirección indican correlación positiva; opuestos, correlación negativa. - CP1 parece capturar un eje de velocidad/agilidad (variables de tiempo); CP2, un eje de potencia/campo.
fviz_pca_biplot(acp,
repel = TRUE,
col.var = "#E74C3C",
col.ind = "#2E86C1",
alpha.ind = 0.7,
ggtheme = theme_minimal(),
title = "Biplot ACP - Atletas y variables") +
labs(subtitle = "Rojo: variables | Azul: atletas")El biplot permite leer simultáneamente la posición de los atletas y la dirección de cada variable. Atletas proyectados en la dirección de una variable tienen valores altos en ella relativo al grupo.
## === Componente 1 ===
## correlation p.value
## Long.jump 0.7941806 6.059893e-06
## Discus 0.7432090 4.842563e-05
## Shot.put 0.7339127 6.723102e-05
## High.jump 0.6100840 1.993677e-03
## Javeline 0.4282266 4.149192e-02
## X400m -0.7016034 1.910387e-04
## X110m.hurdle -0.7641252 2.195812e-05
## X100m -0.8506257 2.727129e-07
##
## === Componente 2 ===
## correlation p.value
## Pole.vault 0.8074511 3.205016e-06
## X1500m 0.7844802 9.384747e-06
## High.jump -0.4652142 2.529390e-02
dimdesc()identifica, mediante correlación de Pearson con test de significancia, qué variables tienen una asociación estadísticamente significativa (\(p < 0.05\)) con cada componente. Esto permite nombrar e interpretar sustantivamente cada dimensión.
Dos dimensiones latentes dominantes: Las dos primeras componentes principales concentran la mayor parte de la varianza explicable, evidenciando que el rendimiento en el decatlón puede reducirse a dos capacidades atléticas fundamentales.
CP1 - Capacidad de velocidad y agilidad: Las
pruebas con mayor contribución a esta componente son las de tiempo
(100m, 400m, 110m.hurdle) y el
salto de longitud (Long.jump). Un atleta con puntuación
alta (positiva) en CP1 destaca en pruebas de velocidad.
CP2 - Capacidad de fuerza y potencia: Las
pruebas de lanzamiento (Shot.put, Discus,
Javeline) y salto (High.jump,
Pole.vault) dominan esta componente. Representa una
dimensión ortogonal de potencia muscular, independiente de la
velocidad.
Análisis realizado con R 4.4.1 — Paquetes: FactoMineR, factoextra, MVN, corrplot.