{"spec_id":"shap-summary","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nshap-summary: SHAP Summary Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 95/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\n\n\n# Workaround for module name collision: remove current dir from path temporarily\nimport_dir = os.path.dirname(os.path.abspath(__file__))\noriginal_path = sys.path.copy()\nsys.path = [p for p in sys.path if p != import_dir and not p.endswith(\"python\")]\nimport altair as alt\n\nsys.path = original_path\n\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\"\n\n# Generate synthetic SHAP values for a model explanation visualization\nnp.random.seed(42)\nn_samples = 300\nn_features = 10\n\n# Feature names representing typical ML model inputs\nfeature_names = [\n    \"Account Age (months)\",\n    \"Transaction Count\",\n    \"Avg Transaction ($)\",\n    \"Credit Score\",\n    \"Income ($K)\",\n    \"Debt Ratio\",\n    \"Payment History\",\n    \"Account Balance ($)\",\n    \"Login Frequency\",\n    \"Support Tickets\",\n]\n\n# Create synthetic feature values (normalized to 0-1 for color mapping)\nfeature_values = np.random.rand(n_samples, n_features)\n\n# Create synthetic SHAP values with varying importances per feature\nfeature_importances = np.array([0.25, 0.20, 0.15, 0.12, 0.10, 0.07, 0.05, 0.03, 0.02, 0.01])\nshap_values = np.zeros((n_samples, n_features))\n\nfor i in range(n_features):\n    base_effect = (feature_values[:, i] - 0.5) * feature_importances[i] * 4\n    noise = np.random.randn(n_samples) * feature_importances[i] * 0.5\n    shap_values[:, i] = base_effect + noise\n\n# Sort features by importance\nmean_abs_shap = np.mean(np.abs(shap_values), axis=0)\nfeature_order = np.argsort(mean_abs_shap)[::-1]\nfeature_order_names = [feature_names[i] for i in feature_order]\n\n# Build dataframe for Altair\nrows = []\nfor feat_idx in feature_order:\n    for sample_idx in range(n_samples):\n        rows.append(\n            {\n                \"Feature\": feature_names[feat_idx],\n                \"SHAP Value\": shap_values[sample_idx, feat_idx],\n                \"Feature Value\": feature_values[sample_idx, feat_idx],\n            }\n        )\n\ndf = pd.DataFrame(rows)\n\n# Calculate feature importance for each row\nimportance_map = dict(zip(feature_order_names, range(len(feature_order_names), 0, -1), strict=True))\ndf[\"importance_score\"] = df[\"Feature\"].map(importance_map)\ndf[\"abs_shap\"] = df[\"SHAP Value\"].abs()\n\n# Create the SHAP summary plot with interactive layers\n# Layer 1: Background scatter (lower importance, subtle)\nbackground_scatter = (\n    alt.Chart(df)\n    .mark_circle(stroke=INK_SOFT, strokeWidth=0.5)\n    .encode(\n        x=alt.X(\n            \"SHAP Value:Q\",\n            title=\"SHAP Value (Impact on Model Output)\",\n            axis=alt.Axis(titleFontSize=22, labelFontSize=18, gridOpacity=0.05),\n        ),\n        y=alt.Y(\"Feature:N\", title=None, sort=feature_order_names, axis=alt.Axis(labelFontSize=18, ticks=False)),\n        color=alt.Color(\n            \"Feature Value:Q\",\n            scale=alt.Scale(scheme=\"brownbluegreen\", domain=[0, 1]),\n            legend=alt.Legend(\n                title=\"Feature Value\", titleFontSize=18, labelFontSize=16, orient=\"right\", gradientLength=250\n            ),\n        ),\n        opacity=alt.Opacity(\n            \"importance_score:Q\", scale=alt.Scale(domain=[1, len(feature_order_names)], range=[0.3, 0.7])\n        ),\n        size=alt.Size(\"abs_shap:Q\", scale=alt.Scale(domain=[0, df[\"abs_shap\"].max()], range=[30, 150])),\n        yOffset=alt.YOffset(\"jitter:Q\", scale=alt.Scale(domain=[-1, 1], range=[-18, 18])),\n        tooltip=[\"Feature\", \"SHAP Value:Q\", \"Feature Value:Q\"],\n    )\n    .transform_calculate(jitter=\"random() * 2 - 1\")\n)\n\nscatter = background_scatter.interactive()\n\n# Add vertical line at x=0\nzero_line = (\n    alt.Chart(pd.DataFrame({\"x\": [0]})).mark_rule(color=INK_SOFT, strokeWidth=2, strokeDash=[5, 3]).encode(x=\"x:Q\")\n)\n\n# Combine scatter and zero line\nchart = (\n    (zero_line + scatter)\n    .properties(\n        width=1600,\n        height=900,\n        background=PAGE_BG,\n        title=alt.Title(\"shap-summary · altair · anyplot.ai\", fontSize=28, anchor=\"middle\", color=INK),\n    )\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=18,\n        titleFontSize=22,\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_legend(\n        fillColor=\"#FFFDF6\" if THEME == \"light\" else \"#242420\",\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        titleFontSize=18,\n        labelFontSize=16,\n    )\n)\n\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}