¿Qué es el ACP?
El Análisis de Componentes Principales (ACP) es una técnica de reducción de dimensionalidad que transforma un conjunto de variables posiblemente correlacionadas en un conjunto más pequeño de variables no correlacionadas llamadas componentes principales (CPs) .
Objetivos típicos:
Capturar la mayor varianza con el menor número de componentes.
Descorrelacionar variables y simplificar la estructura.
Facilitar visualización , preprocesamiento para modelos y compresión .
Intuición: el ACP encuentra direcciones (vectores) en el espacio de los datos que maximizan la varianza. Esas direcciones son los autovectores de la matriz de covarianzas (o correlaciones), y la varianza capturada por cada componente viene dada por su autovalor .
Supuestos y buenas prácticas
Estandarización : si las variables tienen escalas diferentes, estandariza (media 0, var 1).
Linealidad : PCA capta relaciones lineales.
Outliers : pueden dominar la varianza; considera limpiarlos o usar variantes robustas.
Datos numéricos : para variables categóricas puras, usa técnicas específicas (MCA/FAMD).
Flujo de trabajo (resumen)
Estandarizar (opcional pero recomendado).
Ajustar ACP sobre matriz estandarizada.
Evaluar varianza explicada y elegir k componentes.
Inspeccionar cargas (loadings) y scores .
Visualizar: scree plot , biplot .
Proyectar nuevos datos y (si aplica) reconstruir.
Dataset de ejemplo
Usaremos el clásico Iris (4 variables numéricas, 3 especies).
En R: datasets::iris.
En Python: sklearn.datasets.load_iris().
Datos y estandarización
# Paquetes
library (dplyr)
Adjuntando el paquete: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library (ggplot2)
# Cargar iris
data (iris)
df <- iris %>% select (- Species) # solo numéricas
# Estandarizar (media 0, sd 1)
df_scaled <- scale (df)
# Vista rápida
as_tibble (head (df)) %>% print (n= 6 )
# A tibble: 6 × 4
Sepal.Length Sepal.Width Petal.Length Petal.Width
<dbl> <dbl> <dbl> <dbl>
1 5.1 3.5 1.4 0.2
2 4.9 3 1.4 0.2
3 4.7 3.2 1.3 0.2
4 4.6 3.1 1.5 0.2
5 5 3.6 1.4 0.2
6 5.4 3.9 1.7 0.4
apply (df_scaled, 2 , sd) # ~1
Sepal.Length Sepal.Width Petal.Length Petal.Width
1 1 1 1
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
# Cargar iris
iris = load_iris()
X = pd.DataFrame(iris.data, columns= iris.feature_names)
y = pd.Series(iris.target, name= "species" ) # 0,1,2
# Estandarizar
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Vista rápida
X.head()
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
np.std(X_scaled, axis= 0 ) # ~1
Ajuste del ACP
# prcomp usa SVD; center=TRUE, scale.=TRUE también estandariza internamente
pca <- prcomp (df, center = TRUE , scale. = TRUE )
# Varianza explicada
var_exp <- pca$ sdev^ 2
var_exp_ratio <- var_exp / sum (var_exp)
cum_exp_ratio <- cumsum (var_exp_ratio)
var_exp_ratio
[1] 0.729624454 0.228507618 0.036689219 0.005178709
[1] 0.7296245 0.9581321 0.9948213 1.0000000
from sklearn.decomposition import PCA
pca = PCA() # por defecto n_components = min(n_samples, n_features)
pca.fit(X_scaled)
PCA() In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
var_exp = pca.explained_variance_
var_exp_ratio = pca.explained_variance_ratio_
cum_exp_ratio = np.cumsum(var_exp_ratio)
var_exp_ratio, cum_exp_ratio
(array([0.72962445, 0.22850762, 0.03668922, 0.00517871]), array([0.72962445, 0.95813207, 0.99482129, 1. ]))
import prince
df = pd.DataFrame(X_scaled, columns= ["col1" , "col2" , "col3" , "col4" ])
pcaP = prince.PCA(
n_components= 3 ,
n_iter= 3 ,
rescale_with_mean= True ,
rescale_with_std= True ,
copy= True ,
check_input= True ,
engine= 'sklearn' ,
random_state= 42
)
pcaP = pcaP.fit(
df,
sample_weight= None ,
column_weight= None ,
supplementary_columns= None
)
Scree plot y varianza acumulada
scree_df <- data.frame (
PC = paste0 ("PC" , seq_along (var_exp_ratio)),
VarExp = var_exp_ratio,
CumExp = cum_exp_ratio
)
# Scree plot
ggplot (scree_df, aes (x = seq_along (VarExp), y = VarExp)) +
geom_line () + geom_point () +
labs (x = "Componente" , y = "Proporción varianza explicada" ,
title = "Scree plot (R)" ) +
theme_minimal ()
# Varianza acumulada
ggplot (scree_df, aes (x = seq_along (CumExp), y = CumExp)) +
geom_line () + geom_point () +
geom_hline (yintercept = 0.95 , linetype = "dashed" ) +
labs (x = "Componente" , y = "Varianza acumulada" ,
title = "Varianza acumulada (R)" ) +
theme_minimal ()
import matplotlib.pyplot as plt
# Scree plot
plt.figure()
plt.plot(range (1 , len (var_exp_ratio)+ 1 ), var_exp_ratio, marker= "o" )
plt.xlabel("Componente" )
plt.ylabel("Proporción varianza explicada" )
plt.title("Scree plot (Python)" )
plt.show()
# Varianza acumulada
plt.figure()
plt.plot(range (1 , len (cum_exp_ratio)+ 1 ), cum_exp_ratio, marker= "o" )
plt.axhline(0.95 , linestyle= "--" )
plt.xlabel("Componente" )
plt.ylabel("Varianza acumulada" )
plt.title("Varianza acumulada (Python)" )
plt.show()
Cargas (loadings) y scores
Las cargas indican cuánto contribuye cada variable a cada componente (correlaciones variable–componente).
Los scores son las coordenadas de las observaciones en el espacio de componentes.
# Cargas
loadings <- pca$ rotation
round (loadings, 3 )
PC1 PC2 PC3 PC4
Sepal.Length 0.521 -0.377 0.720 0.261
Sepal.Width -0.269 -0.923 -0.244 -0.124
Petal.Length 0.580 -0.024 -0.142 -0.801
Petal.Width 0.565 -0.067 -0.634 0.524
# Scores
scores <- pca$ x
head (scores)
PC1 PC2 PC3 PC4
[1,] -2.257141 -0.4784238 0.12727962 0.024087508
[2,] -2.074013 0.6718827 0.23382552 0.102662845
[3,] -2.356335 0.3407664 -0.04405390 0.028282305
[4,] -2.291707 0.5953999 -0.09098530 -0.065735340
[5,] -2.381863 -0.6446757 -0.01568565 -0.035802870
[6,] -2.068701 -1.4842053 -0.02687825 0.006586116
# Biplot rápido
biplot (pca, cex = 0.7 ) # base R
# Cargas = vectores propios (componentes)
loadings = pca.components_.T # shape: n_features x n_components
pd.DataFrame(loadings, index= iris.feature_names,
columns= [f"PC { i+ 1 } " for i in range (loadings.shape[1 ])]).round (3 )
PC1 PC2 PC3 PC4
sepal length (cm) 0.521 0.377 0.720 -0.261
sepal width (cm) -0.269 0.923 -0.244 0.124
petal length (cm) 0.580 0.024 -0.142 0.801
petal width (cm) 0.565 0.067 -0.634 -0.524
# Scores (transformación de los datos)
scores = pca.transform(X_scaled)
pd.DataFrame(scores, columns= [f"PC { i+ 1 } " for i in range (scores.shape[1 ])]).head()
PC1 PC2 PC3 PC4
0 -2.264703 0.480027 0.127706 -0.024168
1 -2.080961 -0.674134 0.234609 -0.103007
2 -2.364229 -0.341908 -0.044201 -0.028377
3 -2.299384 -0.597395 -0.091290 0.065956
4 -2.389842 0.646835 -0.015738 0.035923
# Biplot sencillo (PC1 vs PC2)
pc1, pc2 = 0 , 1
plt.figure()
plt.scatter(scores[:, pc1], scores[:, pc2], alpha= 0.7 )
# Vectores de variables
for i, feature in enumerate (iris.feature_names):
plt.arrow(0 , 0 , loadings[i, pc1]* 3 , loadings[i, pc2]* 3 , head_width= 0.05 )
plt.text(loadings[i, pc1]* 3.2 , loadings[i, pc2]* 3.2 , feature)
plt.xlabel("PC1" )
plt.ylabel("PC2" )
plt.title("Biplot (Python)" )
plt.axhline(0 , linewidth= 0.5 ); plt.axvline(0 , linewidth= 0.5 )
plt.show()
Elección de número de componentes
Criterios habituales:
Codo del scree plot.
Umbral de varianza acumulada (p. ej., 90–95%).
Kaiser (autovalores > 1) cuando se usa matriz de correlaciones.
Validación en tareas posteriores (clasificación/regresión).
# Elegir k para alcanzar >= 95% varianza
(k <- which (cum_exp_ratio >= 0.95 )[1 ])
k = int (np.argmax(cum_exp_ratio >= 0.95 )) + 1
k
Proyección y reconstrucción
# Proyectar datos a k componentes
k <- which (cum_exp_ratio >= 0.95 )[1 ]
scores_k <- scores[, 1 : k, drop = FALSE ]
# Reconstrucción aproximada (desde scores_k -> espacio original estandarizado)
# X_std_hat = scores_k %*% t(loadings[,1:k])
Xstd_hat <- scores_k %*% t (loadings[, 1 : k])
# "Desestandarizar" para volver a escala original
means <- attr (df_scaled, "scaled:center" )
sds <- attr (df_scaled, "scaled:scale" )
Xrec <- sweep (Xstd_hat, 2 , sds, ` * ` )
Xrec <- sweep (Xrec, 2 , means, ` + ` )
as_tibble (head (Xrec))
# A tibble: 6 × 4
Sepal.Length Sepal.Width Petal.Length Petal.Width
<dbl> <dbl> <dbl> <dbl>
1 5.02 3.51 1.47 0.252
2 4.74 3.03 1.60 0.272
3 4.72 3.20 1.33 0.167
4 4.67 3.09 1.38 0.182
5 5.02 3.60 1.35 0.207
6 5.41 3.90 1.70 0.384
k = int (np.argmax(cum_exp_ratio >= 0.95 )) + 1
scores_k = scores[:, :k]
load_k = loadings[:, :k]
# Reconstrucción en espacio estandarizado
Xstd_hat = np.dot(scores_k, load_k.T)
# Desestandarizar
Xrec = scaler.inverse_transform(Xstd_hat)
pd.DataFrame(Xrec, columns= iris.feature_names).head()
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.018949 3.514854 1.466013 0.251922
1 4.738463 3.030433 1.603913 0.272074
2 4.720130 3.196830 1.328961 0.167414
3 4.668436 3.086770 1.384170 0.182247
4 5.017093 3.596402 1.345411 0.206706
Pipeline para uso en modelos
En ML, es común encadenar escalado + PCA y luego un modelo.
# Ejemplo con caret o tidymodels (aquí base con prcomp para simplicidad)
# Generar componentes y usar los primeros k como features
k <- 2
pc_df <- as.data.frame (scores[, 1 : k])
pc_df$ Species <- iris$ Species
head (pc_df)
PC1 PC2 Species
1 -2.257141 -0.4784238 setosa
2 -2.074013 0.6718827 setosa
3 -2.356335 0.3407664 setosa
4 -2.291707 0.5953999 setosa
5 -2.381863 -0.6446757 setosa
6 -2.068701 -1.4842053 setosa
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
k = 2
pipe = Pipeline([
("scaler" , StandardScaler()),
("pca" , PCA(n_components= k)),
("clf" , LogisticRegression(max_iter= 1000 ))
])
scores_cv = cross_val_score(pipe, X, y, cv= 5 )
scores_cv.mean(), scores_cv.std()
(np.float64(0.9133333333333334), np.float64(0.05416025603090639))
Interpretación de cargas y biplot
Vectores largos: variables con alta contribución al componente.
Ángulos pequeños entre vectores: variables correlacionadas.
Puntos (observaciones) próximos: perfiles similares en variables originales.
Signo de la carga: dirección de la relación con el componente.
Trucos y errores comunes
No estandarizar cuando hay escalas muy distintas distorsiona el resultado
Outliers : revisar o usar alternativas robustas (p. ej., robustbase en R, sklearn + métodos robustos).
Muchas variables : considerar PCA incremental si hay memoria limitada.
Interpretabilidad : PCA rota ejes de forma no supervisada, no siempre mapea a factores interpretables .
Referencias rápidas (concepto)
Componentes = autovectores de covarianzas/correlaciones; varianza = autovalores.
scores = X_estandarizado %*% loadings
Reconstrucción aproximada: X̂_est = scores_k %*% t(loadings_k); desestandarizar para volver a escala original.
Esta web está creada por Dante Conti y Sergi Ramírez, (c) 2025