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.

""" anyplot.ai
probability-weibull: Weibull Probability Plot for Reliability Analysis
Library: seaborn 0.13.2 | Python 3.13.13
Quality: 88/100 | Updated: 2026-06-07
"""
import os
import sys
# Prevent local .py files from shadowing real packages (matplotlib.py, seaborn.py, etc.)
sys.path = [
p
for p in sys.path
if p not in ("", ".") and not p.endswith("/implementations/python") and not p.endswith("/implementations")
]
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from scipy import stats
# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
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 — canonical order, first series always #009E73
IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"]
COLOR_FAILURE = IMPRINT_PALETTE[0] # brand green — failures
COLOR_CENSORED = IMPRINT_PALETTE[1] # lavender — suspended observations
COLOR_FIT = IMPRINT_PALETTE[2] # blue — Weibull fit line
sns.set_theme(
style="ticks",
rc={
"figure.facecolor": PAGE_BG,
"axes.facecolor": PAGE_BG,
"axes.edgecolor": INK_SOFT,
"axes.labelcolor": INK,
"text.color": INK,
"xtick.color": INK_SOFT,
"ytick.color": INK_SOFT,
"grid.color": INK,
"grid.alpha": 0.15,
"legend.facecolor": ELEVATED_BG,
"legend.edgecolor": INK_SOFT,
},
)
# Data — turbine blade fatigue-life (hours)
np.random.seed(42)
shape_true = 2.5
scale_true = 8000
n_failures = 25
n_censored = 7
n_total = n_failures + n_censored
failure_times = np.sort(stats.weibull_min.rvs(shape_true, scale=scale_true, size=n_failures))
censor_times = np.sort(np.random.uniform(2000, 10000, size=n_censored))
all_times = np.concatenate([failure_times, censor_times])
is_censored = np.concatenate([np.zeros(n_failures, dtype=bool), np.ones(n_censored, dtype=bool)])
sort_idx = np.argsort(all_times)
all_times = all_times[sort_idx]
is_censored = is_censored[sort_idx]
# Median rank plotting positions (Benard approximation) for all points
failure_rank = np.cumsum(~is_censored)
median_rank = (failure_rank - 0.3) / (n_total + 0.4)
# Weibull linearized y-axis: ln(-ln(1-F)) — transforms Weibull CDF to straight line
weibull_y = np.log(-np.log(1 - median_rank))
log_times = np.log(all_times)
# Fit line via linear regression on failure points only
failure_mask = ~is_censored
slope, intercept, r_value, _, _ = stats.linregress(log_times[failure_mask], weibull_y[failure_mask])
beta = slope
eta = np.exp(-intercept / slope)
# Fit line data
x_fit = np.linspace(np.log(1000), np.log(20000), 200)
y_fit = slope * x_fit + intercept
df_fit = pd.DataFrame({"log_time": x_fit, "weibull_y": y_fit})
# DataFrame for scatter
df = pd.DataFrame(
{"log_time": log_times, "weibull_y": weibull_y, "Status": np.where(is_censored, "Suspended", "Failure")}
)
# Plot — landscape canvas: figsize=(8, 4.5) × dpi=400 → 3200×1800 px
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)
df_failures = df[df["Status"] == "Failure"]
df_suspended = df[df["Status"] == "Suspended"]
sns.scatterplot(
data=df_failures,
x="log_time",
y="weibull_y",
color=COLOR_FAILURE,
s=110,
marker="o",
edgecolor=PAGE_BG,
linewidth=0.5,
label="Failure",
zorder=5,
ax=ax,
)
sns.scatterplot(
data=df_suspended,
x="log_time",
y="weibull_y",
color="none",
s=110,
marker="D",
edgecolor=COLOR_CENSORED,
linewidth=1.5,
label="Suspended",
zorder=5,
ax=ax,
)
sns.lineplot(
data=df_fit,
x="log_time",
y="weibull_y",
color=COLOR_FIT,
linewidth=2.0,
linestyle="--",
label="Weibull fit",
zorder=4,
ax=ax,
)
# Confidence band on fit line (±1σ prediction interval approximation)
n_fit = failure_mask.sum()
x_mean = log_times[failure_mask].mean()
ss_xx = np.sum((log_times[failure_mask] - x_mean) ** 2)
se_fit = np.sqrt(np.sum((weibull_y[failure_mask] - (slope * log_times[failure_mask] + intercept)) ** 2) / (n_fit - 2))
ci_half = se_fit * np.sqrt(1 / n_fit + (x_fit - x_mean) ** 2 / ss_xx)
ax.fill_between(x_fit, y_fit - ci_half, y_fit + ci_half, color=COLOR_FIT, alpha=0.12, zorder=3)
# Reference line at 63.2% characteristic life
y_632 = np.log(-np.log(1 - 0.632))
ax.axhline(y=y_632, color=INK_SOFT, linewidth=0.8, linestyle=":", alpha=0.6, zorder=3)
ax.text(np.log(14000), y_632 - 0.18, "63.2% (characteristic life)", fontsize=7, color=INK_MUTED, ha="right")
# B10 life — time at 10% cumulative failure probability
b10_y = np.log(-np.log(1 - 0.10))
b10_x = (b10_y - intercept) / slope
b10_time = np.exp(b10_x)
ax.plot(b10_x, b10_y, "s", color=COLOR_FIT, markersize=5, zorder=6)
ax.annotate(
f"B10 ≈ {b10_time:,.0f} h",
xy=(b10_x, b10_y),
xytext=(b10_x + 0.35, b10_y - 0.55),
fontsize=8,
color=INK,
arrowprops={"arrowstyle": "->", "color": INK_SOFT, "linewidth": 0.8},
bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.9},
)
# Weibull parameters box
ax.text(
0.97,
0.06,
f"β = {beta:.2f} (shape)\nη = {eta:.0f} h (scale)\nR² = {r_value**2:.4f}",
transform=ax.transAxes,
fontsize=8,
fontfamily="monospace",
ha="right",
va="bottom",
color=INK,
bbox={"boxstyle": "round,pad=0.4", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.9},
)
# Rugplot for failure time density (distinctive seaborn feature)
df_rug = pd.DataFrame({"log_time": log_times[failure_mask]})
sns.rugplot(data=df_rug, x="log_time", color=COLOR_FAILURE, height=0.02, alpha=0.4, ax=ax)
# Custom x-axis tick labels (real time values from log scale)
time_ticks = [1000, 2000, 3000, 5000, 8000, 12000, 18000]
ax.set_xticks([np.log(t) for t in time_ticks])
ax.set_xticklabels([f"{t:,}" for t in time_ticks])
# Custom y-axis tick labels (cumulative probability from linearized Weibull scale)
prob_ticks = [0.01, 0.05, 0.10, 0.20, 0.40, 0.632, 0.80, 0.90, 0.95, 0.99]
y_tick_vals = [np.log(-np.log(1 - p)) for p in prob_ticks]
ax.set_yticks(y_tick_vals)
ax.set_yticklabels([f"{p * 100:.1f}%" if p != 0.632 else "63.2%" for p in prob_ticks])
ax.set_xlim(np.log(800), np.log(22000))
ax.set_ylim(np.log(-np.log(1 - 0.005)), np.log(-np.log(1 - 0.995)))
# Title fontsize scaled for length (style guide formula)
title = "probability-weibull · python · seaborn · anyplot.ai"
n = len(title)
ratio = 67 / n if n > 67 else 1.0
title_fontsize = max(8, round(12 * ratio))
ax.set_title(title, fontsize=title_fontsize, fontweight="medium", color=INK)
ax.set_xlabel("Time to Failure (hours)", fontsize=10, color=INK)
ax.set_ylabel("Cumulative Failure Probability", fontsize=10, color=INK)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT)
sns.despine(ax=ax)
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)
ax.set_axisbelow(True)
ax.legend(fontsize=8, frameon=True, loc="upper left", facecolor=ELEVATED_BG, edgecolor=INK_SOFT)
fig.subplots_adjust(left=0.1, right=0.97, bottom=0.12, top=0.93)
# Save — bbox_inches must NOT be 'tight' (seaborn canvas rule: figsize × dpi = exact target)
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
plt.close()
Part of Weibull Probability Plot for Reliability Analysis on anyplot.ai.