반응형

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 기반 세그먼테이션에 매우 적합합니다.
- 실루엣 분석을 통해 군집 품질을 반드시 검증해야 합니다.
- 로그 변환은 분포 개선에는 도움이 되나, 군집 품질은 데이터에 따라 달라집니다.
👉 실무에서는 반드시 “변환 전/후 비교”가 필요합니다.
반응형
'Programming' 카테고리의 다른 글
| 텍스트 분석 & NLP 핵심 요약 (완전 통합 정리) (0) | 2026.02.14 |
|---|---|
| 비지도학습(군집화) 마무리 정리: “정답이 없을 때, 무엇을 기준으로 묶을 것인가” (1) | 2026.01.12 |
| DBSCANDensity-Based Spatial Clustering of Applications with Noise (0) | 2026.01.12 |
| Gaussian Mixture Model(GMM) 군집화– K-Means의 한계를 극복하는 확률 기반 군집 알고리즘 (0) | 2026.01.12 |
| Mean Shift 군집화 완전 이해– KDE 기반 자동 군집 개수 결정 알고리즘 (0) | 2026.01.12 |