Skip to content

Interpretability contract

The published, dependency-free surface for introspecting a fitted ml4 model. A tool that explains models (ParleML MLI, or your own SHAP / rule code) imports only these two objects and never touches autogluon.*:

  • InterpretablePipeline — the pipeline surface: transform raw input once, then reuse the encoded frame across probabilities and the member-introspection frame. ml4 computes no attributions itself; an explainer runs its own SHAP on a LeafModelHandle's estimator over that member frame.
  • LeafModelHandle — one trained leaf estimator, unwrapped to its raw library object plus the function that reproduces the exact matrix it was trained on.

Both are rendered in full below.

In short

  • An explainer imports only InterpretablePipeline and LeafModelHandle, never autogluon.*.
  • Every method takes and returns a column-named pandas.DataFrame with real engineered feature names.
  • LeafModelHandle.raw_estimator is the plain library object (LGBMClassifier, …), ready for your own SHAP.
  • FittedPipeline.feature_insight explains the raw text and categorical columns; it is mined once at fit time and travels in the bundle.

One frame format, everywhere

Every method and callable on this contract takes and returns a plain, column-named pandas.DataFrame. That is the whole promise:

  • No scipy.sparse matrices, no bare numpy arrays.
  • No positional f0.. column names — columns carry the real (engineered) feature names, so SHAP drivers and rule antecedents read in business terms.
  • No AutoGluon wrappers — raw_estimator is the library object (LGBMClassifier, XGBClassifier, ExplainableBoosting*, …) directly.

So a consumer never has to sniff types, densify a matrix, or rename columns before running SHAP, partial dependence, or rule mining. FittedPipeline implements this contract; see the FittedPipeline reference and the inference guide.

InterpretablePipeline

Bases: Protocol

The surface a consumer (e.g. MLI) reads — no AutoGluon types cross it.

A regulated run yields a pipeline whose :meth:leaf_models returns exactly one handle; an ensemble returns one per member. :meth:transform once, then reuse the encoded frame across every *_from_encoded call and :meth:prepare_member_introspection_frame.

All frame-valued methods below take and return a column-named :class:pandas.DataFrame — the single frame format promised by this module.

transform

transform(x: DataFrame) -> DataFrame

Raw input → the model's post-FE feature frame (global + fold-local FE).

A column-named pd.DataFrame — the encoded space every *_from_encoded method and :meth:prepare_member_introspection_frame expects as input.

predict_proba_from_encoded

predict_proba_from_encoded(
    encoded: DataFrame,
    *,
    calibrated: bool = True,
    member: Optional[str] = None
) -> DataFrame

Class probabilities from a :meth:transformed frame, as a pd.DataFrame with one column per class label (calibrated by default).

predict_point_from_encoded

predict_point_from_encoded(encoded: DataFrame) -> ndarray

Regression point predictions (original units) from a :meth:transformed frame — the regression analogue of :meth:predict_proba_from_encoded; raises for classification tasks. Lets per-row justify score regressions without probabilities.

prepare_member_introspection_frame

prepare_member_introspection_frame(
    encoded: DataFrame,
) -> DataFrame

The introspected base tree's member frame (global + its fold-local FE) — the space a consumer aligns SHAP / archetypes / rules on; raises only when nothing on the board is introspectable.

A dense, column-named pd.DataFrame — the same frame contract as :attr:LeafModelHandle.trained_matrix_fn (it is that call for the introspected leaf), so its columns equal that leaf's feature_names.

leaf_models

leaf_models() -> List[LeafModelHandle]

The trained leaf estimators to introspect: one for a regulated model, one per member for an ensemble.

scorecard_points

scorecard_points() -> Optional[ScorecardTable]

The deployed scorecard's points table, or None for every other model.

The one artifact ml4 extracts itself (see the module docstring): it hangs off the AutoGluon wrapper, not off :attr:LeafModelHandle.raw_estimator, so it needs its own accessor to reach a consumer at all.

LeafModelHandle dataclass

LeafModelHandle(
    raw_estimator: Any,
    trained_matrix_fn: Callable[[DataFrame], DataFrame],
    weight: float,
    feature_names: List[str],
    kind: str,
    classes_: "Optional[np.ndarray]" = None,
)

One trained leaf estimator, unwrapped from AutoGluon for introspection.

Every field is a plain, library-native value (no AutoGluon wrappers), so a consumer can introspect the leaf without importing autogluon.

Attributes:

Name Type Description
raw_estimator Any

The library object — LGBMClassifier / XGBClassifier / ExplainableBoosting* / RuleFit* / sklearn — the thing detect_model_type recognises. Never the AutoGluon wrapper.

trained_matrix_fn Callable[[DataFrame], DataFrame]

Reproduces the exact feature matrix this leaf was trained on from a :meth:~InterpretablePipeline.transformed frame. Always returns a dense, column-named pd.DataFrame — never a sparse matrix, ndarray, or positionally-named frame — so SHAP / partial-dependence / rules run on the right, readable columns. For a stack-layer (L2+) leaf the frame includes the lower-layer base-model predictions the leaf saw.

weight float

The ensemble weight (1.0 for a single regulated model).

feature_names List[str]

The columns of the trained matrix, in the order the estimator saw them — identical to trained_matrix_fn(...).columns. Real engineered names (e.g. city_TE, age_CL_DIST), never positional f0.. placeholders.

kind str

The model-family tag (lgbm/xgb/cat/glm/ebm/rulefit/scorecard/foundation).

classes_ 'Optional[np.ndarray]'

The estimator's class labels, or None for a regressor.

The fit-time feature insight

The contract above explains the model. FittedPipeline.feature_insight explains the columns the user supplied, and it is the one interpretability artefact that cannot be derived on demand: the served bundle keeps no training rows, and AutoGluon owns the fold-local encoders, so it is mined once during the fit and travels in the bundle.

Two encoding-agnostic halves, both descriptive (never fed back as features):

Kind Question it answers Direction
keywords Which words distinguish one target group's rows from the rest? none — c-TF-IDF relevance is non-negative
categorical Which levels of this column push the target off its base rate? signed target lift

Groups are the run's classes for classification and quantile bins of the target for regression, named at fit time (only the fit knows a bin's edges). Both halves project into the same TextComponentInsight payload (rendered below), so a consumer renders one card grid for either.

FeatureInsight dataclass

FeatureInsight(
    categorical: Dict[str, Dict[str, Any]] = dict(),
    keywords: Dict[
        str, Dict[int, List["ScoredKeyword"]]
    ] = dict(),
    group_names: Dict[int, str] = dict(),
    group_sizes: Dict[int, int] = dict(),
)

Descriptive per-column insight mined once at fit time, served forever after.

keywords / group_names / group_sizes share one group space: the encoded class index for classification, the quantile-bin index for regression. Group display names are stored here rather than resolved by the reader because only the fit knows what a regression bin's edges were — keeping half the naming here and half at the reader would make a bin and a class translate through different code paths for no gain.

insight_columns

insight_columns() -> Dict[str, List[str]]

{kind: columns} for the kinds that actually carry a payload.

The capability gate reads this directly: a kind is absent — not present and empty — when nothing was mined, so "the tab exists" and "the tab has content" cannot disagree.

keyword_groups

keyword_groups(column: str) -> List[Tuple[int, str]]

Ordered (group_id, display name) for the groups mined for column.

One ordering, used by every reader (insight cards and word cloud), so a class picker and the card grid can never disagree about which group is first.

keyword_insight

keyword_insight(
    column: str, *, top_n: int = DEFAULT_KEYWORDS_PER_GROUP
) -> "TextComponentInsight"

Project column's mined keywords into the shared insight payload.

Delegates to the same projector the unsupervised key-drivers surface uses, so a supervised class card and a clustering card render through identical code — the only difference is what a "group" means.

categorical_insight

categorical_insight(
    column: str,
    *,
    class_key: Optional[str] = None,
    top_n: int = 15
) -> "TextComponentInsight"

Project column's stored target-lift stats into the shared insight payload.

compute_feature_insight

compute_feature_insight(
    df: DataFrame,
    target_column: str,
    *,
    is_classification: bool,
    cat_columns: Sequence[str] = (),
    text_columns: Sequence[str] = (),
    seed: int = 42,
    max_text_columns: int = DEFAULT_MAX_TEXT_COLUMNS,
    max_groups: int = DEFAULT_MAX_GROUPS,
    max_rows: int = DEFAULT_MAX_KEYWORD_ROWS,
    keywords_per_group: int = DEFAULT_KEYWORDS_PER_GROUP,
    target_bins: int = DEFAULT_TARGET_BINS
) -> FeatureInsight

Mine the fit-time insight payload from the rows the model trained on.

df must still carry target_column in ORIGINAL units — a regression y-transform is a modelling detail, and quoting a level mean or a bin edge in log space would show the user a number that appears nowhere else in the product.

Neither half raises on a difficult column: compute_categorical_stats skips columns that are absent, constant or too high-cardinality to be a category, and extract_keywords_by_column degrades a failing column to an empty mapping. What is deliberately not here is a blanket try/except around the whole producer — swallowing everything is precisely what let the previous surface fail invisibly for every run.

TextComponentInsight dataclass

TextComponentInsight(
    feature: str,
    encoder: EncoderKind,
    value_kind: ValueKind,
    signed_source: Optional[SignedSource] = None,
    class_key: Optional[str] = None,
    class_keys: List[str] = list(),
    global_stats: Optional[Dict[str, Any]] = None,
    components: List[ComponentInsight] = list(),
)

The full insight payload for one encoded feature (one class slice).

to_dict

to_dict() -> Dict[str, Any]

Return a plain nested dict (lists/dicts/scalars) for JSON / the API.

Next: FittedPipeline · Inference & fast scoring