{"spec_id":"shap-summary","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nshap-summary: SHAP Summary Plot\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 96/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\nZERO_LINE = INK_SOFT\n\n# Data - Generate synthetic SHAP values for ML model interpretability demo\nnp.random.seed(42)\n\n# Simulated feature data (like from a gradient boosting model on tabular data)\nn_samples = 200\nfeature_names = [\n    \"mean radius\",\n    \"mean texture\",\n    \"mean perimeter\",\n    \"mean area\",\n    \"mean smoothness\",\n    \"mean compactness\",\n    \"mean concavity\",\n    \"mean concave points\",\n    \"mean symmetry\",\n    \"mean fractal dimension\",\n    \"radius error\",\n    \"texture error\",\n    \"perimeter error\",\n    \"area error\",\n    \"smoothness error\",\n]\nn_features = len(feature_names)\n\n# Generate realistic feature values (simulating measurement data)\nX = np.zeros((n_samples, n_features))\nX[:, 0] = np.random.normal(14, 3.5, n_samples)  # mean radius\nX[:, 1] = np.random.normal(19, 4, n_samples)  # mean texture\nX[:, 2] = np.random.normal(92, 24, n_samples)  # mean perimeter\nX[:, 3] = np.random.normal(655, 350, n_samples)  # mean area\nX[:, 4] = np.random.normal(0.096, 0.014, n_samples)  # mean smoothness\nX[:, 5] = np.random.normal(0.104, 0.053, n_samples)  # mean compactness\nX[:, 6] = np.random.normal(0.089, 0.08, n_samples)  # mean concavity\nX[:, 7] = np.random.normal(0.049, 0.039, n_samples)  # mean concave points\nX[:, 8] = np.random.normal(0.181, 0.027, n_samples)  # mean symmetry\nX[:, 9] = np.random.normal(0.063, 0.007, n_samples)  # mean fractal dimension\nX[:, 10] = np.random.normal(0.41, 0.28, n_samples)  # radius error\nX[:, 11] = np.random.normal(1.22, 0.55, n_samples)  # texture error\nX[:, 12] = np.random.normal(2.87, 2.02, n_samples)  # perimeter error\nX[:, 13] = np.random.normal(40, 45, n_samples)  # area error\nX[:, 14] = np.random.normal(0.007, 0.003, n_samples)  # smoothness error\n\n# Simulated feature importances with more dramatic variation\nimportances = np.array([0.32, 0.06, 0.14, 0.20, 0.02, 0.04, 0.08, 0.06, 0.01, 0.01, 0.02, 0.01, 0.01, 0.01, 0.01])\n\n# Generate SHAP values that correlate with feature values (simulating real SHAP behavior)\nshap_values = np.zeros((n_samples, n_features))\nfor i in range(n_features):\n    feat_min, feat_max = X[:, i].min(), X[:, i].max()\n    feat_normalized = (X[:, i] - feat_min) / (feat_max - feat_min + 1e-10)\n\n    # SHAP values correlate with feature values, scaled by importance\n    base_effect = (feat_normalized - 0.5) * importances[i] * 2\n    noise = np.random.randn(n_samples) * importances[i] * 0.3\n    shap_values[:, i] = base_effect + noise\n\n# Sort features by mean absolute SHAP value (most important first)\nmean_abs_shap = np.mean(np.abs(shap_values), axis=0)\nsorted_idx = np.argsort(mean_abs_shap)[::-1]\n\n# Show top 15 features for clarity\ntop_n = 15\nsorted_idx = sorted_idx[:top_n]\n\n# Create figure\nfig = go.Figure()\n\n# Store feature data for hover template\nfeature_mins = {}\nfeature_maxs = {}\nfor i in range(n_features):\n    feature_mins[i] = X[:, i].min()\n    feature_maxs[i] = X[:, i].max()\n\n# Add traces for each feature (from bottom to top for proper y-axis ordering)\nfor rank, feat_idx in enumerate(reversed(sorted_idx)):\n    feat_shap = shap_values[:, feat_idx]\n    feat_vals = X[:, feat_idx]\n\n    # Normalize feature values for coloring (0 to 1)\n    feat_min, feat_max = feat_vals.min(), feat_vals.max()\n    feat_normalized = (feat_vals - feat_min) / (feat_max - feat_min + 1e-10)\n\n    # Add jitter to y-position\n    y_base = rank\n    jitter = np.random.uniform(-0.3, 0.3, n_samples)\n    y_positions = y_base + jitter\n\n    # Create color array based on feature values (blue=low, red=high)\n    colors = feat_normalized\n\n    # Create hover text with actual feature values\n    hover_texts = [\n        f\"<b>{feature_names[feat_idx]}</b><br>SHAP Value: {shap:.3f}<br>Feature Value: {val:.3f}<extra></extra>\"\n        for shap, val in zip(feat_shap, feat_vals, strict=False)\n    ]\n\n    fig.add_trace(\n        go.Scatter(\n            x=feat_shap,\n            y=y_positions,\n            mode=\"markers\",\n            marker={\n                \"size\": 8,\n                \"color\": colors,\n                \"colorscale\": \"RdBu_r\",\n                \"cmin\": 0,\n                \"cmax\": 1,\n                \"opacity\": 0.7,\n                \"line\": {\"width\": 0},\n            },\n            text=hover_texts,\n            hoverinfo=\"text\",\n            name=feature_names[feat_idx][:25],\n            showlegend=False,\n        )\n    )\n\n# Add vertical line at x=0\nfig.add_vline(x=0, line_width=2, line_color=ZERO_LINE, line_dash=\"solid\")\n\n# Create y-axis labels (feature names in order from bottom to top)\ny_labels = [feature_names[idx][:25] for idx in reversed(sorted_idx)]\n\n# Add colorbar as a separate trace\ncolorbar_trace = go.Scatter(\n    x=[None],\n    y=[None],\n    mode=\"markers\",\n    marker={\n        \"size\": 0.1,\n        \"color\": [0, 1],\n        \"colorscale\": \"RdBu_r\",\n        \"cmin\": 0,\n        \"cmax\": 1,\n        \"colorbar\": {\n            \"title\": {\"text\": \"Feature Value\", \"font\": {\"size\": 20, \"color\": INK}, \"side\": \"right\"},\n            \"tickfont\": {\"size\": 16, \"color\": INK_SOFT},\n            \"tickvals\": [0, 0.5, 1],\n            \"ticktext\": [\"Low\", \"Medium\", \"High\"],\n            \"len\": 0.5,\n            \"thickness\": 25,\n            \"x\": 1.02,\n            \"y\": 0.5,\n        },\n        \"showscale\": True,\n    },\n    showlegend=False,\n    hoverinfo=\"skip\",\n)\nfig.add_trace(colorbar_trace)\n\n# Update layout\nfig.update_layout(\n    title={\n        \"text\": \"shap-summary · plotly · anyplot.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"SHAP Value (Impact on Model Output)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"zeroline\": True,\n        \"zerolinewidth\": 2,\n        \"zerolinecolor\": ZERO_LINE,\n        \"gridcolor\": GRID,\n        \"showgrid\": True,\n        \"linecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Feature\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 16, \"color\": INK_SOFT},\n        \"tickmode\": \"array\",\n        \"tickvals\": list(range(top_n)),\n        \"ticktext\": y_labels,\n        \"showgrid\": False,\n        \"linecolor\": INK_SOFT,\n    },\n    plot_bgcolor=PAGE_BG,\n    paper_bgcolor=PAGE_BG,\n    margin={\"l\": 200, \"r\": 120, \"t\": 80, \"b\": 80},\n    showlegend=False,\n    font={\"family\": \"sans-serif\", \"color\": INK},\n)\n\n# Save as PNG and HTML (4800 x 2700)\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}