Skip to content

Visualization

SHAP-Go provides a render package that generates chart specifications in a portable JSON format called ChartIR (Chart Intermediate Representation).

Overview

The render package doesn't directly create images. Instead, it produces JSON specifications that can be rendered by:

  • JavaScript libraries (D3.js, Chart.js, ECharts)
  • Python libraries (Matplotlib, Plotly)
  • Custom renderers (any tool that can read JSON)

This approach keeps the Go code lightweight while enabling rich visualizations.

Chart Types

Waterfall Plot

Shows how each feature contributes to a single prediction:

import (
    "encoding/json"
    "github.com/plexusone/shap-go/render"
)

// After getting an explanation
chart := render.Waterfall(explanation, render.WaterfallOptions{
    Title:       "Loan Default Prediction",
    MaxFeatures: 10,
    ShowValues:  true,
})

// Export to JSON
jsonData, _ := json.MarshalIndent(chart, "", "  ")

Output structure:

{
  "type": "waterfall",
  "title": "Loan Default Prediction",
  "baseValue": 0.5,
  "prediction": 0.73,
  "features": [
    {"name": "income", "value": 0.15, "cumulative": 0.65},
    {"name": "debt_ratio", "value": 0.08, "cumulative": 0.73}
  ]
}

Feature Importance (Bar Chart)

Shows average absolute SHAP values across multiple predictions:

// Collect explanations for multiple instances
var explanations []*explainer.Explanation
for _, instance := range testData {
    exp, _ := exp.Explain(ctx, instance)
    explanations = append(explanations, exp)
}

// Generate importance chart
chart := render.FeatureImportance(explanations, render.ImportanceOptions{
    Title:       "Global Feature Importance",
    MaxFeatures: 15,
    SortBy:      "mean_abs",  // or "max_abs"
})

Output structure:

{
  "type": "bar",
  "title": "Global Feature Importance",
  "orientation": "horizontal",
  "features": [
    {"name": "income", "importance": 0.234},
    {"name": "age", "importance": 0.189}
  ]
}

Summary Plot (Beeswarm)

Shows SHAP value distribution for each feature:

chart := render.Summary(explanations, featureValues, render.SummaryOptions{
    Title:       "SHAP Summary",
    MaxFeatures: 20,
    ColorScale:  "bluered",  // Feature value coloring
})

Output structure:

{
  "type": "beeswarm",
  "title": "SHAP Summary",
  "colorScale": "bluered",
  "features": [
    {
      "name": "income",
      "points": [
        {"shap": 0.15, "featureValue": 75000, "normalized": 0.8},
        {"shap": -0.12, "featureValue": 35000, "normalized": 0.3}
      ]
    }
  ]
}

Dependence Plot

Shows relationship between a feature's value and its SHAP value:

chart := render.Dependence(explanations, featureValues, render.DependenceOptions{
    Feature:      "income",
    ColorFeature: "age",  // Optional: color by another feature
    Title:        "Income Dependence",
})

Output structure:

{
  "type": "scatter",
  "title": "Income Dependence",
  "xAxis": {"name": "income", "label": "Feature Value"},
  "yAxis": {"name": "shap", "label": "SHAP Value"},
  "colorAxis": {"name": "age", "label": "Age"},
  "points": [
    {"x": 75000, "y": 0.15, "color": 35},
    {"x": 35000, "y": -0.12, "color": 62}
  ]
}

Rendering ChartIR

With D3.js (JavaScript)

// Load the ChartIR JSON
const chart = await fetch('/api/explanation/chart').then(r => r.json());

if (chart.type === 'waterfall') {
    renderWaterfall(chart);
} else if (chart.type === 'bar') {
    renderBarChart(chart);
}

function renderWaterfall(chart) {
    const svg = d3.select('#chart').append('svg');
    // ... D3 rendering code
}

With Plotly (Python)

import json
import plotly.graph_objects as go

# Load ChartIR
with open('chart.json') as f:
    chart = json.load(f)

if chart['type'] == 'waterfall':
    fig = go.Figure(go.Waterfall(
        x=[f['name'] for f in chart['features']],
        y=[f['value'] for f in chart['features']],
        base=chart['baseValue']
    ))
    fig.show()

With ECharts

const chart = loadChartIR();

if (chart.type === 'bar') {
    const option = {
        title: { text: chart.title },
        xAxis: { type: 'value' },
        yAxis: {
            type: 'category',
            data: chart.features.map(f => f.name)
        },
        series: [{
            type: 'bar',
            data: chart.features.map(f => f.importance)
        }]
    };
    echarts.init(document.getElementById('chart')).setOption(option);
}

Complete Example

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "os"

    "github.com/plexusone/shap-go/explainer"
    "github.com/plexusone/shap-go/explainer/tree"
    "github.com/plexusone/shap-go/render"
)

func main() {
    // Load model
    ensemble, _ := tree.LoadXGBoostModel("model.json")
    exp, _ := tree.New(ensemble)

    ctx := context.Background()

    // Explain multiple instances
    testData := loadTestData()
    var explanations []*explainer.Explanation
    var featureValues [][]float64

    for _, instance := range testData {
        explanation, _ := exp.Explain(ctx, instance)
        explanations = append(explanations, explanation)
        featureValues = append(featureValues, instance)
    }

    // Generate all chart types
    charts := map[string]interface{}{
        "waterfall": render.Waterfall(explanations[0], render.WaterfallOptions{
            Title:       "Individual Prediction",
            MaxFeatures: 10,
        }),
        "importance": render.FeatureImportance(explanations, render.ImportanceOptions{
            Title:       "Feature Importance",
            MaxFeatures: 15,
        }),
        "summary": render.Summary(explanations, featureValues, render.SummaryOptions{
            Title:       "SHAP Summary",
            MaxFeatures: 20,
        }),
        "dependence": render.Dependence(explanations, featureValues, render.DependenceOptions{
            Feature: "income",
            Title:   "Income Dependence",
        }),
    }

    // Save each chart
    for name, chart := range charts {
        data, _ := json.MarshalIndent(chart, "", "  ")
        os.WriteFile(fmt.Sprintf("%s.json", name), data, 0644)
    }
}

ChartIR Specification

ChartIR is a simple JSON format designed for SHAP visualizations:

Common Fields

Field Type Description
type string Chart type: "waterfall", "bar", "beeswarm", "scatter"
title string Chart title
subtitle string Optional subtitle

Waterfall-Specific

Field Type Description
baseValue float Expected value (E[f(x)])
prediction float Model output for this instance
features array Feature contributions

Bar-Specific

Field Type Description
orientation string "horizontal" or "vertical"
features array Feature importance values

Scatter-Specific

Field Type Description
xAxis object X-axis configuration
yAxis object Y-axis configuration
colorAxis object Optional color axis
points array Data points

Customization

Color Scales

render.Summary(explanations, featureValues, render.SummaryOptions{
    ColorScale: "bluered",    // Blue (low) to Red (high)
    // or "viridis", "plasma", "coolwarm"
})

Feature Selection

// Show specific features
render.Waterfall(explanation, render.WaterfallOptions{
    Features: []string{"income", "age", "credit_score"},
})

// Exclude features
render.FeatureImportance(explanations, render.ImportanceOptions{
    ExcludeFeatures: []string{"id", "timestamp"},
})

Sorting

render.FeatureImportance(explanations, render.ImportanceOptions{
    SortBy: "mean_abs",  // Default: mean absolute SHAP
    // or "max_abs", "variance"
})

Integration Examples

REST API

func handleExplanationChart(w http.ResponseWriter, r *http.Request) {
    instance := parseInstance(r)
    explanation, _ := exp.Explain(r.Context(), instance)

    chart := render.Waterfall(explanation, render.WaterfallOptions{
        MaxFeatures: 10,
    })

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(chart)
}

File Export

// JSON
data, _ := json.MarshalIndent(chart, "", "  ")
os.WriteFile("chart.json", data, 0644)

// For web rendering, embed in HTML template
tmpl := `<script>const chartData = {{.}}</script>`

Next Steps