{"spec_id":"diagnostic-regression-panel","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ndiagnostic-regression-panel: Regression Diagnostic Panel (Four-Plot Display)\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 87/100 | Created: 2026-05-13\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path.pop(0)\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\nimport statsmodels.api as sm\nfrom scipy import stats\n\n\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\"\nBRAND = \"#009E73\"\nACCENT = \"#C475FD\"\nINFLUENCE = \"#BD8233\"\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.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\nnp.random.seed(42)\nn = 150\nX = np.column_stack([np.random.normal(0, 1, n), np.random.normal(0, 1, n), np.random.uniform(-2, 2, n)])\ny = X @ np.array([2.5, -1.8, 0.9]) + 5 + np.random.normal(0, 1.5, n)\ny[42] += 12\ny[99] -= 10\nX[130, 0] = 3.5\n\nX_sm = sm.add_constant(X)\nmodel = sm.OLS(y, X_sm).fit()\ninfl = model.get_influence()\n\nfitted = np.asarray(model.fittedvalues)\nresiduals = np.asarray(model.resid)\nstd_res = infl.resid_studentized_internal\nleverage = infl.hat_matrix_diag\ncooks_d = infl.cooks_distance[0]\n\np = X_sm.shape[1]\ntop3 = list(np.argsort(cooks_d)[-3:])\nsorted_idx = np.argsort(std_res)\n(theoretical_q, sample_q), (qq_slope, qq_intercept, _) = stats.probplot(std_res)\n\nfig, axes = plt.subplots(2, 2, figsize=(16, 9), facecolor=PAGE_BG)\nfig.patch.set_facecolor(PAGE_BG)\n\nregplot_sc_kws = {\"color\": BRAND, \"alpha\": 0.65, \"s\": 70, \"edgecolors\": PAGE_BG, \"linewidths\": 0.4, \"zorder\": 2}\nline_kws = {\"color\": ACCENT, \"linewidth\": 2.5, \"zorder\": 3}\nannot_kw = {\"fontsize\": 14, \"color\": INK_MUTED, \"xytext\": (14, 10), \"textcoords\": \"offset points\"}\n\n# Subplot 1: Residuals vs Fitted — sns.regplot with LOWESS smoother\nax1 = axes[0, 0]\nsns.regplot(x=fitted, y=residuals, ax=ax1, lowess=True, ci=None, scatter_kws=regplot_sc_kws, line_kws=line_kws)\nax1.axhline(0, color=INK_SOFT, linewidth=1.2, linestyle=\"--\", alpha=0.7)\nfor i in top3:\n    ax1.annotate(str(i), (fitted[i], residuals[i]), **annot_kw)\nax1.set_xlabel(\"Fitted Values\", fontsize=20, color=INK)\nax1.set_ylabel(\"Residuals\", fontsize=20, color=INK)\nax1.set_title(\"Residuals vs Fitted\", fontsize=20, color=INK, fontweight=\"medium\")\nax1.tick_params(labelsize=16, colors=INK_SOFT)\nsns.despine(ax=ax1)\nax1.spines[\"left\"].set_color(INK_SOFT)\nax1.spines[\"bottom\"].set_color(INK_SOFT)\nax1.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\n\n# Subplot 2: Normal Q-Q — sns.scatterplot with Q-Q reference line\nax2 = axes[0, 1]\nsns.scatterplot(\n    x=theoretical_q, y=sample_q, ax=ax2, color=BRAND, alpha=0.65, s=70, edgecolor=PAGE_BG, linewidth=0.4, zorder=2\n)\nxr = np.array([theoretical_q[0], theoretical_q[-1]])\nax2.plot(xr, qq_slope * xr + qq_intercept, color=ACCENT, linewidth=2.5, zorder=3)\nfor k, orig in enumerate(sorted_idx):\n    if orig in top3:\n        ax2.annotate(str(orig), (theoretical_q[k], sample_q[k]), **annot_kw)\nax2.set_xlabel(\"Theoretical Quantiles\", fontsize=20, color=INK)\nax2.set_ylabel(\"Standardized Residuals\", fontsize=20, color=INK)\nax2.set_title(\"Normal Q-Q\", fontsize=20, color=INK, fontweight=\"medium\")\nax2.tick_params(labelsize=16, colors=INK_SOFT)\nsns.despine(ax=ax2)\nax2.spines[\"left\"].set_color(INK_SOFT)\nax2.spines[\"bottom\"].set_color(INK_SOFT)\nax2.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\n\n# Subplot 3: Scale-Location — sns.regplot with LOWESS smoother\nax3 = axes[1, 0]\nsqrt_abs_res = np.sqrt(np.abs(std_res))\nsns.regplot(x=fitted, y=sqrt_abs_res, ax=ax3, lowess=True, ci=None, scatter_kws=regplot_sc_kws, line_kws=line_kws)\nfor i in top3:\n    ax3.annotate(str(i), (fitted[i], sqrt_abs_res[i]), **annot_kw)\nax3.set_xlabel(\"Fitted Values\", fontsize=20, color=INK)\nax3.set_ylabel(\"√|Standardized Residuals|\", fontsize=20, color=INK)\nax3.set_title(\"Scale-Location\", fontsize=20, color=INK, fontweight=\"medium\")\nax3.tick_params(labelsize=16, colors=INK_SOFT)\nsns.despine(ax=ax3)\nax3.spines[\"left\"].set_color(INK_SOFT)\nax3.spines[\"bottom\"].set_color(INK_SOFT)\nax3.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\n\n# Subplot 4: Residuals vs Leverage — sns.scatterplot with Cook's D contours\nax4 = axes[1, 1]\nsns.scatterplot(\n    x=leverage, y=std_res, ax=ax4, color=BRAND, alpha=0.65, s=70, edgecolor=PAGE_BG, linewidth=0.4, zorder=2\n)\nax4.axhline(0, color=INK_SOFT, linewidth=1.2, linestyle=\"--\", alpha=0.7)\ny_bound = max(np.abs(std_res).max() * 1.1, 4.0)\nlev_rng = np.linspace(max(leverage.min() * 0.5, 1e-4), min(leverage.max() * 1.3, 0.99), 400)\nfor cd_level, ls in [(0.5, \"--\"), (1.0, \"-\")]:\n    cd_vals = np.sqrt(cd_level * p * (1 - lev_rng) / lev_rng)\n    valid = cd_vals <= y_bound\n    if valid.any():\n        ax4.plot(\n            lev_rng[valid],\n            cd_vals[valid],\n            color=INFLUENCE,\n            linewidth=1.8,\n            linestyle=ls,\n            alpha=0.8,\n            label=f\"Cook's D={cd_level}\",\n        )\n        ax4.plot(lev_rng[valid], -cd_vals[valid], color=INFLUENCE, linewidth=1.8, linestyle=ls, alpha=0.8)\nfor i in top3:\n    ax4.annotate(str(i), (leverage[i], std_res[i]), **annot_kw)\nax4.set_ylim(-y_bound, y_bound)\nax4.set_xlabel(\"Leverage\", fontsize=20, color=INK)\nax4.set_ylabel(\"Standardized Residuals\", fontsize=20, color=INK)\nax4.set_title(\"Residuals vs Leverage\", fontsize=20, color=INK, fontweight=\"medium\")\nax4.tick_params(labelsize=16, colors=INK_SOFT)\nsns.despine(ax=ax4)\nax4.spines[\"left\"].set_color(INK_SOFT)\nax4.spines[\"bottom\"].set_color(INK_SOFT)\nax4.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\nax4.legend(fontsize=14, loc=\"upper right\", framealpha=0.9)\n\nfig.suptitle(\"diagnostic-regression-panel · seaborn · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}