{"spec_id":"diagnostic-regression-panel","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\ndiagnostic-regression-panel: Regression Diagnostic Panel (Four-Plot Display)\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 83/100 | Created: 2026-05-13\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Remove the script's own directory from sys.path so \"bokeh\" resolves to the\n# installed package, not this file.\n_this_dir = str(Path(__file__).parent.resolve())\nsys.path = [p for p in sys.path if os.path.abspath(p) != _this_dir and p != \"\"]\n\nimport numpy as np\nfrom bokeh.layouts import column, gridplot\nfrom bokeh.models import Div, Label, Range1d, Span\nfrom bokeh.plotting import figure, output_file, save\nfrom scipy.stats import probplot\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\nfrom sklearn.linear_model import LinearRegression\nfrom statsmodels.nonparametric.smoothers_lowess import lowess\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\"\n\nBRAND = \"#009E73\"  # Okabe-Ito pos 1 — scatter points\nACCENT = \"#C475FD\"  # Okabe-Ito pos 2 — LOWESS / Q-Q reference line\nBLUE = \"#4467A3\"  # Okabe-Ito pos 3 — Cook's distance contours\n\n# Data: Drug-response study — standardised predictors for controlled leverage\nnp.random.seed(42)\nn = 200\n# Standardised predictors (zero mean, unit variance in the bulk)\nlog_dose = np.random.normal(0, 1, n)  # log-dose (standardised)\nbody_mass = np.random.normal(0, 1, n)  # body-mass index (standardised)\nage_std = np.random.normal(0, 1, n)  # patient age (standardised)\n\n# Two points at 2.5–3 SD from centre in predictor space → leverage ≈ 0.12–0.17\nlog_dose[0] = 3.0\nbody_mass[0] = 2.5\nage_std[0] = -2.0\nlog_dose[1] = -2.8\nbody_mass[1] = -2.2\nage_std[1] = 2.5\n\nX = np.column_stack([log_dose, body_mass, age_std])\nn_params = X.shape[1] + 1\nX_design = np.column_stack([np.ones(n), X])\n\nnoise_sd = 4.0\nresponse = 50 + 8 * log_dose - 5 * body_mass + 3 * age_std + np.random.normal(0, noise_sd, n)\nresponse[0] += 14.0  # 3.5-sigma residual at high leverage → Cook's D > 0.5\nresponse[1] -= 11.0  # 2.75-sigma residual at moderate leverage\nresponse[70] += 10.0  # residual outlier at low leverage\n\n# Fit linear regression\nmodel = LinearRegression().fit(X, response)\nfitted = model.predict(X)\nresiduals = response - fitted\n\n# Leverage (hat matrix diagonal)\nH_mat = X_design @ np.linalg.inv(X_design.T @ X_design) @ X_design.T\nleverage = np.diag(H_mat)\n\n# Standardised residuals\nmse = np.sum(residuals**2) / (n - n_params)\nstd_residuals = residuals / np.sqrt(mse * (1 - leverage))\n\n# Cook's distance\ncooks_d = (std_residuals**2 * leverage) / (n_params * (1 - leverage))\n\n# LOWESS smoothers for panels 1 and 3\nsqrt_abs_std = np.sqrt(np.abs(std_residuals))\nlowess_1 = lowess(residuals, fitted, frac=0.5, return_sorted=True)\nlowess_3 = lowess(sqrt_abs_std, fitted, frac=0.5, return_sorted=True)\n\n# Q-Q plot data\n(th_q, samp_q), (qq_slope, qq_intercept, _) = probplot(std_residuals, dist=\"norm\")\n\n# Top 3 most influential observations by Cook's distance\ntop3 = np.argsort(cooks_d)[-3:][::-1]\n\n# Map observation index → position in sorted std_residuals (for Q-Q labels)\nrank_map = {int(obs): rank for rank, obs in enumerate(np.argsort(std_residuals))}\n\nPW, PH = 2400, 1310  # per-panel pixel dimensions\n\n# ── Panel 1: Residuals vs Fitted ──────────────────────────────────────────────\np1 = figure(\n    width=PW,\n    height=PH,\n    title=\"Residuals vs Fitted\",\n    x_axis_label=\"Fitted values\",\n    y_axis_label=\"Residuals\",\n    background_fill_color=PAGE_BG,\n    border_fill_color=PAGE_BG,\n    outline_line_color=INK_SOFT,\n    toolbar_location=None,\n)\np1.title.text_font_size = \"22pt\"\np1.title.text_color = INK\np1.title.text_font_style = \"bold\"\np1.xaxis.axis_label_text_font_size = \"18pt\"\np1.yaxis.axis_label_text_font_size = \"18pt\"\np1.xaxis.major_label_text_font_size = \"15pt\"\np1.yaxis.major_label_text_font_size = \"15pt\"\np1.xaxis.axis_label_text_color = INK\np1.yaxis.axis_label_text_color = INK\np1.xaxis.major_label_text_color = INK_SOFT\np1.yaxis.major_label_text_color = INK_SOFT\np1.xaxis.axis_line_color = INK_SOFT\np1.yaxis.axis_line_color = INK_SOFT\np1.xaxis.major_tick_line_color = INK_SOFT\np1.yaxis.major_tick_line_color = INK_SOFT\np1.xgrid.grid_line_color = INK\np1.ygrid.grid_line_color = INK\np1.xgrid.grid_line_alpha = 0.10\np1.ygrid.grid_line_alpha = 0.10\n\np1.scatter(fitted, residuals, color=BRAND, size=9, alpha=0.65, line_color=PAGE_BG, line_width=0.5)\np1.add_layout(Span(location=0, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=2))\np1.line(lowess_1[:, 0], lowess_1[:, 1], color=ACCENT, line_width=3)\nfor idx in top3:\n    p1.add_layout(\n        Label(\n            x=float(fitted[idx]),\n            y=float(residuals[idx]),\n            text=str(int(idx)),\n            text_font_size=\"14pt\",\n            text_color=INK_SOFT,\n            x_offset=6,\n            y_offset=6,\n        )\n    )\n\n# ── Panel 2: Normal Q-Q ───────────────────────────────────────────────────────\np2 = figure(\n    width=PW,\n    height=PH,\n    title=\"Normal Q-Q\",\n    x_axis_label=\"Theoretical quantiles\",\n    y_axis_label=\"Standardized residuals\",\n    background_fill_color=PAGE_BG,\n    border_fill_color=PAGE_BG,\n    outline_line_color=INK_SOFT,\n    toolbar_location=None,\n)\np2.title.text_font_size = \"22pt\"\np2.title.text_color = INK\np2.title.text_font_style = \"bold\"\np2.xaxis.axis_label_text_font_size = \"18pt\"\np2.yaxis.axis_label_text_font_size = \"18pt\"\np2.xaxis.major_label_text_font_size = \"15pt\"\np2.yaxis.major_label_text_font_size = \"15pt\"\np2.xaxis.axis_label_text_color = INK\np2.yaxis.axis_label_text_color = INK\np2.xaxis.major_label_text_color = INK_SOFT\np2.yaxis.major_label_text_color = INK_SOFT\np2.xaxis.axis_line_color = INK_SOFT\np2.yaxis.axis_line_color = INK_SOFT\np2.xaxis.major_tick_line_color = INK_SOFT\np2.yaxis.major_tick_line_color = INK_SOFT\np2.xgrid.grid_line_color = INK\np2.ygrid.grid_line_color = INK\np2.xgrid.grid_line_alpha = 0.10\np2.ygrid.grid_line_alpha = 0.10\n\np2.scatter(th_q, samp_q, color=BRAND, size=9, alpha=0.65, line_color=PAGE_BG, line_width=0.5)\nqq_x_line = np.array([th_q.min(), th_q.max()])\np2.line(qq_x_line, qq_slope * qq_x_line + qq_intercept, color=ACCENT, line_width=2, line_dash=\"dashed\")\nfor idx in top3:\n    rank = rank_map[int(idx)]\n    p2.add_layout(\n        Label(\n            x=float(th_q[rank]),\n            y=float(samp_q[rank]),\n            text=str(int(idx)),\n            text_font_size=\"14pt\",\n            text_color=INK_SOFT,\n            x_offset=6,\n            y_offset=6,\n        )\n    )\n\n# ── Panel 3: Scale-Location ───────────────────────────────────────────────────\np3 = figure(\n    width=PW,\n    height=PH,\n    title=\"Scale-Location\",\n    x_axis_label=\"Fitted values\",\n    y_axis_label=\"√|Standardized residuals|\",\n    background_fill_color=PAGE_BG,\n    border_fill_color=PAGE_BG,\n    outline_line_color=INK_SOFT,\n    toolbar_location=None,\n)\np3.title.text_font_size = \"22pt\"\np3.title.text_color = INK\np3.title.text_font_style = \"bold\"\np3.xaxis.axis_label_text_font_size = \"18pt\"\np3.yaxis.axis_label_text_font_size = \"18pt\"\np3.xaxis.major_label_text_font_size = \"15pt\"\np3.yaxis.major_label_text_font_size = \"15pt\"\np3.xaxis.axis_label_text_color = INK\np3.yaxis.axis_label_text_color = INK\np3.xaxis.major_label_text_color = INK_SOFT\np3.yaxis.major_label_text_color = INK_SOFT\np3.xaxis.axis_line_color = INK_SOFT\np3.yaxis.axis_line_color = INK_SOFT\np3.xaxis.major_tick_line_color = INK_SOFT\np3.yaxis.major_tick_line_color = INK_SOFT\np3.xgrid.grid_line_color = INK\np3.ygrid.grid_line_color = INK\np3.xgrid.grid_line_alpha = 0.10\np3.ygrid.grid_line_alpha = 0.10\n\np3.scatter(fitted, sqrt_abs_std, color=BRAND, size=9, alpha=0.65, line_color=PAGE_BG, line_width=0.5)\np3.line(lowess_3[:, 0], lowess_3[:, 1], color=ACCENT, line_width=3)\nfor idx in top3:\n    p3.add_layout(\n        Label(\n            x=float(fitted[idx]),\n            y=float(sqrt_abs_std[idx]),\n            text=str(int(idx)),\n            text_font_size=\"14pt\",\n            text_color=INK_SOFT,\n            x_offset=6,\n            y_offset=6,\n        )\n    )\n\n# ── Panel 4: Residuals vs Leverage ────────────────────────────────────────────\np4 = figure(\n    width=PW,\n    height=PH,\n    title=\"Residuals vs Leverage\",\n    x_axis_label=\"Leverage\",\n    y_axis_label=\"Standardized residuals\",\n    background_fill_color=PAGE_BG,\n    border_fill_color=PAGE_BG,\n    outline_line_color=INK_SOFT,\n    toolbar_location=None,\n)\np4.title.text_font_size = \"22pt\"\np4.title.text_color = INK\np4.title.text_font_style = \"bold\"\np4.xaxis.axis_label_text_font_size = \"18pt\"\np4.yaxis.axis_label_text_font_size = \"18pt\"\np4.xaxis.major_label_text_font_size = \"15pt\"\np4.yaxis.major_label_text_font_size = \"15pt\"\np4.xaxis.axis_label_text_color = INK\np4.yaxis.axis_label_text_color = INK\np4.xaxis.major_label_text_color = INK_SOFT\np4.yaxis.major_label_text_color = INK_SOFT\np4.xaxis.axis_line_color = INK_SOFT\np4.yaxis.axis_line_color = INK_SOFT\np4.xaxis.major_tick_line_color = INK_SOFT\np4.yaxis.major_tick_line_color = INK_SOFT\np4.xgrid.grid_line_color = INK\np4.ygrid.grid_line_color = INK\np4.xgrid.grid_line_alpha = 0.10\np4.ygrid.grid_line_alpha = 0.10\n\n# Constrain y-axis so Cook's D contours are visible even when outliers are extreme\np4_ylim = max(4.0, float(np.percentile(np.abs(std_residuals), 96))) * 1.15\np4.y_range = Range1d(-p4_ylim, p4_ylim)\n\np4.scatter(leverage, std_residuals, color=BRAND, size=9, alpha=0.65, line_color=PAGE_BG, line_width=0.5)\np4.add_layout(Span(location=0, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=2))\n\nlev_range = np.linspace(1e-4, min(float(leverage.max()) * 1.3, 0.99), 400)\nfor cook_level, dash, leg_lbl in [(0.5, \"dashed\", \"Cook's D = 0.5\"), (1.0, \"dotted\", \"Cook's D = 1.0\")]:\n    cook_y = np.sqrt(np.abs(cook_level * n_params * (1 - lev_range) / lev_range))\n    # Clip to visible range to avoid rendering artifacts\n    visible = cook_y <= p4_ylim * 1.05\n    if np.any(visible):\n        p4.line(lev_range[visible], cook_y[visible], color=BLUE, line_width=2, line_dash=dash, legend_label=leg_lbl)\n        p4.line(lev_range[visible], -cook_y[visible], color=BLUE, line_width=2, line_dash=dash)\n\nfor idx in top3:\n    sr = float(std_residuals[idx])\n    # Only label if within the visible y-range\n    if abs(sr) <= p4_ylim:\n        p4.add_layout(\n            Label(\n                x=float(leverage[idx]),\n                y=sr,\n                text=str(int(idx)),\n                text_font_size=\"14pt\",\n                text_color=INK_SOFT,\n                x_offset=6,\n                y_offset=6,\n            )\n        )\n\np4.legend.background_fill_color = ELEVATED_BG\np4.legend.border_line_color = INK_SOFT\np4.legend.label_text_color = INK_SOFT\np4.legend.label_text_font_size = \"14pt\"\n\n# ── Assemble and export ───────────────────────────────────────────────────────\ngrid = gridplot([[p1, p2], [p3, p4]], merge_tools=False)\n\ntitle_div = Div(\n    text=(\n        f'<div style=\"font-family: sans-serif; font-size: 28px; font-weight: 500;'\n        f' color: {INK}; background-color: {PAGE_BG}; padding: 18px 24px 6px 24px; margin: 0;\">'\n        f\"diagnostic-regression-panel · bokeh · anyplot.ai</div>\"\n    ),\n    width=4800,\n    height=90,\n)\n\nlayout = column(title_div, grid)\noutput_file(f\"plot-{THEME}.html\")\nsave(layout)\n\nW_SCR, H_SCR = 4800, 2750\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W_SCR},{H_SCR}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W_SCR, H_SCR)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.execute_script(\n    f\"document.body.style.backgroundColor='{PAGE_BG}';\"\n    f\"document.body.style.margin='0';\"\n    f\"document.documentElement.style.backgroundColor='{PAGE_BG}';\"\n)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}