Skip to content

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.