Skip to content

Global explanations

Compute global model-interpretability artifacts. See the Explanations guide.

explain

explain

explain(
    model: Any,
    X: DataFrame,
    *,
    y: ndarray | Series | None = None,
    task_type: TaskType | None = None,
    decision_threshold: float | str | None = None,
    served_threshold: float | None = None,
    positive_class: Any = None,
    config: "ExplanationsConfig | None" = None,
    adapter: Any = None
) -> ExplanationsResult

Compute every requested global-model interpretability artifact.

This is the canonical user-facing entry point. Every artifact module is also importable on its own, but explain(...) is the only path that produces a fully-populated :class:ExplanationsResult with one consistent config round-trip and the per-artifact failure isolation needed for production runs.

Parameters:

Name Type Description Default
model Any

Fitted estimator from one of the supported families (LightGBM, XGBoost, CatBoost, sklearn, sklearn-HGB).

required
X DataFrame

Feature matrix used for every artifact's "global" view.

required
y ndarray | Series | None

Required by the permutation_importance / surrogate_residual / fairness / error_tree sections and by an F-beta decision_threshold. Optional otherwise. Only those read it, so labels an artifact cannot use (missing values, a label outside the model's classes) fail that artifact alone.

None
task_type TaskType | None

Auto-inferred when omitted. Pass explicitly to override the adapter's guess.

None
decision_threshold float | str | None

Single source of truth for the binary-classifier decision cut-off. Resolved exactly once by :func:parleml_mli.core.threshold.resolve_threshold and threaded into the surrogate / error-tree / fairness artifacts, and persisted on :attr:ExplanationsResult.decision_threshold. None (default) keeps the model's own decision rule; a literal float is used as given; "f1" / "auto" / "f<beta>" tune a cut on the model's positive-class probabilities against y (a deterministic 50k-row subsample). Not applicable to regression / multiclass.

None
served_threshold float | None

The deployed model's published cut-off; wins over decision_threshold.

None
positive_class Any

The positive label of a binary y (see :func:parleml_mli.core.target.resolve_target); with a fitted model it must be model.classes_[1], the column every binary artifact scores.

None
config 'ExplanationsConfig | None'

Typed :class:~parleml_mli.config.ExplanationsConfig. Presence of a per-artifact section enables that artifact; None (the default) computes nothing. Each section is a small frozen dataclass holding that artifact's knobs (e.g. PdpConfig(n_features=...)).

None
adapter Any

Optional :class:parleml_mli.adapters.ModelAdapter for raw boosters without a sklearn-style API (None: the one :func:parleml_mli.adapters.detect_model_type recognises). Forwarded to artifacts that accept it.

None

Returns:

Type Description
ExplanationsResult

A fresh container with whichever artifacts succeeded populated. Failures are surfaced via UserWarning; the call never raises for an opt-in artifact.

Raises:

Type Description
ValueError

The call itself is inconsistent: positive_class names no class of the model (or not classes_[1]), decision_threshold is not a valid spec, or an F-beta decision_threshold meets a y it cannot be tuned on (missing labels, or labels outside the model's classes).

Examples:

>>> from parleml_mli.explanations import explain
>>> from parleml_mli.config import ExplanationsConfig, PdpConfig, SurrogateConfig
>>> result = explain(
...     model, X, y=y,
...     config=ExplanationsConfig(pdp=PdpConfig(), surrogate=SurrogateConfig()),
... )
>>> result.plot_surrogate().show()

ExplanationsResult

ExplanationsResult dataclass

ExplanationsResult(
    pdp: dict[str, "PDPResult"] = dict(),
    ale: dict[str, "ALEResult"] = dict(),
    pdp_selection: "EffectSelection | None" = None,
    ale_selection: "EffectSelection | None" = None,
    shap_interactions: dict[str | int, DataFrame] = dict(),
    surrogate: "SurrogateResult | None" = None,
    surrogate_residual: "SurrogateResult | None" = None,
    permutation_importance: "PermutationImportanceResult | None" = None,
    fairness: dict[str, "FairnessResult"] = dict(),
    psi: "PSIResult | None" = None,
    compacity: "CompacityResult | None" = None,
    error_tree: "ErrorTreeResult | None" = None,
    decision_threshold: float | None = None,
    config: dict[str, Any] = dict(),
    schema_version: int = EXPLANATIONS_SCHEMA_VERSION,
)

Cache of every global-model interpretability artifact computed for one run.

Every field defaults to None / empty so a partial run (e.g. explain(config=ExplanationsConfig(pdp=PdpConfig())), which requests PDP only) does not poison downstream readers — they simply skip empty fields.

plot_pdp

plot_pdp(feature: str, **kw: Any) -> Figure

Render the PDP for feature. Raises KeyError if missing.

plot_ale

plot_ale(feature: str, **kw: Any) -> Figure

Render the ALE for feature. Raises KeyError if missing.

plot_shap_interactions

plot_shap_interactions(
    cluster_id: int | str = "global", **kw: Any
) -> Figure

Render the top SHAP interactions for the requested key.

plot_surrogate

plot_surrogate(
    *,
    target_kind: Literal[
        "prediction", "residual"
    ] = "prediction",
    **kw: Any
) -> Figure

Render the surrogate decision tree ("prediction" or "residual").

plot_permutation_importance

plot_permutation_importance(**kw: Any) -> Figure

Render the permutation-importance bar chart.

plot_fairness

plot_fairness(sensitive_feature: str, **kw: Any) -> Figure

Render the per-group AIR / SMD / ME chart for sensitive_feature.

plot_psi

plot_psi(**kw: Any) -> Figure

Render the per-feature PSI bar chart.

plot_compacity

plot_compacity(**kw: Any) -> Figure

Render the SHAP compacity summary.

plot_error_tree

plot_error_tree(**kw: Any) -> Figure

Render the high-error-cohort decision tree.

to_dict

to_dict() -> dict[str, Any]

Return the JSON-clean report.json payload for this result.

Delegates to :mod:parleml_mli.reporting.explanations_serialization so the schema changes are owned by the reporting layer; the dataclass stays free of formatting concerns.

Per-artifact result types

The typed payloads stored on the fields of an ExplanationsResult — the shapes you read off result.explanations_.pdp[feature], .surrogate, .psi, and so on. All are re-exported from the top level (from parleml_mli import PDPResult).

PDPResult dataclass

PDPResult(
    feature_name: str,
    feature_kind: Literal["numeric", "categorical"],
    grid: ndarray,
    average: ndarray,
    ice_std: ndarray | None = None,
    per_cluster_average: dict[int, ndarray] = dict(),
    n_samples_used: int = 0,
    response_method: str = "predict_proba",
    target_class: Any | None = None,
    schema_version: int = 2,
)

Partial-dependence trace for a single feature.

Attributes:

Name Type Description
feature_name str

Column name from the input DataFrame.

feature_kind Literal['numeric', 'categorical']

"numeric" (continuous grid) or "categorical" (per-level grid).

grid ndarray

(G,) array of grid points along the feature axis. For categorical features the entries are the observed category labels, then — when the column has missing values and the model can score one — NaN: the (missing) level, the response with the feature missing on every row (:func:is_missing_level).

average ndarray

(G,) array of model predictions averaged over the rest of X at each grid point.

ice_std ndarray | None

Optional (G,) array: the standard deviation, at each grid point, of the per-row Individual Conditional Expectation (ICE) curves centred at the first grid point (each row's curve minus its value there), so zero at that point. Drives the PDP ± 1σ band, which shows how much the average hides per-row differences in the feature's effect (interactions); rows that differ only in level add nothing. None when the ICE curves could not be computed (e.g. a single sampled row).

per_cluster_average dict[int, ndarray]

Optional {cluster_id: (G,) array} populated by :mod:parleml_mli.archetypes.profiling.overlays after the global computation: each cluster's partial dependence at the points of grid (entry i is the value at grid[i]), averaged over that cluster's rows. Empty when no per-cluster overlay was requested.

n_samples_used int

Number of rows sampled from X for this PDP trace.

response_method str

Sklearn response method used ("predict_proba" / "decision_function" / "predict").

target_class Any | None

Class label pinned for multiclass classifiers (e.g. "fraud", 0, np.int64(2)). None for regression and binary classifiers. Maps to the predict_proba column index via :func:parleml_mli.core.ovr.proba_column_index at compute time; the label is stored here so downstream readers (viewer caption, report writer) can render it without re-deriving from the model.

schema_version int

Bumped when the dataclass shape or a field's meaning changes; mirrors ExplanationsResult.schema_version policy (2: ice_std is the spread of the centred ICE curves).

ALEResult dataclass

ALEResult(
    feature_name: str,
    bin_edges: ndarray,
    ale: ndarray,
    bin_counts: ndarray,
    n_samples_used: int = 0,
    response_method: str = "predict_proba",
    target_class: Any | None = None,
    schema_version: int = 2,
)

First-order ALE curve for a single numeric feature.

Attributes:

Name Type Description
feature_name str

Column name from the input DataFrame.

bin_edges ndarray

(K + 1,) array of quantile bin edges along the feature axis, each a value the feature takes (integers for an integer feature); doubles as the x-axis of the centered ALE curve.

ale ndarray

(K + 1,) centered accumulated local effect, one value per bin edge. Read as the effect on the prediction relative to the dataset mean (the curve integrates to ~0 under the data density).

bin_counts ndarray

(K,) number of sampled rows that fell in each bin.

n_samples_used int

Rows sampled from X for this curve.

response_method str

"predict_proba" / "decision_function" / "predict".

target_class Any | None

Class label pinned for classifiers (None for regression / binary default).

schema_version int

Bumped when the dataclass shape or a field's meaning changes; mirrors ExplanationsResult.schema_version policy (2: bin_edges are inverted-CDF quantiles, values the feature takes).

How the partial-dependence and ALE curves chose their features (.pdp_selection / .ale_selection), and what compute_pdp / compute_ale return — see Which features get a curve:

EffectSelection dataclass

EffectSelection(
    basis: SelectionBasis,
    features: tuple[str, ...],
    skipped: dict[str, str] = dict(),
)

How a feature-effect artifact chose its features, and which columns got no curve.

Attributes:

Name Type Description
basis SelectionBasis

"features" — the caller listed them; "importance" — the top of the supplied importance ranking; "prediction_sensitivity" — the top of the model's own prediction sensitivity (no usable ranking was supplied).

features tuple[str, ...]

The features a curve was computed for, most important first (in the caller's order for basis="features").

skipped dict[str, str]

{column: reason} for every column that got no curve although the caller listed it, or that can never get one (a datetime, identifier, constant, all-missing or too-many-levels column), and for every feature whose curve failed to compute. Columns that are simply outside the top n_features drivers are not listed.

FeatureEffects dataclass

FeatureEffects(
    curves: dict[str, CurveT], selection: EffectSelection
)

Bases: Generic[CurveT]

The curves a feature-effect artifact computed, keyed by feature, and how their features were chosen (:class:EffectSelection).

SurrogateResult dataclass

SurrogateResult(
    tree: IAlgorithm | None,
    engine: EngineKey,
    task_type: TaskType,
    fidelity: float,
    feature_names: list[str],
    rules_text: str,
    rules_dataframe: DataFrame,
    n_samples_used: int = 0,
    tree_diagrams_: list[str] | None = None,
    target_kind: Literal[
        "prediction", "residual"
    ] = "prediction",
    residual_kind: str | None = None,
    fidelity_curve: DataFrame | None = None,
    selected_params: dict[str, Any] | None = None,
    classes_: ndarray | None = None,
    decision_threshold: float | None = None,
    fidelity_train: float = 0.0,
    fidelity_holdout: float | None = None,
    n_holdout_samples: int = 0,
    categories_: CategoryMap = dict(),
    schema_version: int = SCHEMA_VERSION,
)

Tree-based surrogate (predictions or residuals).

Attributes:

Name Type Description
tree IAlgorithm | None

Fitted :class:IAlgorithm wrapper instance (LGBM/XGB/CatBoost/HGB/DT); None only for a result assembled without a fitted engine.

engine EngineKey

The engine key used for the fit ("lgbm" / "xgb" / "catboost" / "hgb" / "dt").

task_type TaskType

:class:TaskType of the original model. Multiclass classifiers report :attr:TaskType.BINARY because mli's task taxonomy has only two first-class values; the precise fan-out is exposed via :attr:classes_.

fidelity float

Accuracy (classification) or R² (regression / residual) comparing the surrogate to the model's predictions on a held-out slice of the surrogate's own sub-sample (default holdout_fraction=0.2). When the sample is too small to split (< 50 rows), or the caller passed holdout_fraction=0.0, this is the in-sample train score and :attr:fidelity_holdout is None.

fidelity_train float

In-sample fidelity on the surrogate's training slice. Always populated. Compare against :attr:fidelity_holdout to read off the "how much did the surrogate overfit the black-box's predictions" gap directly.

fidelity_holdout float | None

Held-out fidelity — equal to :attr:fidelity when a holdout was created. None when no holdout was made.

n_holdout_samples int

Row count of the held-out slice (0 when no holdout was made).

feature_names list[str]

Column names ordered to match the matrix the surrogate consumed.

rules_text str

Human-readable rule dump from the engine's native exporter (LightGBM JSON, XGBoost text, CatBoost JSON, HGB Graphviz DOT, sklearn export_text). Always populated — falls back to a diagnostic string when the engine's exporter is unavailable.

rules_dataframe DataFrame

Long-form [node_id, feature, threshold, categories, samples, value, is_leaf, left, right] representation of every node of tree 0 (see :func:parleml_mli.explanations._engines.engine_rules_dataframe; CatBoost's oblivious trees carry split_type instead of categories) — easier for downstream tabular consumers than the raw tree. Fields not surfaced by a given engine appear as NaN / None.

n_samples_used int

Effective row count after :func:subsample_X_y.

tree_diagrams_ list[str] | None

Annotated Graphviz DOT strings baked at fit time via the shared :func:plot_tree_dot, so leaves carry value=[n0, n1, …] / class=K (classification) or n / mean / std (regression). One entry for binary / regression surrogates; for multiclass, one entry per class in :attr:classes_ order — a one-round multiclass booster is one tree per class, and each DOT is captioned class = <label> so the reader knows which class the splits explain. None when nothing rendered.

target_kind Literal['prediction', 'residual']

"prediction" (default) or "residual". The residual variant fits the model's errors on y.

residual_kind str | None

"signed" / "squared" / "logloss" when target_kind="residual"; None otherwise.

fidelity_curve DataFrame | None

[max_depth, fidelity, n_leaves] (depth sweep) or [trial, fidelity, metric, max_depth] (Optuna search) long-form table. None when optimize=False.

selected_params dict[str, Any] | None

{"max_depth": int, "search": "...", ...} chosen by the optimizer. None when optimize=False.

classes_ ndarray | None

np.ndarray of class labels for classification surrogates; None for regression / residual surrogates.

decision_threshold float | None

For binary classification prediction surrogates only — the probability cut-off used to derive the surrogate target. None means the model's own predict() was used (no custom threshold applied).

categories_ CategoryMap

{str(column): fit-time category universe} for every column treated as categorical, captured on the training frame. :meth:predict / :meth:predict_proba, the surrogate SHAP and :func:plot_surrogate replay it so a frame whose category universe differs from fit still encodes to the same codes the tree was built on. Empty when the surrogate has no categorical features.

schema_version int

:data:SCHEMA_VERSION, checked when the result is unpickled.

n_trees property

n_trees: int

Total tree count in the underlying booster.

predict

predict(X: DataFrame) -> ndarray

Predict on the original (pre-encoding) frame.

Internally re-applies the engine-specific categorical encoding from :attr:feature_names so callers don't need to track which columns were treated as categorical.

When :attr:decision_threshold is set and the task is binary classification, the threshold is applied to predict_proba instead of falling through to engine_predict's 0.5 default — this lets callers tune the precision/recall trade-off without refitting the surrogate.

predict_proba

predict_proba(X: DataFrame) -> ndarray | None

Class probabilities; None for regression / residual surrogates.

PermutationImportanceResult dataclass

PermutationImportanceResult(
    df: DataFrame,
    n_repeats: int,
    scoring: str,
    sample_size: int,
    task_type: TaskType,
    target_class: Any | None = None,
    schema_version: int = 1,
)

Permutation feature importance from :func:sklearn.inspection.permutation_importance.

Attributes:

Name Type Description
df DataFrame

Long-form table [feature, importance_mean, importance_std] sorted descending by importance_mean.

n_repeats int

Number of shuffles per feature.

scoring str

Resolved scorer name (e.g. "roc_auc", "r2").

sample_size int

Effective row count after :func:subsample_X.

task_type TaskType

Inherits from the model.

target_class Any | None

The class label (as it appears in model.classes_) scored as positive: a binary model's positive class, or a multiclass class scored one-vs-rest; None for multiclass global scoring.

schema_version int

Bumped on shape changes.

PSIResult dataclass

PSIResult(
    by_feature: DataFrame,
    n_buckets: int = 10,
    eps: float = 0.0001,
    n_target: int = 0,
    n_reference: int = 0,
    binning_strategy: BinningStrategy = "union",
    pooling_strategy: CategoricalPoolingStrategy = "union",
    meaningful_spread: bool = True,
    comparison: PSIComparison = "class_vs_class",
    schema_version: int = PSI_SCHEMA_VERSION,
    config: dict[str, Any] = dict(),
)

Per-feature PSI / Information Value, target vs reference.

PSI between two deliberately different populations (target class vs reference class) is mathematically identical to Information Value (IV) — the standard credit-scoring metric for "how well does this feature separate good from bad?". It is therefore best read as a class-discriminative power score, not as drift in the production-monitoring sense.

Attributes:

Name Type Description
by_feature DataFrame

DataFrame with one row per feature, sorted by psi descending:

  • feature — feature name
  • kind — "numeric", "categorical", or "ordered_categorical"
  • psi — total PSI / IV (sum across buckets)
  • tier — Information-Value tier: useless / weak / medium / strong / very strong (see :func:_psi_tier)
  • rank_pct — feature's rank percentile within this run (1.0 = most discriminative)
  • rel_tier — relative tier within this run: top (≥ p90) / high (≥ p75) / medium (≥ p50) / low (< p50). Collapsed to all-"low" when :attr:meaningful_spread is False.
  • n_buckets — actual number of buckets used
  • n_target, n_reference — non-null counts per population
n_buckets int

Requested number of buckets for numeric features.

eps float

Optional minimum-proportion floor applied to empty buckets. The default smoothing is sample-size-aware — empty buckets get max(0.5/n, eps) so the metric stays stable across sample sizes — and eps only kicks in if the user wants a stronger floor than the count-based default.

n_target int

Number of target rows used.

n_reference int

Number of reference rows used.

binning_strategy BinningStrategy

Numeric-bin source used for this run; persisted so the viewer can faithfully describe the chart.

pooling_strategy CategoricalPoolingStrategy

Categorical pooling-frequency source used for this run.

meaningful_spread bool

False when max(psi) < MEANINGFUL_PSI_FLOOR or the per-feature PSI range is below :data:MEANINGFUL_SPREAD_FLOOR. When False, rel_tier is suppressed to "low" for every feature and the viewer renders a "no meaningful divergence in this run" banner.

comparison PSIComparison

Which question this result answers — see :data:PSIComparison. "class_vs_class" (Information Value / separability, the profiling-pipeline default) or "monitoring" (train-vs- production drift). Drift graders must only consume "monitoring" results.

schema_version int

:data:PSI_SCHEMA_VERSION, checked when the result is unpickled.

is_empty property

is_empty: bool

True when no per-feature PSI rows exist.

is_monitoring property

is_monitoring: bool

True for a train-vs-current drift report; False for class-vs-class separation (IV).

top

top(n: int = 30) -> DataFrame

Top-n most discriminative features (by PSI desc).

CompacityResult dataclass

CompacityResult(
    approx_threshold: float,
    fixed_k: int,
    per_row_k_for_approx: ndarray,
    per_row_approx_at_k: ndarray,
    per_archetype_summary: DataFrame,
    global_curve: DataFrame,
    per_archetype_curve: dict[int, DataFrame] = dict(),
)

Per-archetype SHAP compacity.

"How few features does the model actually need to explain a decision?" For each row the cumulative top-k absolute SHAP is compared against an approx fraction of the row's total absolute SHAP. Aggregated by archetype to produce a tier label (crisp / moderate / diffuse).

Attributes:

Name Type Description
approx_threshold float

Cumulative-fraction threshold used for per_row_k_for_approx.

fixed_k int

Fixed number of features used for per_row_approx_at_k.

per_row_k_for_approx ndarray

(n_target,) int — smallest k such that the top-k absolute contributions reach approx_threshold of the row's total. Set to n_features when the cumulative ratio never reaches the threshold (degenerate / all-zero rows).

per_row_approx_at_k ndarray

(n_target,) float ∈ [0, 1] — cumulative fraction reached by the top fixed_k features. 0 for all-zero rows.

per_archetype_summary DataFrame

Per-cluster aggregation with columns cluster_id, n, median_k_for_approx, p25_k, p75_k, mean_approx_at_k, tier. Includes cluster_id=-1 when noise rows are present; the plotter excludes them — same convention as :class:ErrorAnalysisResult.

global_curve DataFrame

(k, fraction_cases_with_approx_geq_threshold) across all non-noise rows. Used as the overlay-baseline in the plot.

per_archetype_curve dict[int, DataFrame]

Same shape as global_curve, one DataFrame per non-noise cluster.

tier_for

tier_for(cluster_id: int) -> str | None

Return the tier label for cluster_id or None if unknown.

FairnessResult dataclass

FairnessResult(
    sensitive_feature: str,
    reference_group: str,
    task_type: TaskType,
    group_metrics: DataFrame,
    disparity: DataFrame,
    parity: DataFrame,
    thresholds: dict[str, tuple[float, float | None]],
    flagged: list[tuple[str, str, float]] = list(),
    decision_threshold: float | None = None,
    target_class: Any | None = None,
    n_per_group: dict[str, int] = dict(),
    min_group_size: int = 30,
    group_confusion: (
        dict[str, list[list[int]]] | None
    ) = None,
    schema_version: int = SCHEMA_VERSION,
)

Group-level disparity metrics for a single sensitive feature.

Attributes:

Name Type Description
sensitive_feature str

Column name of the sensitive feature this result describes.

reference_group str

Group used as the denominator in the disparity calculations (defaults to the highest selection rate / mean prediction among groups with at least min_group_size rows).

task_type TaskType

Inherits from the model.

group_metrics DataFrame

Per-group performance / behaviour metrics. Columns depend on the task: classification reports selection rate, TPR, FPR, precision, recall; regression reports mean-prediction, RMSE, MAE.

disparity DataFrame

Per-group AIR, ME, SMD against reference_group.

parity DataFrame

Per-group / per-metric pass / fail of the configured thresholds. Columns: air_low_pass (AIR >= lower), air_high_pass (AIR <= upper, always True when the upper bound is opt-out / None), me_pass, smd_pass, and eligible (the group has at least :attr:min_group_size rows). An undefined (NaN) AIR passes both AIR gates. The gates are raw per-group results: an ineligible group can fail one yet is never flagged.

thresholds dict[str, tuple[float, float | None]]

{"air": (0.8, None), "me": (-0.05, 0.05), "smd": (-0.5, 0.5)} etc. The air upper element is None for the default one-sided EEOC band.

flagged list[tuple[str, str, float]]

Convenience list of (group, metric, value) tuples for every group/metric combination that breached its threshold band. Excludes the reference group (whose AIR is 1.0 and ME / SMD are 0.0 by construction) and ineligible groups; an undefined (NaN) AIR is not a breach.

decision_threshold float | None

Threshold used to convert predict_proba to hard labels for binary classification; None for regression.

target_class Any | None

Class label (e.g. "fraud", 1, True) as it appears in model.classes_, used when computing AIR / disparate-impact metrics; None for regression.

n_per_group dict[str, int]

Group sizes — useful for the viewer to grey-out tiny groups.

min_group_size int

Rows a group needs to be eligible (flaggable, and a candidate default reference).

group_confusion dict[str, list[list[int]]] | None

Binary-classification only: {group: [[tn, fp], [fn, tp]]} — the 2×2 confusion matrix at the decision threshold for each group. Lets the viewer show where a group's errors land (false positives vs false negatives), not just the aggregate selection rate. None for regression.

schema_version int

:data:SCHEMA_VERSION, checked when the result is unpickled.

ErrorTreeResult dataclass

ErrorTreeResult(
    tree: IAlgorithm | None,
    engine: EngineKey,
    task_type: TaskType,
    error_rate: float,
    cohort_summary: DataFrame,
    feature_names: list[str],
    rules_text: str,
    rules_dataframe: DataFrame,
    n_samples_used: int = 0,
    error_definition: Literal[
        "misclassification", "abs_residual_above_threshold"
    ] = "misclassification",
    residual_threshold: float | None = None,
    decision_threshold: float | None = None,
    selected_params: dict[str, Any] | None = None,
    categories_: CategoryMap = dict(),
    tree_diagrams_: list[str] | None = None,
    schema_version: int = SCHEMA_VERSION,
)

Cohort tree fit on the model's per-row error indicator.

Attributes:

Name Type Description
tree IAlgorithm | None

Fitted :class:IAlgorithm wrapper instance predicting is_error. None when the target was degenerate (no errors, or errors on every row) and no tree was fitted.

engine EngineKey

The engine key used for the fit.

task_type TaskType

:class:TaskType of the original model — used by the viewer to phrase the leaf labels.

error_rate float

Overall fraction of rows for which the model was wrong on the sub-sample used to fit the tree.

cohort_summary DataFrame

One row per leaf with columns [leaf_id, n_samples, n_errors, error_rate, error_rate_lo, error_rate_hi, lift, coverage, error_share, rule_text]. error_rate_lo / error_rate_hi are the two-sided 95 % Wilson confidence bounds on error_rate. coverage is the leaf's share of the population (n_samples / N) while error_share is its share of the model's mistakes (n_errors / total_errors). Sorted by the Wilson lower bound (error_rate_lo) descending — so a small leaf that only looks bad by sampling noise does not outrank a large, defensibly-high-error leaf — with ties broken by raw error_rate then n_samples. rule_text is the leaf's exact root→leaf condition chain.

feature_names list[str]

Column names ordered to match the matrix the tree consumed.

rules_text str

Plain-text rule dump from the engine's native exporter.

rules_dataframe DataFrame

Long-form node table (same shape as :attr:SurrogateResult.rules_dataframe).

n_samples_used int

Effective row count after :func:subsample_X_y.

error_definition Literal['misclassification', 'abs_residual_above_threshold']

"misclassification" (classification) or "abs_residual_above_threshold" (regression).

residual_threshold float | None

Concrete threshold used when error_definition is the regression variant. None for classification.

decision_threshold float | None

For binary classification with predict_proba: probability cut-off used when no native predict() was available. None otherwise.

selected_params dict[str, Any] | None

Engine knobs used for the fit.

categories_ CategoryMap

{str(column): fit-time category universe} for every column treated as categorical, captured on the training frame. :func:cohort_membership and :func:plot_error_tree replay it so a frame whose category universe differs from fit still encodes to the codes the tree was built on. Empty when there are no categoricals.

schema_version int

:data:SCHEMA_VERSION, checked when the result is unpickled.

worst_cohort property

worst_cohort: dict[str, Any] | None

Top row of :attr:cohort_summary (highest Wilson-lower error) as a dict.

n_trees property

n_trees: int

Total tree count in the underlying booster (0 without a tree).