Skip to content

FAQ

Why does Target.probability fail on my RandomForest? Forest classifiers average probabilities; there is no sigmoid link to invert. Their raw score is the averaged probability — use Target.raw(range=(0.0, 0.3)).

How do I target a calibrated probability? If model outputs are post-hoc calibrated (p' = g(predict_proba)), decisions are made on the calibrated scale — and Target.probability becomes a silent trap: it inverts the model's own sigmoid link, not g. Example: with an isotonic g mapping model-p 5% to calibrated 2%, Target.probability(op="<=", value=0.02) demands model-p ≤ 2% — a materially harder (or unattainable) target than the intended calibrated-PD ≤ 2%. Use Target.calibrated with any calibrator object (e.g. a probcal calibrator) satisfying the duck-typed protocol — no calibration library is imported:

class SupportsIntervalInverse(Protocol):
    is_monotone_: bool
    def interval_inverse(
        self, lo: float, hi: float, *, space: str = "probability", buffer_logit: float = 0.0
    ) -> tuple[float, float]: ...
target = treecf.Target.calibrated(cal, op="<=", value=0.02)   # calibrated PD ≤ 2%
result = explainer.explain(x, target=target)

Pass buffer_logit=m to guard the counterfactual against future recalibration or central-tendency drift of magnitude ≤ m in log-odds. For a masterscale defined on calibrated PD, bands invert per band:

target = treecf.Target.bands(
    {"A": (0.0, 0.005), "B": (0.005, 0.02), "C": (0.02, 0.10)},
    space="calibrated",
    calibrator=cal,
)

Why is my counterfactual Infeasible? The search exhausted its budget without a candidate satisfying the target and every constraint. Check for contradictory constraints (e.g. everything frozen), an unreachable target interval, or raise time_budget_s.

Can I run treecf where xgboost cannot be installed? Yes. Parsers accept JSON dumps (Booster.save_model("model.json"), dump_model(), CatBoost format="json"), and the genetic backend has no dependencies beyond the wheel itself: pip install treecf on the scoring host, ship the dump file.

What is the Rust core, and do I need a Rust toolchain? backend="genetic" runs a compiled Rust engine bundled inside the platform wheel (44–58× faster than the equivalent numpy implementation — see backends — performance). Installing from a wheel needs no toolchain; only building from the sdist compiles Rust, which requires rustc 1.86 or newer. The engine is held to bitwise parity with Python on tree evaluation and constraint checking, and to statistical parity on end-to-end GA outcomes; every result is float-verified in Python before being returned.

When would I use backend="python"? It is the original numpy implementation of the same genetic algorithm, kept as a reference engine (and as the behavioral baseline the Rust core is tested against). Use it to cross-check results or in environments where the compiled extension cannot load; expect identical result quality, just slower.

Are mined constraints safe to apply automatically? No, by design. They are sample invariants, not domain truths; the API returns them for review (as_code()), and near-invariants are flagged as data-quality findings instead of constraints.

Do NaN flips count as "changes" for sparsity and diversity? Yes — flipping a value to NaN (or back) increments n_changed, pays the configured delta, and counts in distinct_changes diversity cuts.