본문으로 건너뛰기

059 계층적 클러스터링 상세

키워드: 계층적, hierarchical

개요

계층적 클러스터링은 데이터 간의 거리를 기반으로 트리 구조(덴드로그램)를 만들어 클러스터를 형성합니다. 클러스터의 계층 구조를 파악할 수 있는 것이 특징입니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: pycaret[full]>=3.0

계층적 클러스터링 종류

병합형 (Agglomerative) - Bottom-up

초기: 각 데이터가 개별 클러스터
A B C D E

단계 1: 가장 가까운 쌍 병합
[A,B] C D E

단계 2: 다음으로 가까운 쌍 병합
[A,B] [C,D] E

단계 3: 계속 병합
[[A,B],[C,D]] E

단계 4: 최종 단일 클러스터
[[[A,B],[C,D]], E]

분할형 (Divisive) - Top-down

초기: 모든 데이터가 하나의 클러스터
[A, B, C, D, E]

단계 1: 가장 다른 그룹으로 분할
[A, B] [C, D, E]

... 계속 분할

PyCaret에서 계층적 클러스터링

from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

# 059 병합형 계층적 클러스터링
hclust = create_model('hclust')

# 059 클러스터 수 지정
hclust_4 = create_model('hclust', num_clusters=4)

연결 방법 (Linkage)

클러스터 간 거리를 계산하는 방법:

from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt

X = get_config('X').values

# 1. 단일 연결 (Single Linkage)
# 059 두 클러스터 간 가장 가까운 점 사이의 거리
Z_single = linkage(X, method='single')

# 2. 완전 연결 (Complete Linkage)
# 059 두 클러스터 간 가장 먼 점 사이의 거리
Z_complete = linkage(X, method='complete')

# 3. 평균 연결 (Average Linkage)
# 059 모든 점 쌍의 평균 거리
Z_average = linkage(X, method='average')

# 4. Ward 방법 (가장 많이 사용)
# 059 분산 증가를 최소화
Z_ward = linkage(X, method='ward')

덴드로그램

from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt
from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X').values

# 059 Ward 방법으로 계층 구조 생성
Z = linkage(X, method='ward')

# 059 덴드로그램 시각화
plt.figure(figsize=(12, 8))
dendrogram(
Z,
truncate_mode='lastp', # 마지막 p개의 병합만 표시
p=30,
leaf_rotation=90,
leaf_font_size=10,
show_contracted=True
)
plt.xlabel('Sample Index or (Cluster Size)')
plt.ylabel('Distance')
plt.title('Hierarchical Clustering Dendrogram')
plt.axhline(y=15, color='r', linestyle='--', label='Cut Line (4 clusters)')
plt.legend()
plt.tight_layout()
plt.savefig('dendrogram.png', dpi=150)

# 059 덴드로그램에서 수평선을 그어 클러스터 수 결정

최적 클러스터 수 결정

from scipy.cluster.hierarchy import fcluster, linkage
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X').values
Z = linkage(X, method='ward')

# 059 다양한 클러스터 수에서 실루엣 점수
scores = []
for k in range(2, 11):
labels = fcluster(Z, k, criterion='maxclust')
score = silhouette_score(X, labels)
scores.append({'k': k, 'silhouette': score})

import pandas as pd
df = pd.DataFrame(scores)

plt.figure(figsize=(10, 6))
plt.bar(df['k'], df['silhouette'], color='steelblue')
plt.xlabel('Number of Clusters')
plt.ylabel('Silhouette Score')
plt.title('Optimal Clusters for Hierarchical Clustering')
plt.xticks(df['k'])
plt.grid(True, alpha=0.3, axis='y')
plt.savefig('hclust_silhouette.png', dpi=150)

print(df)

연결 방법 비교

from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import silhouette_score
import pandas as pd
from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X').values

# 059 연결 방법별 비교
linkages = ['ward', 'complete', 'average', 'single']
results = []

for link in linkages:
for k in [3, 4, 5]:
clustering = AgglomerativeClustering(
n_clusters=k,
linkage=link
)
labels = clustering.fit_predict(X)
score = silhouette_score(X, labels)

results.append({
'Linkage': link,
'K': k,
'Silhouette': score
})

df = pd.DataFrame(results)
print("연결 방법별 실루엣 점수:")
print(df.pivot(index='Linkage', columns='K', values='Silhouette').round(4))

거리 측정 방법

from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import silhouette_score
from scipy.spatial.distance import pdist, squareform

X = get_config('X').values

# 059 유클리드 거리 (기본)
dist_euclidean = pdist(X, metric='euclidean')

# 059 맨해튼 거리
dist_manhattan = pdist(X, metric='cityblock')

# 059 코사인 거리
dist_cosine = pdist(X, metric='cosine')

# 059 상관 거리
dist_correlation = pdist(X, metric='correlation')

print(f"유클리드 평균 거리: {dist_euclidean.mean():.4f}")
print(f"맨해튼 평균 거리: {dist_manhattan.mean():.4f}")
print(f"코사인 평균 거리: {dist_cosine.mean():.4f}")

클러스터 프로파일링

from pycaret.clustering import *
from pycaret.datasets import get_data
import pandas as pd

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

# 059 계층적 클러스터링
hclust = create_model('hclust', num_clusters=4)
clustered = assign_model(hclust)

# 059 클러스터별 프로파일
print("=== 클러스터 프로파일 ===\n")

profile = clustered.groupby('Cluster').agg(['mean', 'std', 'count'])
print(profile)

# 059 클러스터별 특성 비교
cluster_means = clustered.groupby('Cluster').mean()
print("\n클러스터별 평균:")
print(cluster_means)

덴드로그램 기반 클러스터 선택

from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
import matplotlib.pyplot as plt
import numpy as np
from pycaret.clustering import *
from pycaret.datasets import get_data

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X').values
Z = linkage(X, method='ward')

# 059 거리 임계값으로 클러스터 수 결정
distances = Z[:, 2]

# 059 거리 변화량 (엘보우와 유사)
distance_diff = np.diff(distances)

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(range(len(distances)), distances, 'b-')
plt.xlabel('Merge Step')
plt.ylabel('Distance')
plt.title('Merge Distance Over Steps')

plt.subplot(1, 2, 2)
plt.plot(range(len(distance_diff)), distance_diff, 'r-')
plt.xlabel('Merge Step')
plt.ylabel('Distance Increase')
plt.title('Distance Increase (Elbow-like)')

plt.tight_layout()
plt.savefig('hclust_distance_analysis.png', dpi=150)

# 059 큰 점프가 있는 지점에서 자르면 좋은 클러스터 수

K-Means vs 계층적 클러스터링

from pycaret.clustering import *
from pycaret.datasets import get_data
from sklearn.metrics import silhouette_score
import time

data = get_data('jewellery')
clust = setup(data, normalize=True, session_id=42, verbose=False)

X = get_config('X')

# 059 K-Means
start = time.time()
kmeans = create_model('kmeans', num_clusters=4)
kmeans_time = time.time() - start
kmeans_labels = assign_model(kmeans)['Cluster']
kmeans_score = silhouette_score(X, kmeans_labels)

# 059 계층적
start = time.time()
hclust = create_model('hclust', num_clusters=4)
hclust_time = time.time() - start
hclust_labels = assign_model(hclust)['Cluster']
hclust_score = silhouette_score(X, hclust_labels)

print("=== K-Means vs 계층적 클러스터링 ===")
print(f"\nK-Means:")
print(f" 실루엣 점수: {kmeans_score:.4f}")
print(f" 소요 시간: {kmeans_time:.4f}초")

print(f"\n계층적:")
print(f" 실루엣 점수: {hclust_score:.4f}")
print(f" 소요 시간: {hclust_time:.4f}초")
항목K-Means계층적
클러스터 수미리 지정덴드로그램에서 선택 가능
시간 복잡도O(nkt)O(n²) 이상
대용량적합부적합
결과 해석중심점덴드로그램
클러스터 형태구형다양

장단점

장점:

  • K를 미리 지정하지 않아도 됨
  • 덴드로그램으로 클러스터 구조 시각화
  • 다양한 연결 방법 선택 가능
  • 비구형 클러스터도 탐지 가능 (single linkage)

단점:

  • 계산 비용이 높음 (대용량 부적합)
  • 한 번 병합/분할하면 되돌릴 수 없음
  • 이상치에 민감 (특히 single linkage)
  • 메모리 사용량 높음

정리

  • 계층적 클러스터링은 트리 구조로 클러스터 형성
  • Ward 방법이 가장 많이 사용됨
  • 덴드로그램으로 최적 클러스터 수 결정
  • 소규모 데이터에 적합
  • 클러스터 계층 구조 파악에 유용

다음 글 예고

다음 글에서는 DBSCAN 클러스터링 상세를 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #059