데이터분석 6기/본캠프

2025-04-22 심화 프로젝트 - 군집(PCA, KMeans, DBSCAN)

seyeon1130 2025. 4. 22. 22:22

우리팀의 진행 방식

 

 

 

  1. 팀원들끼리 군집 프로세스를 하나씩 해나가며 코드를 각자 짜고 결과를 모두 맞춘다.
  2. 이후 컬럼들과 머신러닝 하이퍼파라미터 값들을 변경해가며 실험한다.
  3. 실험 결과 중 가장 군집이 잘되고, 우리가 의도한 바가 드러나는 결과를 선정한다.
  4. 그렇게 나타난 고객 군집으로 각각의  고객 군집마다 인사이트를 도출한다.
  5.  

차원축소를 위해 customer_id drop

df_customer.drop('customer_id', axis=1, inplace=True)

차원축소(2차원, 3차원) PCA

 

2차원
# pca 2차원 임의 시행 
pca2 = PCA(n_components=2,svd_solver='full', random_state=42)
pca2.fit(df_customer)
# 설정한 주성분의 갯수로 전체 데이터 분산을 얼만큼 설명 가능한지 
print(pca2.explained_variance_ratio_.sum())

# pca 시행
pca2_df = pca2.fit_transform(df_customer)
pca2_df = pd.DataFrame(data = pca2_df, columns = ['PC1','PC2'])  

# Show the first 5 firms
pca2_df.head()

#시각화
plt.figure(figsize=(8,6))
plt.scatter(
    pca2_df['PC1'],
    pca2_df['PC2'],
    c='orange', s=50, alpha=0.6
)

plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title('2D PCA Scatter Plot')
plt.grid(True)
plt.show()

PCA(n_components=2,svd_solver='full', random_state=42)

이걸 해아 값이 매번 달라지지 않는다.

 

아마도 나잇대 10대 ~ 60대가 총 6개로 나뉘는 것 같다.

 

 

3차원
# pca 3차원 임의 시행 
pca3 = PCA(n_components=3,svd_solver='full', random_state=42)
pca3.fit(df_customer)
# 설정한 주성분의 갯수로 전체 데이터 분산을 얼만큼 설명 가능한지 
print(pca3.explained_variance_ratio_.sum())

# pca 시행
pca3_df = pca3.fit_transform(df_customer)
pca3_df = pd.DataFrame(data = pca3_df, columns = ['PC1','PC2','PC3'])  

# Show the first 5 firms
pca3_df.head()

#시각화
# 3D 시각화
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

ax.scatter(
    pca3_df['PC1'],
    pca3_df['PC2'],
    pca3_df['PC3'],
    c='skyblue', s=50, alpha=0.6
)

ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
ax.set_zlabel('PC3')
ax.set_title('3D PCA Scatter Plot')

plt.show()

 

엘보우 기법으로 K값 알아내기

2차원

 

# 1. 기본 KMeans 모델 정의
model = KMeans(random_state = 42)

# 2. ElbowVisualizer 정의 (k 범위 지정)
visualizer = KElbowVisualizer(model, k=(3, 12))

# 3. PCA 2차원 결과에 대해 학습 및 시각화
visualizer.fit(pca2_df)
visualizer.show()

저기서 random_state =42 안넣었더니 모든 팀원들 값 다 다르게 나왔다. 랜덤값 지정 꼭 해야한다,

 

 

3차원

 

# 1. 기본 KMeans 모델 정의
model = KMeans(random_state = 42)

# 2. ElbowVisualizer 정의 (k 범위 지정)
visualizer = KElbowVisualizer(model, k=(3, 12))

# 3. PCA 3차원 결과에 대해 학습 및 시각화
visualizer.fit(pca3_df)
visualizer.show()

 

그래서 k는 5,6,7으로 세 개 돌려보기로 결정!

 

KMeans 실행

 

# 클러스터 개수 목록
ks = [5, 6, 7]
# 그래프 크기 설정
plt.figure(figsize=(18, 5))
for i, k in enumerate(ks):
    # KMeans 모델 생성 및 학습
    kmeans = KMeans(n_clusters=k, random_state=42)
    labels = kmeans.fit_predict(pca2_df)
    # 라벨 컬럼 붙이기
    kmeans_df = pd.concat([pca2_df, pd.DataFrame({'Cluster': labels})], axis=1)
    # subplot 지정
    plt.subplot(1, 3, i+1)
    sns.scatterplot(data=kmeans_df, x='PC1', y='PC2', hue='Cluster', palette='Set3')
    plt.title(f'KMeans Clustering (k = {k})')
    plt.xlabel('PC1')
    plt.ylabel('PC2')
    plt.legend(title='Cluster')
    kmeans_sil = silhouette_score(pca2_df, labels)
    print("K-Means: 실루엣 점수 =", kmeans_sil, "| 클러스터 레벨 =", k)
plt.tight_layout()
plt.show()

 

 

 

흠.. 이 컬럼으로는 KMeans가 안맞는 것 같다. 저 여섯개가 나뉘어야할 것 같은데,, 그럼 DBSCAN 이 더 잘맞겠다

 

 

3차원

 

# 그래프 크기 설정
fig = plt.figure(figsize=(18, 5))

for i, k in enumerate(ks):
    # KMeans 모델 생성 및 학습
    kmeans = KMeans(n_clusters=k, random_state=42)
    labels = kmeans.fit_predict(pca3_df)

    # 라벨 컬럼 붙이기
    kmeans_df3 = pd.concat([pca3_df, pd.DataFrame({'Cluster': labels})], axis=1)

    # subplot: 1행 3열 중 i+1번째 3D 플롯
    ax = fig.add_subplot(1, 3, i+1, projection='3d')
    scatter = ax.scatter(
        kmeans_df3['PC1'], 
        kmeans_df3['PC2'], 
        kmeans_df3['PC3'], 
        c=kmeans_df3['Cluster'], 
        cmap='Set3',
    )
    ax.set_title(f'KMeans Clustering (k = {k})')
    ax.set_xlabel('PC1')
    ax.set_ylabel('PC2')
    ax.set_zlabel('PC3')

    # silhouette score 출력
    kmeans_sil = silhouette_score(pca3_df, labels)
    print(f"K-Means: 실루엣 점수 = {kmeans_sil:.4f} | 클러스터 개수 = {k}")

plt.tight_layout()
plt.show()

이 각도에서는 그나마 K가 6일 때가 괜찮은 느낌? 근데 이건 또 다른 각도에서 봐야 알듯 하다.

 

DBSCAN

2차원
# DBSCAN
dbscan = DBSCAN(eps=0.5, min_samples=5)
dbscan_labels = dbscan.fit_predict(pca2_df)
dbscan_labels_unique = np.unique(dbscan_labels)
n_clusters_dbscan = len(dbscan_labels_unique[dbscan_labels_unique != -1])  # -1 (noise)
if n_clusters_dbscan > 1:
    dbscan_sil = silhouette_score(pca2_df, dbscan_labels)
    print(dbscan_sil)
else:
    dbscan_sil = "실루엣 점수 계산 불가 (클러스터 수 부족)"
db2_df = pca2_df.copy()
db2_df['Cluster'] = dbscan_labels
# 시각화
sns.scatterplot(data=db2_df, x='PC1', y='PC2', hue='Cluster', palette='Set3')
plt.title("DBSCAN Clustering")
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(title='Cluster')
plt.show()

 

실루엣 점수 : 0.21560774465875293

실루엣 점수는 낮게 나왔지만 역시 우리가 원하는 결과가 나왔다!!!

 

3차원
dbscan_labels = dbscan.fit_predict(pca3_df)
dbscan_labels_unique = np.unique(dbscan_labels)
n_clusters_dbscan = len(dbscan_labels_unique[dbscan_labels_unique != -1])  # -1 (noise)
if n_clusters_dbscan > 1:
    dbscan_sil = silhouette_score(pca3_df, dbscan_labels)
    print(dbscan_sil)
else:
    dbscan_sil = "실루엣 점수 계산 불가 (클러스터 수 부족)"
db3_df = pca3_df.copy()
db3_df['Cluster'] = dbscan_labels
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
scatter = ax.scatter(
    db3_df['PC1'], db3_df['PC2'], db3_df['PC3'],
    c=db3_df['Cluster'], cmap='Set3'
)
ax.set_title("DBSCAN Clustering (3D PCA)")
ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
ax.set_zlabel('PC3')
# 범례 추가 (옵션)
legend1 = ax.legend(*scatter.legend_elements(), title="Cluster", loc='best')
ax.add_artist(legend1)
plt.show()

 

실루엣 점수: 0.06872320992525256

 

실루엣 점수가 엄청 낮다.. DBSCAN 쓴다면 2차원으로 할듯???

 

레이더 차트(KMeans로 한 거)

# 클러스터 라벨을 df_customer에 추가
df_customer = df_customer.reset_index(drop=True)
df_customer = pd.concat([df_customer, pd.DataFrame({'Cluster': labels})], axis=1)

# 클러스터별 평균값 계산
clustered = df_customer.groupby('Cluster').mean()

# 시각화에 쓸 feature 목록 설정 (Cluster 제외)
categories = list(clustered.columns)
num_vars = len(categories)

# 각 축의 각도 계산
angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
angles += angles[:1]  # 도형 닫기 위해 첫 값 추가

# 파스텔 색상 정의
pastel_colors = [
    '#FFC0CB',  # 분홍
    '#FFB347',  # 주황
    '#8f6ee4',  # 보라
    '#77DD77',  # 연두
    '#45b3e0',  # 파랑
    '#FDFD96',  # 연노랑
]

# 레이더 차트 그리기
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))

for i, (idx, row) in enumerate(clustered.iterrows()):
    values = row.tolist()
    values += values[:1]
    color = pastel_colors[i % len(pastel_colors)]
    ax.plot(angles, values, label=f'Cluster {idx}', color=color)
    ax.fill(angles, values, alpha=0.25, color=color)

# 카테고리 라벨 세팅
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)

# 제목, 범례 추가
plt.title("Radar Chart by Cluster", size=15)
plt.legend(loc='upper right', bbox_to_anchor=(1.1, 1.1))
plt.show()

 

 

지금은 인코딩한 총 87개 커럼이 있어서 제대로 구분되지는 않는다.

컬럼을 다시 정해서 그려봐야겠다.

계층 클러스터링

 

# 클러스터 개수 목록
ks = [5, 6, 7]

# 그래프 크기 설정
plt.figure(figsize=(18, 5))

for i, k in enumerate(ks):
    # Agglomerative 모델 생성 및 학습
    agg = AgglomerativeClustering(n_clusters=k)
    agg_labels = agg.fit_predict(pca2_df)
    agg_sil = silhouette_score(pca2_df, agg_labels)

    print(f"Agglomerative (k={k}): 실루엣 점수 = {agg_sil:.4f} | 클러스터 라벨 = {np.unique(agg_labels)}")

    # 라벨 컬럼 붙이기
    agg_df2 = pd.concat([pca2_df, pd.DataFrame({'Cluster': agg_labels})], axis=1)

    # subplot 지정
    plt.subplot(1, 3, i+1)
    sns.scatterplot(data=agg_df2, x='PC1', y='PC2', hue='Cluster', palette='Set3')
    plt.title(f'AgglomerativeClustering (k = {k})')
    plt.xlabel('PC1')
    plt.ylabel('PC2')
    plt.legend(title='Cluster')

plt.tight_layout()
plt.show()

 

이거는 메모리가 터져버려서 내일 하는 걸로,,,

 

 

코드 날라감 이슈

 

 

오늘 메모리 터지면서 작성한 코드가 모두 날라가는 대참사가 일어났다.

진짜 갑자기 온 몸에서 식은땀이 확......

 

진짜 착한일(?) 하길 잘한게 팀원분들이 코드 안돌아갈 때마다 내 코드 보내드리면서 같이 수정해드려서 코드가 기록 돼있었다

 

 

이런식으로,,헤헷

 

 

 

 

 

GPT 세연

 

오늘 팀원분들 코드 틀린거 다같이 보는데 내가 많이 맞춰서 기분 좋았다 ㅎㅎ

근데 지금 보니 좀 나 GPT 같음

 

 

 

 

 

 

칭찬도 받았따 헤헷

 

 

 

 

휴.. 우선 화요일 끄읏...