Skip to content

Local sensitivity

Per-case ICE / LOCO / archetype-counterfactual primitives that extend a CaseExplanation. See the Decision-support guide.

ICE — individual conditional expectation

compute_ice_for_case

compute_ice_for_case(
    *,
    adapter: "InferenceAdapter",
    x_encoded: DataFrame,
    feature_names: Sequence[str],
    background: DataFrame,
    target_id: int,
    calibrated: bool,
    grid_resolution: int = 25,
    encoding_registry: Any | None = None
) -> list[IceCurve]

Compute per-feature ICE curves for one row.

Parameters:

Name Type Description Default
adapter 'InferenceAdapter'

Active :class:parleml_mli._inference_adapter.InferenceAdapter. Must already be paired with a result whose predict_both accepts the column shape of x_encoded.

required
x_encoded DataFrame

Single-row engineered frame (1 x p).

required
feature_names Sequence[str]

Columns to sweep. Typically the top-K features from CaseExplanation.shap_drivers. ValueError raised if any name is not in x_encoded.columns.

required
background DataFrame

Historical encoded frame (sample of result._X_target) used to define each feature's grid via 1st-99th percentiles.

required
target_id int

Class index whose probability column drives the sweep. Ignored for regression.

required
calibrated bool

Forwarded to adapter.predict_both.

required
grid_resolution int

Number of points per grid (default 25 — matches the plan's compute budget).

25
encoding_registry Any | None

Optional parleml_mli.core.encoding.EncodingRegistry-like object used to populate IceCurve.origin_feature.

None

Returns:

Type Description
list[IceCurve]

One curve per requested feature, in the same order. Non-numeric / all-NaN / missing features produce an IceCurve with skipped_reason set and empty grid/predictions.

IceCurve dataclass

IceCurve(
    feature: str,
    origin_feature: str,
    grid: ndarray = (lambda: empty(0, dtype=float))(),
    predictions: ndarray = (
        lambda: empty(0, dtype=float)
    )(),
    current_value: float | None = None,
    current_prediction: float = float("nan"),
    skipped_reason: str | None = None,
)

One feature's predicted-score-vs-feature-value curve for one row.

Attributes:

Name Type Description
feature str

Encoded feature name as the algorithm sees it.

origin_feature str

Business-readable name from the encoding registry; falls back to feature when no registry record exists.

grid ndarray

(G,) float grid of values the feature was swept over. Empty when skipped_reason is set.

predictions ndarray

(G,) model score at each grid point (proba[target_id] for classification, regression value otherwise). Empty when skipped_reason is set.

current_value float | None

The query row's actual value at this feature. None when the column is missing from the row.

current_prediction float

adapter.predict_both score at the unmodified row.

skipped_reason str | None

None on the happy path; an explanatory string when the feature was non-numeric / all-NaN / missing from the background.

plot_ice_for_case

plot_ice_for_case(
    curves: "list[IceCurve]",
    *,
    decision_threshold: float | None = None,
    save_path: str | None = None
) -> Figure

Faceted line chart, one subplot per non-skipped ICE curve.

Skipped curves are listed in a caption beneath the figure rather than rendered. Returns an :func:empty_fig when every curve was skipped.

LOCO — leave-one-covariate-out

compute_loco_for_case

compute_loco_for_case(
    *,
    adapter: "InferenceAdapter",
    x_encoded: DataFrame,
    baseline_values: dict[str, Any],
    target_id: int,
    calibrated: bool,
    top_k: int = 10,
    encoding_registry: Any | None = None
) -> list[LocoScore]

Score the impact of replacing each feature with its baseline.

Builds a single (n_features + 1, n_features) perturbation frame — row 0 is the unmodified row, rows 1..n have column j set to baseline_values[col_j]. One adapter.predict_both call yields the full delta vector.

Features missing from baseline_values are scored as delta = 0.0 and grouped into a single :class:UserWarning.

Returns:

Type Description
list[LocoScore]

Top-top_k features by abs(delta), descending.

LocoScore dataclass

LocoScore(
    feature: str,
    origin_feature: str,
    delta: float,
    baseline_value: float | int | str,
    direction: Literal[
        "towards_positive", "towards_negative", "neutral"
    ],
)

One feature's leave-one-out delta.

Attributes:

Name Type Description
feature str

Encoded feature name.

origin_feature str

Business-readable origin column from the encoding registry; falls back to feature.

delta float

score(x_minus_j) - score(x) at the requested target_id. Positive => removing the feature pushed the score up (the feature was holding the score back). Negative => removing it pushed the score down (the feature was driving the score up).

baseline_value float | int | str

The value substituted in for j — median (numeric) or mode (categorical).

direction Literal['towards_positive', 'towards_negative', 'neutral']

Human-readable bucket for the figure legend.

compute_baseline_values

compute_baseline_values(X: DataFrame) -> dict[str, Any]

Median for numeric columns, mode for everything else.

Pure function — called once per build_case_explanation invocation that enables LOCO. Not cached on :class:parleml_mli.ProfilingResult: a per-instance cache would risk inflating the joblib via __dict__ pickling, and a 200-column frame is < 30 ms to recompute.

plot_loco_for_case

plot_loco_for_case(
    scores: "list[LocoScore]",
    *,
    save_path: str | None = None
) -> Figure

Horizontal bar chart of LOCO deltas (sorted by |delta|).

Archetype counterfactual

compute_archetype_counterfactual

compute_archetype_counterfactual(
    *,
    result: "ProfilingResult",
    x_encoded: DataFrame,
    matched_archetype: int,
    target_archetype: int | None = None,
    top_k_features: int = 5,
    alpha_steps: int = 11,
    min_support: float = 0.02
) -> ArchetypeCounterfactual | None

1-D blend from the case to a safer medoid; None when no flip.

See the module docstring and the plan §8 for the full design.

ArchetypeCounterfactual dataclass

ArchetypeCounterfactual(
    from_archetype: int,
    to_archetype: int,
    severity_from: str | None,
    severity_to: str | None,
    odds_ratio_delta: float,
    feature_deltas: Series,
    alpha: float,
    confidence: float,
    distance_to_target_medoid: float,
    path_summary: DataFrame,
    picker_source: PickerSource = "odds_ratio",
)

Result of a one-shot archetype-scoped counterfactual search.

Attributes:

Name Type Description
from_archetype, to_archetype

Cluster IDs.

severity_from, severity_to

Raw severity labels (whatever source the picker landed on), or None when no severity was discoverable.

odds_ratio_delta float

summary_df.loc[to, "odds_ratio"] - summary_df.loc[from, "odds_ratio"] when available; nan otherwise.

feature_deltas Series

Index = origin feature name, value = the dominant signed encoded delta (largest-magnitude component) for that origin at the flip step. Top-K origins ranked by the L1 norm of their encoded deltas so one-hot category flips don't cancel to zero (see LOC-3).

alpha float

Path fraction ∈ (0, 1] that triggered the flip.

confidence float

∈ [0, 1] — 1.0 when the path walks straight out of the base archetype into the target, falling toward 0 for each extra archetype it wanders through on the way. See :func:_path_confidence.

distance_to_target_medoid float

Clustering-space distance at α*.

path_summary DataFrame

DataFrame with columns alpha, matched, distance — used by the figure.

picker_source PickerSource

Which rung of the §8.2 ladder selected to_archetype.

plot_archetype_counterfactual

plot_archetype_counterfactual(
    cf: "ArchetypeCounterfactual",
    *,
    medoid_coords: ndarray | None = None,
    query_coord: ndarray | None = None,
    path_coords: ndarray | None = None,
    save_path: str | None = None
) -> Figure

Two-panel CF figure.

Left (when medoid_coords is provided): 2-D scatter of medoid positions with the projected path. Right: signed bar chart of feature_deltas. When medoid_coords is None, the figure collapses to the right panel only — what the notebook surface typically wants.