{"spec_id":"shap-waterfall","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nshap-waterfall: SHAP Waterfall Plot for Feature Attribution\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 80/100 | Created: 2026-05-07\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file (pygal.py) from shadowing the installed pygal package\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or os.getcwd()) != _here]\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data – customer churn prediction SHAP explanation\nnp.random.seed(42)\n\nfeature_names = [\n    \"Monthly Charges\",\n    \"Contract Type\",\n    \"Tenure\",\n    \"Online Security\",\n    \"Internet Service\",\n    \"Tech Support\",\n    \"Num. of Services\",\n    \"Payment Method\",\n    \"Device Protection\",\n    \"Streaming TV\",\n]\nshap_raw = [0.20, 0.15, -0.12, -0.10, 0.08, -0.06, 0.05, 0.04, -0.03, 0.02]\nbase_value = 0.25\nfinal_value = round(base_value + sum(shap_raw), 4)  # 0.48\n\n# Sort ascending by abs magnitude (pygal draws first label at bottom, last at top)\nidx = sorted(range(len(shap_raw)), key=lambda i: abs(shap_raw[i]), reverse=False)\nfeatures = [feature_names[i] for i in idx]\nshap_vals = [shap_raw[i] for i in idx]\n\n# Compute cumulative starting positions from base_value\ncum_starts = []\nrunning = base_value\nfor v in shap_vals:\n    cum_starts.append(running)\n    running += v\n\n# Waterfall stacking: invisible spacer + visible SHAP bar per feature\n# Positive: spacer extends 0→cum_start, colored bar extends cum_start→cum_end\n# Negative: spacer extends 0→cum_end (left edge), colored bar covers the negative delta\nspacer_data = []\nshap_data = []\n\nfor i, v in enumerate(shap_vals):\n    if v >= 0:\n        spacer = cum_starts[i]\n        visible = v\n        color = \"#AE3030\"  # imprint red — positive SHAP\n    else:\n        spacer = cum_starts[i] + v  # left edge = cum_end\n        visible = abs(v)\n        color = \"#4467A3\"  # Okabe-Ito blue for negative SHAP\n    spacer_data.append({\"value\": spacer, \"color\": PAGE_BG})\n    shap_data.append({\"value\": visible, \"color\": color, \"label\": f\"{v:+.3f}\"})\n\n# Feature labels with embedded SHAP values\nlabels_with_vals = [f\"{feat}  ({v:+.2f})\" for feat, v in zip(features, shap_vals, strict=True)]\n\n# Style\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(PAGE_BG, \"#AE3030\"),\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    value_font_size=16,\n    stroke_width=0,\n)\n\n# HorizontalStackedBar enables waterfall stacking via spacer + visible series\nchart = pygal.HorizontalStackedBar(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=(\n        \"Customer Churn Prediction  ·  shap-waterfall  ·  pygal  ·  anyplot.ai\\n\"\n        f\"Base value: {base_value:.2f}  →  Predicted probability: {final_value:.2f}\"\n    ),\n    x_title=(\n        f\"SHAP Value (impact on predicted churn probability)\"\n        f\"  ·  base = {base_value:.2f}  |  prediction = {final_value:.2f}\"\n    ),\n    show_legend=False,\n    show_y_guides=False,\n    show_x_guides=True,\n)\n\n# Reference lines at base value and final prediction\nchart.x_guides = [base_value, final_value]\nchart.x_labels = labels_with_vals\n\n# Spacer series (background-colored, visually invisible)\nchart.add(\"\", spacer_data)\n# Visible SHAP contribution bars (per-bar color encodes polarity)\nchart.add(\"SHAP Contribution\", shap_data)\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}