> ## Documentation Index
> Fetch the complete documentation index at: https://docs.summand.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Predictors (EBM)

> Fits an Explainable Boosting Machine and writes feature importance, per-feature shape functions, and pairwise interactions.

The **Predictors** component is a two-piece chain:

* `ebm_model` (internal) — fits the EBM and persists it to S3.
* `ebm_graphs` (user-visible) — extracts the global explanation, serializes shape functions and importance scores, and writes the JSON the dataset page renders.

You pick `ebm_graphs` in the experiment editor; `ebm_model` runs automatically as a dependency. The split exists so changing display-only behavior doesn't refit the model.

## Why EBMs

[Explainable Boosting Machines](https://github.com/interpretml/interpret) sit at the intersection of "interpretable enough to read" and "accurate enough to ship":

* **Per-feature shape functions are exact.** No SHAP approximations. The model literally is the sum of those curves plus a small number of pairwise interaction terms.
* **Predictions decompose row-by-row.** Every prediction has an additive breakdown — *"here's exactly which features pushed this score up or down."*
* **Categorical features handled natively.** EBMs use `feature_types=['nominal']` for categoricals; no one-hot encoding pollution in the explanations.
* **Performance is competitive.** Within a few points of XGBoost on most tabular targets, while remaining fully readable.

## Inputs

| Input           | Type             | Required | Description                                                        |
| --------------- | ---------------- | -------- | ------------------------------------------------------------------ |
| `target_column` | column reference | ✓        | The column to explain or predict. Must be in the dataset's schema. |

The target's distribution determines the task type:

* **Binary classification** — exactly two distinct values.
* **Regression** — continuous numeric.
* **Multiclass** is on the roadmap; today the component rejects targets with three or more distinct categorical values.

## Output shape (`ebm_graphs`)

```json theme={null}
{
  "features": [
    {
      "name": "tenure_months",
      "index": 0,
      "importance": 0.342,
      "type": "continuous",
      "names": [0, 12, 24, 36, 48, 60, 72],
      "scores": [-0.85, -0.34, 0.12, 0.41, 0.47, 0.39, 0.21],
      "upper_bounds": [-0.71, -0.21, 0.24, 0.55, 0.62, 0.51, 0.34],
      "lower_bounds": [-0.99, -0.47, 0.00, 0.27, 0.32, 0.27, 0.08],
      "density": {
        "names": [...],
        "scores": [...]
      }
    },
    {
      "name": "region & plan_tier",
      "index": 14,
      "type": "interaction",
      "importance": 0.082,
      "scores": [[...], [...]],
      "names": [["us-east", "us-west", "eu"], ["free", "pro"]]
    }
  ],
  "feature_names": [...],
  "feature_importances": [...],
  "metadata": { ... }
}
```

The artifact is written gzipped as `graphs.json.gz`. Each feature has:

* **`scores`** — the y-axis of the shape function (the contribution at each x-value).
* **`upper_bounds` / `lower_bounds`** — confidence bounds, derived from EBM's bagged ensemble.
* **`density`** — how many rows fall at each x-value (helps you spot when the curve is undersupported).
* **`importance`** — the global feature importance (mean absolute contribution across the training set).

Pairwise interaction terms appear as 2-D `scores` matrices with two `names` arrays (one per axis).

## Display

The Predictors component ships with a bespoke React viewer (`EBMVisualization`) instead of the generic block renderer. It draws:

* **Feature importance bar chart** — sorted descending.
* **Shape functions per feature** — line chart with confidence band, density underneath.
* **Pairwise interactions as heatmaps** — for the small number of interaction terms EBM keeps.

## Filtering from chat

Summand reads `ebm_graphs` via the `analyze` tool. Without parameters, it gets the summary fields (importances, feature names). With a `feature_name` parameter, it gets the full shape function for one feature plus an LTTB-downsampled version for context efficiency:

```text theme={null}
analyze({ component: "ebm_graphs", target: ..., params: { feature_name: "tenure_months" } })
```

This lets the agent ground answers like *"Why is tenure important?"* in the actual shape function rather than approximating from importance alone.

## Compute profile

| Component    | Profile | Tier                                |
| ------------ | ------- | ----------------------------------- |
| `ebm_model`  | Fargate | 4 CPU, 16 GB; scales with row count |
| `ebm_graphs` | Fargate | 2 CPU, 8 GB                         |

Most datasets fit in a few minutes on the standard tier. Datasets with millions of rows or hundreds of features take longer; the dispatcher routes them to the larger Fargate tier automatically when the row count crosses an internal threshold.

## Common gotchas

<AccordionGroup>
  <Accordion title="The model says nothing predicts my target">
    Either the target genuinely is noise relative to the columns you provided, or the target is a near-copy of one of the inputs (e.g. `revenue = price × quantity` and you have all three). EBM excludes near-tautological features, which can leave the result looking empty. Try removing suspect feature columns and re-running.
  </Accordion>

  <Accordion title="Shape function is wiggly / noisy">
    Two causes: insufficient data (under \~500 rows for the relevant feature value), or the feature is near-collinear with another feature. Check the density underneath the curve — if it's spiky, the curve is undersupported in that region.
  </Accordion>

  <Accordion title="My target column is mostly null">
    EBM needs at least a few hundred non-null target rows. Filter the source before running the experiment, or pick a different target.
  </Accordion>
</AccordionGroup>
