The association miner looks for pairwise conditional patterns between
nominal values, such as “when segment = premium, shipping = express
in 95% of cases.” Each rule has one condition and one outcome. Findings
use conditional_rules/if_then, as PRISM findings do, so matching rules
can share one finding with separate evidence records.
What it mines¶
Eligible features are the nominal columns of the encoded matrix with at most 16 distinct levels (standard budget). For every ordered pair of values across two different features, the miner counts joint occurrences with bitset masks — train rows in one mask, holdout rows in another. The holdout is one fifth of the sampled rows, chosen by a stable hash of the original row id, and it is not shown to the rule during discovery: a rule qualifies on the train split (support ≥ 30, conditional share ≥ 0.75, lift ≥ 1.5) and is then re-counted on the holdout, which is the evidence the finding carries.
Denominators are pair-complete: rows where either side is missing simply
do not participate in that pair’s counts. At most 20 findings survive on
standard (40 on deep), ranked deterministically.
import random
import polars as pl
import onceover
rng = random.Random(23)
n = 4_000
plan = rng.choices(["basic", "pro"], weights=[60, 40], k=n)
support = [
"phone" if (rng.random() < 0.9 if p == "pro" else rng.random() < 0.2)
else "chat"
for p in plan
]
df = pl.DataFrame({"plan": plan, "support": support})
report = onceover.profile(df)
rules = [f for f in report.findings if f["category"] == "conditional_rules"
and any(e["method"]["detector"] == "association_rules" for e in f["evidence"])]
for f in rules:
print(f["claim"])
for e in f["evidence"]:
if e["method"]["detector"] == "association_rules":
m = e["metrics"]
print(f" train: n={m['train_n']} confidence={m['train_confidence']:.3f} lift={m['train_lift']:.2f} | "
f"holdout: n={m['holdout_n']} confidence={m['holdout_confidence']:.3f} lift={m['holdout_lift']:.2f}")When plan = "pro", support = "phone" holds in 300 of 342 matching diagnostic rows (87.72%; baseline 47.53%)
train: n=3190 confidence=0.906 lift=1.89 | holdout: n=810 confidence=0.877 lift=1.85
When support = "chat", plan = "basic" holds in 383 of 425 matching diagnostic rows (90.12%; baseline 57.78%)
train: n=3190 confidence=0.929 lift=1.53 | holdout: n=810 confidence=0.901 lift=1.56
When plan = "basic", support = "chat" holds in 383 of 468 matching diagnostic rows (81.84%; baseline 52.47%)
train: n=3190 confidence=0.796 lift=1.53 | holdout: n=810 confidence=0.818 lift=1.56
Consolidation with rules¶
When PRISM and the association miner discover the same condition–outcome pair, the report shows one finding. Its canonical statement includes the category, typed conditions, and typed outcome. The evidence list retains the association miner’s train/holdout record, PRISM’s discovery record, and the shared evaluator’s partition record, each with its scope and validation label. These records may use overlapping rows; they do not constitute three independent confirmations.
plan = pro ⇒ support = phone and support = phone ⇒ plan = pro remain
separate statements. Their conditional rates and supporting evidence can
differ.
Limits¶
The miner searches pairs of nominal values. It does not search three-item conditions, apply self-sufficiency correction against background knowledge (Webb’s filter), or test statistical significance. Its holdout recount is a consistency check. Lift uses the outcome’s rate on the same rows; a rare outcome can have high lift despite a modest conditional rate. Read lift alongside the joint count and conditional rate. Wider itemsets and self-sufficiency filtering are future directions (Directions).