본문 바로가기

TIL

[240405] 클러스터링 분석 - ④ K-means 군집화 (실습)

[파이썬으로 하는 클러스터링 분석 by 강민구 튜터]

 

▶ 이론 내용: [240404] 클러스터링 분석 - ④ K-means 군집화

 

▶ 실습 코드 - K-means 군집화

 

1. 패키지 호출 및 데이터 불러오기 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

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

import plotly.graph_objects as go
from collections import Counter

# 데이터셋 불러오기 
df_bts = pd.read_csv('Spotify_BTS_AudioFeatures.csv')
df_bts.head(3)

 

2. 데이터 전처리 

# 1. 칼럼 성격에 따라 info용 DataFrame과 분석용 DataFrame으로 분리
df_bts_info = df_bts[['Title', 'Artist', 'Release']]
df_bts_anal = df_bts.drop(['Unnamed: 0', 'Title', 'Artist', 'Release', 'key', 'id'], axis = 1)

# 2. 분석용 DataFrame 스케일링 진행
scaler = StandardScaler()
df_bts_anal_s = pd.DataFrame(data=scaler.fit_transform(df_bts_anal), columns= df_bts_anal.columns)
df_bts_anal_s['duration'] = df_bts_anal_s['duration_ms']
df_bts_anal_s = df_bts_anal_s.drop(['duration_ms'], axis = 1)

df_bts_anal_s.head()

 

 

3. K-means 실행

1) random

# K-means 실행
# https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
kmenas = KMeans(n_clusters=6, random_state=0, init='random').fit(df_bts_anal_s)

# kmenas.cluster_centers_ 각 클러스터의 centroids 값
df_centroids = pd.DataFrame(kmenas.cluster_centers_, columns=df_bts_anal_s.columns)
df_centroids

# 클러스터링 완료된 결과 
kmenas.labels_

# (practice) 군집별 size 추가해보기
# 내 풀이 
df_bts_anal_s['label'] = kmeans.labels_
df_centroids['size'] = df_bts_anal_s.groupby('label').size()
df_bts_anal_s = df_bts_anal_s.drop(['label'], axis=1)
df_centroids

# 튜터님 풀이
from collections import Counter
c = Counter()
c.update(kmeans.labels_)
# Counter({2: 36, 4: 20, 3: 25, 1: 23, 0: 18, 7: 20, 5: 1, 6: 3, 8: 1})

label_counts_prac = [c[i] for i in df_centroids.index]
df_centroids['size'] = label_counts_prac
df_centroids

 

# Centroid 시각화를 통해 군집의 특성 파악
# iterrows(): 각 행을 시리즈로 반환하는 함수로 index, 행 값이 나옴   

def plot_radar_from_centroid(df_centroids):
    df_centroids = df_centroids.drop(['size'], axis = 1)
    fig = go.Figure()
    categories = df_centroids.columns
    for row in df_centroids.iterrows():
        # plotly 라이브러리 내 add_trace(): 그려진 그래프에 새로운 그래프 추가 가능 ex) go.Scatter, go.Bar 등
        fig.add_trace(go.Scatterpolar(
            r = row[1].tolist(), # row[1]은 칼럼과 centroid 값임
            theta = categories, # 각 모서리에 들어갈 이름은 칼럼 이름 
            fill = 'toself', 
            name = 'cluster {}'.format(row[0])
        ))

    # update_layout(): 외적인 요소 추가 
        # 제목 추가 : title_text="제목 내용",title_font_size=n
        # fig.update_xaxes(x축 내용)
        # fig.update_yaxes(y축 내용)
    fig.update_layout(
        autosize = False,
        width = 1000,
        height = 800
    )
    fig.show()

 

2) K-means++

# K-means++로 초기값 지정
kmenas_pp = KMeans(n_clusters=6, random_state=0).fit(df_bts_anal_s)
df_centroids_pp = pd.DataFrame(kmenas_pp.cluster_centers_, columns=df_centroids.columns)

# collection 모듈에서 가져와야 함 
# 구체적인 설명 https://velog.io/@eunhye_/python-collections-Counter
# 여러 형태를 인자로 받아서, 중복을 제거하고 각 원소의 개수(count) 값을 딕셔너리 형태로 출력
    # >>> Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
    # Counter({'blue': 3, 'red': 2, 'green': 1})    
c2 = Counter()
c2.update(kmenas_pp.labels_)

label_counts2 = [c2[x] for x in df_centroids_pp.index]
df_centroids_pp['label'] = label_counts2
df_centroids_pp

 

plot_radar_from_centroid(df_centroids_pp.iloc[:, :12])

4. 실루엣 계수 계산

# Silhouette 계수 계산 
# https://scikit-learn.org/stable/modules/generated/sklearn.metrics.silhouette_score.html

silhouette_avg = silhouette_score(df_bts_anal_s, kmenas_pp.labels_)
print(silhouette_avg) # 0.22184451332968882

silhouette_values = silhouette_samples(df_bts_anal_s, kmenas_pp.labels_)
# print(silhouette_values)

 

(cf) 실루엣 계수 지표 

- Silhouette Score: 전체 데이터셋에 대한 실루엣 계수 평균값. 전체 유사성과 분리도를 나타내며, 1에 가까울수록 좋음

- Silhouette Samples: 개별 포인트의 실루엣 계수 값. 개별 클러스터의 품질을 파악 및 시각화에 활용 

# k값에 따른 Silhouette 계수 계산

for k in range(3, 10):
    kmeans = KMeans(n_clusters=k, random_state=0, n_init=10).fit(df_bts_anal_s)
    silhouette_avg = silhouette_score(df_bts_anal_s, kmeans.labels_)
    print(f'k = {k} -> silhouette index {silhouette_avg}')

 

5. 실루엣 계수 시각화

# 튜터님 정답
k = 7

fig, ax = plt.subplots(1, 1)
kmeans = KMeans(n_clusters=k, random_state=0).fit(df_bts_anal_s)
silhouette_values = silhouette_samples(df_bts_anal_s, kmeans.labels_)

y_ticks = [] 
y_lower = y_upper = 0

for c_num in np.unique(kmeans.labels_):
    cluster_silhouette_values = silhouette_values[kmeans.labels_ == c_num] 
    y_upper += len(cluster_silhouette_values)
    cluster_silhouette_values.sort()

    ax.barh(range(y_lower, y_upper), cluster_silhouette_values, height = 1)
    y_lower += len(cluster_silhouette_values)

# 내 풀이 #1차 시도

from yellowbrick.cluster import SilhouetteVisualizer 

n = 7 
p_kmeans = KMeans(n_clusters=n, random_state=0).fit(df_bts_anal_s)
p_silhouette_avg = silhouette_score(df_bts_anal_s, p_kmeans.labels_)

features = p_kmeans.labels_
visualizer = SilhouetteVisualizer(p_kmeans, colors='yellowbrick')
visualizer.fit(df_bts_anal_s)
visualizer.show()

# 내 풀이 # 2차 시도
from matplotlib import cm

def visualize_silhouette(cluster_lists, cluster_dataframe):
    '''''''''
    input: 클러스터링 개수 리스트, 클러스터 대상 데이터프레임  
    output: 클러스터링 개수별 실루엣 계수 그래프 생성
    '''''''''
     
    n_cols = len(cluster_lists)

    # 클러스터 개수 리스트에 맞춰 subplot 그릴 수 있도록 axs 생성
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)

    for idx, n_cluster in enumerate(cluster_lists):
        kmeans = KMeans(n_clusters=n_cluster, random_state=0, max_iter=500)
        kmeans_lables = kmeans.fit_predict(cluster_dataframe)

        silhouette_avg = silhouette_score(cluster_dataframe, kmeans.labels_)
        silhouette_values = silhouette_samples(cluster_dataframe, kmeans.labels_)

        y_lower = 10 

        axs[idx].set_title('Number of Cluster : {}\n Silhouette Score : {}'.format(n_cluster, round(silhouette_avg, 3)))
        axs[idx].set_xlabel('The Silhouette cofficient values')
        axs[idx].set_ylabel('Cluster Label')
        # axs[idx].set_xlim([-0.1, 1])
        # axs[idx].set_ylim([0, len()])
        axs[idx].set_yticks([])
        # axs[idx].set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1])

        for i in range(n_cluster):
            ith_cluster_sil_values = silhouette_values[kmeans_lables==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[idx].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, facecolor=color, alpha = 0.7)
            axs[idx].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
            y_lower = y_upper + 10
        axs[idx].axvline(x=silhouette_avg, color='red', linestyle='--')


visualize_silhouette([3, 4, 5, 6, 7], df_bts_anal_s)