Principal Component Analysis (PCA) in Python with scikit-learn

Principal component analysis in Python takes three lines with scikit-learn: scale your features, create PCA(n_components=2), and call fit_transform(). You get a smaller set of new columns that keep most of the information:

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X_2d = PCA(n_components=2).fit_transform(StandardScaler().fit_transform(X))

I tested this on Python 3.12.5 with scikit-learn 1.9.1, using the wine and iris datasets that ship with scikit-learn, so you won’t need to download anything.

We’ll run a basic PCA, see why scaling matters, pick the number of components, plot the result, read the loadings, and use PCA inside a model pipeline.

How to do PCA in Python with scikit-learn

The wine dataset has 178 wines measured on 13 chemical features. PCA squeezes those 13 columns into 2 new ones, called principal components:

from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X, y = load_wine(return_X_y=True)
print("original shape:", X.shape)

X_scaled = StandardScaler().fit_transform(X)
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X_scaled)

print("reduced shape: ", X_2d.shape)
print("variance kept by each component:", pca.explained_variance_ratio_.round(3))
print(f"total kept: {pca.explained_variance_ratio_.sum():.1%}")
print("first wine as 2 numbers:", X_2d[0].round(3))

Output:

original shape: (178, 13)
reduced shape:  (178, 2)
variance kept by each component: [0.362 0.192]
total kept: 55.4%
first wine as 2 numbers: [3.317 1.443]
Command Prompt output of scikit-learn PCA reducing the 13-feature wine dataset to two principal components with explained variance
Two components keep about 55% of the variance in the 13 original features.

Each component is a weighted mix of the original features. The first one points in the direction where the data varies most, and the second captures the most of what’s left.

If scikit-learn isn’t installed yet, run python -m pip install scikit-learn first; it brings NumPy and SciPy with it.

Why you should scale data before PCA

PCA chases variance, so a feature measured in big numbers drowns out the rest. In the wine data, proline goes past 1,600 while hue stays under 2:

from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X, _ = load_wine(return_X_y=True)

raw = PCA(n_components=2).fit(X)
scaled = PCA(n_components=2).fit(StandardScaler().fit_transform(X))

print("without scaling:", raw.explained_variance_ratio_.round(3))
print("with scaling:   ", scaled.explained_variance_ratio_.round(3))
print("feature ranges: proline up to", X[:, 12].max(), "| hue up to", X[:, 10].max())

Output:

without scaling: [0.998 0.002]
with scaling:    [0.362 0.192]
feature ranges: proline up to 1680.0 | hue up to 1.71
Command Prompt output comparing PCA explained variance on the wine dataset without and with StandardScaler
Without scaling, the first component “explains” almost everything, but it’s really just proline.

StandardScaler gives every feature a mean of 0 and a standard deviation of 1. Scikit-learn’s PCA centers the data, but it doesn’t scale it for you.

How many principal components should you keep?

Look at the cumulative explained variance and stop when it’s high enough for your job. Or pass a fraction like 0.95 and let scikit-learn count for you:

import numpy as np
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X_scaled = StandardScaler().fit_transform(load_wine().data)

full = PCA().fit(X_scaled)
cumulative = np.cumsum(full.explained_variance_ratio_)
for k, share in enumerate(cumulative[:8], start=1):
    print(f"{k} components -> {share:.1%}")

pca95 = PCA(n_components=0.95).fit(X_scaled)
print("components needed for 95%:", pca95.n_components_)

Output:

1 components -> 36.2%
2 components -> 55.4%
3 components -> 66.5%
4 components -> 73.6%
5 components -> 80.2%
6 components -> 85.1%
7 components -> 89.3%
8 components -> 92.0%
components needed for 95%: 10
Command Prompt output of cumulative explained variance by number of PCA components and the number needed for 95 percent
It takes 10 of the 13 components to keep 95% of the variance in the wine data.

For charts, 2 or 3 components is the norm. For speeding up a model, 90% to 95% of the variance is a common target.

Plotting PCA results in Python

Reducing to two components means you can draw data that had four or more dimensions. Here are the iris flowers, colored by species:

import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

iris = load_iris()
X_2d = PCA(n_components=2).fit_transform(StandardScaler().fit_transform(iris.data))

fig, ax = plt.subplots(figsize=(7, 5))
for label, name in enumerate(iris.target_names):
    pts = X_2d[iris.target == label]
    ax.scatter(pts[:, 0], pts[:, 1], label=name, alpha=0.8)
ax.set_xlabel("Principal component 1")
ax.set_ylabel("Principal component 2")
ax.set_title("Iris flowers after PCA (4 features -> 2)")
ax.legend()
plt.tight_layout()
plt.show()
Matplotlib scatter plot of the iris dataset after principal component analysis, with setosa, versicolor and virginica in different colors
Setosa separates cleanly along the first component, while versicolor and virginica overlap a little.

If you’re new to matplotlib, the for loop here just draws one species at a time so each gets its own color and legend entry.

Reading PCA loadings

pca.components_ holds the weight of each original feature in each component. Putting it in a pandas DataFrame makes it readable:

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

iris = load_iris()
pca = PCA(n_components=2).fit(StandardScaler().fit_transform(iris.data))

loadings = pd.DataFrame(pca.components_.T, index=iris.feature_names, columns=["PC1", "PC2"])
print(loadings.round(3))

Output:

                     PC1    PC2
sepal length (cm)  0.521  0.377
sepal width (cm)  -0.269  0.923
petal length (cm)  0.580  0.024
petal width (cm)   0.565  0.067

Petal length, petal width and sepal length all load heavily on PC1, so it’s roughly a “flower size” axis. PC2 is mostly sepal width.

Using PCA in a scikit-learn pipeline

PCA is often a preprocessing step before a model. A pipeline keeps scaling and PCA inside cross-validation, so test data never leaks into the fit:

from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_wine(return_X_y=True)

for k in (2, 5, 13):
    model = make_pipeline(StandardScaler(), PCA(n_components=k), LogisticRegression(max_iter=1000))
    scores = cross_val_score(model, X, y, cv=5)
    print(f"{k:>2} components: accuracy {scores.mean():.3f}")

Output:

 2 components: accuracy 0.955
 5 components: accuracy 0.978
13 components: accuracy 0.983
Command Prompt output of a scikit-learn pipeline with StandardScaler, PCA and logistic regression scored with cross-validation
Even 2 components classify the wines well; 5 or more are near perfect on this small dataset.

On wide datasets with hundreds of features, this is where PCA really pays off: faster training with little or no loss in accuracy.

PCA in Python: key attributes

Attribute or argumentWhat it gives you
n_components=2Keep exactly 2 components
n_components=0.95Keep enough components for 95% of the variance
explained_variance_ratio_Share of variance per component
components_Loadings: feature weights per component
inverse_transform()Map reduced data back to the original features (approximately)

More machine learning in Python to explore next:

Frequently asked questions

How do I do principal component analysis in Python?

Scale the data with StandardScaler, then use PCA(n_components=k).fit_transform(X_scaled) from scikit-learn.

Do I need to standardize data before PCA?

Usually, yes. Without scaling, features with large units dominate the components.

What does explained_variance_ratio_ mean?

It’s the share of the total variance each component captures. Add them up to see how much information you kept.

How do I choose n_components?

Plot the cumulative explained variance, or pass a fraction such as 0.95 to keep 95% of it.

Can I use PCA without scikit-learn?

Yes. NumPy’s np.linalg.svd on centered data gives the same components, but scikit-learn is easier.

Where is the full PCA reference?

Every parameter is listed in the scikit-learn PCA documentation.