본문 바로가기

TIL

[240403] 클러스터링 분석 - ③ 계층적 군집화와 덴드로그램 (실습)

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

 

▶ 이론 내용:  [240402] 클러스터링 분석 - ③ 계층적 군집화와 덴드로그램

 

▶ 실습 코드 - 계층적 군집화

 

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.decomposition import PCA

from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage

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

df_bts.head()

 

 

 

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. 덴드로그램 작성 

# 덴드로그램 작성
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.dendrogram.html

plt.figure(figsize=(15, 10))
plt.title('Dendrogram')
plt.xlabel('Songs') # 각 행의 개별 데이터 
plt.ylabel('Euclidean distances')

dgram = dendrogram(linkage(df_bts_anal_s, method = 'ward'))
plt.show()

 

 

# X label에 index가 아닌 노래 제목으로 나타내보기
# dendrogram 함수의 'leaf_label_func' 파라미터에 넣을 함수 작성
    # leaf_label_func는 함수나 람다식을 받는데, 받은 식에 인덱스를 한 개씩 전달해 주는 듯
    # 인덱스가 들어가면 같은 인덱스의 제목이 적힌 값을 반환하는 함수가 필요

## 튜터님 정답 
def get_song_info(idx):
    return "{}".format(df_bts_info.iloc[idx]['Title'])
    # "{}".format 빼도 이슈는 없음 
    # df_bts_info['Title'][idx]로 써도 됨. 다만 iloc가 더 명시적이다고 함
get_song_info(1)


## 내 풀이 
	# info 데이터프레임에서 타이틀을 가져와 리스트 만들고, 스케일링 데이터셋에 다시 붙인 다음에 출력..
    # leaf_label_func 함수의 전달인자를 제대로 파악하지 못해 복잡하게 함수를 짬

def get_song_info(idx):
    """"
    input: index (int-type)
    output: title of a song (str-type)
    """    
    df_bts_anal_s_title = df_bts_anal_s.copy()
    song_list = []
    for i in range(len(df_bts_anal_s_title)):
        song_list.append(df_bts_info['Title'][i])
    df_bts_anal_s_title['title'] = song_list

    return df_bts_anal_s_title['title'][idx]
# 그래프 그리기
plt.figure(figsize=(15,10))
plt.title('Dendrogram')
plt.xlabel('Songs')
plt.ylabel('Euclidean distances')
dgram = dendrogram(linkage(df_bts_anal_s, method='ward'), leaf_label_func=get_song_info)
plt.show()

 

4. 계층적 군집화 실습 

# 계층적 군집화 실행
# https://scikit-learn.org/stable/modules/generated/sklearn.cluster.AgglomerativeClustering.html

h_clustering = AgglomerativeClustering(n_clusters = 5, affinity = 'euclidean', linkage = 'ward')
h_label = h_clustering.fit_predict(df_bts_anal_s)
h_label

 

 

 

# 각 Cluster별 노래 제목을 한 번에 출력해주는 함수 생성

## 튜터님 풀이 
    # def 함수 안에서 선언된 변수는 '지역변수'로 함수가 끝나면 작동하지 않음
    # global은 '전역변수' 함수로, 어떤 변수를 전역으로 사용하겠다고 명시해주는 역할
    # global 함수는 추천하는 함수는 아님
    #보통 Class를 만들어 속성함수로 불러오거나 지역변수로 Dataframe을 받아오고 이를 조작

def print_cluster_result(n_clusters, affinity = 'euclidean', linkage = 'ward'):
  global df_bts_info
  h_clustering = AgglomerativeClustering(n_clusters = n_clusters, linkage = linkage,\
                                         affinity = affinity)
  h_label = h_clustering.fit_predict(df_bts_anal_s)
  df_bts_info["cluster"] = h_label
  for i in range(0, n_clusters):
    print("Cluster {}".format(i))
    print("-----------------------")
    df_print = df_bts_info[df_bts_info["cluster"] == i]
    df_print_list = df_print["Title"].sort_values(ascending = True).tolist() #정렬>리스트화
    print("\n".join(df_print_list)) # 리스트에 각 요소를 줄바꿈 하여 하나로 합침 
    print("-----------------------\n")
  df_bts_info = df_bts_info.drop(["cluster"], axis = 1)


# 내 풀이 
def print_cluster_result(n_clusters = n, affinity = 'euclidean', linkage = 'ward'):
    '''''''''
    input: n_clusters(int-type), affinity(str-type), linkage(str-type)
    output: None -- print만 하는 함수라고 생각했을 때 return 값은 None
    '''''''''
    # n_clusters를 설정한 n값으로 조정되도록 n_clusters = n_clusters로 기입 
    h_clustering = AgglomerativeClustering(n_clusters = n_clusters, affinity = 'euclidean', linkage = 'ward')
    h_label = h_clustering.fit_predict(df_bts_anal_s)

    df_bts_h_clustering = df_bts_info.copy()
    df_bts_h_clustering['h_label'] = h_label

    cluster_song = {}

    for idx, label in enumerate(h_label):
        if label not in cluster_song: # 이걸 해야지 키 값이 리셋이 안됨 
            cluster_song[label] = []  # 라벨을 key 값으로 지정하고, value는 list로 들어가게끔 처리
        cluster_song[label].append(df_bts_h_clustering['Title'][idx]) # value를 [] 처리해뒀으니 거기다 title 하나씩 넣기 
    cluster_song = dict(sorted(cluster_song.items())) # dict 순서 sorted로 정렬하면 리스트형태가 되니까 다시 dict형으로 수정 

    for c_label, titles in cluster_song.items():
        print(f'Cluster_{c_label}')
        print('-----------------------')
        for title in titles:       # key값 하나에 대해 for문을 한 번 더해서 제목이 나오도록 설정 
            print(title)
        print('-----------------------')
        print('')
        
        
 print_cluster_result(n_clusters = 7)