Skip to content

Aggregation

The pipeline's per-feature outcomes — scores from Stage 4, p-values and decisions from Stage 5 — finally roll up into one model-level verdict. Aggregation answers the question an alerting system actually asks: is this model drifting, and where?

from swift.aggregation import aggregate_scores, compute_importance_weights

agg = aggregate_scores(scores, weights=None)
# Returns: AggregatedScores(swift_max, swift_mean, max_feature, swift_weighted)

weights = compute_importance_weights(shap_values, feature_names)
# Returns: dict[str, float] — non-negative weights summing to 1

Three aggregation strategies

Given per-feature scores \(\text{SWIFT}_j\):

  • swift_max — the score of the most-drifted feature, with its name in max_feature. The right statistic for alerting: one badly drifted feature is a problem even if the average looks fine.
  • swift_mean — the unweighted mean across features: overall drift pressure.
  • swift_weighted — an importance-weighted sum \(\sum_j w_j \, \text{SWIFT}_j\), computed only when a weights dict is passed (otherwise None).

The natural weights come from compute_importance_weights: normalized mean absolute SHAP per feature on the reference data,

\[ w_j \;=\; \frac{\overline{|\text{SHAP}_j|}}{\sum_k \overline{|\text{SHAP}_k|}}, \]

so drift in the model's workhorse features counts more than drift in marginal ones. (If all SHAP values are zero, the weights fall back to uniform.)

SWIFTMonitor.test() calls aggregate_scores without weights, so the SWIFTResult it returns always has swift_weighted=None — compute weighted aggregates yourself from monitor.shap_values_ and the score dict when you want them.

The result object

Everything from a test() call lands in a SWIFTResult (a frozen dataclass from swift.types):

Field / property Meaning
feature_results tuple of per-feature FeatureSWIFTResult records
swift_max maximum SWIFT score across features
swift_mean mean SWIFT score across features
swift_weighted importance-weighted score (None unless computed with weights)
alpha significance level used for testing
correction_method the CorrectionMethod applied
num_features number of features monitored
num_drifted number of features flagged as drifted
drifted_features names of the flagged features

Each FeatureSWIFTResult carries the per-feature detail:

Field Meaning
feature_name the feature
swift_score Wasserstein distance on the SHAP-transformed distributions
wasserstein_order WassersteinOrder.W1 or .W2
p_value permutation-test p-value (None if not computed)
is_drifted drift flag after multiple testing correction
num_buckets number of buckets used for the feature (incl. the null bucket)

A typical read:

result = monitor.test(X_mon)

result.drifted_features          # ('utilization', 'income')
result.num_drifted               # 2
result.swift_max                 # 0.0834

for fr in result.feature_results:
    status = "DRIFTED" if fr.is_drifted else "ok"
    print(f"{fr.feature_name}: score={fr.swift_score:.4f}, p={fr.p_value:.4f} [{status}]")

plot_swift_scores consumes SWIFTResult objects directly — see Visualization.


That closes the pipeline. Where to go from here: Configuration for tuning every parameter, or the API reference — Aggregation and Types for full signatures.