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