DataScience

쿠폰은 아무나 막 주는게 아니다 : K-Means 클러스터링 이란? CRM 고객분석 / 타겟 마케팅

Pro.Dev 2025. 3. 24. 16:03
반응형

머신러닝 기반 CRM 타겟 마케팅 대시보드

Why?? - CRM 안하는 회사는 망하니깐!!

오늘날 기업들은 수많은 고객 데이터를 보유하고 있지만, 이를 효과적으로 활용하는 것은 여전히 큰 도전과제입니다. 특히 고객의 구매 패턴과 행동 양식을 이해하고 이에 맞춘 타겟 마케팅을 진행하는 것은 마케팅 효율성과 고객 경험 향상에 필수적입니다. 이번 블로그에서는 비지도 학습(Unsupervised Learning)의 한 종류인 클러스터링 기법을 활용한 CRM 타겟 마케팅 대시보드를 소개합니다.

비지도 학습(Unsupervised Learning)과 고객 세그먼테이션

왜 비지도 학습인가?

마케팅에서 고객을 분류할 때, 우리는 데이터 자체에서 패턴을 발견해야 합니다. 바로 이 지점에서 비지도 학습의 강점이 드러납니다. 레이블이 없는 데이터에서 자연스러운 그룹을 발견하는 클러스터링은 고객 세그먼테이션의 핵심 도구입니다.

K-Means 클러스터링의 활용 : 배달 음식 주문 데이터 가지고, 비슷한 고객들로 묶어 볼게요~

음식 주문 데이터 링크 : https://drive.google.com/file/d/12FPO4PfTntvUwLXnpXHd8Q5otbdnRJQu/view?usp=sharing

우리의 대시보드는 K-Means 클러스터링 알고리즘을 통해 고객들을 유사한 행동 패턴을 가진 그룹으로 분류합니다. 이 접근 방식의 주요 이점은 다음과 같습니다:

  1. 데이터 기반 의사결정: 주관적인 판단이 아닌 실제 고객 행동 데이터에 기반한 세분화
  2. 숨겨진 패턴 발견: 기존에 인식하지 못했던 고객 그룹과 행동 패턴 발견
  3. 맞춤형 전략 개발: 각 클러스터의 특성에 맞는 정교한 마케팅 접근법 수립

대시보드의 핵심 기능

1. 데이터 기반 고객 클러스터링

우리의 시스템은 다음과 같은 주요 고객 행동 지표를 활용하여 클러스터링을 수행합니다:

  • 시간대별 주문 비율: 점심, 저녁, 야간 주문 패턴
  • 주중/주말 주문 패턴: 평일과 주말의 주문 행동 차이
  • 평균 주문량: 한 번 주문 시 평균적인 주문 규모

이러한 행동 데이터는 표준화 과정을 거쳐 클러스터링에 활용됩니다. 최적의 클러스터 수를 결정하기 위해 엘보우 방법(Elbow Method)과 실루엣 점수(Silhouette Score)를 분석하여 데이터에 가장 적합한 클러스터 수를 자동으로 결정합니다.

아래 실무에서 사용가능한 코드 입니다.

import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score

# 데이터 로드
df = pd.read_csv('customer_rfm_original.csv')

# 클러스터링에 사용할 특성 선택
features = ['점심_비율', '저녁_비율', '야간_비율', '주말_비율', '평균_인분']
X = df[features].copy()

# 결측치 처리
X.fillna(X.mean(), inplace=True)

# 데이터 표준화
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 최적 클러스터 수 찾기 (k=2~10)
k_range = range(2, 11)
silhouette_scores = []

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
    cluster_labels = kmeans.fit_predict(X_scaled)
    silhouette_avg = silhouette_score(X_scaled, cluster_labels)
    silhouette_scores.append(silhouette_avg)
    print(f"k={k}: 실루엣 스코어={silhouette_avg:.4f}")

# 최적 클러스터 수 선택 (실루엣 스코어 기준)
optimal_k = k_range[np.argmax(silhouette_scores)]
print(f"\n최적 클러스터 수: {optimal_k}")

# 최종 K-means 클러스터링 수행
final_kmeans = KMeans(n_clusters=optimal_k, random_state=42, n_init=10)
df['group'] = final_kmeans.fit_predict(X_scaled)

# 각 클러스터의 특성 확인
cluster_features = df.groupby('group')[features].mean()
print("\n클러스터별 특성 평균:")
print(cluster_features)

# 클러스터별 고객 수 확인
print("\n클러스터별 고객 수:")
print(df['group'].value_counts())

# 결과 CSV 파일로 저장
df.to_csv('customer_rfm_result.csv', index=False)
print("\n결과가 'customer_rfm_result.csv' 파일에 저장되었습니다.")

# 클러스터 특성을 기반으로 이름 제안 (참고용)
print("\n클러스터 특성 분석 및 이름 제안 (마케터가 검토해야 함):")
for i in range(optimal_k):
    cluster_data = cluster_features.iloc[i]
    main_time = features[np.argmax(cluster_data[:3])]
    time_name = "점심" if main_time == "점심_비율" else "저녁" if main_time == "저녁_비율" else "야간"
    weekend = "주말" if cluster_data["주말_비율"] > 0.5 else "평일"
    size = "대량" if cluster_data["평균_인분"] > 3.5 else "중간" if cluster_data["평균_인분"] > 2.0 else "소량"

    print(f"클러스터 {i}:")
    print(f"  주요 특성: {time_name} 시간대, {weekend} 주문, {size} 주문({cluster_data['평균_인분']:.1f}인분)")

2. RFM 분석과의 통합

전통적인 RFM(Recency, Frequency, Monetary) 분석과 클러스터링을 결합하여 더욱 정교한 고객 세그먼테이션을 제공합니다:

  • Recency: 마지막 구매 이후 경과 일수
  • Frequency: 구매 빈도
  • Monetary: 총 구매 금액

RFM 점수와 클러스터 정보를 결합함으로써, 단순한 구매 이력뿐만 아니라 구매 행동 패턴까지 고려한 다차원적 고객 이해가 가능해집니다.

# RFM 점수 계산 함수
def calculate_rfm_score(df):
    # Recency 점수 계산 (최근성이 높을수록 점수 낮음)
    r_scores = pd.qcut(df['recency_days'], q=5, labels=[5, 4, 3, 2, 1])

    # Frequency 점수 계산 (빈도가 높을수록 점수 높음)
    f_scores = pd.qcut(df['frequency'].rank(method='first'), q=5, labels=[1, 2, 3, 4, 5])

    # Monetary 점수 계산 (금액이 높을수록 점수 높음)
    m_scores = pd.qcut(df['monetary'].rank(method='first'), q=5, labels=[1, 2, 3, 4, 5])

3. 타겟 마케팅 자동화

클러스터링 결과에 기반하여 각 고객 그룹에 최적화된 마케팅 전략을 자동으로 제안합니다:

  • 맞춤형 마케팅 메시지: 각 클러스터의 특성에 맞는 커뮤니케이션 전략
  • 최적 채널 추천: 이메일, SMS, 앱 푸시 등 가장 효과적인 접근 채널
  • 프로모션 설계: 클러스터별 구매 패턴에 맞는 할인 및 프로모션
  • 발송 시점 최적화: 고객의 활동 패턴에 맞춘 마케팅 타이밍 추천

클러스터 분석 사례

우리의 분석을 통해 발견된 주요 고객 클러스터와 특징은 다음과 같습니다:

  1. 직장인 점심 그룹
    • 평일 점심 시간대 주문 비율이 높음
    • 3-4인분 주문이 일반적
    • 주로 중식이나 간편식 선호
    • 마케팅 전략: 평일 점심 특별 세트 메뉴, 단체 주문 할인
  2. 주말 가족 그룹
    • 주말 주문 비율이 높음
    • 평균 주문량이 많음
    • 다양한 메뉴 주문 경향
    • 마케팅 전략: 주말 가족 세트, 어린이 메뉴 증정 이벤트
  3. 야식 애호가 그룹
    • 야간 시간대 주문 비율이 높음
    • 1-2인분 주문이 일반적
    • 간식류나 야식 메뉴 선호
    • 마케팅 전략: 야간 특별 할인, 야식 세트 프로모션
  4. VIP/기업 고객 그룹
    • 고액 주문 비율이 높음
    • 다양한 시간대에 주문
    • 평균 주문량이 매우 많음
    • 마케팅 전략: 프리미엄 메뉴 추천, 기업 케이터링 서비스

기술적 구현

본 대시보드는 다음과 같은 기술 스택으로 구현되었습니다:

  • 데이터 처리: Python, Pandas
  • 머신러닝 알고리즘: Scikit-learn의 K-Means, StandardScaler, PCA
  • 시각화 및 대시보드: Dash, Plotly
  • 인터랙티브 기능: HTML/CSS, 자바스크립트

특히 클러스터링 과정에서는 다음과 같은 기술적 접근을 사용했습니다:

# 데이터 준비 및 전처리
features = df[['점심_비율', '저녁_비율', '야간_비율', '주말_비율', '평균_인분']]
features = features.fillna(features.mean())  # 결측치 처리
scaler = StandardScaler()
scaled_features = scaler.fit_transform(features)

# 최적의 클러스터 수 결정
silhouette_scores = []
for k in range(2, 11):
    kmeans = KMeans(n_clusters=k, random_state=42)
    cluster_labels = kmeans.fit_predict(scaled_features)
    silhouette_avg = silhouette_score(scaled_features, cluster_labels)
    silhouette_scores.append(silhouette_avg)

optimal_k = silhouette_scores.index(max(silhouette_scores)) + 2

# 최종 클러스터링 수행
kmeans = KMeans(n_clusters=optimal_k, random_state=42)
df['group'] = kmeans.fit_predict(scaled_features)

비즈니스 성과

이 대시보드를 도입한 기업들은 다음과 같은 성과를 보고했습니다:

  • 캠페인 응답률 35% 향상: 맞춤형 메시지와 타이밍으로 고객 참여도 증가
  • 객단가 15% 증가: 클러스터별 최적화된 추천 상품으로 매출 향상
  • 마케팅 비용 20% 절감: 정확한 타겟팅으로 마케팅 효율성 증대
  • 고객 유지율 25% 개선: 개인화된 경험 제공으로 고객 충성도 강화

기존의 FRM 분석 방법도 함께 사용한다!!

RFM 분석 구현 코드

RFM 분석을 파이썬으로 구현하는 방법을 살펴보겠습니다. 아래 코드는 실제 이커머스 데이터를 활용해 고객 세그먼테이션을 수행하는 전체 과정을 담고 있습니다.

# 필요한 라이브러리 임포트
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# 경고 메시지 무시
import warnings
warnings.filterwarnings('ignore')

# 데이터 로드 (예: 이커머스 거래 데이터)
# 실제 사용시에는 파일 경로를 적절히 수정해주세요
df = pd.read_csv('online_retail_data.csv')

# 데이터 기본 확인
print("데이터 형태:", df.shape)
print("\n처음 5개 행:")
print(df.head())

# 데이터 전처리
# 결측치 확인 및 제거
print("\n결측치 개수:")
print(df.isnull().sum())
df = df.dropna()

# 중복 제거
df = df.drop_duplicates()

# 금액이 0보다 큰 거래만 필터링
df = df[df['Quantity'] > 0]
df = df[df['UnitPrice'] > 0]

# 총 구매액 계산
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']

# 분석 기준일 설정 (데이터셋의 마지막 날짜 + 1일)
last_date = pd.to_datetime(df['InvoiceDate']).max() + timedelta(days=1)
print(f"\n분석 기준일: {last_date.strftime('%Y-%m-%d')}")

# 고객별 RFM 지표 계산
rfm = df.groupby('CustomerID').agg({
    'InvoiceDate': lambda x: (last_date - pd.to_datetime(x.max())).days,  # Recency
    'InvoiceNo': 'nunique',  # Frequency
    'TotalPrice': 'sum'  # Monetary
})

# 열 이름 변경
rfm.columns = ['Recency', 'Frequency', 'Monetary']

# RFM 데이터 확인
print("\nRFM 데이터 샘플:")
print(rfm.head())

# 이상치 처리 (선택사항)
# 상위 1%의 거래액은 이상치로 간주하고 99% 백분위수로 대체
monetary_threshold = np.percentile(rfm['Monetary'], 99)
rfm.loc[rfm['Monetary'] > monetary_threshold, 'Monetary'] = monetary_threshold

# RFM 점수 계산 (1-5점 척도, 5점이 가장 좋음)
# Recency: 낮을수록 좋음 (최근에 구매)
# Frequency, Monetary: 높을수록 좋음

# Recency 점수화 (낮을수록 점수 높음)
r_labels = range(5, 0, -1)
r_quartiles = pd.qcut(rfm['Recency'], 5, labels=r_labels)
rfm['R_Score'] = r_quartiles

# Frequency 점수화 (높을수록 점수 높음)
f_labels = range(1, 6)
f_quartiles = pd.qcut(rfm['Frequency'].rank(method='first'), 5, labels=f_labels)
rfm['F_Score'] = f_quartiles

# Monetary 점수화 (높을수록 점수 높음)
m_labels = range(1, 6)
m_quartiles = pd.qcut(rfm['Monetary'].rank(method='first'), 5, labels=m_labels)
rfm['M_Score'] = m_quartiles

# RFM 통합 점수 계산
rfm['RFM_Score'] = rfm['R_Score'].astype(str) + rfm['F_Score'].astype(str) + rfm['M_Score'].astype(str)

# 단순 점수 합계 계산 (3-15점)
rfm['RFM_Sum'] = rfm['R_Score'] + rfm['F_Score'] + rfm['M_Score']

print("\nRFM 점수가 적용된 데이터:")
print(rfm.head())

# RFM 세그먼트 정의
def segment_customer(row):
    if row['RFM_Sum'] >= 13:
        return '최우수 고객(Champions)'
    elif 10 <= row['RFM_Sum'] < 13:
        if row['R_Score'] >= 4:
            return '충성 고객(Loyal)'
        else:
            return '휴면 우수고객(At Risk)'
    elif 6 <= row['RFM_Sum'] < 10:
        if row['R_Score'] >= 3:
            return '잠재 고객(Potential)'
        else:
            return '관심 필요 고객(Need Attention)'
    else:
        return '이탈 위험 고객(Lost)'

rfm['Segment'] = rfm.apply(segment_customer, axis=1)

# 세그먼트별 고객 수 확인
segment_counts = rfm['Segment'].value_counts().reset_index()
segment_counts.columns = ['Segment', 'Count']
print("\n세그먼트별 고객 수:")
print(segment_counts)

# 시각화 1: 세그먼트별 고객 분포
plt.figure(figsize=(10, 6))
sns.barplot(x='Segment', y='Count', data=segment_counts)
plt.title('고객 세그먼트 분포', fontsize=14)
plt.xlabel('세그먼트')
plt.ylabel('고객 수')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('customer_segments.png')

# 시각화 2: RFM 분포 (히트맵)
# 세그먼트별 평균 R, F, M 값 계산
segment_rfm = rfm.groupby('Segment').agg({
    'Recency': 'mean',
    'Frequency': 'mean',
    'Monetary': 'mean'
}).reset_index()

# 정규화 (시각화 목적)
scaler = StandardScaler()
segment_rfm_scaled = segment_rfm.copy()
segment_rfm_scaled[['Recency', 'Frequency', 'Monetary']] = scaler.fit_transform(segment_rfm[['Recency', 'Frequency', 'Monetary']])

# Recency는 낮을수록 좋으므로 부호 변경
segment_rfm_scaled['Recency'] = -segment_rfm_scaled['Recency']

# 히트맵 데이터 준비
heatmap_data = segment_rfm_scaled.set_index('Segment')

plt.figure(figsize=(12, 8))
sns.heatmap(heatmap_data, annot=segment_rfm.set_index('Segment'), fmt='.1f', cmap='coolwarm', linewidths=.5)
plt.title('세그먼트별 RFM 특성', fontsize=14)
plt.tight_layout()
plt.savefig('rfm_heatmap.png')

# 시각화 3: 3D 산점도 (R, F, M 분포)
fig = px.scatter_3d(
    rfm.reset_index(), x='Recency', y='Frequency', z='Monetary',
    color='Segment', opacity=0.7, size='RFM_Sum',
    title='3D RFM 분포'
)
fig.update_layout(scene=dict(
    xaxis_title='Recency (낮을수록 좋음)',
    yaxis_title='Frequency (높을수록 좋음)',
    zaxis_title='Monetary (높을수록 좋음)'
))
fig.write_html('rfm_3d_scatter.html')

# K-Means 클러스터링을 통한 세분화 (선택사항)
# RFM 값 정규화
features = rfm[['Recency', 'Frequency', 'Monetary']].copy()
features_scaled = StandardScaler().fit_transform(features)

# 최적 클러스터 수 찾기 (엘보우 방법)
inertia = []
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(features_scaled)
    inertia.append(kmeans.inertia_)

plt.figure(figsize=(10, 6))
plt.plot(range(1, 11), inertia, 'o-')
plt.xlabel('클러스터 수 (k)')
plt.ylabel('관성 (Inertia)')
plt.title('엘보우 방법을 통한 최적 클러스터 수 결정')
plt.grid(True)
plt.savefig('elbow_method.png')

# 실루엣 점수로 클러스터 품질 평가
silhouette_scores = []
for k in range(2, 11):
    kmeans = KMeans(n_clusters=k, random_state=42)
    labels = kmeans.fit_predict(features_scaled)
    silhouette_scores.append(silhouette_score(features_scaled, labels))

plt.figure(figsize=(10, 6))
plt.plot(range(2, 11), silhouette_scores, 'o-')
plt.xlabel('클러스터 수 (k)')
plt.ylabel('실루엣 점수')
plt.title('실루엣 점수를 통한 클러스터 품질 평가')
plt.grid(True)
plt.savefig('silhouette_score.png')

# 최적 k값 선택 (예: 실루엣 점수 최대화)
optimal_k = silhouette_scores.index(max(silhouette_scores)) + 2
print(f"\n최적 클러스터 수: {optimal_k}")

# 최종 K-Means 클러스터링 적용
kmeans = KMeans(n_clusters=optimal_k, random_state=42)
rfm['Cluster'] = kmeans.fit_predict(features_scaled)

# 클러스터별 특성 분석
cluster_analysis = rfm.groupby('Cluster').agg({
    'Recency': 'mean',
    'Frequency': 'mean',
    'Monetary': 'mean',
    'RFM_Sum': 'mean',
    'CustomerID': 'count'  # 고객 수
}).reset_index()
cluster_analysis.columns = ['Cluster', 'Avg_Recency', 'Avg_Frequency', 'Avg_Monetary', 'Avg_RFM_Score', 'Customer_Count']

print("\n클러스터별 특성:")
print(cluster_analysis)

# 클러스터별 마케팅 전략 제안
def suggest_marketing_strategy(cluster_analysis):
    strategies = []

    for _, row in cluster_analysis.iterrows():
        cluster = row['Cluster']
        recency = row['Avg_Recency']
        frequency = row['Avg_Frequency']
        monetary = row['Avg_Monetary']

        # 클러스터 특성 기반 전략 수립
        if recency < 30 and frequency > 10 and monetary > 1000:
            strategy = f"클러스터 {cluster}: VIP 고객 관리 프로그램, 프리미엄 혜택 제공, 충성도 보상"
        elif recency < 30 and frequency < 5:
            strategy = f"클러스터 {cluster}: 교차판매, 첫 구매 경험 개선, 유도성 할인"
        elif recency > 60 and frequency > 5:
            strategy = f"클러스터 {cluster}: 재활성화 캠페인, 오랜만에 돌아온 고객 할인, 관심 상품 알림"
        elif monetary > 500 and frequency < 3:
            strategy = f"클러스터 {cluster}: 구매 빈도 증가 유도, 정기 구매 프로그램 추천"
        else:
            strategy = f"클러스터 {cluster}: 기본 참여 프로그램, 인지도 향상 캠페인"

        strategies.append(strategy)

    return strategies

marketing_strategies = suggest_marketing_strategy(cluster_analysis)
print("\n클러스터별 추천 마케팅 전략:")
for strategy in marketing_strategies:
    print(strategy)

# 결과 저장
rfm.to_csv('rfm_analysis_results.csv')
print("\nRFM 분석 결과가 'rfm_analysis_results.csv'에 저장되었습니다.")

# 분석 요약 출력
print("\n===== RFM 분석 요약 =====")
print(f"총 분석 고객 수: {len(rfm)}")
print(f"세그먼트 수: {len(segment_counts)}")
print(f"클러스터 수: {optimal_k}")
print("주요 고객 세그먼트 (상위 2개):")
for i, (segment, count) in enumerate(zip(segment_counts['Segment'].values[:2], segment_counts['Count'].values[:2])):
    print(f"  {i+1}. {segment}: {count}명 ({count/len(rfm)*100:.1f}%)")

위 코드를 실행하면 다음과 같은 작업이 수행됩니다:

  1. 고객별 RFM 지표 계산
    • Recency: 마지막 구매 이후 경과 일수
    • Frequency: 구매 빈도 (구매 횟수)
    • Monetary: 총 구매 금액
  2. RFM 점수화 및 세그먼트 분류
    • 각 지표를 1-5점으로 점수화
    • 점수 합산 및 규칙 기반 세그먼트 분류
  3. K-Means 클러스터링을 통한 세분화
    • 엘보우 방법과 실루엣 점수를 통한 최적 클러스터 수 결정
    • 클러스터별 특성 분석 및 마케팅 전략 제안
  4. 다양한 시각화 생성
    • 세그먼트별 고객 분포
    • 세그먼트별 RFM 특성 히트맵
    • 3D 산점도로 RFM 분포 확인

이 코드는 실제 e-commerce 데이터에 바로 적용할 수 있으며, 필요에 따라 세그먼트 정의나 시각화 방법을 수정할 수 있습니다.

결론

비지도 학습 클러스터링을 활용한 CRM 타겟 마케팅은 단순한 고객 분류를 넘어 진정한 데이터 기반 마케팅 자동화의 핵심입니다. 고객의 행동 패턴을 심층적으로 이해하고, 이에 기반한 맞춤형 전략을 수립함으로써 기업은 마케팅 효율성을 극대화하고 고객 경험을 향상시킬 수 있습니다.

이제 마케팅은 더 이상 직감이나 경험에만 의존하지 않습니다. 머신러닝과 데이터 과학의 힘을 빌려 고객을 더 깊이 이해하고, 더 정확하게 소통하는 새로운 시대가 열렸습니다. 우리의 CRM 타겟 마케팅 대시보드가 그 여정의 시작점이 되기를 희망합니다.

반응형