{"spec_id":"diagnostic-regression-panel","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ndiagnostic-regression-panel: Regression Diagnostic Panel (Four-Plot Display)\nLibrary: matplotlib 3.11.1 | Python 3.13.15\nQuality: 90/100 | Created: 2026-08-24\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport statsmodels.api as sm\nfrom scipy import stats\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\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 — position 1 is always brand green\nBRAND = \"#009E73\"\nLOWESS_COLOR = \"#C475FD\"  # Imprint palette position 2\nAMBER = \"#DDCC77\"  # semantic anchor — Cook's distance warning contours\n\n# Data: fit an OLS dose-response model on heteroscedastic, mildly non-linear data\nnp.random.seed(42)\nn = 120\ndose_mg = np.random.uniform(5, 95, n)\nnoise_scale = 1.0 + 0.035 * dose_mg\nresponse = 22 + 1.65 * dose_mg + 0.012 * dose_mg**2 + np.random.normal(0, 5, n) * noise_scale\n\n# A few deliberately influential observations (high leverage / high residual)\nresponse[3] += 55\nresponse[15] += 48\ndose_mg[7] = 98\n\ndesign_matrix = sm.add_constant(dose_mg)\nmodel = sm.OLS(response, design_matrix).fit()\ninfluence = model.get_influence()\n\nfitted = model.fittedvalues\nresid = model.resid\nstd_resid = influence.resid_studentized_internal\nleverage = influence.hat_matrix_diag\ncooks_d = influence.cooks_distance[0]\nn_params = design_matrix.shape[1]\n\ntop_influential = np.argsort(cooks_d)[-3:][::-1]\n\n# Plot — 2x2 diagnostic grid, square canvas since the layout is grid-based/symmetric\nfig, axes = plt.subplots(2, 2, figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\nfor panel in axes.flat:\n    panel.set_facecolor(PAGE_BG)\n\npoint_kw = {\"s\": 65, \"color\": BRAND, \"alpha\": 0.6, \"edgecolors\": PAGE_BG, \"linewidth\": 0.4}\nhighlight_kw = {\"s\": 130, \"facecolors\": \"none\", \"edgecolors\": INK, \"linewidth\": 1.3, \"zorder\": 5}\nlabel_kw = {\"fontsize\": 7, \"color\": INK_SOFT, \"textcoords\": \"offset points\"}\n# Distinct offset per influential point (same point keeps the same offset in every\n# subplot) so the three labels fan out instead of clustering together.\nlabel_offsets = [(6, 6), (7, -12), (-15, 5)]\n\n# Subplot 1: Residuals vs Fitted\nax = axes[0, 0]\nax.scatter(fitted, resid, **point_kw)\nax.scatter(fitted[top_influential], resid[top_influential], **highlight_kw)\nax.axhline(0, color=INK_SOFT, linewidth=1.0, linestyle=\"--\")\nlowess_rf = sm.nonparametric.lowess(resid, fitted, frac=0.6)\nax.plot(lowess_rf[:, 0], lowess_rf[:, 1], color=LOWESS_COLOR, linewidth=2.0)\nfor i, idx in enumerate(top_influential):\n    ax.annotate(str(idx), (fitted[idx], resid[idx]), xytext=label_offsets[i], **label_kw)\nax.set_title(\"Residuals vs Fitted\", fontsize=11, color=INK)\nax.set_xlabel(\"Fitted values\", fontsize=9, color=INK)\nax.set_ylabel(\"Residuals\", fontsize=9, color=INK)\n\n# Subplot 2: Normal Q-Q\nax = axes[0, 1]\nosm, osr = stats.probplot(std_resid, dist=\"norm\", fit=False)\nax.scatter(osm, osr, **point_kw)\nqq_bound = (min(osm.min(), osr.min()), max(osm.max(), osr.max()))\nax.plot(qq_bound, qq_bound, color=INK_SOFT, linewidth=1.0, linestyle=\"--\")\nsorted_idx = np.argsort(std_resid)\nrank_of = {obs: rank for rank, obs in enumerate(sorted_idx)}\nqq_influential_x = [osm[rank_of[idx]] for idx in top_influential]\nqq_influential_y = [osr[rank_of[idx]] for idx in top_influential]\nax.scatter(qq_influential_x, qq_influential_y, **highlight_kw)\nfor i, idx in enumerate(top_influential):\n    ax.annotate(str(idx), (osm[rank_of[idx]], osr[rank_of[idx]]), xytext=label_offsets[i], **label_kw)\nax.set_title(\"Normal Q-Q\", fontsize=11, color=INK)\nax.set_xlabel(\"Theoretical quantiles\", fontsize=9, color=INK)\nax.set_ylabel(\"Standardized residuals\", fontsize=9, color=INK)\n\n# Subplot 3: Scale-Location\nax = axes[1, 0]\nsqrt_std_resid = np.sqrt(np.abs(std_resid))\nax.scatter(fitted, sqrt_std_resid, **point_kw)\nax.scatter(fitted[top_influential], sqrt_std_resid[top_influential], **highlight_kw)\nlowess_sl = sm.nonparametric.lowess(sqrt_std_resid, fitted, frac=0.6)\nax.plot(lowess_sl[:, 0], lowess_sl[:, 1], color=LOWESS_COLOR, linewidth=2.0)\nfor i, idx in enumerate(top_influential):\n    ax.annotate(str(idx), (fitted[idx], sqrt_std_resid[idx]), xytext=label_offsets[i], **label_kw)\nax.set_title(\"Scale-Location\", fontsize=11, color=INK)\nax.set_xlabel(\"Fitted values\", fontsize=9, color=INK)\nax.set_ylabel(\"√|Standardized residuals|\", fontsize=9, color=INK)\n\n# Subplot 4: Residuals vs Leverage with Cook's distance contours\nax = axes[1, 1]\nax.scatter(leverage, std_resid, **point_kw)\nax.scatter(leverage[top_influential], std_resid[top_influential], **highlight_kw)\nax.axhline(0, color=INK_SOFT, linewidth=0.8, linestyle=\":\")\nh_grid = np.linspace(max(leverage.min(), 1e-3), leverage.max() * 1.05, 200)\nfor cooks_level, style in ((0.5, \"--\"), (1.0, \"-\")):\n    bound = np.sqrt(cooks_level * n_params * (1 - h_grid) / h_grid)\n    ax.plot(h_grid, bound, color=AMBER, linewidth=1.2, linestyle=style)\n    ax.plot(h_grid, -bound, color=AMBER, linewidth=1.2, linestyle=style)\n    ax.text(h_grid[-1], bound[-1], f\"D={cooks_level:g}\", fontsize=7, color=AMBER, va=\"bottom\")\nfor i, idx in enumerate(top_influential):\n    ax.annotate(str(idx), (leverage[idx], std_resid[idx]), xytext=label_offsets[i], **label_kw)\nax.set_title(\"Residuals vs Leverage\", fontsize=11, color=INK)\nax.set_xlabel(\"Leverage\", fontsize=9, color=INK)\nax.set_ylabel(\"Standardized residuals\", fontsize=9, color=INK)\n\n# Shared chrome across all four subplots\nfor panel in axes.flat:\n    panel.tick_params(axis=\"both\", labelsize=8, colors=INK_MUTED, labelcolor=INK_MUTED)\n    panel.spines[\"top\"].set_visible(False)\n    panel.spines[\"right\"].set_visible(False)\n    for side in (\"left\", \"bottom\"):\n        panel.spines[side].set_color(INK_SOFT)\n    panel.yaxis.grid(True, alpha=0.15, linewidth=0.7, color=INK)\n\ntitle = \"diagnostic-regression-panel · python · matplotlib · anyplot.ai\"\ntitle_fontsize = 12 if len(title) <= 67 else max(8, round(12 * 67 / len(title)))\nfig.suptitle(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, y=0.995)\n\n# Save\nfig.tight_layout(rect=(0, 0, 1, 0.97))\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)  # bbox_inches MUST stay default\n"}