Skip to content

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 by basket_col or by timestamp_col within basket_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 by max_item_support automatically). 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_support drops 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 the budget tier (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), so max_rules counts 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 by config.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_col or entity_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 as mt with 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:

  1. get_insight(kind="association_summary") — baskets, items, rules kept and why, significance and holdout checks, how many were set aside by each reason, and breadth_note / breadth_share_of_lift: how much of the ranked lift is basket breadth.
  2. get_insight(kind="association_rules") — the shipped list (rank not null) and the flagged rows: pruned_by names 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). Read lift_breadth_adjusted beside lift; rank pairwise affinity by it (sort_by="lift_breadth_adjusted") when breadth is most of the lift.
  3. get_insight(kind="association_itemsets") and get_insight(kind="association_items") — the frequent sets; items so common they dominate the rules.
  4. get_insight(kind="association_evidence", params={rule_id}) for the doubtful top rules — its breadth_strata set 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).
  5. 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, …) with apply {"kind": "association_shortlist", "rule_ids": […]} flags those rules shortlisted on 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.