Skip to content

Core types & enums

Shared infrastructure used across the public API — the task / space / model enums and the feature-encoding registries.

Enums

TaskType, SpaceType, ModelType, ClusterSelectionMethod, and ShapDistanceMetric are re-exported from the top level (from parleml_mli import TaskType).

types

TaskType

Bases: str, Enum

is_classification property

is_classification: bool

True for both binary and multiclass classification.

is_multiclass property

is_multiclass: bool

True only for multiclass classification (> 2 classes).

Encoding registries

How original feature columns map to model-input columns — supplied via fit_profiler(..., encoding_registry=...) for models trained on encoded frames.

EncodingRegistry

Bases: Protocol

Everything the rest of mli needs from a registry.

Replaces the old CleanFn surface. Implementations must be pickle-safe — no closures over un-importable modules, no lambdas in instance state. See :class:CompositeRegistry for the canonical pattern.

origin

origin(engineered_name: str) -> str | None

Return the canonical business-origin column, or None.

Replaces CleanFn. Must accept any string, never raise, return None when the name has no recognisable origin (e.g. an extra ID column that slipped into X).

describe

describe(engineered_name: str) -> FeatureEncoding

Return the full structured encoding for the given column.

Must always return a :class:FeatureEncoding (never None) — when nothing is known, return kind="UNKNOWN" / kind_family="UNKNOWN" / origin_columns=(name,).

classify_band

classify_band(
    engineered_name: str, value: float
) -> str | None

Return the band label for value under this encoding.

None when the encoding has no bands defined (the typical case for raw and aggregation columns) or when value falls outside every [lo, hi) band (below the first lo or at/above the last hi). Define a catch-all band with an open bound (±math.inf) to capture extremes explicitly.

known_kinds

known_kinds() -> Iterable[str]

All kind values this registry can emit. For tests / docs.

encoding_glossary

encoding_glossary(
    *, kinds_present: Iterable[str] | None = None
) -> str

Return the universal encoding-glossary block.

Modulated by which kinds actually appeared in this run (so a project that doesn't use _GP gets no _GP paragraph). Returns an empty string for :class:IdentityRegistry.

feature_family_glossary

feature_family_glossary(columns: Iterable[str]) -> str

Return a short paragraph per recognised prefix family.

E.g. when columns contain CSM_*/CH_* members, emit a paragraph for each — derived from the registry's structural knowledge of those families. Returns an empty string for :class:IdentityRegistry.

IdentityRegistry

Drop-in default: every column is its own origin, no encoding.

Used whenever the caller does not supply an explicit encoding_registry — fit_profiler falls back to this so public-dataset demos and projects without a naming convention keep working unchanged.

CompositeRegistry

CompositeRegistry(
    parsers: list[Parser],
    *,
    known_origin_columns: (
        frozenset[str] | Iterable[str]
    ) = frozenset(),
    known_origin_prefixes: (
        tuple[str, ...] | Iterable[str]
    ) = (),
    derived_features: (
        frozenset[str] | Iterable[str]
    ) = frozenset(),
    ip_columns: (
        frozenset[str] | Iterable[str]
    ) = frozenset(),
    zip_columns: (
        frozenset[str] | Iterable[str]
    ) = frozenset(),
    target_columns: tuple[str, ...] | Iterable[str] = (),
    encoding_glossary_template: str = "",
    family_glossary: dict[str, str] | None = None
)

Compose parsers in priority order to build a registry.

Most-specific parsers come first; the chain is walked left-to-right and the first parser that returns a non-None :class:FeatureEncoding wins. When every parser passes, the registry returns an UNKNOWN fallback rather than raising.

See :class:~parleml_mli.adapters.ml4_encoding.Ml4OriginRegistry for ml4 pipelines, or build a custom :class:CompositeRegistry for legacy / rtml-style naming.

bind

bind(
    X_columns: Iterable[str] | Any,
    *,
    target_col: str | None = None
) -> "CompositeRegistry"

Auto-populate known_origin_columns / target_columns.

Accepts either an explicit iterable of column names or any object with a .columns attribute (e.g. pd.DataFrame). Idempotent: rebinding with the same inputs is a no-op; rebinding with different inputs invalidates the describe-cache.

Returns self for fluent chaining.

FeatureEncoding dataclass

FeatureEncoding(
    kind: str,
    kind_family: str,
    origin_columns: tuple[str, ...],
    semantic: str,
    range_hint: str | None = None,
    band_thresholds: (
        tuple[tuple[str, float, float], ...] | None
    ) = None,
    metadata: dict[str, str] = dict(),
)

The structured semantics of a single engineered column.

Frozen + slotted for pickle/joblib stability and cheap hashability. Always returned by :meth:EncodingRegistry.describe; never None. When the registry cannot classify a name it returns one with kind="UNKNOWN" / kind_family="UNKNOWN" and origin_columns=(name,) rather than raising.

kind instance-attribute

kind: str

Precise, machine-friendly classification of this column.

Examples: "WE", "WE2", "TE", "AGG_COUNT", "AGG_SUM", "IP_PREF", "MATCH", "DERIVED", "RAW", "UNKNOWN".

kind_family instance-attribute

kind_family: str

Coarse family this kind belongs to.

Used by prompt builders / narrators that want to group features without caring about the precise variant. Examples: "WE" (covers WE + WE2), "AGG" (covers all AGG_*), "STATELESS" (covers email / IP / time / address decompositions), "RAW", "UNKNOWN".

origin_columns instance-attribute

origin_columns: tuple[str, ...]

One or more business-origin columns this feature derives from.

The first element is the canonical origin used by SHAP-rollup and origin(). For interaction / match / distance columns the tuple holds both operands in alphabetical order.

semantic instance-attribute

semantic: str

One-line plain-English description of what this column measures.

Composed by the registry from the layered decomposition (e.g. "Weight of Evidence vs FRAUD_FLAG of the first 3 octets of CUSTOMER_IP_ADDRESS"). Used verbatim by the LLM rule narrator.

range_hint class-attribute instance-attribute

range_hint: str | None = None

Free-text guidance about the numeric scale, when known.

Example: "log-odds; 0 ≈ neutral; |x| ≥ 2 ≈ strong". None when the column is non-numeric or the registry has no opinion.

band_thresholds class-attribute instance-attribute

band_thresholds: (
    tuple[tuple[str, float, float], ...] | None
) = None

Optional [(label, low_inclusive, high_exclusive), …] bands.

Used by :meth:EncodingRegistry.classify_band. None when the encoding does not admit fixed business-meaningful thresholds.

metadata class-attribute instance-attribute

metadata: dict[str, str] = field(default_factory=dict)

Free-form per-encoding metadata.

Common keys: "prefix_family" (for raw prefix-tagged columns), "window_days" / "agg_op" / "agg_col" (for aggregations), "inner_kind" (for layered encoders, e.g. "WE" over "IP_PREF"). Read by prompt builders that need finer detail than the public dataclass fields surface.

Feature mapping

align_original_data

align_original_data(
    X_engineered: DataFrame,
    X_original: DataFrame,
    key_column: str,
) -> tuple[DataFrame, DataFrame, ndarray]

Align the original dataset to the engineered dataset by key.

  1. Deduplicate X_engineered by key_column (drop oversampled rows).
  2. Inner-join on key_column, preserving the deduplicated engineered row order.

Returns:

Name Type Description
X_eng_dedup DataFrame

Engineered data after deduplication, index reset.

X_orig_aligned DataFrame

Original data rows matching the deduplicated engineered data, same row order, index reset.

dedup_mask ndarray

Boolean mask into the original (pre-dedup) X_engineered indicating which rows survived deduplication. Use this to slice labels and shap_values consistently.

Positive class and decision threshold

One owner each: resolve_target decides a binary run's positive class (an explicit positive_class names a label by ml4's string-or-numeric rule) and resolve_threshold its decision cut. Every entry point resolves both once and stores them on ProfilingResult.target_ / ProfilingResult.threshold_; every threshold-aware artifact, error analysis included, reads that record.

resolve_target

resolve_target(
    y: ArrayLike | None,
    *,
    model: Any = None,
    classes: ArrayLike | None = None,
    positive_class: Any = None,
    task_type: TaskType | str | None = None
) -> ResolvedTarget

Resolve the task, class order and positive class of a run once.

Parameters:

Name Type Description Default
y ArrayLike | None

Ground-truth labels, or None when only the class order is known.

required
model Any

Fitted estimator; its classes_ fixes the class order.

None
classes ArrayLike | None

Explicit class order (e.g. a persisted classes_); wins over model.classes_.

None
positive_class Any

Explicit positive label (binary only), matched to a label by ml4's :func:~parleml_ml4.delivery.scoring.resolve_label rule — string form or numeric form, so a stored "True" / "1.0" names True / 1.0.

None
task_type TaskType | str | None

Task; inferred from model / classes / y when None.

None

Raises:

Type Description
ValueError

A binary target with more than two classes, missing labels, an explicit positive_class that names no class, a single observed label that nothing names as positive or negative, or — with a model — a positive class other than model.classes_[1].

ResolvedTarget dataclass

ResolvedTarget(
    task_type: TaskType,
    classes: tuple[Any, ...] | None,
    positive_label: Any | None,
    positive_col: int | None,
    source: TargetSource,
    y_bin: ndarray | None = None,
)

The resolved target of one run.

Attributes:

Name Type Description
task_type TaskType

BINARY / MULTICLASS / REGRESSION.

classes tuple[Any, ...] | None

Class labels in the model's order (classes= or model.classes_); without either, the observed labels — negative first for a binary target, sorted for multiclass. None for regression.

positive_label Any | None

The positive class (binary only).

positive_col int | None

Index of :attr:positive_label in :attr:classes — the predict_proba / SHAP column the positive class occupies.

source TargetSource

How :attr:positive_label was chosen (see :data:TargetSource).

y_bin ndarray | None

int8 {0, 1} indicator of the positive class over the rows the target was resolved from; None without y or for non-binary tasks.

is_binary property

is_binary: bool

True for a binary task.

negative_label property

negative_label: Any | None

The binary negative class, or None when it is not known.

positive_key property

positive_key: str | None

:func:class_key of :attr:positive_label.

mask property

mask: ndarray | None

Boolean positive-class mask over the resolved rows.

base_rate property

base_rate: float | None

Share of positive rows, or None without resolved rows.

binarise

binarise(y: ArrayLike) -> ndarray

int8 {0, 1} indicator of the positive class for y.

Raises:

Type Description
ValueError

The target is not binary or has no positive class, y has missing values, or y carries a label outside :attr:classes.

subset

subset(rows: ArrayLike | slice) -> ResolvedTarget

The same target restricted to rows of :attr:y_bin.

to_meta

to_meta() -> dict[str, Any]

JSON-safe summary for run metadata.

resolve_threshold

resolve_threshold(
    spec: float | str | None,
    *,
    target: ResolvedTarget | None,
    scores: ArrayLike | None = None,
    served: float | None = None
) -> ResolvedThreshold

Resolve the binary decision threshold of one run.

Parameters:

Name Type Description Default
spec float | str | None

float cut, "f1" / "auto" / "f<beta>" to tune, or None to keep the model's own decision.

required
target ResolvedTarget | None

The run's :class:~parleml_mli.core.target.ResolvedTarget.

required
scores ArrayLike | None

Positive-class scores aligned with target.y_bin — the rows an in-sample tune fits on.

None
served float | None

The deployed pipeline's published cut; wins over spec.

None

Raises:

Type Description
ValueError

An unparseable spec, or a cut outside [0, 1].

ResolvedThreshold dataclass

ResolvedThreshold(
    value: float | None,
    source: ThresholdSource,
    spec: float | str | None = None,
    beta: float | None = None,
    n_rows: int = 0,
    scale: ThresholdScale = "model",
)

A resolved binary decision threshold.

Attributes:

Name Type Description
value float | None

The cut on the positive-class score, or None when thresholding is disabled / not applicable.

source ThresholdSource

How :attr:value was obtained (see :data:ThresholdSource).

spec float | str | None

The spec it was resolved from.

beta float | None

F-beta weight of a tuned spec.

n_rows int

Rows a tuned / degenerate cut was fitted on.

scale ThresholdScale

Probability scale :attr:value lives on (see :data:ThresholdScale).

cut property

cut: float

The cut applied to positive-class probabilities: :attr:value, or :data:MODEL_DEFAULT_CUT (the model's own decision) without one.

is_tuned property

is_tuned: bool

True when an F-beta sweep chose :attr:value.

to_meta

to_meta() -> dict[str, Any]

JSON-safe summary for run metadata.

Persisted artifacts

Every artifact mli persists carries a schema_version; loading one written under another schema (or none) raises StaleArtifactError, re-exported from the top level (from parleml_mli import StaleArtifactError), with the instruction to rebuild it.

StaleArtifactError

Bases: StaleArtifactError

A persisted artifact was written under a schema this mli does not read.

ml4's refusal, so one except catches a stale artifact of either library; like it, the message never names the file (path does).

require_schema_version

require_schema_version(
    state: dict[str, Any], *, artifact: str, expected: int
) -> None

Raise :class:StaleArtifactError unless state carries schema_version == expected.

state is the pickled __dict__ handed to the artifact's __setstate__.