Cumulative Explained Variance for PCA Component Selection — plotnine

A line plot showing the cumulative proportion of explained variance as a function of the number of Principal Component Analysis (PCA) components. This visualization helps determine the optimal number of components to retain by displaying the trade-off between dimensionality reduction and information preservation. The cumulative curve typically exhibits an elbow pattern where additional components yield diminishing returns, and horizontal threshold lines (e.g., 90%, 95%) guide component selection decisions.

Cumulative Explained Variance for PCA Component Selection rendered with plotnine

Python source (plotnine)

""" anyplot.ai
line-pca-variance-cumulative: Cumulative Explained Variance for PCA Component Selection
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 90/100 | Updated: 2026-05-29
"""

import os

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_col,
    geom_hline,
    geom_line,
    geom_point,
    geom_segment,
    geom_text,
    ggplot,
    labs,
    scale_x_continuous,
    scale_y_continuous,
    theme,
    theme_minimal,
)
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler


# Theme tokens — Imprint palette, theme-adaptive chrome
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Imprint palette
BRAND = "#009E73"  # Imprint position 1 — ALWAYS first series
IMPRINT_RED = "#AE3030"  # Imprint matte red — semantic anchor for elbow emphasis

# Data — PCA on the Wine dataset (13 features)
X_scaled = StandardScaler().fit_transform(load_wine().data)
pca = PCA().fit(X_scaled)

n_components = np.arange(1, len(pca.explained_variance_ratio_) + 1)
cumulative_var = np.cumsum(pca.explained_variance_ratio_) * 100
individual_var = pca.explained_variance_ratio_ * 100

df = pd.DataFrame({"component": n_components, "cumulative": cumulative_var, "individual": individual_var})

# Elbow detection via maximum second-derivative change
diffs = np.diff(cumulative_var)
elbow_idx = int(np.argmax(np.abs(np.diff(diffs)))) + 1
elbow_c = int(n_components[elbow_idx])
elbow_v = cumulative_var[elbow_idx]
elbow_df = pd.DataFrame(
    {"component": [elbow_c], "cumulative": [elbow_v], "label": [f"Elbow: {elbow_c} components\n({elbow_v:.1f}%)"]}
)

# Threshold reference lines
thresholds = pd.DataFrame({"y": [90.0, 95.0, 99.0], "label": ["90%", "95%", "99%"], "x": [12.5, 12.5, 12.5]})

# Title fontsize — scaled by length per plot-generator.md formula
title_str = "line-pca-variance-cumulative · python · plotnine · anyplot.ai"
n = len(title_str)
ratio = 67 / n if n > 67 else 1.0
title_fontsize = max(8, round(12 * ratio))

# Plot
plot = (
    ggplot(df, aes(x="component", y="cumulative"))
    # Threshold dashed reference lines (structural chrome — INK_SOFT)
    + geom_hline(yintercept=90.0, linetype="dashed", color=INK_SOFT, size=0.5, alpha=0.6)
    + geom_hline(yintercept=95.0, linetype="dashed", color=INK_SOFT, size=0.5, alpha=0.6)
    + geom_hline(yintercept=99.0, linetype="dashed", color=INK_SOFT, size=0.5, alpha=0.6)
    # Individual variance as translucent bars — contextual background layer
    + geom_col(aes(y="individual"), fill=BRAND, width=0.5, alpha=0.15, show_legend=False)
    # Vertical drop-line from elbow to x-axis
    + geom_segment(
        data=elbow_df,
        mapping=aes(x="component", xend="component", y=0, yend="cumulative"),
        linetype="dotted",
        color=IMPRINT_RED,
        size=0.6,
        alpha=0.6,
    )
    # Main cumulative variance line
    + geom_line(color=BRAND, size=1.0)
    # Data point markers — PAGE_BG fill for theme-adaptive white circles
    + geom_point(color=BRAND, size=2.5, fill=PAGE_BG, stroke=1.2, shape="o")
    # Highlighted elbow point
    + geom_point(
        data=elbow_df,
        mapping=aes(x="component", y="cumulative"),
        color=IMPRINT_RED,
        size=5.0,
        fill=IMPRINT_RED,
        stroke=1.5,
        shape="o",
        alpha=0.9,
    )
    # Elbow annotation
    + geom_text(
        data=elbow_df,
        mapping=aes(x="component", y="cumulative", label="label"),
        ha="left",
        va="bottom",
        size=3.0,
        color=IMPRINT_RED,
        fontweight="bold",
        nudge_x=0.4,
        nudge_y=2.5,
    )
    # Threshold labels
    + geom_text(
        data=thresholds,
        mapping=aes(x="x", y="y", label="label"),
        ha="right",
        va="bottom",
        size=2.5,
        color=INK_MUTED,
        nudge_y=0.5,
    )
    # Scales
    + scale_x_continuous(breaks=n_components, labels=[str(i) for i in n_components], expand=(0.02, 0.4))
    + scale_y_continuous(
        limits=(0, 102), breaks=[0, 20, 40, 60, 80, 100], labels=["0%", "20%", "40%", "60%", "80%", "100%"]
    )
    + labs(x="Number of Principal Components", y="Cumulative Explained Variance (%)", title=title_str)
    + theme_minimal()
    + theme(
        figure_size=(8, 4.5),
        text=element_text(size=7, color=INK_SOFT),
        axis_title=element_text(size=10, color=INK),
        axis_text=element_text(size=8, color=INK_SOFT),
        plot_title=element_text(size=title_fontsize, color=INK, ha="left"),
        panel_grid_major_x=element_blank(),
        panel_grid_minor=element_blank(),
        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.12),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        panel_border=element_rect(color=INK_SOFT, fill=None),
        axis_line_x=element_line(color=INK_SOFT, size=0.4),
        axis_ticks_major_x=element_line(color=INK_SOFT, size=0.3),
        plot_margin=0.02,
    )
)

# Save
plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False)

Part of Cumulative Explained Variance for PCA Component Selection on anyplot.ai.

Other implementations