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