SythraOpen app

Association Rule Mining in Python: Apriori Math, Support, Confidence, and Lift Explained

Association Rule Mining is an unsupervised machine learning technique used in Market Basket Analysis to uncover actionable 'if-then' item relationships across transactions. The Apriori Algorithm uses the anti-monotonicity property (all subsets of a frequent itemset must also be frequent) to prune search space exponentially, filtering rules with Support (frequency), Confidence (conditional probability), and Lift (correlation over independence).

Sythra

12 min read

XLinkedIn
Association Rule Mining in Python: Apriori Math, Support, Confidence, and Lift Explained — cover illustration

Association Rule Mining is an unsupervised data mining technique designed to discover interesting 'if-then' relationships and co-occurrence patterns among items in massive transactional databases. Unlike supervised models that predict a specific target column, association rule mining requires no labeled outcomes; it scans unlabeled baskets to determine which items naturally gravitate together — unlocking the foundational science behind retail recommendation engines and Market Basket Analysis.

1. Key Concepts & Metric Glossary

Every association rule is expressed in the form ABA \Rightarrow B, where AA is the antecedent (the item bought first) and BB is the consequent (the item added as a result). Here are the primary statistical metrics used to evaluate rule quality:

Metric / TermMathematical FormulaPlain-English MeaningTarget / Range
Itemset (II)A collection of items: {i1,i2,,ik}\{i_1, i_2, \dots, i_k\}Any group of products found inside a customer's basket.Size k1k \ge 1
Supportsupport(A)=count(A)N\text{support}(A) = \frac{\text{count}(A)}{N}How common is this itemset across all NN total transactions?[0,1][0, 1] (Higher = more universal)
Rule Supportsupport(AB)=support(AB)\text{support}(A \Rightarrow B) = \text{support}(A \cup B)How frequently are both AA and BB purchased together?[0,1][0, 1] (Filters out rare noise)
Confidenceconfidence(AB)=support(AB)support(A)\text{confidence}(A \Rightarrow B) = \frac{\text{support}(A \cup B)}{\text{support}(A)}When a customer buys AA, what percentage also buys BB?[0,1][0, 1] (Directional certainty)
Liftlift(AB)=confidence(AB)support(B)\text{lift}(A \Rightarrow B) = \frac{\text{confidence}(A \Rightarrow B)}{\text{support}(B)}How much does buying AA increase the odds of buying BB over chance?>1>1: Positive, =1=1: Independent, <1<1: Negative
Leverageleverage=support(AB)support(A)support(B)\text{leverage} = \text{support}(A \cup B) - \text{support}(A)\text{support}(B)Difference between observed joint frequency and expected frequency under independence.[0.25,+0.25][-0.25, +0.25] (0 = independent)
Convictionconviction=1support(B)1confidence(AB)\text{conviction} = \frac{1 - \text{support}(B)}{1 - \text{confidence}(A \Rightarrow B)}Ratio of expected frequency of AA without BB to observed incorrect predictions.[0,)[0, \infty) (1 = independent, \infty = perfect rule)

2. Mathematical Foundations & The Downward Closure Property

Let a transactional database D={T1,T2,,TN}D = \{T_1, T_2, \dots, T_N\} consist of NN transactions, where each transaction TiT_i is a subset of the master item catalog II. An association rule is an implication of the form:

ABwhere AI,  BI,  and AB=A \Rightarrow B \quad \text{where } A \subset I, \; B \subset I, \; \text{and } A \cap B = \emptyset

The Directional Asymmetry of Confidence

Confidence represents conditional probability: confidence(AB)=P(BA)\text{confidence}(A \Rightarrow B) = P(B \mid A). Crucially, confidence is directionally asymmetric:

confidence(AB)=support(AB)support(A)support(AB)support(B)=confidence(BA)\text{confidence}(A \Rightarrow B) = \frac{\text{support}(A \cup B)}{\text{support}(A)} \neq \frac{\text{support}(A \cup B)}{\text{support}(B)} = \text{confidence}(B \Rightarrow A)

For instance, if nearly everyone who buys printer toner also buys printer paper, confidence(TonerPaper)\text{confidence}(\text{Toner} \Rightarrow \text{Paper}) might be 95%95\%. However, millions of people buy paper without buying toner, meaning confidence(PaperToner)\text{confidence}(\text{Paper} \Rightarrow \text{Toner}) could be as low as 5%5\%. Direction dictates shelf placement!

Lift: Correcting for Consequent Popularity

Confidence can be deceiving if the consequent item BB is universally popular. Lift controls for baseline frequency by dividing confidence by the independent support of BB:

lift(AB)=confidence(AB)support(B)=support(AB)support(A)×support(B)\text{lift}(A \Rightarrow B) = \frac{\text{confidence}(A \Rightarrow B)}{\text{support}(B)} = \frac{\text{support}(A \cup B)}{\text{support}(A) \times \text{support}(B)}

Notice that unlike confidence, Lift is mathematically symmetric: lift(AB)=lift(BA)\text{lift}(A \Rightarrow B) = \text{lift}(B \Rightarrow A). It measures the strength of the mutual association rather than direction.

The Anti-Monotonicity Principle (Apriori Pruning Property)

For a catalog of dd unique items, there exist 2d12^d - 1 possible candidate itemsets. If a supermarket stocks just 100 items, searching all combinations would require evaluating 210011.26×10302^{100} - 1 \approx 1.26 \times 10^{30} itemsets — a computational impossibility.

The Apriori Algorithm overcomes this combinatorial explosion through the Downward Closure Property (Anti-Monotonicity):

A,Bif AB    support(B)support(A)\forall A, B \quad \text{if } A \subseteq B \implies \text{support}(B) \le \text{support}(A)

3. Step-by-Step Worked Numerical Trace

Let us trace the Apriori algorithm step by step on a real 8-basket supermarket dataset with min_support=0.50\text{min\_support} = 0.50 (must appear in at least 44 out of 88 transactions):

  • T1T_1: {Bread, Milk}
  • T2T_2: {Bread, Diapers, Beer, Eggs}
  • T3T_3: {Milk, Diapers, Beer, Cola}
  • T4T_4: {Bread, Milk, Diapers, Beer}
  • T5T_5: {Bread, Milk, Diapers, Cola}
  • T6T_6: {Bread, Milk, Diapers, Beer}
  • T7T_7: {Milk, Diapers, Beer}
  • T8T_8: {Bread, Milk, Diapers}

Pass 1: Frequent 1-Itemsets (L1L_1)

Count the occurrence of every individual item across all 8 transactions:

  • Milk: 7/8=0.8757/8 = 0.875 (Survives)
  • Diapers: 7/8=0.8757/8 = 0.875 (Survives)
  • Bread: 6/8=0.7506/8 = 0.750 (Survives)
  • Beer: 5/8=0.6255/8 = 0.625 (Survives)
  • Cola: 2/8=0.2502/8 = 0.250 (Pruned: below 0.50)
  • Eggs: 1/8=0.1251/8 = 0.125 (Pruned: below 0.50)

Pass 2: Frequent 2-Itemsets (L2L_2)

Form candidate pairs exclusively from surviving items {Milk, Diapers, Bread, Beer}:

  • {Milk, Diapers}: 6/8=0.7506/8 = 0.750 (Survives)
  • {Diapers, Beer}: 5/8=0.6255/8 = 0.625 (Survives)
  • {Milk, Bread}: 5/8=0.6255/8 = 0.625 (Survives)
  • {Bread, Diapers}: 5/8=0.6255/8 = 0.625 (Survives)
  • {Milk, Beer}: 4/8=0.5004/8 = 0.500 (Survives)
  • {Bread, Beer}: 3/8=0.3753/8 = 0.375 (Pruned: below 0.50)

Pass 3: Frequent 3-Itemsets (L3L_3)

Generate 3-item combinations from pairs. Before counting, prune candidate {Bread, Diapers, Beer} because its subset {Bread, Beer} was already pruned! The surviving 3-itemsets are:

  • {Milk, Bread, Diapers}: 4/8=0.5004/8 = 0.500 (Survives)
  • {Milk, Diapers, Beer}: 4/8=0.5004/8 = 0.500 (Survives)

4. The Milk & Bread Trap: Why Lift Is Non-Negotiable

Now examine two rules generated with minimum confidence min_confidence=0.70\text{min\_confidence} = 0.70 (70%70\%):

  • Rule A: {Diapers} \Rightarrow {Beer}
    confidence=0.6250.8750.714(71.4%),lift=0.7140.6251.143\text{confidence} = \frac{0.625}{0.875} \approx 0.714 \quad (71.4\%), \quad \text{lift} = \frac{0.714}{0.625} \approx 1.143
    Because lift=1.143>1\text{lift} = 1.143 > 1, buying diapers provides a genuine +14.3%+14.3\% uplift in the probability of purchasing beer.
  • Rule B: {Bread} \Rightarrow {Milk}
    confidence=0.6250.7500.833(83.3%),lift=0.8330.8750.952\text{confidence} = \frac{0.625}{0.750} \approx 0.833 \quad (83.3\%), \quad \text{lift} = \frac{0.833}{0.875} \approx 0.952
    Notice that confidence is an impressive 83.3%83.3\%. But look at lift: 0.952<1.00.952 < 1.0!

5. From-Scratch Python Implementation

Here is the complete from-scratch implementation of the Apriori algorithm and rule generation in pure Python using only standard library utilities:

from itertools import combinations, chain

def compute_support(itemset, transactions):
    """Calculates the fraction of transactions containing the itemset."""
    itemset_set = set(itemset)
    count = sum(1 for t in transactions if itemset_set.issubset(t))
    return count / len(transactions)

def get_frequent_itemsets(candidates, transactions, min_support):
    """Filters candidate itemsets against the minimum support threshold."""
    frequent = {}
    for candidate in candidates:
        sup = compute_support(candidate, transactions)
        if sup >= min_support:
            frequent[frozenset(candidate)] = sup
    return frequent

def generate_candidates(prev_frequent, k):
    """
    Self-joins frequent (k-1)-itemsets and applies Apriori pruning:
    a candidate k-itemset is only kept if ALL its (k-1) subsets are frequent.
    """
    items = sorted(prev_frequent.keys(), key=lambda x: sorted(x))
    candidates = set()
    n = len(items)

    for i in range(n):
        for j in range(i + 1, n):
            union = items[i] | items[j]
            if len(union) == k:
                # Downward closure check: all (k-1) subsets must be in prev_frequent
                subsets_valid = all(
                    frozenset(sub) in prev_frequent
                    for sub in combinations(union, k - 1)
                )
                if subsets_valid:
                    candidates.add(frozenset(union))
    return candidates

def apriori_from_scratch(transactions, min_support=0.5):
    """Full Apriori algorithm returning all frequent itemsets."""
    all_unique_items = set(chain.from_iterable(transactions))
    candidates_1 = [{item} for item in all_unique_items]
    current_frequent = get_frequent_itemsets(candidates_1, transactions, min_support)
    all_frequent = dict(current_frequent)

    k = 2
    while current_frequent:
        candidates_k = generate_candidates(current_frequent, k)
        current_frequent = get_frequent_itemsets(candidates_k, transactions, min_support)
        all_frequent.update(current_frequent)
        k += 1

    return all_frequent

def generate_association_rules(frequent_itemsets, transactions, min_confidence=0.7):
    """Generates A => B rules and calculates Support, Confidence, and Lift."""
    rules = []
    n_transactions = len(transactions)

    for itemset, itemset_support in frequent_itemsets.items():
        if len(itemset) < 2:
            continue
        items_list = list(itemset)

        for r in range(1, len(items_list)):
            for ant_tuple in combinations(items_list, r):
                antecedent = frozenset(ant_tuple)
                consequent = itemset - antecedent

                ant_support = frequent_itemsets.get(antecedent) or compute_support(antecedent, transactions)
                confidence = itemset_support / ant_support

                if confidence >= min_confidence:
                    cons_support = frequent_itemsets.get(consequent) or compute_support(consequent, transactions)
                    lift = confidence / cons_support
                    leverage = itemset_support - (ant_support * cons_support)
                    conviction = (1.0 - cons_support) / (1.0 - confidence) if confidence < 1.0 else float("inf")

                    rules.append({
                        "antecedent": set(antecedent),
                        "consequent": set(consequent),
                        "support": itemset_support,
                        "confidence": confidence,
                        "lift": lift,
                        "leverage": leverage,
                        "conviction": conviction
                    })
    return rules

# Verification test on 8 grocery baskets
if __name__ == "__main__":
    transactions = [
        {"Bread", "Milk"},
        {"Bread", "Diapers", "Beer", "Eggs"},
        {"Milk", "Diapers", "Beer", "Cola"},
        {"Bread", "Milk", "Diapers", "Beer"},
        {"Bread", "Milk", "Diapers", "Cola"},
        {"Bread", "Milk", "Diapers", "Beer"},
        {"Milk", "Diapers", "Beer"},
        {"Bread", "Milk", "Diapers"},
    ]

    frequent = apriori_from_scratch(transactions, min_support=0.5)
    print(f"Total frequent itemsets found: {len(frequent)}")

    rules = generate_association_rules(frequent, transactions, min_confidence=0.7)
    print(f"Total rules meeting 70% confidence: {len(rules)}\n")
    for r in sorted(rules, key=lambda x: -x["lift"]):
        ant = " + ".join(sorted(r["antecedent"]))
        cons = " + ".join(sorted(r["consequent"]))
        print(f"{{{ant}}} => {{{cons}}}")
        print(f"  Support: {r['support']:.3f} | Conf: {r['confidence']:.3f} | Lift: {r['lift']:.3f} | Leverage: {r['leverage']:.3f}\n")

6. Production Implementation via mlxtend

In production workflows, Python developers use the highly optimized mlxtend library to run Market Basket Analysis on tabular DataFrames:

import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import apriori, association_rules

# 1. Input Transactions (8 retail baskets)
transactions = [
    ["Bread", "Milk"],
    ["Bread", "Diapers", "Beer", "Eggs"],
    ["Milk", "Diapers", "Beer", "Cola"],
    ["Bread", "Milk", "Diapers", "Beer"],
    ["Bread", "Milk", "Diapers", "Cola"],
    ["Bread", "Milk", "Diapers", "Beer"],
    ["Milk", "Diapers", "Beer"],
    ["Bread", "Milk", "Diapers"],
]

# 2. One-hot encode transactions into a Boolean DataFrame
te = TransactionEncoder()
te_array = te.fit(transactions).transform(transactions)
df = pd.DataFrame(te_array, columns=te.columns_)

# 3. Mine frequent itemsets with min_support = 50%
frequent_itemsets = apriori(df, min_support=0.5, use_colnames=True)
frequent_itemsets["length"] = frequent_itemsets["itemsets"].apply(lambda x: len(x))

print("=== Frequent Itemsets (min_support = 0.5) ===")
print(frequent_itemsets.sort_values(by="support", ascending=False).to_string(index=False))

# 4. Generate association rules evaluated on Confidence and Lift
rules = association_rules(
    frequent_itemsets,
    metric="confidence",
    min_threshold=0.7,
    num_itemsets=len(frequent_itemsets)
)

# Format for clean display
rules["antecedents"] = rules["antecedents"].apply(lambda x: ", ".join(list(x)))
rules["consequents"] = rules["consequents"].apply(lambda x: ", ".join(list(x)))

cols_to_show = ["antecedents", "consequents", "support", "confidence", "lift", "leverage", "conviction"]
print("\n=== Discovered Association Rules (min_confidence = 0.7) ===")
print(rules[cols_to_show].sort_values(by="lift", ascending=False).to_string(index=False))

7. Advanced Metrics: Leverage & Conviction

Beyond Lift, production recommendation pipelines rely on Leverage and Conviction to filter out spurious correlations:

  • Leverage: leverage(AB)=support(AB)support(A)×support(B)\text{leverage}(A \Rightarrow B) = \text{support}(A \cup B) - \text{support}(A) \times \text{support}(B). Unlike Lift (which is a ratio), Leverage measures the absolute surplus probability above independence in units of customer volume. leverage=0\text{leverage} = 0 indicates independence.
  • Conviction: conviction(AB)=1support(B)1confidence(AB)\text{conviction}(A \Rightarrow B) = \frac{1 - \text{support}(B)}{1 - \text{confidence}(A \Rightarrow B)}. Measures the expected error rate under independence divided by the observed error rate. A conviction of \infty means the rule is an absolute law (confidence is 100%100\%; AA is never seen without BB).

8. Apriori vs. FP-Growth: Algorithmic Comparison

FeatureApriori AlgorithmFP-Growth (Frequent Pattern Tree)
Database Passeskk passes (one full scan per itemset length)Exactly 2 database scans
Candidate GenerationExplicit self-join (CkC_k) with subset testingZero candidate generation (mines tree recursively)
Data StructureArray of transaction sets or hash treesCompact prefix tree (FP-Tree) in memory
Execution SpeedSlow on dense data and low supportUp to 10×10\times to 100×100\times faster than Apriori
Best Use CaseEducational intuition and small catalogsEnterprise e-commerce and large transaction databases

9. Top 5 Pitfalls & Common Mistakes

  1. Trusting Confidence Without Checking Lift: Blindly reporting high-confidence rules where consequent items are staples (Milk, Bananas) leads to promoting items that shoppers would have bought anyway.
  2. The Rare Item Problem: High-value items (televisions, laptops) have low support (0.001%0.001\%), so uniform minimum support thresholds discard them while retaining low-margin groceries.
  3. Conflating Association with Causation: Discovering ABA \Rightarrow B does not mean purchasing AA causes someone to purchase BB; an unobserved third factor (like a rainy day) could cause purchases of both umbrellas and hot coffee.
  4. Ignoring Directional Asymmetry: Placing beer next to diapers boosts beer sales, but placing diapers next to beer does not necessarily boost diaper sales.
  5. Memory Exhaustion on Low Support: Lowering min_support too far on catalogs with thousands of items causes exponential candidate explosion, crashing server memory.

10. Summary & Key Takeaways

  • Market Basket Analysis: Uncovers co-occurrence patterns in transaction records without requiring labeled outcomes.
  • Support, Confidence, and Lift: Support measures overall frequency; Confidence measures directional probability; Lift measures true association over chance.
  • The Downward Closure Property: All subsets of a frequent itemset must also be frequent, enabling Apriori to prune unpromising candidates.
  • Always Verify Lift > 1: Rules with Lift <1.0< 1.0 indicate independence or negative association, even when confidence approaches 90%90\%.
  • Use FP-Growth for Scale: For enterprise catalogs, transition from Apriori's kk database scans to FP-Growth's tree-based mining.

Common questions

What is the primary difference between Support, Confidence, and Lift?

Support measures how often items appear together across all transactions (frequency). Confidence measures how often item B is bought when item A is purchased (conditional certainty). Lift measures how much more likely item B is purchased when item A is present compared to B's baseline popularity (association strength).

What is the Apriori property (anti-monotonicity)?

The Apriori property states that all subsets of a frequent itemset must also be frequent. Conversely, if an itemset is infrequent, none of its supersets can ever be frequent. This allows the algorithm to prune vast combinatorial branches without counting transactions.

Why is Lift symmetric while Confidence is asymmetric?

Confidence divides joint support by the antecedent support (P(B|A)), which changes depending on which item is the antecedent. Lift divides joint support by the product of both individual supports (P(A and B) / (P(A) * P(B))), which produces identical values regardless of direction.

What does a Lift value of less than 1.0 mean?

A Lift below 1.0 indicates a negative association or substitution effect. It means purchasing item A actually decreases the probability that the customer will purchase item B compared to random chance, even if the rule has a seemingly high confidence score.

When should you use FP-Growth instead of the Apriori algorithm?

FP-Growth should be used on large production datasets, dense transaction logs, or when minimum support is set low. While Apriori generates millions of candidate pairs and scans the database repeatedly, FP-Growth builds a compact prefix tree and mines frequent patterns in just two database passes.

Explore

Related topics

Keep going — these sit next to this concept in a real learning path.

Browse all machine learning explainers →