Models and decision points¶
Stage 1 of the pipeline answers one question: where can the model's behavior change at all? A tree ensemble is piecewise constant — a prediction only changes when a feature value crosses a split threshold in some tree. SWIFT therefore starts by extracting those thresholds, called decision points, from the trained model. They become the bucket boundaries in Stage 2: every downstream comparison happens at exactly the resolution the model itself uses.
from swift.extraction import extract_decision_points
decision_points = extract_decision_points(model, feature_names)
# Returns: dict[str, ndarray] — feature name → sorted array of unique thresholds
For a LightGBM model with 100 trees and a feature that appears in 50 splits, the extraction step collects all 50 unique threshold values for that feature.
Dispatch on model type¶
extract_decision_points reads the serialized trees directly and dispatches on the
model type:
| Model | How the trees are read |
|---|---|
LightGBM Booster |
model.dump_model(), recursing through each tree_structure |
XGBoost Booster |
model.get_dump(dump_format="json"), recursing through each JSON tree |
XGBoost is imported lazily — it is an optional dependency, and a LightGBM-only environment never touches it. Any other model type raises
TypeError: Unsupported model type: <name>. SWIFT supports LightGBM Booster and XGBoost Booster.
The scikit-learn wrappers (LGBMClassifier, XGBClassifier, …) are not accepted
directly — pass the underlying booster instead, e.g.
SWIFTMonitor(model=clf.booster_) for LightGBM, or train with the native API
(lgb.train). See the FAQ.
Per-stage functions are also available directly when you want to control dispatch yourself:
from swift.extraction import extract_decision_points_lgb, extract_decision_points_xgb
dp = extract_decision_points_lgb(lgb_booster, feature_names)
dp = extract_decision_points_xgb(xgb_booster, feature_names)
What gets collected¶
For each feature, every split threshold used across all trees is collected, then
deduplicated and sorted ascending (np.unique, as float64). Two details are worth
knowing:
- Features never used in any split get an empty array. In Stage 2 this becomes a single catch-all bucket, and the feature's SWIFT score is 0 by construction — a feature the model ignores cannot raise a drift alarm (Buckets — the catch-all bucket).
- Only numeric split thresholds are collected. In LightGBM trees, categorical splits
store their threshold as a string like
"0||1||3"(a set of category indices, not a numeric boundary); these are skipped during extraction.
Feature-name resolution¶
feature_names must match the model's feature order. For XGBoost, split identifiers in
the tree dump may be either positional ("f0", "f1", …) or the real feature names the
model was trained with — extraction builds a mapping that resolves both to the canonical
names you pass in. Splits on identifiers that resolve to no known feature are ignored.
Next stage: the sorted thresholds become interval boundaries in Buckets. Full signatures: API reference — Extraction.