Rule Explanation#
Functions#
verbalize_rule#
- iguanas.rule_explanation.verbalize_rule(rule: str) str[source]#
Convert a rule expression to a plain-English sentence.
- Parameters:
rule (str) – Rule expression using
X["col"]notation with&/|operators.- Returns:
Plain-English description of the rule.
- Return type:
str
Examples
>>> verbalize_rule('(X["age"] > 30) & (X["income"] < 50000)') 'age is greater than 30 AND income is less than 50000'
>>> verbalize_rule('(X["score"] >= 0.8) | (X["flag"] == 1)') 'score is at least 0.8 OR flag is equal to 1'
compute_coverage_overlap#
- iguanas.rule_explanation.compute_coverage_overlap(R: polars.DataFrame) polars.DataFrame[source]#
Compute pairwise Jaccard overlap between rule predictions.
For each pair of rules, computes the Jaccard similarity: (samples flagged by both) / (samples flagged by either). A high score means the two rules flag nearly the same population.
- Parameters:
R (pl.DataFrame) – Boolean DataFrame of rule predictions (columns = rules, rows = samples).
- Returns:
Long-format DataFrame with columns:
rule_a,rule_b,jaccard,flagged_by_both,flagged_by_either. Only the upper triangle of the pair matrix is returned. Sorted byjaccarddescending (most overlapping first).- Return type:
pl.DataFrame
Examples
>>> import polars as pl >>> R = pl.DataFrame({ ... "rule_A": [True, True, False, False], ... "rule_B": [True, False, True, False], ... }) >>> compute_coverage_overlap(R) shape: (1, 5) ┌────────┬────────┬─────────┬────────────────┬──────────────────┐ │ rule_a │ rule_b │ jaccard │ flagged_by_both │ flagged_by_either│ │ --- │ --- │ --- │ --- │ --- │ │ str │ str │ f64 │ i64 │ i64 │ ╞════════╪════════╪═════════╪════════════════╪══════════════════╡ │ rule_A │ rule_B │ 0.333… │ 1 │ 3 │ └────────┴────────┴─────────┴────────────────┴──────────────────┘
compute_counterfactual#
- iguanas.rule_explanation.compute_counterfactual(rule: str, sample: polars.DataFrame, epsilon: float = 1e-06) list[dict[str, Any]][source]#
Find minimal feature changes to un-flag a sample from a rule.
For each atomic condition in the rule that is currently satisfied by
sample, computes the smallest perturbation of that feature that would violate the condition. Results are sorted byabs_changeascending so the caller can pick the least-effort option.AND rules: breaking any single condition un-flags the sample. The first (cheapest) entry is sufficient.
OR rules: every currently-satisfied condition must be broken to un-flag the sample. All returned entries together form the counterfactual.
- Parameters:
rule (str) – Rule expression to un-flag the sample from.
sample (pl.DataFrame) – Single-row DataFrame representing the sample to explain. Must contain all columns referenced in
rule.epsilon (float, default=1e-6) – Small perturbation used when breaking strict inequalities.
- Returns:
Candidate counterfactuals sorted by
abs_changeascending. Each dict has keys:feature— feature name to changecondition— the atomic condition that would be brokencurrent_value— current feature valuesuggested_value— value that breaks the conditionabs_change— magnitude of the required change
Returns an empty list if the sample is not flagged by the rule or if no atomic conditions can be broken numerically.
- Return type:
list[dict]
- Raises:
ValueError – If
sampledoes not have exactly one row.
Examples
>>> sample = pl.DataFrame({"age": [45], "income": [80_000]}) >>> rule = '(X["age"] > 30) & (X["income"] >= 50_000)' >>> compute_counterfactual(rule, sample) [ {'feature': 'age', 'condition': ..., 'current_value': 45.0, 'suggested_value': 29.999999, 'abs_change': 15.000001}, {'feature': 'income', ...}, ]