Playbook — association rules (market basket)¶
For experiments of type: "association": which items occur together — {tortilla_chips, salsa} →
guacamole — mined from baskets, with support, confidence, lift and a significance test per rule.
Nothing is predicted: the result describes the data, and its rules feed bundles, cross-sell
audiences and placement. The plan carries an association block (the grain mapping, a budget
tier and optional config cut-offs; describe_platform("plan_schema", name="association") lists
every setting). The delivery lifecycle is get_playbook("deliver"); this playbook is what each of
its steps does here.
Frame¶
- What a basket is — the unit "occurring together" means: an order, a visit, a customer's
holdings. That sets the
grain: basket— one row per basket with a packed item list (items_col,item_delimiter);long— one row per(entity, item): every item an entity holds is one basket (entity_col,target_key_col);event— one row per(entity, item, time), grouped into baskets bybasket_color bytimestamp_colwithinbasket_time_granularity.- What the rules are for — a bundle, a "goes with" recommendation, a targeting audience; that
says which side matters (
config.focus_items,config.focus_side) and which items are uninteresting (config.exclude_items; an item in nearly every basket is excluded bymax_item_supportautomatically). A common item is not uninteresting: lift already divides by how often the consequent is bought, so do not propose dropping items in half the baskets — "pasta → tomato sauce" is the shelf rule the person wants. Exclude only what the basket did not choose (a bag fee, an auto-added item). An item every entity holds is different: the confidence of any rule from it equals its consequent's own support, so its lift is exactly 1 and it carries no association information —max_item_supportdrops it; report it as the base everyone has, and do not ask to keep it in the mining. - The cut-offs — the person's: how strong a rule must be to act on (
config.min_confidence,config.min_lift), how many rules they can review (config.max_rules), and thebudgettier (fast,balanced,thorough). One rule per item set ships by default (config.one_rule_per_itemset: the other direction of a pair points at it), somax_rulescounts findings, not directions. - Known events — a promotion or a period that could manufacture co-occurrence: ask, and plan to check its rules against the rest.
- Metric —
describe_platform("metric_catalog", context="association"): support, confidence, lift, leverage, conviction; rules are ranked byconfig.rank_by.
Data¶
- Recipe — the baskets, bounded at the moment (a window of orders up to a date), with
author_recipe. - Roles — on the plan's block:
items_colorentity_col+target_key_col,basket_col/timestamp_col(event grain),target_group_col(an item category). No target. On the dataset, mark the basket id as the key and the item list asmtwith its delimiter. - Leakage — there is no label; the risk is an item that is a consequence of the basket being
recorded (a "bag fee", a default add-on): exclude it with
config.exclude_items. - Signal — no probe: say so at the data stop.
Validation¶
Mining is in-sample by default. config.holdout_fraction withholds a share of baskets; each rule
is then re-measured on them (holds_up, lift_holdout) — the only verdict not measured on the
data that chose the rule. Propose a holdout whenever rules will be acted on. significant
(false-discovery control over the rules that passed the filters) and fwer_significant (over the
whole searched space) are the significance flags: get_playbook("schema-association"), Reading
the quality flags.
Breadth. Lift compares with independence over all baskets, and broad baskets (a customer
holding six products) hold more of everything — on holdings, most of a lift can be breadth.
Every rule carries lift_breadth_adjusted (and lift_breadth_adjusted_holdout): the lift among
baskets holding the same number of other items. Near 1 means the rule describes broad baskets,
not the pair; association_summary.breadth_note says when that is most of the ranked lift, and
association_evidence gives the per-breadth counts. A rule with a multi-item antecedent ranks
only when each item raises the consequent's rate at equal breadth by min_superset_lift_gain;
n_rules_pruned_behind_sub_rule counts the longer rules set aside behind a shorter one.
Baseline¶
On a basket table — one row per basket, its items in one delimited column (basket grain):
{
"experiment": {
"workspace_id": "<workspace_id>", "name": "basket rules baseline", "type": "association",
"dataset_id": "<dataset_id>",
"association": {
"grain": "basket", "items_col": "items", "budget": "balanced",
"config": {"holdout_fraction": 0.2}
}
},
"hypothesis": {"lever": "baseline", "expected_effect": "A few strong cross-sell pairs hold up on withheld baskets."}
}
On a holdings table — one row per customer and product (long grain):
{
"experiment": {
"workspace_id": "<workspace_id>", "name": "holdings rules baseline", "type": "association",
"dataset_id": "<dataset_id>",
"association": {
"grain": "long", "entity_col": "customer_id", "target_key_col": "product",
"target_group_col": "category", "budget": "balanced"
}
},
"hypothesis": {"lever": "baseline", "expected_effect": "Product holdings co-occur beyond chance across categories."}
}
The floor first: independence. A lift of 1 means the items co-occur exactly as often as chance says. A rule set with no supported result provides insufficient evidence under this design; it does not prove that no association exists. Holdout support is useful, but repeated selection on that holdout erodes independence. A high lift on a rare consequent can still have little absolute reach; consider counts, stability and the action. Co-occurrence is not incremental sales uplift.
Iterate¶
Lever (hypothesis.lever) |
Change |
|---|---|
recipe |
the baskets: the window, the basket definition, the population |
features |
exclude_items, focus_items, max_item_support |
split |
holdout_fraction |
other |
the budget tier, min_support, max_itemset_len, max_antecedent_len, min_improvement, min_superset_lift_gain — with the evidence in note |
A cut-off the person set (min_confidence, min_lift, max_rules) is theirs, like a metric.
Before a run to "surface" a rule, read the runners-up: the rule may already be there, set aside
with a reason.
Insights¶
Interpretation checklist: choose the relevant available reads for the question and candidate:
get_insight(kind="association_summary")— baskets, items, rules kept and why, significance and holdout checks, how many were set aside by each reason, andbreadth_note/breadth_share_of_lift: how much of the ranked lift is basket breadth.get_insight(kind="association_rules")— the shipped list (ranknot null) and the flagged rows:pruned_bynames a shorter rule an extra item failed to beat (on confidence, or at equal breadth), the ranked rule over the same items (one_rule_per_itemset),"baseline"or"max_rules"(a runner-up beyond the cap). Readlift_breadth_adjustedbesidelift; rank pairwise affinity by it (sort_by="lift_breadth_adjusted") when breadth is most of the lift.get_insight(kind="association_itemsets")andget_insight(kind="association_items")— the frequent sets; items so common they dominate the rules.get_insight(kind="association_evidence", params={rule_id})for the doubtful top rules — itsbreadth_strataset the rule's baskets beside chance within each breadth group;get_insight(kind="association_audience", params={rule_id})for the ones to act on (entity grains).get_chart_data("association", "network", experiment_id)— the item network.
Rules are co-occurrences: "tend to appear together", never one item causing another. Then the
Gems table (get_playbook("iterate")).
Challenge¶
| Check | Reads required |
|---|---|
| Leakage | items that are artefacts of recording (fees, defaults) among the strongest rules (get_insight(kind="association_rules")) |
| Validation design | a holdout for rules that will be acted on; holds_up on the rules proposed |
| Stability | how many shipped rules are fwer_significant and hold up; a list dominated by one ubiquitous item; a known promotion behind a rule; how much of the lift is breadth (breadth_note, lift_breadth_adjusted) |
| Plausible rules | would a domain expert recognise the top rules? get_insight(kind="association_evidence", params={rule_id}) for the doubtful ones |
Decide¶
There is no simulation kind: the decision is which rules to act on — the shortlist. Propose
the rules that hold up and are fwer_significant, ranked, each with its holdout support,
confidence and lift and its audience size where one exists, by rule_id; the alternatives are
stricter or looser cut-offs (a new run with the person's values).
Release and operate¶
- An association run is not registered. Its release is the published rule set and the
approved shortlist:
register_model(action="record_release", experiment_id, …)withapply{"kind": "association_shortlist", "rule_ids": […]}flags those rulesshortlistedon the run and in its published rules table (get_insight(kind="association_rules", params={"shortlisted_only": true})reads them back). - Results:
get_playbook("schema-association"). - Operate: mine the new period as a new plan (
run_experiment(action="plan", parent_experiment_id=…)) and compare the rules that hold up across the two.