Weibull Probability Plot for Reliability Analysis — ggplot2

A Weibull probability plot displays failure or lifetime data on Weibull probability paper (logarithmic x-axis for time/cycles, linearized Weibull CDF on y-axis) with a fitted straight line. It is the standard tool in reliability engineering for estimating Weibull distribution parameters (shape and scale), assessing whether data follow a Weibull distribution, and extrapolating failure probabilities. The slope of the fitted line gives the shape parameter (beta), while the characteristic life (eta) is read at the 63.2% failure probability crossing.

Weibull Probability Plot for Reliability Analysis rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' probability-weibull: Weibull Probability Plot for Reliability Analysis
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 90/100 | Created: 2026-06-07

library(ggplot2)
library(scales)
library(ragg)

set.seed(42)

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

IMPRINT_PALETTE <- c(
  "#009E73",  # 1 — brand green (failures)
  "#AE3030",  # 5 — matte red (censored — semantic: suspended/lost)
  "#4467A3"   # 3 — blue (fitted line)
)

# Data: turbine blade fatigue-life data (hours to failure)
# Simulate Weibull(shape=2.3, scale=8000) with some right-censored observations
n_total <- 40
true_shape <- 2.3
true_scale <- 8000

# Generate failure times
all_times <- true_scale * (-log(runif(n_total)))^(1 / true_shape)
# Right-censor at 10000 hours
censor_time <- 10000
is_censored <- all_times > censor_time
time_to_failure <- pmin(all_times, censor_time)

# Sort by time for proper rank ordering
ord <- order(time_to_failure)
time_to_failure <- time_to_failure[ord]
is_censored <- is_censored[ord]

# Count actual failures for median rank calculation
n_fail <- sum(!is_censored)
failure_idx <- which(!is_censored)

# Median rank (Benard's approximation): (i - 0.3) / (n + 0.4)
# Only assign plotting positions to failures; censored are plotted separately
median_ranks <- (seq_along(failure_idx) - 0.3) / (n_total + 0.4)

# Weibull linearized y-axis: y = ln(-ln(1 - F))
weibull_y <- log(-log(1 - median_ranks))

# Data frame for failure points
df_fail <- data.frame(
  time  = time_to_failure[failure_idx],
  wb_y  = weibull_y,
  type  = "Failure"
)

# For censored points: use the last assigned median rank as a ceiling (right-censored)
# Place censored obs at a y that reflects "at least survived this long"
# — just plot them at the axis mid-level annotated as censored; no formal rank
censored_times <- time_to_failure[is_censored]
df_cens <- data.frame(
  time = censored_times,
  wb_y = rep(median(weibull_y), length(censored_times)),
  type = "Censored (suspended)"
)

# Fit line via OLS on linearised data (log(t) ~ wb_y)
log_times <- log(df_fail$time)
fit <- lm(weibull_y ~ log_times, data = df_fail)
beta_hat  <- coef(fit)[2]            # slope = shape parameter
eta_hat   <- exp(-coef(fit)[1] / coef(fit)[2])  # scale parameter (characteristic life)

# Fitted line over the data range
x_seq     <- seq(log(min(df_fail$time) * 0.7), log(max(df_fail$time) * 1.3), length.out = 200)
y_fit_seq <- coef(fit)[1] + coef(fit)[2] * x_seq
df_line   <- data.frame(time = exp(x_seq), wb_y = y_fit_seq)

# 63.2% reference line: wb_y at F=0.632 = ln(-ln(1-0.632)) ≈ 0
ref_y  <- log(-log(1 - 0.632))  # ≈ -0.0006 ≈ 0
ref_63 <- data.frame(
  x_start = min(df_line$time),
  x_end   = exp(-coef(fit)[1] / coef(fit)[2]),  # = eta_hat
  y_val   = ref_y
)

# Title
plot_title <- "probability-weibull · r · ggplot2 · anyplot.ai"
title_len  <- nchar(plot_title)
base_title_size <- 12
title_size <- max(8, round(base_title_size * 67 / title_len))

# Custom Weibull probability y-axis breaks and labels
# Convert F values to wb_y = ln(-ln(1-F))
f_breaks  <- c(0.01, 0.05, 0.10, 0.20, 0.30, 0.50, 0.632, 0.80, 0.90, 0.95, 0.99)
wb_breaks <- log(-log(1 - f_breaks))
f_labels  <- paste0(formatC(f_breaks * 100, format = "g", digits = 3), "%")

# Plot
p <- ggplot() +
  # Fitted Weibull line
  geom_line(
    data = df_line,
    aes(x = time, y = wb_y),
    color     = IMPRINT_PALETTE[3],
    linewidth = 1.0,
    linetype  = "solid"
  ) +
  # 63.2% horizontal reference line
  geom_segment(
    aes(x = ref_63$x_start, xend = ref_63$x_end,
        y = ref_63$y_val,   yend = ref_63$y_val),
    color     = INK_MUTED,
    linewidth = 0.5,
    linetype  = "dashed"
  ) +
  # Vertical drop to x-axis at eta
  geom_segment(
    aes(x = ref_63$x_end, xend = ref_63$x_end,
        y = ref_63$y_val, yend = min(wb_breaks) - 0.3),
    color     = INK_MUTED,
    linewidth = 0.5,
    linetype  = "dashed"
  ) +
  # Failure points (filled)
  geom_point(
    data = df_fail,
    aes(x = time, y = wb_y, shape = "Failure", color = "Failure"),
    size  = 2.8,
    alpha = 0.85,
    fill  = IMPRINT_PALETTE[1]
  ) +
  # Censored points (hollow)
  geom_point(
    data = df_cens,
    aes(x = time, y = wb_y, shape = "Censored (suspended)", color = "Censored (suspended)"),
    size   = 2.8,
    alpha  = 0.85,
    stroke = 1.2
  ) +
  # Parameter annotation
  annotate(
    "label",
    x     = min(df_fail$time) * 1.1,
    y     = max(wb_breaks) - 0.2,
    label = sprintf("β (shape) = %.2f\nη (scale) = %.0f h", beta_hat, eta_hat),
    hjust = 0,
    vjust = 1,
    size  = 3.5,
    color = INK_SOFT,
    fill  = ELEVATED_BG,
    label.size = 0.3
  ) +
  # 63.2% label
  annotate(
    "text",
    x     = ref_63$x_start * 1.05,
    y     = ref_63$y_val + 0.08,
    label = "63.2%",
    hjust = 0,
    size  = 2.5,
    color = INK_MUTED
  ) +
  scale_x_log10(
    labels = label_comma(),
    name   = "Time to Failure (hours)"
  ) +
  scale_y_continuous(
    breaks = wb_breaks,
    labels = f_labels,
    limits = c(min(wb_breaks) - 0.3, max(wb_breaks) + 0.1),
    name   = "Cumulative Failure Probability"
  ) +
  scale_color_manual(
    name   = NULL,
    values = c("Failure" = IMPRINT_PALETTE[1], "Censored (suspended)" = IMPRINT_PALETTE[2])
  ) +
  scale_shape_manual(
    name   = NULL,
    values = c("Failure" = 19, "Censored (suspended)" = 1)
  ) +
  labs(title = plot_title) +
  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  = element_line(color = INK_SOFT, linewidth = 0.15),
    panel.grid.minor  = element_blank(),
    panel.border      = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.4),
    axis.title        = element_text(color = INK,      size = 10),
    axis.text         = element_text(color = INK_SOFT, size = 8),
    axis.text.x       = element_text(color = INK_SOFT, size = 8),
    plot.title        = element_text(color = INK,      size = title_size, face = "bold",
                                     margin = margin(b = 8)),
    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3),
    legend.text       = element_text(color = INK_SOFT, size = 8),
    legend.title      = element_text(color = INK,      size = 9),
    legend.position   = "bottom",
    legend.key        = element_rect(fill = NA, color = NA),
    plot.margin       = margin(12, 16, 12, 12)
  )

# 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/probability-weibull/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": "probability-weibull",
  "language": "r",
  "library": "ggplot2",
  "page": "https://anyplot.ai/probability-weibull/r/ggplot2",
  "hub": "https://anyplot.ai/probability-weibull",
  "code_json": "https://api.anyplot.ai/specs/probability-weibull/ggplot2/code",
  "spec_json": "https://api.anyplot.ai/specs/probability-weibull",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/probability-weibull/r/ggplot2/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/probability-weibull/r/ggplot2/plot-dark.png",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Weibull Probability Plot for Reliability Analysis on anyplot.ai.

Other implementations