Skip to content

Association Rule Learning (Apriori Algorithm)

What you’ll learn

  • the five rule metrics — support, confidence, lift, leverage and conviction — each derived and each computed by hand on the same baskets
  • why confidence alone is dangerous, shown with a rule at 80% confidence and lift 1.23
  • a rule with lift 0.31: buying chips makes bread less likely
  • the downward-closure property, and the candidate counts it eliminates: 70 → 0 at level 4
  • the full Apriori loop traced level by level on 20 baskets
  • why the number of rules explodes, and how to control it
  • FP-Growth, and when the level-wise scan stops being viable

Intuition

Every technique so far in this phase has grouped rows. Association rule learning groups columns — or rather, it finds columns that tend to be present together.

The classic setting is a supermarket. Each transaction is a set of items. You want statements like “customers who buy bread also buy butter” — and, much more importantly, you want to know which of those statements are worth acting on.

That second part is where almost all the difficulty lives. “Customers who buy anchovies also buy bread” is true 80% of the time. It is also useless, because 65% of all customers buy bread. The rule tells you nothing you did not already know from the shelf.

Distinguishing a real association from a popular consequent is the whole job, and it requires more than one number.

diagram Diagram mermaid

The data

Twenty baskets, small enough to count by hand:

#Items#Items
1bread, butter, milk11bread, butter, milk
2bread, butter12beer, chips
3bread, milk, jam13bread, milk
4butter, milk14butter, jam
5bread, butter, milk, jam15bread, butter, milk, cereal
6beer, chips16chips, salsa
7bread, jam17bread, butter
8beer, chips, salsa18milk, cereal, bread
9bread, butter, jam19beer, chips, bread
10milk, cereal20bread, butter, milk

Item counts, which are the foundation of everything below:

ItemCountSupport
bread130.65
butter100.50
milk100.50
chips50.25
jam50.25
beer40.20
cereal30.15
salsa20.10
figureLevel 1 of Apriori: count every itemmatplotlib
A horizontal bar chart of eight grocery items sorted by support, with bread at 0.65 and salsa at 0.10, and a dashed vertical threshold line at 0.20.A horizontal bar chart of eight grocery items sorted by support, with bread at 0.65 and salsa at 0.10, and a dashed vertical threshold line at 0.20.
Support is the fraction of the 20 baskets containing the item. With min_support = 0.20, cereal (0.15) and salsa (0.10) are eliminated immediately — and by downward closure, so is every larger itemset that contains them.

The math

Write TT for the set of transactions, T=N|T| = N, and σ(X)\sigma(X) for the number of transactions containing itemset XX.

Support — how often the itemset appears at all:

supp(X)=σ(X)N\operatorname{supp}(X) = \frac{\sigma(X)}{N}

Confidence of the rule ABA \Rightarrow B — the conditional probability:

conf(AB)=supp(AB)supp(A)=P^(BA)\operatorname{conf}(A \Rightarrow B) = \frac{\operatorname{supp}(A \cup B)}{\operatorname{supp}(A)} = \hat{P}(B \mid A)

Lift — confidence divided by the consequent’s base rate:

lift(AB)=conf(AB)supp(B)=supp(AB)supp(A)supp(B)\operatorname{lift}(A \Rightarrow B) = \frac{\operatorname{conf}(A \Rightarrow B)}{\operatorname{supp}(B)} = \frac{\operatorname{supp}(A \cup B)}{\operatorname{supp}(A)\operatorname{supp}(B)}

This is the ratio of the observed joint frequency to the frequency expected under independence. Read it as:

  • lift>1\text{lift} > 1AA and BB appear together more often than chance. Positive association.
  • lift=1\text{lift} = 1 — independent. The rule tells you nothing.
  • lift<1\text{lift} < 1 — they appear together less often than chance. Negative association.

Lift is symmetric: lift(AB)=lift(BA)\operatorname{lift}(A \Rightarrow B) = \operatorname{lift}(B \Rightarrow A). Confidence is not.

Leverage — the same comparison as a difference rather than a ratio:

leverage(AB)=supp(AB)supp(A)supp(B)\operatorname{leverage}(A \Rightarrow B) = \operatorname{supp}(A \cup B) - \operatorname{supp}(A)\operatorname{supp}(B)

Zero under independence. Because it is a difference, it weights by how many transactions are actually affected — useful when a huge lift comes from a handful of baskets.

Conviction — how much more often the rule would be wrong if AA and BB were independent:

conviction(AB)=1supp(B)1conf(AB)\operatorname{conviction}(A \Rightarrow B) = \frac{1 - \operatorname{supp}(B)}{1 - \operatorname{conf}(A \Rightarrow B)}

It is 1 under independence and \infty when confidence is 1 (the rule has no counterexamples). Unlike lift, conviction is directional.

MetricRangeIndependent valueDirection-sensitive
Support[0,1][0, 1]no
Confidence[0,1][0, 1]supp(B)\operatorname{supp}(B)yes
Lift[0,)[0, \infty)1no
Leverage[0.25,0.25][-0.25, 0.25]0no
Conviction[0,)[0, \infty)1yes

Worked example by hand

bread ⇒ butter

Bread appears in 13 baskets, butter in 10, and both together in 8 (baskets 1, 2, 5, 9, 11, 15, 17, 20).

supp({bread,butter})=820=0.40\operatorname{supp}(\{\text{bread}, \text{butter}\}) = \frac{8}{20} = 0.40
conf=0.400.65=0.6154\operatorname{conf} = \frac{0.40}{0.65} = 0.6154
lift=0.61540.50=1.2308\operatorname{lift} = \frac{0.6154}{0.50} = 1.2308
leverage=0.400.65×0.50=0.400.325=0.0750\operatorname{leverage} = 0.40 - 0.65 \times 0.50 = 0.40 - 0.325 = 0.0750
conviction=10.5010.6154=0.500.3846=1.3000\operatorname{conviction} = \frac{1 - 0.50}{1 - 0.6154} = \frac{0.50}{0.3846} = 1.3000

A mild positive association: butter is about 23% more likely in a bread basket than in a random one.

The same pair, reversed

conf(butterbread)=0.400.50=0.8000,lift=0.80000.65=1.2308\operatorname{conf}(\text{butter} \Rightarrow \text{bread}) = \frac{0.40}{0.50} = 0.8000, \qquad \operatorname{lift} = \frac{0.8000}{0.65} = 1.2308

The confidence jumped from 0.62 to 0.80 while the lift did not move at all. Confidence went up purely because bread is more common than butter — swapping the direction changed the denominator, not the relationship. Lift, being symmetric, is unfooled.

Conviction does change: (10.65)/(10.80)=0.35/0.20=1.7500(1 - 0.65)/(1 - 0.80) = 0.35/0.20 = 1.7500 against 1.3000 the other way. It is directional by design, and it says the butter ⇒ bread direction is the stronger prediction.

beer ⇒ chips

Beer appears in 4 baskets (6, 8, 12, 19), chips in 5 (6, 8, 12, 16, 19). Together: 4.

supp=420=0.20,conf=0.200.20=1.0000,lift=1.00000.25=4.0000\operatorname{supp} = \frac{4}{20} = 0.20, \qquad \operatorname{conf} = \frac{0.20}{0.20} = 1.0000, \qquad \operatorname{lift} = \frac{1.0000}{0.25} = 4.0000
leverage=0.200.20×0.25=0.15,conviction=10.2511=\operatorname{leverage} = 0.20 - 0.20 \times 0.25 = 0.15, \qquad \operatorname{conviction} = \frac{1 - 0.25}{1 - 1} = \infty

Every beer basket contains chips. Lift 4.0 means four times the chance expected by independence, and conviction is infinite because there is not a single counterexample.

chips ⇒ bread, the rule that is worse than nothing

Chips appears in 5 baskets, bread in 13, both together in 1 (basket 19).

supp=120=0.05,conf=0.050.25=0.2000,lift=0.20000.65=0.3077\operatorname{supp} = \frac{1}{20} = 0.05, \qquad \operatorname{conf} = \frac{0.05}{0.25} = 0.2000, \qquad \operatorname{lift} = \frac{0.2000}{0.65} = 0.3077
leverage=0.050.25×0.65=0.1125\operatorname{leverage} = 0.05 - 0.25 \times 0.65 = -0.1125

Lift 0.31. Chips buyers are three times less likely to buy bread than a random customer. Negative leverage confirms it. That is a real, actionable finding — the two are substitutes, not complements — and it is invisible to anyone who only ranks by confidence.

The full table

RuleSupportConfidenceLiftLeverageConviction
beer ⇒ chips0.201.00004.00000.1500\infty
chips ⇒ beer0.200.80004.00000.15004.0000
butter ⇒ bread0.400.80001.23080.07501.7500
milk ⇒ bread0.400.80001.23080.07501.7500
jam ⇒ bread0.200.80001.23080.03751.7500
bread ⇒ butter0.400.61541.23080.07501.3000
jam ⇒ milk0.100.40000.8000-0.02500.8333
chips ⇒ bread0.050.20000.3077-0.11250.4375

Four rules sit at exactly 0.8000 confidence, and their lifts range from 4.00 down to 1.23. Ranking by confidence would put chips ⇒ beerchips ⇒ beer — a genuinely strong rule — level with three bread-is-popular truisms.

figureConfidence and lift tell different storiesmatplotlib
Two bar charts of the same six rules in the same order. The left ranks them by confidence, decreasing smoothly. The right shows lift, with beer to chips towering at 4.0, three bars just above 1, and two red bars below 1.Two bar charts of the same six rules in the same order. The left ranks them by confidence, decreasing smoothly. The right shows lift, with beer to chips towering at 4.0, three bars just above 1, and two red bars below 1.
Left, ranked by confidence. Right, the same rules' lift. The three middle rules look almost identical on confidence but earn their score entirely from bread's 65% base rate. The two red bars are lift below 1 — associations that are genuinely negative.
figureEvery rule as a pointmatplotlib
A scatter plot with support on the x axis and confidence on the y axis, points coloured by lift on a blue-to-red scale, with the strongest and weakest rules labelled.A scatter plot with support on the x axis and confidence on the y axis, points coloured by lift on a blue-to-red scale, with the strongest and weakest rules labelled.
High confidence alone puts a point at the top of the plot; only the colour tells you whether the rule beats independence. The interesting rules are the red ones, and they are not always the highest.

The Apriori algorithm

Naively, finding all frequent itemsets over mm items means checking 2m12^m - 1 subsets. With just 100 products that is more than 103010^{30} candidates.

Apriori’s insight is the downward-closure property (also called the Apriori property):

XY    supp(X)supp(Y)X \subseteq Y \;\Longrightarrow\; \operatorname{supp}(X) \ge \operatorname{supp}(Y)

Adding an item to an itemset can only reduce the number of baskets containing it. So:

If an itemset is infrequent, every superset of it is infrequent.

That contrapositive lets you prune whole branches without counting them. The algorithm becomes a level-wise scan:

  1. Count all 1-itemsets; keep those with support \ge min_supportmin_support. Call this L1L_1.
  2. Generate candidate (k+1)(k{+}1)-itemsets by joining pairs in LkL_k that share k1k-1 items.
  3. Prune any candidate having a kk-subset that is not in LkL_k — no counting needed.
  4. Scan the transactions once to count the survivors; keep the frequent ones as Lk+1L_{k+1}.
  5. Stop when Lk+1L_{k+1} is empty.

Traced on the twenty baskets

With min_support = 0.20min_support = 0.20:

LevelAll possible itemsetsCandidates Apriori generatesFrequent
1-itemsets886
2-itemsets28155
3-itemsets5611
4-itemsets7000

At level 1, cereal (0.15) and salsa (0.10) drop out. At level 2, Apriori only builds candidates from the 6 survivors — 15 pairs rather than 28 — and 5 clear the threshold. At level 3 only one candidate survives pruning, {bread, butter, milk}{bread, butter, milk} at support 0.25. At level 4 there is nothing left to join, so the algorithm stops after examining 24 candidates instead of 162.

figureDownward closure collapses the searchmatplotlib
A grouped bar chart with three bars per level. The grey bars for all possible itemsets rise to 70 at level 4, while the amber candidate bars fall to zero and the blue frequent bars fall to zero at level 3.A grouped bar chart with three bars per level. The grey bars for all possible itemsets rise to 70 at level 4, while the amber candidate bars fall to zero and the blue frequent bars fall to zero at level 3.
Grey: every itemset of that size. Amber: what Apriori actually generates after pruning. Blue: what survives the support threshold. The gap between grey and amber is the pruning, and it widens at every level — which is what makes the algorithm feasible at all.

From itemsets to rules

Each frequent itemset of size kk yields 2k22^k - 2 candidate rules — every way of splitting it into a non-empty antecedent and a non-empty consequent. For {bread, butter, milk}{bread, butter, milk} at support 0.25, that is 6 rules:

RuleConfidenceLift
bread, butter ⇒ milk0.25 / 0.40 = 0.62501.2500
bread, milk ⇒ butter0.25 / 0.40 = 0.62501.2500
butter, milk ⇒ bread0.25 / 0.30 = 0.83331.2821
bread ⇒ butter, milk0.25 / 0.65 = 0.38461.2821
butter ⇒ bread, milk0.25 / 0.50 = 0.50001.2500
milk ⇒ bread, butter0.25 / 0.50 = 0.50001.2500

Note the support is 0.25 for all six — it is a property of the itemset, not of how you split it. Only the confidence and lift differ.

diagram Diagram mermaid

See it move

sketch Pruning the itemset lattice p5.js
All subsets of four items, arranged by size. Infrequent nodes go red, and downward closure immediately kills every superset above them — watch entire branches disappear without ever being counted.

Item D has support 0.12, below the threshold. The moment it goes red, every itemset containing it — AD, BD, CD, ABD, ACD, BCD, ABCD, seven of the fifteen nodes — is pruned without a single count.

The next sketch is the metrics themselves: move the overlap between two item sets and watch confidence and lift disagree.

sketch Confidence rises, lift stays put p5.js
Two circles of transactions. Drag the overlap by clicking; confidence tracks the overlap relative to A, but lift compares it against what independence would predict.

Click to swap the base rates. With supp(B)=0.65\operatorname{supp}(B) = 0.65, confidence can reach 0.8 while lift is barely above 1. Drop BB to 0.25 and the same overlap produces a far higher lift — because the same co-occurrence is now genuinely surprising.

In code

scikit-learn does not implement Apriori. The standard library is mlxtendmlxtend:

bash
pip install mlxtend
bash
pip install mlxtend
apriori_basics.py
import pandas as pd
from mlxtend.frequent_patterns import apriori, association_rules
from mlxtend.preprocessing import TransactionEncoder
 
baskets = [
    ["bread", "butter", "milk"], ["bread", "butter"], ["bread", "milk", "jam"],
    ["butter", "milk"], ["bread", "butter", "milk", "jam"], ["beer", "chips"],
    ["bread", "jam"], ["beer", "chips", "salsa"], ["bread", "butter", "jam"],
    ["milk", "cereal"], ["bread", "butter", "milk"], ["beer", "chips"],
    ["bread", "milk"], ["butter", "jam"], ["bread", "butter", "milk", "cereal"],
    ["chips", "salsa"], ["bread", "butter"], ["milk", "cereal", "bread"],
    ["beer", "chips", "bread"], ["bread", "butter", "milk"],
]
 
# One-hot: one row per basket, one boolean column per item.
te = TransactionEncoder()
df = pd.DataFrame(te.fit_transform(baskets), columns=te.columns_)
 
freq = apriori(df, min_support=0.20, use_colnames=True)
print(freq.sort_values("support", ascending=False))
 
rules = association_rules(freq, metric="lift", min_threshold=1.0)
cols = ["antecedents", "consequents", "support", "confidence", "lift", "leverage", "conviction"]
print(rules[cols].sort_values("lift", ascending=False).round(4))
apriori_basics.py
import pandas as pd
from mlxtend.frequent_patterns import apriori, association_rules
from mlxtend.preprocessing import TransactionEncoder
 
baskets = [
    ["bread", "butter", "milk"], ["bread", "butter"], ["bread", "milk", "jam"],
    ["butter", "milk"], ["bread", "butter", "milk", "jam"], ["beer", "chips"],
    ["bread", "jam"], ["beer", "chips", "salsa"], ["bread", "butter", "jam"],
    ["milk", "cereal"], ["bread", "butter", "milk"], ["beer", "chips"],
    ["bread", "milk"], ["butter", "jam"], ["bread", "butter", "milk", "cereal"],
    ["chips", "salsa"], ["bread", "butter"], ["milk", "cereal", "bread"],
    ["beer", "chips", "bread"], ["bread", "butter", "milk"],
]
 
# One-hot: one row per basket, one boolean column per item.
te = TransactionEncoder()
df = pd.DataFrame(te.fit_transform(baskets), columns=te.columns_)
 
freq = apriori(df, min_support=0.20, use_colnames=True)
print(freq.sort_values("support", ascending=False))
 
rules = association_rules(freq, metric="lift", min_threshold=1.0)
cols = ["antecedents", "consequents", "support", "confidence", "lift", "leverage", "conviction"]
print(rules[cols].sort_values("lift", ascending=False).round(4))

Doing it without a dependency is about fifteen lines, and worth writing once:

apriori_from_scratch.py
from itertools import combinations
 
def support(itemset, baskets):
    s = set(itemset)
    return sum(1 for b in baskets if s <= set(b)) / len(baskets)
 
def apriori(baskets, min_support):
    """Level-wise search with downward-closure pruning."""
    items = sorted({i for b in baskets for i in b})
    frequent = {frozenset([i]) for i in items if support([i], baskets) >= min_support}
    all_frequent, prev, k = dict(), frequent, 2
 
    while prev:
        for fs in prev:
            all_frequent[fs] = support(fs, baskets)
        candidates = set()
        for a, b in combinations(prev, 2):
            union = a | b
            if len(union) != k:
                continue
            # PRUNE: every k-1 subset must already be frequent
            if all(frozenset(s) in prev for s in combinations(union, k - 1)):
                candidates.add(union)
        prev = {c for c in candidates if support(c, baskets) >= min_support}
        k += 1
    return all_frequent
 
freq = apriori(baskets, 0.20)
for fs, s in sorted(freq.items(), key=lambda kv: (-kv[1], sorted(kv[0]))):
    print(f"{s:.2f}  {set(sorted(fs))}")
apriori_from_scratch.py
from itertools import combinations
 
def support(itemset, baskets):
    s = set(itemset)
    return sum(1 for b in baskets if s <= set(b)) / len(baskets)
 
def apriori(baskets, min_support):
    """Level-wise search with downward-closure pruning."""
    items = sorted({i for b in baskets for i in b})
    frequent = {frozenset([i]) for i in items if support([i], baskets) >= min_support}
    all_frequent, prev, k = dict(), frequent, 2
 
    while prev:
        for fs in prev:
            all_frequent[fs] = support(fs, baskets)
        candidates = set()
        for a, b in combinations(prev, 2):
            union = a | b
            if len(union) != k:
                continue
            # PRUNE: every k-1 subset must already be frequent
            if all(frozenset(s) in prev for s in combinations(union, k - 1)):
                candidates.add(union)
        prev = {c for c in candidates if support(c, baskets) >= min_support}
        k += 1
    return all_frequent
 
freq = apriori(baskets, 0.20)
for fs, s in sorted(freq.items(), key=lambda kv: (-kv[1], sorted(kv[0]))):
    print(f"{s:.2f}  {set(sorted(fs))}")

Controlling the explosion

Rule counts grow ferociously as you loosen min_supportmin_support. Three defences:

Set min_supportmin_support from a count, not a fraction. “At least 50 baskets” is a statement you can defend; “0.001” is not. Compute the fraction from the count you need.

Filter on lift and leverage. Lift alone promotes rules built on three transactions. Leverage weights by how many baskets are actually affected, so requiring both lift > 1.2lift > 1.2 and leverage > 0.01leverage > 0.01 removes the noise.

Restrict the consequent. If you only care about what drives one product, ask only for rules with that item on the right:

filter_rules.py
target = {"butter"}
useful = rules[
    (rules["consequents"] == frozenset(target))
    & (rules["lift"] > 1.2)
    & (rules["leverage"] > 0.01)
].sort_values("lift", ascending=False)
filter_rules.py
target = {"butter"}
useful = rules[
    (rules["consequents"] == frozenset(target))
    & (rules["lift"] > 1.2)
    & (rules["leverage"] > 0.01)
].sort_values("lift", ascending=False)

FP-Growth

Apriori scans the whole transaction database once per level. On millions of transactions with tens of thousands of items, that is the bottleneck.

FP-Growth builds a compressed prefix tree (the FP-tree) in two passes and then mines it recursively without generating candidates at all. It is typically an order of magnitude faster and gives identical results — the frequent itemsets are a property of the data, not of the algorithm.

fpgrowth.py
from mlxtend.frequent_patterns import fpgrowth
 
freq_fp = fpgrowth(df, min_support=0.20, use_colnames=True)
# Same itemsets, same supports, different route.
fpgrowth.py
from mlxtend.frequent_patterns import fpgrowth
 
freq_fp = fpgrowth(df, min_support=0.20, use_colnames=True)
# Same itemsets, same supports, different route.

Use Apriori to learn the concepts and for small data. Use FP-Growth in production.

algorithmAprioriUnsupervised — frequent itemset mining and association rules

APInot in scikit-learn; use mlxtend.frequent_patterns.apriori

Assumes

  • Transactions are unordered sets — no quantity, no sequence, no time
  • Items are categorical and the vocabulary is manageable
  • Co-occurrence is what you care about; correlation is not causation

Cost

train
O(2^m) worst case; downward closure makes it tractable in practice, with one database scan per level
predict
O(1) lookup against the rule table
memory
O(number of candidate itemsets), which is the usual failure point

supp(X) — fraction of transactions containing X; conf(A=>B) = supp(A and B)/supp(A); lift = conf / supp(B)

Hyperparameters that matter

  • min_supportdefault 0.5 in mlxtendThe main lever. Too high finds nothing; too low explodes combinatorially. Set it from a basket count.
  • min_threshold (on lift)default 1.0Rules below 1 are negative associations — sometimes exactly what you want to see.
  • max_lendefault NoneCaps itemset size. Setting 2 or 3 bounds the runtime hard.
  • use_colnamesdefault FalseReturn item names rather than column indices. Always set True.

Reach for it when

  • Market basket analysis, cross-sell and product placement
  • Web log and clickstream co-occurrence
  • Finding co-occurring symptoms, diagnoses or error codes
  • Any question of the form 'what shows up together?'

Look elsewhere when

  • Order or timing matters — use sequential pattern mining instead
  • Features are continuous — you would have to bin them first, and the bins drive the answer
  • You need causality; these are co-occurrence counts and nothing more
  • The item vocabulary is enormous and min_support has to be tiny

Pitfalls

Ranking by confidence. Four rules on this page share confidence 0.8000 with lifts from 1.23 to 4.00. Confidence rewards popular consequents; sort by lift or leverage.

Ignoring lift below 1. chips ⇒ breadchips ⇒ bread at lift 0.31 is one of the most informative rules in the set — the items are substitutes. Most tooling filters these out by default with min_threshold=1.0min_threshold=1.0.

Reading causation into co-occurrence. Beer and chips co-occur; neither causes the other. Both are caused by the same occasion. Acting on the rule (place them together) can still be correct; explaining it as causation is not.

Setting min_supportmin_support too low. The candidate count grows super-exponentially. Below about 0.01 on a real retail dataset you will exhaust memory before the level-3 scan finishes.

Forgetting the base rates. Every metric on this page is relative to supp(B)\operatorname{supp}(B). An item in 90% of baskets can never produce an interesting rule, because confidence cannot exceed 1 and the lift ceiling is 1/0.9=1.111/0.9 = 1.11.

Trusting rules built on a handful of transactions. A rule with support 0.001 and lift 12 rests on maybe five baskets. Check the raw count, and use leverage, which is bounded by how much of the data is affected.

Treating the item vocabulary as fixed. “Milk” and “semi-skimmed milk 2L” are different items to the algorithm. Aggregating to a sensible product hierarchy before mining usually matters more than any parameter.

Compare

AprioriFP-GrowthECLAT
Strategylevel-wise candidate generationprefix-tree, no candidatesvertical tid-lists, depth-first
Database scansone per leveltwoone
Speedslowestfastestfast
Memorycandidate setsthe FP-treetid-list intersections
Resultsidenticalidenticalidentical
Easiest to explainyesnono
quizCheck yourself
  1. A rule has confidence 0.85 and lift 0.95. What does that mean?

    Show answer

    B — The consequent is so common that seeing the antecedent actually makes it slightly LESS likely — Lift below 1 means the pair co-occurs less often than independence predicts. Confidence 0.85 only looks impressive until you notice the consequent's base rate must be about 0.89. This is exactly the trap that ranking by confidence walks into.

  2. The itemset {A, B} has support 0.15, below your threshold of 0.20. What can you say about {A, B, C}?

    Show answer

    B — Its support is at most 0.15, so it is also infrequent and can be pruned unexamined — Downward closure: adding an item can only shrink the set of transactions containing the itemset. supp({A,B,C}) <= supp({A,B}) = 0.15 < 0.20. That single fact is what makes Apriori feasible — on the page's data it removed 7 of 15 lattice nodes at once.

  3. conf(bread => butter) is 0.6154 but conf(butter => bread) is 0.8000. Yet both have lift 1.2308. Why?

    Show answer

    B — Confidence divides by supp(antecedent), which differs by direction; lift divides by both supports, making it symmetric — lift = supp(A and B) / (supp(A) supp(B)), which is unchanged when you swap A and B. Confidence uses only supp(A) in the denominator, so it rises whenever the consequent is more common than the antecedent.

  4. Your retail dataset has 200,000 transactions and 8,000 products, and Apriori runs out of memory at level 3. What is the best first move?

    Show answer

    B — Raise min_support and switch to FP-Growth, which needs two database scans instead of one per level — Memory goes on candidate itemsets, which explode as min_support falls. FP-Growth avoids candidate generation entirely by compressing the transactions into a prefix tree, and returns identical results.

  5. Which metric would you use to demote a rule with lift 15 that appears in only 4 of 100,000 baskets?

    Show answer

    B — Leverage, because it is a difference in supports and is therefore bounded by how much data is affected — Leverage is supp(A and B) - supp(A)supp(B). With supp(A and B) = 0.00004 it is essentially zero no matter how large the ratio is. Lift is scale-free and so rewards tiny, unreliable counts; leverage does not.

🧪 Try It Yourself

Exercise 1 – Support by counting

Exercise 2 – Confidence is not symmetric, lift is

Exercise 3 – Find the negative association

Exercise 4 – Downward closure in action

Exercise 5 – All five metrics at once

Recap

  • Support counts how often; confidence is P^(BA)\hat{P}(B \mid A); lift compares that against independence.
  • Confidence is directional, lift is symmetric: bread ⇒ butter and butter ⇒ bread gave 0.6154 and 0.8000 confidence but the same 1.2308 lift.
  • Four different rules in the data share confidence 0.8000 with lifts from 1.23 to 4.00. Never rank by confidence alone.
  • chips ⇒ breadchips ⇒ bread has lift 0.3077 and leverage -0.1125 — a real negative association that a lift > 1lift > 1 filter would discard.
  • Leverage demotes high-lift rules resting on a handful of baskets; conviction is infinite when a rule has no counterexamples.
  • Downward closure — an infrequent itemset has no frequent supersets — pruned the search from 162 possible itemsets to 24 ever counted.
  • Apriori scans the database once per level; FP-Growth does it in two passes and returns identical results.

Exercise 6 – Confidence without lift is a trap

Next

Principal Component Analysis (PCA) — the second half of this phase. Instead of grouping rows or items, PCA reduces the number of columns, and it does it by finding the directions along which the data actually varies.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did