Anàlisis de Components Principals (ACP)

Author

Dante Conti, Sergi Ramirez, (c) IDEAI

Published

March 6, 2026

Modified

March 6, 2026

1 ¿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.

2 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).

3 Flujo de trabajo (resumen)

  1. Estandarizar (opcional pero recomendado).
  2. Ajustar ACP sobre matriz estandarizada.
  3. Evaluar varianza explicada y elegir k componentes.
  4. Inspeccionar cargas (loadings) y scores.
  5. Visualizar: scree plot, biplot.
  6. Proyectar nuevos datos y (si aplica) reconstruir.

4 Dataset de ejemplo

Usaremos el clásico Iris (4 variables numéricas, 3 especies).
En R: datasets::iris.
En Python: sklearn.datasets.load_iris().

5 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)
Warning: package 'ggplot2' was built under R version 4.5.2
# 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
array([1., 1., 1., 1.])

6 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
cum_exp_ratio
[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
)

7 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()

8 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()

8.1 Elección de número de componentes

Criterios habituales:

  1. Codo del scree plot.
  2. Umbral de varianza acumulada (p. ej., 90–95%).
  3. Kaiser (autovalores > 1) cuando se usa matriz de correlaciones.
  4. Validación en tareas posteriores (clasificación/regresión).
# Elegir k para alcanzar >= 95% varianza
(k <- which(cum_exp_ratio >= 0.95)[1])
[1] 2
k = int(np.argmax(cum_exp_ratio >= 0.95)) + 1
k
2

9 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

10 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))

10.1 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.

11 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.

12 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.

Aquesta web està creada por Dante Conti y Sergi Ramírez, (c) 2026