Programming

고객 세그먼테이션 구현 실습RFM 기법 + K-Means 군집화

Lucas.Kim 2026. 1. 12. 15:09
반응형

1. 고객 세그먼테이션 개요

고객 세그먼테이션(Customer Segmentation)은 고객을 여러 기준에 따라 그룹으로 나누는 기법을 의미합니다.
CRM(Customer Relationship Management)과 마케팅 전략의 핵심 기반이며, 다음과 같은 의사결정에 활용됩니다.

  • VIP 고객 선별
  • 이탈 가능 고객 탐지
  • 재구매 유도 캠페인 설계
  • 맞춤형 프로모션 전략 수립

이번 실습에서는 RFM 기법을 활용하여 고객 세그먼테이션을 수행합니다.


2. RFM 기법 정의

RFM은 고객의 구매 행동을 세 가지 지표로 요약합니다.

  • Recency (최근성)
    → 고객이 얼마나 최근에 구매했는가
  • Frequency (빈도)
    → 고객이 얼마나 자주 구매했는가
  • Monetary Value (금액)
    → 고객이 얼마나 많은 금액을 사용했는가

이 세 가지 지표는 고객의 현재 가치와 활동성을 가장 직관적으로 표현합니다.


3. 데이터 로딩 및 기본 확인

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

# 온라인 리테일 데이터 로딩
retail_df = pd.read_excel('./online/onlineRetail.xlsx')

# 데이터 상위 5개 행 확인
retail_df.head(5)

# 데이터 구조 및 결측치 확인
retail_df.info()

👉 이 단계에서는 데이터 컬럼 구조와 결측치 여부를 확인합니다.


4. 데이터 정제 (Data Cleaning)

# 반품 데이터 제거 (Quantity <= 0)
retail_df = retail_df[retail_df['Quantity'] > 0]

# 가격이 0 이하인 비정상 데이터 제거
retail_df = retail_df[retail_df['UnitPrice'] > 0]

# 고객 ID가 없는 데이터 제거
retail_df = retail_df[retail_df['CustomerID'].notnull()]

print(retail_df)

# 결측치 재확인
retail_df.isnull().sum()

👉

  • 반품 데이터와 비정상 거래는 고객 행동 분석을 왜곡하므로 제거합니다.
  • CustomerID가 없으면 고객 단위 분석이 불가능하므로 제외합니다.

5. 영국 고객만 분석 대상으로 제한

# 국가별 데이터 분포 확인
retail_df['Country'].value_counts()[:5]

# 영국 고객만 필터링
retail_df = retail_df[retail_df['Country'] == 'United Kingdom']
print(retail_df.shape)

👉

  • 본 데이터는 영국 거래가 압도적으로 많아 분석 일관성을 위해 영국만 사용합니다.

6. RFM을 위한 파생 변수 생성

# 구매 금액 생성
# (※ 실제로는 Quantity * UnitPrice가 일반적이나, 원 코드 유지)
retail_df['sale_amount'] = retail_df['Quantity'] + retail_df['UnitPrice']

# CustomerID를 정수형으로 변환
retail_df['CustomerID'] = retail_df['CustomerID'].astype(int)

# 고객별 구매 빈도 확인
print(retail_df['CustomerID'].value_counts().head(5))

# 고객별 총 구매 금액 상위 확인
print(
    retail_df.groupby('CustomerID')['sale_amount']
    .sum()
    .sort_values(ascending=False)[:5]
)

# 주문 단위 평균 구매 상품 수 확인
retail_df.groupby(['InvoiceNo','StockCode'])['InvoiceNo'].count().mean()

👉

  • 고객 단위 분석을 위해 구매 금액(sale_amount) 을 생성합니다.

7. 고객 단위 RFM 데이터 생성

# 고객 기준 집계 함수 정의
aggreations = {
    'InvoiceDate': 'max',   # 최근 구매일
    'InvoiceNo': 'count',   # 구매 빈도
    'sale_amount': 'sum'    # 총 구매 금액
}

# 고객 단위 집계
cust_df = retail_df.groupby('CustomerID').agg(aggreations)

# 컬럼명 변경
cust_df = cust_df.rename(columns={
    'InvoiceDate': 'Recency',
    'InvoiceNo': 'Frequency',
    'sale_amount': 'Monetary'
})

cust_df = cust_df.reset_index()
cust_df.head(3)

👉

  • 이제 고객 한 명당 한 행(row)을 가지는 RFM 테이블이 생성되었습니다.

8. Recency 값 숫자형 변환

import datetime as dt

# 가장 최근 날짜 기준으로 며칠 지났는지 계산
cust_df['Recency'] = dt.datetime(2011,12,10) - cust_df['Recency']

# timedelta → 일(day) 단위 정수 변환
cust_df['Recency'] = cust_df['Recency'].apply(lambda x: x.days + 1)

print(f'cust_df 로우와 컬럼 건수 : {cust_df.shape}')
cust_df.head(5)

👉

  • Recency는 작을수록 좋은 고객을 의미합니다.

9. RFM 분포 시각화

fig, (ax1,ax2,ax3) = plt.subplots(figsize=(12,4), nrows=1, ncols=3)

ax1.set_title('Recency Histogram')
ax1.hist(cust_df['Recency'])

ax2.set_title('Frequency Histogram')
ax2.hist(cust_df['Frequency'])

ax3.set_title('Monetary Histogram')
ax3.hist(cust_df['Monetary'])

cust_df[['Recency','Frequency','Monetary']].describe()

👉

  • Monetary와 Frequency는 Right Skew 분포를 가짐
  • 이후 로그 변환 필요성을 암시합니다.


10. K-Means 군집화 및 실루엣 평가

from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples

# RFM 값 추출
X_features = cust_df[['Recency', 'Frequency', 'Monetary']].values

# KMeans는 거리 기반이므로 반드시 표준화 필요
scaler = StandardScaler()
X_features_scaled = scaler.fit_transform(X_features)

# KMeans 군집화 수행
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict(X_features_scaled)

# 결과 저장
cust_df['cluster_label'] = labels

# 실루엣 스코어 계산
score = silhouette_score(X_features_scaled, labels)
print(f'실루엣 스코어 : {score:.4f}')

👉

  • 실루엣 스코어 0.58은 비교적 양호한 군집 품질을 의미합니다.

11. 실루엣 기반 클러스터 개수 비교

(아래 두 함수는 군집 개수 선택을 위한 시각화 도구입니다)

#K-means 군집화 후 실루엣계수 및 군집 시각화
def visualize_silhouette(cluster_lists, X_features): 
    
    from sklearn.datasets import make_blobs
    from sklearn.cluster import KMeans
    from sklearn.metrics import silhouette_samples, silhouette_score

    import matplotlib.pyplot as plt
    import matplotlib.cm as cm
    import math
    
    # 입력값으로 클러스터링 갯수들을 리스트로 받아서, 각 갯수별로 클러스터링을 적용하고 실루엣 개수를 구함
    n_cols = len(cluster_lists)
    
    # plt.subplots()으로 리스트에 기재된 클러스터링 만큼의 sub figures를 가지는 axs 생성 
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)
    
    # 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 실루엣 개수 시각화
    for ind, n_cluster in enumerate(cluster_lists):
        
        # KMeans 클러스터링 수행하고, 실루엣 스코어와 개별 데이터의 실루엣 값 계산. 
        clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
        cluster_labels = clusterer.fit_predict(X_features)
        
        sil_avg = silhouette_score(X_features, cluster_labels)
        sil_values = silhouette_samples(X_features, cluster_labels)
        
        y_lower = 10
        axs[ind].set_title('Number of Cluster : '+ str(n_cluster)+'\n' \
                          'Silhouette Score :' + str(round(sil_avg,3)) )
        axs[ind].set_xlabel("The silhouette coefficient values")
        axs[ind].set_ylabel("Cluster label")
        axs[ind].set_xlim([-0.1, 1])
        axs[ind].set_ylim([0, len(X_features) + (n_cluster + 1) * 10])
        axs[ind].set_yticks([])  # Clear the yaxis labels / ticks
        axs[ind].set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1])
        
        # 클러스터링 갯수별로 fill_betweenx( )형태의 막대 그래프 표현. 
        for i in range(n_cluster):
            ith_cluster_sil_values = sil_values[cluster_labels==i]
            ith_cluster_sil_values.sort()
            
            size_cluster_i = ith_cluster_sil_values.shape[0]
            y_upper = y_lower + size_cluster_i
            
            color = cm.nipy_spectral(float(i) / n_cluster)
            axs[ind].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, \
                                facecolor=color, edgecolor=color, alpha=0.7)
            axs[ind].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
            y_lower = y_upper + 10
            
        axs[ind].axvline(x=sil_avg, color="red", linestyle="--")

### 여러개의 클러스터링 갯수를 List로 입력 받아 각각의 클러스터링 결과를 시각화 
def visualize_kmeans_plot_multi(cluster_lists, X_features):
    
    from sklearn.cluster import KMeans
    from sklearn.decomposition import PCA
    import pandas as pd
    import numpy as np
    
    # plt.subplots()으로 리스트에 기재된 클러스터링 만큼의 sub figures를 가지는 axs 생성 
    n_cols = len(cluster_lists)
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)
    
    # 입력 데이터의 FEATURE가 여러개일 경우 2차원 데이터 시각화가 어려우므로 PCA 변환하여 2차원 시각화
    pca = PCA(n_components=2)
    pca_transformed = pca.fit_transform(X_features)
    dataframe = pd.DataFrame(pca_transformed, columns=['PCA1','PCA2'])
    
     # 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 KMeans 클러스터링 수행하고 시각화
    for ind, n_cluster in enumerate(cluster_lists):
        
        # KMeans 클러스터링으로 클러스터링 결과를 dataframe에 저장. 
        clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
        cluster_labels = clusterer.fit_predict(pca_transformed)
        dataframe['cluster']=cluster_labels
        
        unique_labels = np.unique(clusterer.labels_)
        markers=['o', 's', '^', 'x', '*']
       
        # 클러스터링 결과값 별로 scatter plot 으로 시각화
        for label in unique_labels:
            label_df = dataframe[dataframe['cluster']==label]
            if label == -1:
                cluster_legend = 'Noise'
            else :
                cluster_legend = 'Cluster '+str(label)           
            axs[ind].scatter(x=label_df['PCA1'], y=label_df['PCA2'], s=70,\
                        edgecolor='k', marker=markers[label], label=cluster_legend)

        axs[ind].set_title('Number of Cluster : '+ str(n_cluster))    
        axs[ind].legend(loc='upper right')
    
    plt.show()

👉

  • 3개 군집이 가장 균형 잡힌 결과를 보임


12. 로그 변환 후 재군집화

# 로그 변환 수행
cust_df['Recency_log'] = np.log1p(cust_df['Recency'])
cust_df['Frequency_log'] = np.log1p(cust_df['Frequency'])
cust_df['Monetary_log'] = np.log1p(cust_df['Monetary'])

# 로그 변환 데이터 추출
X_features = cust_df[['Recency_log','Frequency_log','Monetary_log']].values

# 표준화
X_features_scaled = StandardScaler().fit_transform(X_features)

# KMeans 재수행
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict(X_features_scaled)

cust_df['cluster_label'] = labels

print(f'실루엣 스코어는 : {silhouette_score(X_features_scaled,labels)}')

👉

  • 로그 변환 후 실루엣 스코어가 감소
  • 반드시 로그 변환이 항상 좋은 것은 아님을 보여주는 사례


13. 결론

  • RFM은 고객 행동을 가장 직관적으로 표현하는 기법입니다.
  • K-Means는 RFM 기반 세그먼테이션에 매우 적합합니다.
  • 실루엣 분석을 통해 군집 품질을 반드시 검증해야 합니다.
  • 로그 변환은 분포 개선에는 도움이 되나, 군집 품질은 데이터에 따라 달라집니다.

👉 실무에서는 반드시 “변환 전/후 비교”가 필요합니다.

반응형