{"spec_id":"ks-test-comparison","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nks-test-comparison: Kolmogorov-Smirnov Plot for Distribution Comparison\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom scipy import stats\n\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\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 — semantic assignment: green = good, red = bad\nGOOD_COLOR = \"#009E73\"  # Imprint position 1 — brand green (good / pass)\nBAD_COLOR = \"#AE3030\"  # Imprint position 5 — matte red (bad / fail)\n\n# Data\nnp.random.seed(42)\ngood_customers = np.random.beta(5, 2, 500) * 100\nbad_customers = np.random.beta(2, 4, 500) * 100\n\n# K-S test\nks_stat, p_value = stats.ks_2samp(good_customers, bad_customers)\n\n# Compute ECDFs\ngood_sorted = np.sort(good_customers)\nbad_sorted = np.sort(bad_customers)\ngood_ecdf = np.arange(1, len(good_sorted) + 1) / len(good_sorted)\nbad_ecdf = np.arange(1, len(bad_sorted) + 1) / len(bad_sorted)\n\n# Find maximum distance point\nall_values = np.sort(np.concatenate([good_sorted, bad_sorted]))\ngood_cdf_at_all = np.searchsorted(good_sorted, all_values, side=\"right\") / len(good_sorted)\nbad_cdf_at_all = np.searchsorted(bad_sorted, all_values, side=\"right\") / len(bad_sorted)\ndifferences = np.abs(good_cdf_at_all - bad_cdf_at_all)\nmax_idx = np.argmax(differences)\nmax_x = all_values[max_idx]\nmax_y_good = good_cdf_at_all[max_idx]\nmax_y_bad = bad_cdf_at_all[max_idx]\nmid_y = (max_y_good + max_y_bad) / 2\n\n# Plot — landscape 3200×1800 px (figsize=(8, 4.5), dpi=400)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# \"Bad Customers\" — matte red with white outline for visual separation from gridlines\nax.step(bad_sorted, bad_ecdf, where=\"post\", linewidth=3.5, color=PAGE_BG, zorder=3)\nax.step(\n    bad_sorted, bad_ecdf, where=\"post\", linewidth=2.2, color=BAD_COLOR, label=\"Bad Customers\", linestyle=\"--\", zorder=4\n)\n\n# \"Good Customers\" — brand green, solid line\nax.step(\n    good_sorted,\n    good_ecdf,\n    where=\"post\",\n    linewidth=2.2,\n    color=GOOD_COLOR,\n    label=\"Good Customers\",\n    linestyle=\"-\",\n    zorder=3,\n)\n\n# Vertical reference line at maximum distance point — stronger visual anchor\nax.axvline(max_x, color=INK_SOFT, linestyle=\"--\", linewidth=0.9, alpha=0.45, zorder=2)\n\n# Shaded region at maximum distance — wider band and higher alpha for visibility\nax.fill_betweenx([max_y_bad, max_y_good], max_x - 2.5, max_x + 2.5, color=GOOD_COLOR, alpha=0.20, zorder=1)\n\n# K-S distance line with double-ended annotation arrows\nax.annotate(\n    \"\",\n    xy=(max_x, max_y_good),\n    xytext=(max_x, max_y_bad),\n    arrowprops={\"arrowstyle\": \"<->\", \"color\": INK, \"linewidth\": 1.8, \"linestyle\": \":\", \"shrinkA\": 0, \"shrinkB\": 0},\n    zorder=5,\n)\n\n# Endpoint markers at the KS distance extremes\nax.scatter([max_x, max_x], [max_y_bad, max_y_good], color=INK, s=80, zorder=6, edgecolors=PAGE_BG, linewidth=1.2)\n\n# K-S statistic annotation box\nax.annotate(\n    f\"D = {ks_stat:.3f}\",\n    xy=(max_x, mid_y),\n    xytext=(max_x + 9, mid_y + 0.07),\n    fontsize=9,\n    fontweight=\"bold\",\n    color=INK,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_MUTED, \"linewidth\": 1.2, \"connectionstyle\": \"arc3,rad=-0.15\"},\n    va=\"center\",\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.92},\n)\n\n# Interpretation box — lower right\np_label = \"p-value < 0.001\" if p_value < 0.001 else f\"p-value = {p_value:.4f}\"\nax.text(\n    0.97,\n    0.10,\n    f\"Strong separation: distributions are\\nsignificantly different\\n{p_label}\",\n    transform=ax.transAxes,\n    fontsize=8,\n    ha=\"right\",\n    va=\"bottom\",\n    color=INK_SOFT,\n    linespacing=1.45,\n    bbox={\"boxstyle\": \"round,pad=0.35\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.88},\n)\n\n# Title and axis labels\ntitle = \"ks-test-comparison · python · matplotlib · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, pad=10)\nax.set_xlabel(\"Credit Score (0–100)\", fontsize=10, color=INK)\nax.set_ylabel(\"Cumulative Proportion\", fontsize=10, color=INK)\n\n# Tick styling\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_ylim(-0.02, 1.06)\nax.set_xlim(-2, 102)\n\n# Legend\nleg = ax.legend(fontsize=8, loc=\"upper left\", framealpha=0.92)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\n# Spines and grid\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n    ax.spines[s].set_linewidth(0.6)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)\n\n# Save — do NOT add bbox_inches='tight' (causes canvas drift)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}