Getting started¶
Install¶
pip install treecf # bundled Rust search engine
pip install "treecf[xgboost,viz]" # parser extras, matplotlib plots
numpy is the only Python dependency; the genetic engine is a compiled Rust core shipped inside the wheel. Model parsers accept JSON dumps directly, so explanations can be generated on machines where the training framework (or any solver) is not installed.
First counterfactual¶
import numpy as np
import xgboost as xgb
from treecf import Counterfactual, Explainer, Target, Freeze, Monotone, constraint
# a binary classifier trained on your data
clf = xgb.XGBClassifier(n_estimators=100, max_depth=4).fit(X_train, y_train)
exp = Explainer(
model=clf, # or "model.json", or a dump dict
background=X_train, # fits robust distance normalizers (MAD chain)
constraints=[
Freeze("age_of_bureau_file"), # immutable
Monotone("age", "increase"), # can only grow
constraint("max_dpd_30d <= max_dpd_12m"), # inter-feature consistency
],
)
res = exp.explain(
x_row,
target=Target.probability(range=(0.0, 0.04)), # get under the 4% PD cutoff
seed=0,
)
if isinstance(res, Counterfactual):
print(res.changes) # {"feature": (from, to), ...}
else:
print(res.reason) # Infeasible: why no plan was found
The search is heuristic (proof="heuristic"), feasibility-first, and
seed-deterministic; on toy suites it brackets a brute-force optimum. It runs on
the bundled Rust engine in milliseconds even on 300-tree models;
backend="python" runs the reference numpy implementation of the same
algorithm. How it works walks the whole pipeline.
Read the result¶
| Field | Meaning |
|---|---|
x_cf |
counterfactual instance (NaN where a missing state was chosen) |
changes |
feature → (factual, counterfactual) for every changed feature |
distance, n_changed |
weighted L1 distance and L0 count |
score_raw, score_prob |
raw model output and its sigmoid when applicable |
proof |
always "heuristic" — the search never claims optimality |
snapped |
per-feature outcome of value_policy snapping |
Every result is re-verified in float space against the IR before it is returned: the target and each constraint are checked on the actual returned values.
Visualize it¶
from treecf.viz import plot_changes, plot_waterfall, plot_effort
plot_changes(res) # dumbbells: from -> to per feature
plot_waterfall(exp, res, target=t) # SHAP-style: exact score deltas, cutoff line
plot_effort(exp, res) # where the applicant's effort goes (J split)
Alternatives for one instance¶
One plan is rarely the whole story. Ask for several distinct plans for the same row and compare them side by side:
from treecf.viz import plot_alternatives, plot_tradeoff
batch = exp.explain_batch(x_row.reshape(1, -1), target=t, n_per_example=3, seed=0)
plans = batch.for_id(0) # up to 3 distinct plans for this row
plot_alternatives(plans, explainer=exp) # every plan's changes, standardized to Δ/σ
plot_tradeoff(plans, target=t) # cost vs achieved score: which plan buys what
diversity="lever-blocking" instead re-solves with each plan's biggest lever
frozen — and reports levers that turn out to be essential.
For advice grouped by what a person controls together, ask for one plan per named feature group — see Coalitions:
result = exp.explain_coalitions(
x_row, target=t,
coalitions={"debt": ["max_dpd_30d", "max_dpd_12m"], "income": ["income_monthly"]},
include_full=True, # adds the unrestricted "(all levers)" baseline
)
plot_alternatives(result, explainer=exp) # coalition names label the plans
Scale to a dataset¶
batch = exp.explain_batch(
X_declined, # e.g. today's declined applications
target=Target.probability(range=(0.0, 0.30)),
n_per_example=2, # counterfactuals per example
diversity="seeds", # or "lever-blocking" (also finds essential levers)
ids=app_ids,
seed=0,
)
batch.save("counterfactuals_today.json") # compute once, store...
stored = BatchResult.load("counterfactuals_today.json")
stored.for_id("APP-00042") # ...look up any time
stored.to_frame() # or analyze as a pandas DataFrame
Solves run in parallel inside the Rust core. treecf.viz_batch plots the whole
batch — lever usage, per-plan effort, cost/sparsity/feasibility — as shown in the
credit-risk walkthrough.
Where next¶
- How it works — the pipeline from objective to verified answer.
- Concepts — one page per stage: models, targets, constraints, missing values, plausibility, backends.
- Tutorials — runnable notebooks.
- API reference.