고객 세분화 프로젝트
비지도학습(Unsupervised Learning)을 활용하여 고객 데이터를 의미 있는 그룹으로 나누는 프로젝트입니다. K-Means와 DBSCAN을 비교하고, PCA로 시각화하여 각 군집의 특성을 해석합니다.프로젝트 개요
프로젝트 실습
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 고객 데이터 생성 (실무에서는 실제 데이터를 사용)
np.random.seed(42)
n = 500
df = pd.DataFrame({
"age": np.concatenate([
np.random.normal(25, 5, 150),
np.random.normal(35, 8, 200),
np.random.normal(55, 7, 150),
]).clip(18, 80).astype(int),
"annual_income": np.concatenate([
np.random.normal(30, 10, 150),
np.random.normal(60, 15, 200),
np.random.normal(90, 20, 150),
]).clip(10, 200).round(1),
"spending_score": np.concatenate([
np.random.normal(70, 15, 150),
np.random.normal(50, 20, 200),
np.random.normal(30, 12, 150),
]).clip(1, 100).astype(int),
"visit_frequency": np.concatenate([
np.random.poisson(8, 150),
np.random.poisson(4, 200),
np.random.poisson(2, 150),
]).clip(0, 20),
"avg_purchase": np.concatenate([
np.random.normal(15, 5, 150),
np.random.normal(45, 15, 200),
np.random.normal(80, 25, 150),
]).clip(5, 200).round(1),
})
print(f"데이터 크기: {df.shape}")
print(f"\n기술 통계:\n{df.describe()}")
# 변수 분포 확인
fig, axes = plt.subplots(1, 5, figsize=(20, 4))
for ax, col in zip(axes, df.columns):
df[col].hist(bins=30, ax=ax, edgecolor="black")
ax.set_title(col)
plt.tight_layout()
plt.show()
# 산점도 매트릭스
sns.pairplot(df, diag_kind="kde")
plt.suptitle("변수 간 산점도", y=1.02)
plt.show()
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# 스케일링 (클러스터링 전 필수)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df)
# PCA로 차원 축소 (시각화 + 노이즈 제거)
pca = PCA()
X_pca = pca.fit_transform(X_scaled)
# 설명 분산 비율 확인
plt.figure(figsize=(8, 4))
plt.bar(range(1, len(pca.explained_variance_ratio_) + 1),
pca.explained_variance_ratio_, label="개별 설명 분산")
plt.step(range(1, len(pca.explained_variance_ratio_) + 1),
np.cumsum(pca.explained_variance_ratio_), label="누적 설명 분산",
where="mid", color="red")
plt.xlabel("주성분 번호")
plt.ylabel("설명 분산 비율")
plt.title("PCA 설명 분산 비율")
plt.legend()
plt.show()
# 2차원으로 투영 (시각화용)
X_pca_2d = X_pca[:, :2]
print(f"2차원 설명 분산: {pca.explained_variance_ratio_[:2].sum():.2%}")
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# 엘보우 방법 + 실루엣 점수
k_range = range(2, 11)
inertias = []
silhouette_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_scaled)
inertias.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X_scaled, kmeans.labels_))
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 엘보우 방법
axes[0].plot(k_range, inertias, "bo-")
axes[0].set_xlabel("군집 수 (k)")
axes[0].set_ylabel("Inertia")
axes[0].set_title("엘보우 방법")
# 실루엣 점수
axes[1].plot(k_range, silhouette_scores, "ro-")
axes[1].set_xlabel("군집 수 (k)")
axes[1].set_ylabel("실루엣 점수")
axes[1].set_title("실루엣 점수")
plt.tight_layout()
plt.show()
# 최적 k 확인
best_k = k_range[np.argmax(silhouette_scores)]
print(f"최적 군집 수: {best_k} (실루엣 점수: {max(silhouette_scores):.4f})")
from sklearn.metrics import silhouette_samples
# 최적 k로 K-Means 학습
kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10)
labels_kmeans = kmeans.fit_predict(X_scaled)
# PCA 2D 시각화
plt.figure(figsize=(10, 7))
scatter = plt.scatter(X_pca_2d[:, 0], X_pca_2d[:, 1],
c=labels_kmeans, cmap="viridis", alpha=0.6, s=30)
plt.colorbar(scatter, label="군집")
plt.xlabel("PC1")
plt.ylabel("PC2")
plt.title(f"K-Means 클러스터링 (k={best_k})")
plt.show()
# 군집별 특성 분석
df["cluster_kmeans"] = labels_kmeans
cluster_summary = df.groupby("cluster_kmeans").agg(["mean", "std"]).round(2)
print("군집별 특성 요약:")
print(cluster_summary)
# 군집별 프로필 시각화 (레이더 차트 대신 히트맵)
cluster_means = df.groupby("cluster_kmeans").mean()
cluster_normalized = (cluster_means - cluster_means.min()) / (cluster_means.max() - cluster_means.min())
plt.figure(figsize=(10, 5))
sns.heatmap(cluster_normalized, annot=cluster_means.round(1).values,
fmt="", cmap="YlOrRd", yticklabels=[f"군집 {i}" for i in range(best_k)])
plt.title("군집별 특성 프로필 (정규화 색상, 원본 값 표기)")
plt.tight_layout()
plt.show()
from sklearn.cluster import DBSCAN
# DBSCAN 클러스터링 (밀도 기반)
dbscan = DBSCAN(eps=1.2, min_samples=10)
labels_dbscan = dbscan.fit_predict(X_scaled)
n_clusters = len(set(labels_dbscan)) - (1 if -1 in labels_dbscan else 0)
n_noise = (labels_dbscan == -1).sum()
print(f"DBSCAN 군집 수: {n_clusters}, 노이즈 포인트: {n_noise}")
# K-Means vs DBSCAN 비교
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
axes[0].scatter(X_pca_2d[:, 0], X_pca_2d[:, 1],
c=labels_kmeans, cmap="viridis", alpha=0.6, s=30)
axes[0].set_title(f"K-Means (k={best_k})")
axes[1].scatter(X_pca_2d[:, 0], X_pca_2d[:, 1],
c=labels_dbscan, cmap="viridis", alpha=0.6, s=30)
axes[1].set_title(f"DBSCAN (군집: {n_clusters}, 노이즈: {n_noise})")
for ax in axes:
ax.set_xlabel("PC1")
ax.set_ylabel("PC2")
plt.tight_layout()
plt.show()
# 각 군집에 의미 있는 이름 부여
cluster_names = {}
for c in range(best_k):
group = df[df["cluster_kmeans"] == c]
age_avg = group["age"].mean()
income_avg = group["annual_income"].mean()
spending_avg = group["spending_score"].mean()
# 특성 기반 이름 부여 (예시)
if spending_avg > 60 and income_avg < 50:
name = "젊은 소비형"
elif income_avg > 70 and spending_avg < 40:
name = "고소득 절약형"
elif income_avg > 50 and spending_avg > 50:
name = "고소득 소비형"
else:
name = f"일반 고객 {c}"
cluster_names[c] = name
print(f"군집 {c} ({name}): 나이={age_avg:.0f}, "
f"소득={income_avg:.0f}, 소비점수={spending_avg:.0f}")
# 비즈니스 전략 제안
print("\n--- 마케팅 전략 제안 ---")
for c, name in cluster_names.items():
group = df[df["cluster_kmeans"] == c]
print(f"\n[{name}] ({len(group)}명, {len(group)/len(df)*100:.1f}%)")
print(f" 평균 방문: {group['visit_frequency'].mean():.1f}회")
print(f" 평균 구매: ${group['avg_purchase'].mean():.1f}")
Q: K-Means와 DBSCAN 중 어떤 것을 선택해야 하나요?
Q: K-Means와 DBSCAN 중 어떤 것을 선택해야 하나요?
K-Means는 구형(spherical) 군집에 적합하고 군집 수를 미리 지정해야 합니다. DBSCAN은 임의 형태의 군집을 찾을 수 있고 노이즈를 자동으로 분리합니다. 데이터 분포가 복잡하거나 이상치가 많으면 DBSCAN이, 군집이 비교적 균일하면 K-Means가 유리합니다. 자세한 비교는 클러스터링을 참고하세요.
Q: 클러스터링 결과의 품질은 어떻게 평가하나요?
Q: 클러스터링 결과의 품질은 어떻게 평가하나요?
내부 지표로는 실루엣 점수(높을수록 좋음), Calinski-Harabasz Index, Davies-Bouldin Index를 사용합니다. 그러나 비즈니스 관점에서 “군집이 실제로 의미 있고 행동 가능한가”가 가장 중요한 평가 기준입니다.
체크리스트
- 클러스터링 전 스케일링의 중요성을 이해하고 적용할 수 있다
- 엘보우 방법과 실루엣 점수로 최적 군집 수를 결정할 수 있다
- PCA로 고차원 군집 결과를 시각화할 수 있다
- 군집 결과를 비즈니스 관점에서 해석할 수 있다

