Quick answer: The Apriori algorithm finds groups of items that frequently appear together in a dataset of transactions β the classic example is “customers who buy bread and butter usually buy jam too”. It works level by level: first count single items, keep those above a minimum support threshold, combine survivors into pairs, count again, and repeat. Its key trick, the Apriori principle, says any subset of a frequent itemset must itself be frequent, which lets it skip the vast majority of candidate combinations.
This guide explains support, confidence and lift with real numbers, walks through the algorithm on a small grocery dataset, gives you a from-scratch Python implementation and the library version, compares Apriori with FP-Growth, and covers the mistakes that produce misleading rules.
The problem: market basket analysis
A supermarket records millions of receipts. Each receipt is a transaction: a set of items bought together. Management wants to know which products are associated to plan shelf placement, bundles and recommendations. With 10,000 products the number of combinations is astronomical, so brute force is impossible. Apriori (Agrawal and Srikant, 1994) made the analysis practical, and the same idea powers “frequently bought together” on e-commerce sites.
Three metrics you must understand
Use this dataset throughout:
| Transaction | Items |
|---|---|
| T1 | bread, butter, jam |
| T2 | bread, butter |
| T3 | bread, milk |
| T4 | butter, jam, milk |
| T5 | bread, butter, jam, milk |
| T6 | bread, jam |
- Support β how often an itemset appears.
support({butter, jam}) = 3/6 = 0.50because T1, T4 and T5 contain both. - Confidence β how often the right side appears when the left side does. For butter β jam:
0.50 / support({butter}) = 0.50 / 0.67 = 0.75. Three of four butter buyers also bought jam. - Lift β confidence divided by the right side’s base rate:
0.75 / support({jam}) = 0.75 / 0.67 = 1.125. Above 1 is a genuine association; 1 is independence; below 1 means the items repel each other.
Confidence alone misleads. bread β butter has confidence 0.60, but butter’s base rate is 0.67, so lift is 0.9 β bread makes butter slightly less likely here. Always report lift.
How Apriori works, step by step
Set minimum support to 0.5 (at least 3 of 6 transactions).
- Count 1-itemsets. bread 5/6, butter 4/6, jam 4/6, milk 3/6. All pass.
- Generate and count pairs from the survivors: {bread, butter} 3, {bread, jam} 3, {butter, jam} 3 pass; the three pairs containing milk appear only twice and fail.
- Generate triples by joining frequent pairs: only {bread, butter, jam}. {bread, butter, milk} is never generated because {bread, milk} already failed β the Apriori principle at work.
- Count the triple. It appears in T1 and T5 only (0.33) and fails. No larger candidates exist, so the algorithm stops.
- Generate rules from each frequent itemset of size 2 or more, compute confidence and lift for every antecedent/consequent split, and keep those above the confidence threshold.
The Apriori principle is the whole point: if {bread, milk} is infrequent, every superset of it must be too, so none is ever counted. In real retail data this prunes well over 99% of candidates.
Hands-on: Apriori from scratch in Python
This implementation mirrors the steps above and works on any list of Python sets.
from itertools import combinations
transactions = [
{"bread", "butter", "jam"},
{"bread", "butter"},
{"bread", "milk"},
{"butter", "jam", "milk"},
{"bread", "butter", "jam", "milk"},
{"bread", "jam"},
]
def support(itemset, transactions):
return sum(1 for t in transactions if itemset <= t) / len(transactions)
def apriori(transactions, min_support):
items = {item for t in transactions for item in t}
current = {frozenset([i]) for i in items
if support(frozenset([i]), transactions) >= min_support}
frequent = {}
k = 1
while current:
for s in current:
frequent[s] = support(s, transactions)
candidates = set()
for a, b in combinations(current, 2):
union = a | b
if len(union) == k + 1:
# Apriori principle: every k-subset must be frequent
if all(frozenset(sub) in current for sub in combinations(union, k)):
candidates.add(union)
current = {c for c in candidates if support(c, transactions) >= min_support}
k += 1
return frequent
def association_rules(frequent, min_confidence):
rules = []
for itemset, sup in frequent.items():
if len(itemset) < 2:
continue
for r in range(1, len(itemset)):
for antecedent in combinations(itemset, r):
antecedent = frozenset(antecedent)
consequent = itemset - antecedent
confidence = sup / frequent[antecedent]
lift = confidence / frequent[consequent]
if confidence >= min_confidence:
rules.append((antecedent, consequent, sup, confidence, lift))
return sorted(rules, key=lambda r: r[4], reverse=True)
frequent = apriori(transactions, min_support=0.5)
for ante, cons, sup, conf, lift in association_rules(frequent, min_confidence=0.6):
print(f"{set(ante)} -> {set(cons)} support={sup:.2f} confidence={conf:.2f} lift={lift:.2f}")
Running it prints rules such as {'butter'} -> {'jam'} with confidence 0.75 and lift 1.12. The frequent[antecedent] lookup never fails because every subset of a frequent itemset is itself frequent and therefore in the dictionary.
Hands-on: the library version with mlxtend
In practice use a tested implementation; mlxtend works directly with pandas.
# pip install mlxtend pandas
import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import apriori, association_rules
dataset = [sorted(t) for t in transactions] # reuse the list from above
encoder = TransactionEncoder()
onehot = encoder.fit(dataset).transform(dataset) # one column per item, True/False
df = pd.DataFrame(onehot, columns=encoder.columns_)
frequent = apriori(df, min_support=0.5, use_colnames=True)
rules = association_rules(frequent, metric="lift", min_threshold=1.0,
num_itemsets=len(df)) # num_itemsets needed in mlxtend 0.23.2+
print(rules[["antecedents", "consequents", "support", "confidence", "lift"]])
Real receipts usually arrive as a long table of (order_id, product) rows; df.groupby("order_id")["product"].apply(list) converts them into the list-of-lists the encoder expects.
Apriori versus FP-Growth
| Algorithm | Strategy | Database scans | Strength | Weakness |
|---|---|---|---|---|
| Apriori | Breadth-first candidate generation and pruning | One per itemset size | Simple, easy to explain and implement | Slow on large data with low support thresholds |
| FP-Growth | Compress data into an FP-tree, mine recursively | Two | Much faster, no candidate generation | Tree can be large; harder to implement |
Learn Apriori first because it teaches the concepts; switch to FP-Growth (mlxtend.frequent_patterns.fpgrowth, same interface) for large data. Interviewers like to ask why Apriori is exponential in the worst case (2^n itemsets) yet practical thanks to pruning; our Top Google Interview Questions for Python Developers covers trade-offs like this.
Five mistakes that produce misleading rules
- Judging rules by confidence alone. A popular consequent inflates confidence; always check lift.
- Setting support badly. Too low and you get millions of noisy rules and a run that never finishes; too high and you only learn that people buy milk and bread. Start at 1β5% for retail data and tune.
- Treating association as causation. Nappies and beer appearing together reflects a shopper profile, not cause and effect.
- Ignoring item hierarchy. “Butter 500g” and “Butter 100g” are different items to the algorithm; aggregate to a category level first.
- Not validating over time. Rules mined from Diwali week will not hold in March. Re-mine on a rolling window.
Frequently asked questions
Is Apriori supervised or unsupervised learning?
Unsupervised: there is no target variable, only co-occurrence patterns discovered in unlabelled data.
Why does Apriori need multiple passes over the data?
Each level of candidates must be counted against every transaction, so k levels need k scans β the main reason FP-Growth, with two scans, is faster on large data.
Where is Apriori used besides retail?
Recommendation engines, web usage mining, fraud detection, bioinformatics, healthcare and IT operations (alerts that fire together).
Key takeaways
- Apriori mines frequent itemsets level by level and uses the principle “every subset of a frequent set is frequent” to prune candidates.
- Support filters rarity, confidence measures rule reliability, and lift tells you whether the association is real.
- You can implement it in 40 lines of Python; use
mlxtendor FP-Growth at scale. - Good rules need sensible thresholds, item aggregation and validation over time.
Want to master algorithms like Apriori and apply them in real applications? The Techknowledgehub Full Stack Development course builds algorithm foundations alongside modern web development, with live mentoring, projects and placement support. For free walkthroughs, subscribe to our YouTube channel.



