Bias-Variance Tradeoff Curve in ggplot2 (R)

The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, plotnine, Pygal, Seaborn; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: Bias-Variance Tradeoff Curve in Python, R, Julia and JavaScript.

A theoretical visualization of the bias-variance tradeoff showing how total prediction error decomposes into bias squared, variance, and irreducible noise as a function of model complexity. The plot displays multiple curves: bias squared (decreasing with complexity), variance (increasing with complexity), irreducible error (constant), and total error (U-shaped). This is one of the most fundamental conceptual plots in machine learning for understanding model selection, overfitting, and underfitting.

Bias-Variance Tradeoff Curve rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' curve-bias-variance-tradeoff: Bias-Variance Tradeoff Curve
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 92/100 | Created: 2026-05-28

library(ggplot2)
library(ragg)

set.seed(42)

# --- Theme tokens ---
THEME       <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG     <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
INK         <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT    <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
INK_MUTED   <- if (THEME == "light") "#6B6A63" else "#A8A79F"
ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420"

IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3", "#BD8233")

# --- Data ---
n            <- 100
complexity   <- seq(1, 10, length.out = n)
irreducible  <- rep(0.15, n)
bias_squared <- 0.70 * exp(-0.5 * (complexity - 1))
variance     <- 0.70 * exp(-0.5 * (10 - complexity))
total_error  <- bias_squared + variance + irreducible

optimal_idx <- which.min(total_error)
optimal_x   <- complexity[optimal_idx]
optimal_y   <- total_error[optimal_idx]

bias_ann_y <- bias_squared[which.min(abs(complexity - 1.4))] + 0.04
var_ann_y  <- variance[which.min(abs(complexity - 8.5))] + 0.04
tot_ann_y  <- total_error[which.min(abs(complexity - 3.0))] + 0.05

# --- Long format ---
components <- c("Bias²", "Variance", "Total Error", "Irreducible Error")
df_long <- data.frame(
    complexity = rep(complexity, 4),
    error      = c(bias_squared, variance, total_error, irreducible),
    component  = factor(rep(components, each = n), levels = components)
)

curve_colors    <- setNames(IMPRINT_PALETTE, components)
curve_linetypes <- c("Bias²" = "solid", "Variance" = "longdash",
                     "Total Error" = "solid", "Irreducible Error" = "dotted")
curve_lwidths   <- c("Bias²" = 1.0, "Variance" = 1.0,
                     "Total Error" = 1.7, "Irreducible Error" = 0.9)

title_str <- "curve-bias-variance-tradeoff · r · ggplot2 · anyplot.ai"

# --- Plot ---
p <- ggplot(df_long,
            aes(x = complexity, y = error,
                color = component, linetype = component,
                linewidth = component)) +
    annotate("rect", xmin = 1, xmax = optimal_x,
             ymin = -Inf, ymax = Inf, fill = "#009E73", alpha = 0.05) +
    annotate("rect", xmin = optimal_x, xmax = 10,
             ymin = -Inf, ymax = Inf, fill = "#AE3030", alpha = 0.05) +
    geom_vline(xintercept = optimal_x, color = INK_SOFT,
               linetype = "dashed", linewidth = 0.5) +
    geom_line() +
    scale_color_manual(values = curve_colors) +
    scale_linetype_manual(values = curve_linetypes) +
    scale_linewidth_manual(values = curve_lwidths) +
    annotate("text", x = (1 + optimal_x) / 2, y = 0.86,
             label = "Underfitting", color = INK_MUTED, size = 3.0) +
    annotate("text", x = (optimal_x + 10) / 2, y = 0.86,
             label = "Overfitting", color = INK_MUTED, size = 3.0) +
    annotate("text", x = optimal_x + 0.2, y = optimal_y - 0.04,
             label = "Optimal", color = INK_SOFT, size = 2.8, hjust = 0) +
    annotate("text", x = 1.4, y = bias_ann_y,
             label = "Bias²", color = IMPRINT_PALETTE[1],
             size = 3.2, hjust = 0, fontface = "bold") +
    annotate("text", x = 8.5, y = var_ann_y,
             label = "Variance", color = IMPRINT_PALETTE[2],
             size = 3.2, hjust = 0.5, fontface = "bold") +
    annotate("text", x = 3.0, y = tot_ann_y,
             label = "Total Error", color = IMPRINT_PALETTE[3],
             size = 3.0, hjust = 0.5, fontface = "bold") +
    annotate("text", x = 7.0, y = 0.10,
             label = "Irreducible Error", color = IMPRINT_PALETTE[4],
             size = 2.8, hjust = 0.5) +
    scale_x_continuous(
        breaks = c(1, optimal_x, 10),
        labels = c("Low", "Optimal", "High"),
        expand = c(0.02, 0)
    ) +
    scale_y_continuous(expand = c(0.02, 0)) +
    labs(
        title    = title_str,
        subtitle = "Total Error = Bias² + Variance + Irreducible Error",
        x        = "Model Complexity",
        y        = "Prediction Error"
    ) +
    theme_minimal(base_size = 8) +
    theme(
        plot.background    = element_rect(fill = PAGE_BG, color = PAGE_BG),
        panel.background   = element_rect(fill = PAGE_BG, color = NA),
        panel.grid.major.y = element_line(color = INK, linewidth = 0.15),
        panel.grid.major.x = element_blank(),
        panel.grid.minor   = element_blank(),
        panel.border       = element_blank(),
        axis.title         = element_text(color = INK, size = 10),
        axis.text          = element_text(color = INK_SOFT, size = 8),
        axis.line          = element_line(color = INK_SOFT, linewidth = 0.4),
        plot.title         = element_text(color = INK, size = 12, face = "bold"),
        plot.subtitle      = element_text(color = INK_MUTED, size = 8),
        legend.position    = "none",
        plot.margin        = margin(15, 20, 15, 15)
    )

# --- Save ---
ggsave(
    filename = sprintf("plot-%s.png", THEME),
    plot     = p,
    device   = ragg::agg_png,
    width    = 8,
    height   = 4.5,
    units    = "in",
    dpi      = 400
)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/curve-bias-variance-tradeoff/ggplot2/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "curve-bias-variance-tradeoff",
  "language": "r",
  "library": "ggplot2",
  "page": "https://anyplot.ai/curve-bias-variance-tradeoff/r/ggplot2",
  "hub": "https://anyplot.ai/curve-bias-variance-tradeoff",
  "code_json": "https://api.anyplot.ai/specs/curve-bias-variance-tradeoff/ggplot2/code",
  "spec_json": "https://api.anyplot.ai/specs/curve-bias-variance-tradeoff",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/curve-bias-variance-tradeoff/r/ggplot2/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/curve-bias-variance-tradeoff/r/ggplot2/plot-dark.png",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Bias-Variance Tradeoff Curve on anyplot.ai.

Other implementations