Skip to content

API: constraints

constraint

constraint(
    text: str, feature_names: Sequence[str] | None = None
) -> Linear

Parse "2*a - b <= 3"-style sugar into a canonical Linear object.

Only linear expressions over +/-/* and one of <=/>=/ == are accepted; anything richer (nonlinear terms, multiple comparisons) must be written as constraint objects directly. Terms on both sides are folded into coefficients/rhs on the left-hand side's convention, so "a <= b" and "a - b <= 0" produce the same Linear. See Constraints.

PARAMETER DESCRIPTION
text

The constraint string, e.g. "2*a - b <= 3" or "income >= 0".

TYPE: str

feature_names

When given, every identifier in text is validated against it immediately; when omitted (the default), unknown identifiers are only caught later, at Explainer/compile_constraints time.

TYPE: Sequence[str] | None DEFAULT: None

RETURNS DESCRIPTION
The parsed ``Linear`` constraint.
RAISES DESCRIPTION
ConstraintParseError

If text contains an unexpected character, is missing an operator or a term, has trailing tokens after the right-hand side, references no feature at all, or (when feature_names is given) references an identifier not in it. The message carries a caret marking the offending token.

Source code in src/treecf/constraints/parser.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def constraint(text: str, feature_names: Sequence[str] | None = None) -> Linear:
    """Parse ``"2*a - b <= 3"``-style sugar into a canonical ``Linear`` object.

    Only linear expressions over ``+``/``-``/``*`` and one of ``<=``/``>=``/
    ``==`` are accepted; anything richer (nonlinear terms, multiple
    comparisons) must be written as constraint objects directly. Terms on
    both sides are folded into ``coefficients``/``rhs`` on the left-hand
    side's convention, so ``"a <= b"`` and ``"a - b <= 0"`` produce the same
    ``Linear``. See [Constraints](../concepts/constraints.md).

    Parameters
    ----------
    text
        The constraint string, e.g. ``"2*a - b <= 3"`` or
        ``"income >= 0"``.
    feature_names
        When given, every identifier in ``text`` is validated
        against it immediately; when omitted (the default), unknown
        identifiers are only caught later, at
        ``Explainer``/``compile_constraints`` time.

    Returns
    -------
    The parsed ``Linear`` constraint.

    Raises
    ------
    ConstraintParseError
        If ``text`` contains an unexpected character, is
        missing an operator or a term, has trailing tokens after the
        right-hand side, references no feature at all, or (when
        ``feature_names`` is given) references an identifier not in it.
        The message carries a caret marking the offending token.
    """
    tokens = _tokenize(text)
    parser = _Parser(text, tokens, feature_names)
    return parser.parse()

objects

Canonical constraint objects. Frozen dataclasses; validation at compile time.

Pass these (or a constraint() string) to Explainer(..., constraints=[...]). See Constraints for what each one compiles to and how they compose.

Freeze dataclass

Freeze(feature: str)

The feature is immutable: the counterfactual keeps the factual value.

ATTRIBUTE DESCRIPTION
feature

The feature name to freeze.

TYPE: str

Monotone dataclass

Monotone(feature: str, direction: str)

The feature may only move in one direction from the factual value.

ATTRIBUTE DESCRIPTION
feature

The feature name to constrain.

TYPE: str

direction

"increase" (the counterfactual value must be >= the factual) or "decrease" (<= the factual).

TYPE: str

Range dataclass

Range(feature: str, lo: float, hi: float)

Hard domain bounds for the counterfactual value (inclusive).

ATTRIBUTE DESCRIPTION
feature

The feature name to bound.

TYPE: str

lo

Lower bound, inclusive.

TYPE: float

hi

Upper bound, inclusive.

TYPE: float

Linear dataclass

Linear(
    coefficients: dict[str, float],
    op: str,
    rhs: float,
    missing_policy: str = "satisfied",
)

Linear inter-feature constraint: sum(coef * feature) op rhs.

missing_policy resolves the constraint when a referenced feature is NaN in the counterfactual: "satisfied" (vacuously true, the default), "violated"/"forbid_missing" (the counterfactual may not use NaN there). The exact backend supports single-feature and the canonical two-feature order-pair shape exactly; any other multi-feature shape raises ConstraintValidationError naming backend="genetic" as the fallback — see Certification.

ATTRIBUTE DESCRIPTION
coefficients

{feature: coefficient} for every feature in the sum; at least one entry.

TYPE: dict[str, float]

op

"<=", ">=", or "==".

TYPE: str

rhs

The right-hand-side constant.

TYPE: float

missing_policy

"satisfied" (the default — the constraint is vacuously satisfied when a referenced feature is NaN) or "violated"/"forbid_missing" (a NaN there fails the constraint, so the counterfactual may not use NaN on a referenced feature).

TYPE: str

Equals dataclass

Equals(feature: str, value: float)

Binary-feature equality (used standalone or inside Implies).

ATTRIBUTE DESCRIPTION
feature

The feature name to compare.

TYPE: str

value

The value feature must equal (typically 0.0/1.0 for a binary indicator).

TYPE: float

Implies dataclass

Implies(condition: Equals, consequence: Equals)

If condition holds then consequence must hold; binary features only.

ATTRIBUTE DESCRIPTION
condition

The antecedent equality.

TYPE: Equals

consequence

The equality condition requires when it holds.

TYPE: Equals

OneHot dataclass

OneHot(features: tuple[str, ...])

The listed binary columns sum to exactly one.

ATTRIBUTE DESCRIPTION
features

The mutually exclusive binary feature names; at least two.

TYPE: tuple[str, ...]

AllowedCategories dataclass

AllowedCategories(feature: str, allowed: object)

The categorical feature may only take the listed category codes or names.

Entries are integer codes (0..cardinality-1) or display names resolved through the model's category names. Only valid on a categorical feature; several declarations on one feature intersect.

ATTRIBUTE DESCRIPTION
feature

The categorical feature name to restrict.

TYPE: str

allowed

The permitted codes (ints) or category names (strs).

TYPE: tuple[int | str, ...]

Source code in src/treecf/constraints/objects.py
163
164
165
166
def __init__(self, feature: str, allowed: object) -> None:
    object.__setattr__(self, "feature", feature)
    items = (allowed,) if isinstance(allowed, int | str) else tuple(allowed)  # type: ignore[arg-type]
    object.__setattr__(self, "allowed", items)

AllowMissing dataclass

AllowMissing(
    feature: str,
    delta_miss: float,
    delta_from_miss: float | None = None,
)

NaN is a feasible counterfactual value for this feature.

delta_miss prices the value<->NaN transition; pass delta_from_miss for an asymmetric NaN->value cost (defaults to delta_miss). See Missing values.

ATTRIBUTE DESCRIPTION
feature

The feature name NaN is allowed on.

TYPE: str

delta_miss

Distance cost of a value-to-NaN change on this feature.

TYPE: float

delta_from_miss

Distance cost of a NaN-to-value change on this feature; defaults to delta_miss when None.

TYPE: float | None

Mining

suggest_constraints

suggest_constraints(
    X: FloatArray,
    feature_names: Sequence[str] | None = None,
    min_support: float = 1.0,
    top_k: int = 50,
    report_threshold: float = 0.999,
    include_ranges: bool = False,
) -> SuggestionSet

Mine candidate invariants from a background sample, for human review.

Scans X for pairwise orders and equalities, binary implications (a=1 => b=1), one-hot groups, missingness links (miss(a) => miss(b)), and integer-valuedness; optionally also observed 1st/99th percentile ranges. Mined constraints are sample invariants, not domain truths — min_support=1.0 on a finite sample can be coincidence — so nothing here is ever auto-applied; inspect result[i].as_code() and pass the ones you accept to Explainer(..., constraints=[...]) yourself. See Constraints — mining candidates from data.

PARAMETER DESCRIPTION
X

Background sample, one row per instance, aligned to feature_names (or the model's own feature order, if that is how feature_names was derived).

TYPE: FloatArray

feature_names

Column names for X; defaults to ["f0", "f1", ...] when omitted.

TYPE: Sequence[str] | None DEFAULT: None

min_support

Minimum fraction of co-present rows a pairwise order must hold on to be suggested ("order" kind only; every other kind requires exact 1.0 support). Defaults to 1.0 (only invariants with zero observed violations).

TYPE: float DEFAULT: 1.0

top_k

Maximum number of suggestions to return, after ranking by support and rationale strength; findings are not subject to this limit.

TYPE: int DEFAULT: 50

report_threshold

Minimum support for a near-invariant (one below min_support) to be reported as a DataQualityFinding instead of silently dropped.

TYPE: float DEFAULT: 0.999

include_ranges

When True, also emit one advisory "range" suggestion per feature with its observed 1st/99th percentile band (padded by 10%); these carry constraint=None.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
A ``SuggestionSet`` with ``suggestions`` (candidate constraints,
ranked and trimmed to ``top_k``) and ``findings`` (near-invariants
that fell short of ``min_support``).
Source code in src/treecf/mining.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
def suggest_constraints(
    X: FloatArray,
    feature_names: Sequence[str] | None = None,
    min_support: float = 1.0,
    top_k: int = 50,
    report_threshold: float = 0.999,
    include_ranges: bool = False,
) -> SuggestionSet:
    """Mine candidate invariants from a background sample, for human review.

    Scans ``X`` for pairwise orders and equalities, binary implications
    (``a=1 => b=1``), one-hot groups, missingness links (``miss(a) =>
    miss(b)``), and integer-valuedness; optionally also observed 1st/99th
    percentile ranges. Mined constraints are sample invariants, not domain
    truths — ``min_support=1.0`` on a finite sample can be coincidence — so
    nothing here is ever auto-applied; inspect ``result[i].as_code()`` and
    pass the ones you accept to ``Explainer(..., constraints=[...])``
    yourself. See
    [Constraints — mining candidates from
    data](../concepts/constraints.md#mining-candidates-from-data).

    Parameters
    ----------
    X
        Background sample, one row per instance, aligned to
        ``feature_names`` (or the model's own feature order, if that is
        how ``feature_names`` was derived).
    feature_names
        Column names for ``X``; defaults to ``["f0", "f1",
        ...]`` when omitted.
    min_support
        Minimum fraction of co-present rows a pairwise order
        must hold on to be suggested (``"order"`` kind only; every other
        kind requires exact ``1.0`` support). Defaults to ``1.0`` (only
        invariants with zero observed violations).
    top_k
        Maximum number of suggestions to return, after ranking by
        support and rationale strength; findings are not subject to this
        limit.
    report_threshold
        Minimum support for a near-invariant (one below
        ``min_support``) to be reported as a ``DataQualityFinding``
        instead of silently dropped.
    include_ranges
        When ``True``, also emit one advisory ``"range"``
        suggestion per feature with its observed 1st/99th percentile band
        (padded by 10%); these carry ``constraint=None``.

    Returns
    -------
    A ``SuggestionSet`` with ``suggestions`` (candidate constraints,
    ranked and trimmed to ``top_k``) and ``findings`` (near-invariants
    that fell short of ``min_support``).
    """
    X = np.asarray(X, dtype=np.float64)
    n, p = X.shape
    names = list(feature_names) if feature_names is not None else [f"f{i}" for i in range(p)]
    present = ~np.isnan(X)

    binary = [
        j
        for j in range(p)
        if present[:, j].any() and np.isin(X[present[:, j], j], (0.0, 1.0)).all()
    ]
    binary_set = set(binary)

    suggestions: list[SuggestedConstraint] = []
    findings: list[DataQualityFinding] = []

    # --- pairwise order / equality (O(p^2 n), vectorized per anchor column) ---
    order_edges: dict[tuple[int, int], SuggestedConstraint] = {}
    equal_pairs: list[tuple[int, int]] = []
    for a in range(p):
        for b in range(a + 1, p):
            both = present[:, a] & present[:, b]
            n_both = int(both.sum())
            if n_both == 0:
                continue
            va, vb = X[both, a], X[both, b]
            viol_ab = int((va > vb).sum())  # violations of a <= b
            viol_ba = int((vb > va).sum())
            if viol_ab == 0 and viol_ba == 0:
                equal_pairs.append((a, b))
                suggestions.append(
                    SuggestedConstraint(
                        constraint=Linear({names[a]: 1.0, names[b]: -1.0}, op="==", rhs=0.0),
                        kind="equality",
                        support=1.0,
                        n_rows_checked=n_both,
                        n_violations=0,
                        rationale=f"{names[a]} == {names[b]} on every co-present row; "
                        "usually a redundant feature, not a constraint to impose",
                    )
                )
                continue
            for lo_idx, hi_idx, viol in ((a, b, viol_ab), (b, a, viol_ba)):
                support = 1.0 - viol / n_both
                if support >= min_support:
                    order_edges[(lo_idx, hi_idx)] = SuggestedConstraint(
                        constraint=Linear(
                            {names[lo_idx]: 1.0, names[hi_idx]: -1.0}, op="<=", rhs=0.0
                        ),
                        kind="order",
                        support=support,
                        n_rows_checked=n_both,
                        n_violations=viol,
                        evidence=_violation_evidence(X, present, lo_idx, hi_idx),
                        rationale=_order_rationale(names[lo_idx], names[hi_idx]),
                    )
                elif support >= report_threshold:
                    findings.append(
                        DataQualityFinding(
                            kind="near_invariant",
                            description=f"{names[lo_idx]} <= {names[hi_idx]} holds on "
                            f"{support:.4%} of rows — likely an ETL defect",
                            support=support,
                            n_rows_checked=n_both,
                            n_violations=viol,
                            evidence=_violation_evidence(X, present, lo_idx, hi_idx),
                        )
                    )

    # equality-class collapse, then transitive reduction of the <= graph
    representative = _union_find(p, equal_pairs)
    rep_edges = {
        (representative[a], representative[b])
        for (a, b) in order_edges
        if representative[a] != representative[b]
    }
    reduced = transitive_reduction(rep_edges)
    for (a, b), suggestion in order_edges.items():
        edge = (representative[a], representative[b])
        if edge in reduced and edge[0] != edge[1]:
            suggestions.append(suggestion)
            reduced.discard(edge)  # one edge per class pair

    # --- binary implications A=1 => B=1 ---
    for a in binary:
        a_is_one = present[:, a] & (X[:, a] == 1.0)
        if not a_is_one.any():
            continue
        for b in binary:
            if a == b:
                continue
            checked = a_is_one & present[:, b]
            if not checked.any():
                continue
            if (X[checked, b] == 1.0).all():
                suggestions.append(
                    SuggestedConstraint(
                        constraint=Implies(Equals(names[a], 1.0), Equals(names[b], 1.0)),
                        kind="implication",
                        support=1.0,
                        n_rows_checked=int(checked.sum()),
                        n_violations=0,
                        rationale=f"{names[a]}=1 always co-occurs with {names[b]}=1",
                    )
                )

    # --- one-hot groups: exclusivity components with row sum == 1 ---
    complete_binary = [j for j in binary if present[:, j].all()]
    for component in _exclusivity_components(X, complete_binary):
        if len(component) < 2:
            continue
        if np.all(X[:, component].sum(axis=1) == 1.0):
            suggestions.append(
                SuggestedConstraint(
                    constraint=OneHot(tuple(names[j] for j in component)),
                    kind="onehot",
                    support=1.0,
                    n_rows_checked=n,
                    n_violations=0,
                    rationale="binary columns with row sum identically 1",
                )
            )

    # --- missingness links miss(A) => miss(B) ---
    for a in range(p):
        miss_a = ~present[:, a]
        if not miss_a.any():
            continue
        for b in range(p):
            if a == b or present[:, b].all():
                continue
            if (~present[miss_a, b]).all():
                both_ways = bool((~present[~present[:, b], a]).all())
                suggestions.append(
                    SuggestedConstraint(
                        constraint=None,
                        kind="missing_link",
                        support=1.0,
                        n_rows_checked=int(miss_a.sum()),
                        n_violations=0,
                        rationale=(
                            f"miss({names[a]}) {'<=>' if both_ways else '=>'} miss({names[b]}); "
                            "consider joint AllowMissing / missing_policy"
                        ),
                    )
                )
            if bool((~present[~present[:, b], a]).all()):
                break  # symmetric link already reported from this anchor

    # --- integer-valuedness -> value_policy suggestion ---
    for j in range(p):
        col = X[present[:, j], j]
        if len(col) and j not in binary_set and np.all(col == np.round(col)):
            suggestions.append(
                SuggestedConstraint(
                    constraint=None,
                    kind="integer",
                    support=1.0,
                    n_rows_checked=len(col),
                    n_violations=0,
                    rationale=(
                        f"{names[j]} is integer-valued; "
                        f'value_policy={{"{names[j]}": "integer"}}'
                    ),
                )
            )

    if include_ranges:
        for j in range(p):
            col = X[present[:, j], j]
            if len(col):
                lo, hi = np.percentile(col, [1, 99])
                pad = 0.1 * (hi - lo)
                suggestions.append(
                    SuggestedConstraint(
                        constraint=None,
                        kind="range",
                        support=1.0,
                        n_rows_checked=len(col),
                        n_violations=0,
                        rationale=f"observed 1-99% range [{lo:.4g}, {hi:.4g}] padded by {pad:.4g}",
                    )
                )

    suggestions.sort(key=_rank_key, reverse=True)
    return SuggestionSet(suggestions=tuple(suggestions[:top_k]), findings=tuple(findings))

SuggestedConstraint dataclass

SuggestedConstraint(
    constraint: Constraint | None,
    kind: str,
    support: float,
    n_rows_checked: int,
    n_violations: int,
    evidence: list[dict[str, object]] = list(),
    rationale: str = "",
)

One candidate invariant mined from a background sample, for human review.

suggest_constraints never applies a suggestion itself; the workflow is to inspect as_code()/rationale/evidence, decide which suggestions are real domain rules, and pass their constraint objects to Explainer(..., constraints=[...]) explicitly. See Constraints — mining candidates from data.

ATTRIBUTE DESCRIPTION
constraint

The compiled constraint object this suggestion proposes, or None for an advisory kind ("missing_link", "integer", "range") that has no direct constraint-object form — read rationale for what to do about it instead.

TYPE: Constraint | None

kind

"order" (a <= b on every co-present row), "equality" (a == b, usually a redundant feature), "implication" (a=1 => b=1 on binary features), "onehot" (mutually exclusive binary group), "missing_link" (miss(a) => miss(b) or <=>), "integer" (integer-valued column, a value_policy candidate), or "range" (observed 1–99th percentile band, only when include_ranges=True).

TYPE: str

support

Fraction of checked rows the invariant held on, in [0, 1]; 1.0 for every kind except "order", which can be suggested down to min_support.

TYPE: float

n_rows_checked

Number of rows the check was evaluated over; what counts as checkable depends on kind — co-present rows for "order"/"equality", rows where the antecedent holds and the consequent feature is present for "implication", rows where a is missing for "missing_link", present rows for "integer"/"range".

TYPE: int

n_violations

Number of those rows that violated the invariant; 0 for every kind except "order".

TYPE: int

evidence

Up to 5 violating rows, {"row": index, "values": (a, b)} — populated for "order" suggestions only.

TYPE: list[dict[str, object]]

rationale

Human-readable justification: shared name tokens for "order", or the advisory text for kinds with no constraint.

TYPE: str

as_code

as_code() -> str

This suggestion rendered as a copy-pasteable Python snippet.

RETURNS DESCRIPTION
A ``constraint(...)``/``Implies(...)``/``OneHot(...)`` call (or,
for kinds with no direct constraint form, a ``#``-commented
description) followed by a ``# support=..., n=...`` trailer.
Source code in src/treecf/mining.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def as_code(self) -> str:
    """This suggestion rendered as a copy-pasteable Python snippet.

    Returns
    -------
    A ``constraint(...)``/``Implies(...)``/``OneHot(...)`` call (or,
    for kinds with no direct constraint form, a ``#``-commented
    description) followed by a ``# support=..., n=...`` trailer.
    """
    tail = f"  # support={self.support:.4f}, n={self.n_rows_checked}"
    if self.kind == "order" and isinstance(self.constraint, Linear):
        coeffs = self.constraint.coefficients
        smaller = max(coeffs, key=lambda k: coeffs[k])
        larger = min(coeffs, key=lambda k: coeffs[k])
        return f'constraint("{smaller} <= {larger}")' + tail
    if self.kind == "equality" and isinstance(self.constraint, Linear):
        a, b = list(self.constraint.coefficients)
        return f'# equality: {a} == {b} — likely a redundant feature' + tail
    if self.kind == "implication" and isinstance(self.constraint, Implies):
        c = self.constraint
        return (
            f'Implies(Equals("{c.condition.feature}", {c.condition.value:g}), '
            f'Equals("{c.consequence.feature}", {c.consequence.value:g}))' + tail
        )
    if self.kind == "onehot" and isinstance(self.constraint, OneHot):
        inner = ", ".join(f'"{f}"' for f in self.constraint.features)
        return f"OneHot(({inner}))" + tail
    return f"# {self.kind}: {self.rationale}" + tail

DataQualityFinding dataclass

DataQualityFinding(
    kind: str,
    description: str,
    support: float,
    n_rows_checked: int,
    n_violations: int,
    evidence: list[dict[str, object]] = list(),
)

A near-invariant that fell short of min_support — likely an ETL defect.

Reported separately from SuggestedConstraint because support in [report_threshold, min_support) usually means a rule that should be universal is being violated by a small number of dirty rows, not that the rule is genuinely conditional — worth fixing upstream rather than encoding the exception as a constraint.

ATTRIBUTE DESCRIPTION
kind

Always "near_invariant" in this release.

TYPE: str

description

Human-readable summary, e.g. "a <= b holds on 99.95% of rows — likely an ETL defect".

TYPE: str

support

Fraction of checked rows the near-invariant held on, in [report_threshold, min_support).

TYPE: float

n_rows_checked

Number of rows where both referenced features were present.

TYPE: int

n_violations

Number of those rows that violated the near-invariant.

TYPE: int

evidence

Up to 5 violating rows, {"row": index, "values": (a, b)}.

TYPE: list[dict[str, object]]

Plausibility

Plausibility dataclass

Plausibility(if_ir: EnsembleIR, max_anomaly_score: float)

A hard isolation-forest bound keeping counterfactuals inside the data manifold.

Construct through Plausibility.isolation_forest rather than the constructor directly. Pass the result as Explainer(..., plausibility=...); every returned counterfactual then also satisfies anomaly_score(x_cf) <= max_anomaly_score, enforced as a hard constraint by every backend. Cannot be combined with AllowMissing or a NaN-containing factual (isolation forests define no NaN routing). See Plausibility.

ATTRIBUTE DESCRIPTION
if_ir

The isolation forest, parsed through the same tree IR the model uses.

TYPE: EnsembleIR

max_anomaly_score

The upper bound on anomaly_score; lower values are stricter (closer to the training distribution).

TYPE: float

normalizer property

normalizer: float

The average path length c(n) for the forest's subsample size n.

Standard isolation-forest normalizer, derived from if_ir's max_samples metadata; used to turn a raw total path length into the [0, 1] anomaly score.

min_total_path property

min_total_path: float

The feasibility bound compiled into every backend's plausibility check.

Equivalent to max_anomaly_score re-expressed as a lower bound on the summed depth-adjusted path length across every tree: a counterfactual is plausible iff its total path length is at least this value.

isolation_forest classmethod

isolation_forest(
    model_or_ir: object, max_anomaly_score: float = 0.55
) -> Plausibility

Build a Plausibility bound from a fitted isolation forest.

PARAMETER DESCRIPTION
model_or_ir

A native isolation-forest model (currently sklearn's IsolationForest) or an already-parsed EnsembleIR.

TYPE: object

max_anomaly_score

Upper bound on the isolation-forest anomaly score, in (0, 1); lower is a stricter plausibility requirement. Defaults to 0.55.

TYPE: float DEFAULT: 0.55

RETURNS DESCRIPTION
A ``Plausibility`` wrapping the parsed forest.
RAISES DESCRIPTION
TreecfError

If max_anomaly_score is not in (0, 1).

Source code in src/treecf/plausibility.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@classmethod
def isolation_forest(
    cls, model_or_ir: object, max_anomaly_score: float = 0.55
) -> Plausibility:
    """Build a ``Plausibility`` bound from a fitted isolation forest.

    Parameters
    ----------
    model_or_ir
        A native isolation-forest model (currently
        sklearn's ``IsolationForest``) or an already-parsed
        ``EnsembleIR``.
    max_anomaly_score
        Upper bound on the isolation-forest anomaly
        score, in ``(0, 1)``; lower is a stricter plausibility
        requirement. Defaults to ``0.55``.

    Returns
    -------
    A ``Plausibility`` wrapping the parsed forest.

    Raises
    ------
    TreecfError
        If ``max_anomaly_score`` is not in ``(0, 1)``.
    """
    if not 0.0 < max_anomaly_score < 1.0:
        raise TreecfError("max_anomaly_score must lie in (0, 1)")
    if isinstance(model_or_ir, EnsembleIR):
        if_ir = model_or_ir
    else:
        from treecf.ir.parsers.sklearn import parse_isolation_forest

        if_ir = parse_isolation_forest(model_or_ir)
    return cls(if_ir=if_ir, max_anomaly_score=max_anomaly_score)

anomaly_score

anomaly_score(x: FloatArray) -> float

The isolation-forest anomaly score at x, in [0, 1].

2 ** (-mean_path / normalizer): close to 1 for a point the forest isolates in very few splits (anomalous), close to 0 for one that takes many (typical). A point is plausible under this bound iff its score is <= max_anomaly_score.

PARAMETER DESCRIPTION
x

A feature vector, aligned to the forest's feature order.

TYPE: FloatArray

RETURNS DESCRIPTION
The anomaly score at ``x``.
Source code in src/treecf/plausibility.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def anomaly_score(self, x: FloatArray) -> float:
    """The isolation-forest anomaly score at ``x``, in ``[0, 1]``.

    ``2 ** (-mean_path / normalizer)``: close to ``1`` for a point the
    forest isolates in very few splits (anomalous), close to ``0`` for
    one that takes many (typical). A point is plausible under this bound
    iff its score is ``<= max_anomaly_score``.

    Parameters
    ----------
    x
        A feature vector, aligned to the forest's feature order.

    Returns
    -------
    The anomaly score at ``x``.
    """
    total = raw_score(self.if_ir, np.asarray(x, dtype=np.float64))
    mean_path = total / len(self.if_ir.trees)
    return float(2.0 ** (-mean_path / self.normalizer))