Market Basket Analysis¶
연관 규칙 분석을 활용한 장바구니 분석 가이드.
개요¶
Market Basket Analysis(MBA)는 고객의 구매 패턴에서 함께 구매되는 상품 조합을 발견하는 기법이다.
| 구분 | 설명 |
|---|---|
| 목적 | 상품 간 연관 관계 발견 |
| 핵심 기법 | Association Rule Mining |
| 대표 알고리즘 | Apriori, FP-Growth |
| 적용 분야 | 리테일, 이커머스, 추천 시스템 |
핵심 개념¶
주요 지표¶
| 지표 | 정의 | 해석 |
|---|---|---|
| Support | \(P(A \cap B)\) | 함께 구매된 비율 |
| Confidence | \(P(B\|A)\) | A 구매 시 B 구매 확률 |
| Lift | \(\frac{P(B\|A)}{P(B)}\) | 기대 대비 실제 연관성 |
| Conviction | \(\frac{1-P(B)}{1-Conf(A→B)}\) | 규칙의 방향성 강도 |
수식¶
Support: $\(Support(A \rightarrow B) = \frac{|A \cap B|}{N}\)$
Confidence: $\(Confidence(A \rightarrow B) = \frac{Support(A \cap B)}{Support(A)}\)$
Lift: $\(Lift(A \rightarrow B) = \frac{Support(A \cap B)}{Support(A) \times Support(B)}\)$
Lift 해석¶
| Lift 값 | 해석 |
|---|---|
| > 1 | 양의 연관 (함께 구매 경향) |
| = 1 | 독립 (연관 없음) |
| < 1 | 음의 연관 (대체 관계) |
알고리즘¶
1. Apriori¶
원리: 빈발 아이템셋의 부분집합도 빈발하다 (Downward Closure)
┌─────────────────────────────────────────────────────────┐
│ Apriori 알고리즘 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1단계: 빈발 1-아이템셋 │
│ {빵}: 4, {우유}: 5, {버터}: 3, {맥주}: 3 │
│ (min_support = 3 이상) │
│ │
│ 2단계: 후보 2-아이템셋 생성 │
│ {빵,우유}, {빵,버터}, {빵,맥주}, ... │
│ │
│ 3단계: 빈발 2-아이템셋 필터링 │
│ {빵,우유}: 3 ✓, {우유,버터}: 3 ✓ │
│ │
│ 4단계: 반복 (k-아이템셋 → k+1-아이템셋) │
│ ... │
│ │
│ 5단계: 연관 규칙 생성 │
│ {빵} → {우유}: conf=0.75, lift=1.2 │
│ │
└─────────────────────────────────────────────────────────┘
시간 복잡도: \(O(2^n)\) (최악), 실제로는 pruning으로 감소
2. FP-Growth¶
원리: 데이터베이스 2번 스캔, FP-Tree 구조 활용
┌─────────────────────────────────────────────────────────┐
│ FP-Tree 구조 │
├─────────────────────────────────────────────────────────┤
│ │
│ [Root] │
│ / \ │
│ [우유:5] [빵:1] │
│ / \ │
│ [빵:3] [버터:2] │
│ | │
│ [버터:1] │
│ │
│ Header Table: │
│ 우유 → [Node1] │
│ 빵 → [Node2] → [Node3] │
│ 버터 → [Node4] → [Node5] │
│ │
└─────────────────────────────────────────────────────────┘
장점: - DB 스캔 2회 (Apriori는 k회) - 후보 생성 없음 - 대규모 데이터에 효율적
Python 구현¶
mlxtend 활용¶
from mlxtend.frequent_patterns import apriori, fpgrowth, association_rules
from mlxtend.preprocessing import TransactionEncoder
import pandas as pd
# 트랜잭션 데이터
transactions = [
['빵', '우유', '버터'],
['빵', '우유'],
['우유', '버터'],
['빵', '맥주', '기저귀'],
['우유', '맥주', '기저귀'],
]
# 원핫 인코딩
te = TransactionEncoder()
te_array = te.fit(transactions).transform(transactions)
df = pd.DataFrame(te_array, columns=te.columns_)
# 빈발 아이템셋 추출
frequent = fpgrowth(df, min_support=0.3, use_colnames=True)
# 연관 규칙 생성
rules = association_rules(frequent, metric="lift", min_threshold=1.0)
# 상위 규칙 확인
print(rules.sort_values('lift', ascending=False).head(10))
대규모 데이터 처리¶
from pyspark.ml.fpm import FPGrowth
# Spark FP-Growth
fp = FPGrowth(itemsCol="items", minSupport=0.01, minConfidence=0.5)
model = fp.fit(transactions_df)
# 빈발 아이템셋
model.freqItemsets.show()
# 연관 규칙
model.associationRules.show()
고급 기법¶
1. 시계열 장바구니 분석¶
구매 순서를 고려한 시퀀스 패턴:
from mlxtend.frequent_patterns import apriori
from collections import defaultdict
def sequential_patterns(transactions, time_window='7D'):
"""시간 순서 고려 패턴"""
sequences = defaultdict(list)
for tx in transactions.sort_values('timestamp'):
user = tx['user_id']
sequences[user].append({
'item': tx['item'],
'time': tx['timestamp']
})
# A 구매 후 B 구매 패턴 추출
sequential_rules = []
for user, seq in sequences.items():
for i, item1 in enumerate(seq):
for item2 in seq[i+1:]:
if item2['time'] - item1['time'] <= pd.Timedelta(time_window):
sequential_rules.append((item1['item'], item2['item']))
return Counter(sequential_rules)
2. 멀티레벨 분석¶
상품 계층 (카테고리 → 서브카테고리 → 상품) 적용:
def multilevel_mba(transactions, hierarchy):
"""
hierarchy = {
'코카콜라': ('음료', '탄산'),
'사이다': ('음료', '탄산'),
'오렌지주스': ('음료', '주스'),
}
"""
results = {}
# Level 1: 카테고리
cat_transactions = [[hierarchy[item][0] for item in tx] for tx in transactions]
results['category'] = fpgrowth(encode(cat_transactions), min_support=0.05)
# Level 2: 서브카테고리
subcat_transactions = [[hierarchy[item][1] for item in tx] for tx in transactions]
results['subcategory'] = fpgrowth(encode(subcat_transactions), min_support=0.03)
# Level 3: 상품
results['product'] = fpgrowth(encode(transactions), min_support=0.01)
return results
3. 조건부 장바구니 분석¶
특정 조건 (시간대, 고객 세그먼트 등) 별 분석:
def conditional_mba(transactions, condition_col):
"""조건별 MBA"""
results = {}
for condition in transactions[condition_col].unique():
subset = transactions[transactions[condition_col] == condition]
frequent = fpgrowth(encode(subset['items']), min_support=0.02)
rules = association_rules(frequent, metric="lift", min_threshold=1.2)
results[condition] = rules
return results
# 예: 시간대별 분석
weekday_rules = conditional_mba(transactions, 'day_of_week')
비즈니스 활용¶
1. 상품 배치¶
┌─────────────────────────────────────────────────────────┐
│ 연관 상품 기반 매장 배치 │
├─────────────────────────────────────────────────────────┤
│ │
│ Lift > 2 상품군 │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 파스타│──│ 토마토│──│올리브유│ → 인접 배치 │
│ └───────┘ └───────┘ └───────┘ │
│ │
│ Lift < 0.8 상품군 │
│ ┌───────┐ ┌───────┐ │
│ │ 콜라 │ VS │ 사이다│ → 대체재, 분산 배치 │
│ └───────┘ └───────┘ │
│ │
│ 고Lift + 저Support 상품 │
│ → 교차 판매 기회 (프로모션 연계) │
│ │
└─────────────────────────────────────────────────────────┘
2. 번들 상품¶
def recommend_bundles(rules, min_lift=2.0, min_support=0.02):
"""번들 상품 추천"""
bundle_candidates = rules[
(rules['lift'] >= min_lift) &
(rules['support'] >= min_support)
].copy()
# 번들 가격 최적화
for idx, row in bundle_candidates.iterrows():
items = list(row['antecedents']) + list(row['consequents'])
individual_price = sum(get_price(item) for item in items)
# 번들 할인율 계산 (Lift 기반)
discount_rate = min(0.2, (row['lift'] - 1) * 0.05)
bundle_price = individual_price * (1 - discount_rate)
bundle_candidates.loc[idx, 'bundle_price'] = bundle_price
return bundle_candidates
3. 추천 시스템¶
def get_recommendations(cart_items, rules, top_n=5):
"""장바구니 기반 추천"""
cart_set = frozenset(cart_items)
recommendations = []
for idx, rule in rules.iterrows():
antecedents = rule['antecedents']
consequents = rule['consequents']
# 장바구니에 antecedents가 포함되어 있고
# consequents가 포함되어 있지 않으면 추천
if antecedents.issubset(cart_set) and not consequents.issubset(cart_set):
for item in consequents:
if item not in cart_set:
recommendations.append({
'item': item,
'confidence': rule['confidence'],
'lift': rule['lift'],
'score': rule['confidence'] * rule['lift']
})
# 중복 제거 및 정렬
unique_recs = {}
for rec in recommendations:
item = rec['item']
if item not in unique_recs or rec['score'] > unique_recs[item]['score']:
unique_recs[item] = rec
return sorted(unique_recs.values(), key=lambda x: x['score'], reverse=True)[:top_n]
4. 프로모션 전략¶
| 규칙 패턴 | 전략 |
|---|---|
| 높은 Lift, 낮은 Support | 타겟 프로모션 (신규 조합 홍보) |
| 높은 Lift, 높은 Support | 번들 할인 |
| 낮은 Lift, 높은 Support | 개별 마진 유지 |
| A→B 일방향 강함 | B를 미끼 상품으로 활용 |
시각화¶
네트워크 그래프¶
import networkx as nx
import matplotlib.pyplot as plt
def plot_association_network(rules, min_lift=1.5):
"""연관 규칙 네트워크 시각화"""
G = nx.DiGraph()
for idx, row in rules[rules['lift'] >= min_lift].iterrows():
for ant in row['antecedents']:
for con in row['consequents']:
G.add_edge(ant, con, weight=row['lift'], confidence=row['confidence'])
pos = nx.spring_layout(G)
# Edge width = lift, color = confidence
edges = G.edges(data=True)
weights = [e[2]['weight'] for e in edges]
colors = [e[2]['confidence'] for e in edges]
plt.figure(figsize=(12, 8))
nx.draw(G, pos, with_labels=True,
edge_color=colors, edge_cmap=plt.cm.Reds,
width=[w/2 for w in weights],
node_size=2000, node_color='lightblue')
plt.title("Association Rules Network")
plt.show()
히트맵¶
import seaborn as sns
def plot_lift_heatmap(rules):
"""Lift 히트맵"""
# Pivot table 생성
pivot_data = []
for idx, row in rules.iterrows():
for ant in row['antecedents']:
for con in row['consequents']:
pivot_data.append({
'antecedent': ant,
'consequent': con,
'lift': row['lift']
})
pivot_df = pd.DataFrame(pivot_data).pivot(
index='antecedent', columns='consequent', values='lift'
)
plt.figure(figsize=(12, 10))
sns.heatmap(pivot_df, annot=True, cmap='RdYlGn', center=1)
plt.title("Association Rules Lift Heatmap")
plt.show()
실무 고려사항¶
임계값 설정¶
| 지표 | 권장 범위 | 비고 |
|---|---|---|
| min_support | 0.01-0.05 | 희소 데이터는 낮게 |
| min_confidence | 0.3-0.6 | 비즈니스 목적에 따라 |
| min_lift | 1.2-2.0 | 실질적 연관성 |
데이터 전처리¶
def preprocess_transactions(df):
"""트랜잭션 전처리"""
# 1. 반품 제거
df = df[df['quantity'] > 0]
# 2. 이상치 제거 (대량 구매)
df = df[df['quantity'] < df['quantity'].quantile(0.99)]
# 3. 희소 상품 필터링
item_counts = df['item'].value_counts()
valid_items = item_counts[item_counts >= 10].index
df = df[df['item'].isin(valid_items)]
# 4. 그룹화
transactions = df.groupby('transaction_id')['item'].apply(list).tolist()
return transactions
성능 최적화¶
| 문제 | 해결책 |
|---|---|
| 메모리 부족 | FP-Growth 사용, 샘플링 |
| 너무 많은 규칙 | 임계값 상향, 상위 N개만 |
| 희소 아이템 | 카테고리 레벨 분석 |
요약¶
Market Basket Analysis는 상품 간 연관 관계를 발견하여 상품 배치, 번들링, 추천, 프로모션에 활용한다. Support/Confidence/Lift 지표로 규칙의 품질을 평가하고, FP-Growth 알고리즘으로 대규모 데이터를 효율적으로 처리한다.