Chapter 116 · Practice Challenges, Solved¶
Five market-basket exercises, worked in mlxtend on the Chapter 116 grocery transactions.
import numpy as np, pandas as pd, matplotlib.pyplot as plt
import seaborn as sns # seaborn = high-level statistical plots (heatmaps, pairplots, count/bar plots)
from matplotlib.colors import ListedColormap
EM="#4338ca"; DEEP="#3730a3"; LIGHT="#c7d2fe"; INK="#1a2138"; GRID="#e6e9f2"; RED="#ef4444"; AMBER="#d97706"; GREEN="#059669"; BLUE="#2563eb"; PUR="#9333ea"; GREY="#94a3b8"; SLATE="#475569"; ORG="#4338ca"; CYAN="#0891b2"
plt.rcParams.update({"figure.facecolor":"white","axes.facecolor":"white","figure.dpi":110,"font.size":11,
"axes.edgecolor":GRID,"axes.grid":True,"grid.color":GRID,"axes.axisbelow":True,"axes.spines.top":False,
"axes.spines.right":False,"axes.titlesize":12,"axes.titleweight":"bold","legend.frameon":False})
sns.set_style("whitegrid")
BASE="https://raw.githubusercontent.com/johnfisher-ai/Statistics-Data-Science-AI-Visual-Book/main/data/"
from mlxtend.frequent_patterns import apriori, fpgrowth, association_rules
try: df = pd.read_excel('../../data/association-rule-mining--baskets.xlsx', sheet_name='Data')
except FileNotFoundError: df = pd.read_excel(BASE + 'association-rule-mining--baskets.xlsx', sheet_name='Data')
basket = df.assign(v=1).pivot_table(index='transaction_id', columns='item', values='v', fill_value=0).astype(bool)
print('basket matrix:', basket.shape)
basket matrix: (900, 25)
support = basket.mean().sort_values(ascending=False)
print(support.head(5).round(3).to_string())
print('most frequent item:', support.idxmax(), '(support %.2f)' % support.max())
item bread 0.334 jam 0.187 cereal 0.172 butter 0.170 bacon 0.167 most frequent item: bread (support 0.33)
Solution. Bread has the highest support (in about a third of baskets), the popular staple every rule must be compared against.
freq = apriori(basket, min_support=0.05, use_colnames=True)
print('frequent itemsets at 5% support:', len(freq))
print(freq['itemsets'].apply(len).value_counts().sort_index().to_string())
frequent itemsets at 5% support: 54 itemsets 1 25 2 20 3 8 4 1
Solution. A higher support threshold keeps fewer, more reliable itemsets, tune it to trade coverage against trustworthiness.
freq = apriori(basket, min_support=0.03, use_colnames=True)
rules = association_rules(freq, metric='lift', min_threshold=1.0)
rules['A'] = rules['antecedents'].apply(lambda s: ', '.join(sorted(s)))
rules['B'] = rules['consequents'].apply(lambda s: ', '.join(sorted(s)))
best = rules.sort_values('lift', ascending=False).iloc[0]
print('top rule: %s -> %s | support %.3f, confidence %.3f, lift %.2f' % (best.A, best.B, best.support, best.confidence, best.lift))
top rule: salsa -> chips, soda | support 0.092, confidence 0.615, lift 5.70
Solution. The strongest rules link the cookout items (chips, salsa, soda) with lift above 5, buying two of them makes the third far likelier than average.
r = rules[(rules.A=='bananas') & (rules.B=='bread')][['support','confidence','lift']]
print(r.round(3).to_string(index=False))
print('bread baseline support = %.2f' % basket['bread'].mean())
print('confidence 0.37 barely beats the 0.33 baseline -> lift ~1.1 -> essentially independent')
support confidence lift 0.047 0.368 1.102 bread baseline support = 0.33 confidence 0.37 barely beats the 0.33 baseline -> lift ~1.1 -> essentially independent
Solution. Bananas do not really drive bread; bread is simply in a third of all baskets, so 37% confidence is almost the baseline. Lift near 1 exposes the coincidence, always judge a rule by lift, not confidence.
a = apriori(basket, min_support=0.03, use_colnames=True)
f = fpgrowth(basket, min_support=0.03, use_colnames=True)
sa = set(frozenset(x) for x in a['itemsets']); sf = set(frozenset(x) for x in f['itemsets'])
print('Apriori itemsets:', len(sa), '| FP-Growth itemsets:', len(sf))
print('identical set of itemsets:', sa == sf)
Apriori itemsets: 61 | FP-Growth itemsets: 61 identical set of itemsets: True
Solution. FP-Growth finds exactly the same frequent itemsets as Apriori but with a compact tree instead of repeated scans, so on large catalogs it is much faster while giving identical rules.