Skip to content

Constrain the search

Shared objects

Snippets on this page continue from the objects the quickstart builds with credit_demo(): exp, x, target, X_bg, the solved res and batch, and cal, a fitted monotone calibrator (see the FAQ).

Constraints are what make a counterfactual a plan rather than an adversarial example: they encode what can change, in which direction, and what must remain consistent. They are declared once on the Explainer and compiled for every backend identically — the semantics never depend on which engine runs. This page is the working catalog; Constraints covers compilation and violation handling.

Declaring

from treecf import (
    AllowedCategories, Equals, Freeze, Implies, Monotone, OneHot, Range, constraint,
)

Freeze("age_of_bureau_file")                    # immutable
Monotone("age", "increase")                     # directional
Range("utilization", 0.0, 1.5)                  # hard bounds
constraint("max_dpd_30d <= max_dpd_12m")        # string sugar -> Linear
constraint("2*a - b <= c + 5")                  # any linear expression
Implies(Equals("has_mortgage", 0), Equals("mortgage_balance", 0))
OneHot(("channel_web", "channel_app", "channel_branch"))
AllowedCategories("occupation", ["clerk", "manager"])   # categorical whitelist

Pass them at construction:

from treecf import Explainer, Freeze, Monotone

exp = Explainer("model.json", background=X_bg,
                constraints=[Freeze("tenure_months"), Monotone("dpd_12m", "decrease")])

Categorical features

A feature with native categorical splits is unordered: Freeze and AllowedCategories are its two constraint forms, and AllowedCategories accepts display names (when categories= named them) or raw codes. Order- and arithmetic-shaped constraints (Range, Monotone, Linear, Equals, Implies, OneHot) are rejected on categorical features at construction with ConstraintValidationError — there is no order to be monotone in. See Categorical features.

Missing values

AllowMissing("feature") opts a feature into NaN as a counterfactual value, with a transition cost; the interaction with linear constraints and the NaN routing rules are in Missing values.

Mining candidates from data

suggest_constraints scans the background for near-invariant order pairs and data-quality findings, and returns candidates you accept explicitly — mined rules are suggestions, never silently applied:

import treecf

suggestions = treecf.suggest_constraints(X_bg, feature_names=exp.ir.feature_names)
accepted = [s.constraint for s in suggestions if s.kind == "order"]

Next

With the levers declared, run the search; the target came from set the target.