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 |
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: |
None
|
served_threshold
|
float | None
|
The deployed model's published cut-off; wins over
|
None
|
positive_class
|
Any
|
The positive label of a binary |
None
|
config
|
'ExplanationsConfig | None'
|
Typed :class: |
None
|
adapter
|
Any
|
Optional :class: |
None
|
Returns:
| Type | Description |
|---|---|
ExplanationsResult
|
A fresh container with whichever artifacts succeeded populated.
Failures are surfaced via |
Raises:
| Type | Description |
|---|---|
ValueError
|
The call itself is inconsistent: |
Examples:
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 ¶
Render the PDP for feature. Raises KeyError if missing.
plot_ale ¶
Render the ALE for feature. Raises KeyError if missing.
plot_shap_interactions ¶
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 ¶
Render the permutation-importance bar chart.
plot_fairness ¶
Render the per-group AIR / SMD / ME chart for sensitive_feature.
to_dict ¶
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']
|
|
grid |
ndarray
|
|
average |
ndarray
|
|
ice_std |
ndarray | None
|
Optional |
per_cluster_average |
dict[int, ndarray]
|
Optional |
n_samples_used |
int
|
Number of rows sampled from |
response_method |
str
|
Sklearn response method used ( |
target_class |
Any | None
|
Class label pinned for multiclass classifiers (e.g. |
schema_version |
int
|
Bumped when the dataclass shape or a field's meaning changes; mirrors
|
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
|
|
ale |
ndarray
|
|
bin_counts |
ndarray
|
|
n_samples_used |
int
|
Rows sampled from |
response_method |
str
|
|
target_class |
Any | None
|
Class label pinned for classifiers ( |
schema_version |
int
|
Bumped when the dataclass shape or a field's meaning changes; mirrors
|
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 |
tuple[str, ...]
|
The features a curve was computed for, most important first (in the
caller's order for |
skipped |
dict[str, str]
|
|
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: |
engine |
EngineKey
|
The engine key used for the fit ( |
task_type |
TaskType
|
:class: |
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 |
fidelity_train |
float
|
In-sample fidelity on the surrogate's training slice. Always
populated. Compare against :attr: |
fidelity_holdout |
float | None
|
Held-out fidelity — equal to :attr: |
n_holdout_samples |
int
|
Row count of the held-out slice ( |
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 |
rules_dataframe |
DataFrame
|
Long-form |
n_samples_used |
int
|
Effective row count after :func: |
tree_diagrams_ |
list[str] | None
|
Annotated Graphviz DOT strings baked at fit time via the shared
:func: |
target_kind |
Literal['prediction', 'residual']
|
|
residual_kind |
str | None
|
|
fidelity_curve |
DataFrame | None
|
|
selected_params |
dict[str, Any] | None
|
|
classes_ |
ndarray | None
|
|
decision_threshold |
float | None
|
For binary classification prediction surrogates only — the
probability cut-off used to derive the surrogate target.
|
categories_ |
CategoryMap
|
|
schema_version |
int
|
:data: |
predict ¶
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 ¶
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 |
n_repeats |
int
|
Number of shuffles per feature. |
scoring |
str
|
Resolved scorer name (e.g. |
sample_size |
int
|
Effective row count after :func: |
task_type |
TaskType
|
Inherits from the model. |
target_class |
Any | None
|
The class label (as it appears in |
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
|
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
|
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
|
|
comparison |
PSIComparison
|
Which question this result answers — see :data: |
schema_version |
int
|
:data: |
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 |
fixed_k |
int
|
Fixed number of features used for |
per_row_k_for_approx |
ndarray
|
|
per_row_approx_at_k |
ndarray
|
|
per_archetype_summary |
DataFrame
|
Per-cluster aggregation with columns |
global_curve |
DataFrame
|
|
per_archetype_curve |
dict[int, DataFrame]
|
Same shape as |
tier_for ¶
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 |
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 |
parity |
DataFrame
|
Per-group / per-metric pass / fail of the configured
thresholds. Columns: |
thresholds |
dict[str, tuple[float, float | None]]
|
|
flagged |
list[tuple[str, str, float]]
|
Convenience list of |
decision_threshold |
float | None
|
Threshold used to convert |
target_class |
Any | None
|
Class label (e.g. |
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 |
group_confusion |
dict[str, list[list[int]]] | None
|
Binary-classification only: |
schema_version |
int
|
:data: |
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: |
engine |
EngineKey
|
The engine key used for the fit. |
task_type |
TaskType
|
:class: |
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
|
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: |
n_samples_used |
int
|
Effective row count after :func: |
error_definition |
Literal['misclassification', 'abs_residual_above_threshold']
|
|
residual_threshold |
float | None
|
Concrete threshold used when |
decision_threshold |
float | None
|
For binary classification with |
selected_params |
dict[str, Any] | None
|
Engine knobs used for the fit. |
categories_ |
CategoryMap
|
|
schema_version |
int
|
:data: |