군집분석의 종류

군집화 vs 분류
x가 y(공통목표)에 대한 영향력만 보게 됨
군집이라는 건 y가 제공되지 않는 비지도 학습 중 하나
비지도학습(y가 주어지지 않음)은 군집, 연관규칙, 추천시스템으로 나눌 수 있음
⇒ 유사한 것끼리 엮어주는 것
군집화
- 일련의 관측값들을 적절한 기준으로 서로 유사한 관측값끼리 그룹으로 묶는 기법
- 사전에 그룹이 어떤 형태인지 모르는 상태에서 실행
- 목적 변수가 없는 상태에서 유사한 특성을 가진 개체끼리 그룹화
계층적 군집화
: 계층적 트리 모형을 개발해 개별 개체들을 순차적, 계층적으로 유사한 개체 또는 그룹과 통합하여 그룹화를 수행하는 알고리즘
- 모든 샘플은 하나의 집단의 요소이다 (처음에는 10개의 요소가 있다면 10개의 집단)
- 병합적 방법 : 2개씩 가까운 거리에 있는 요소끼리 하나의 집단으로 묶어감
- 분할정 방법 : 하나의 큰 군집에서 작은 군집으로 분할해 나가는 방식
- 시각화 : 덴드로그램

- 길 수 있으며 고립된 군집을 찾는데 중점을 둔 방법
- 최장 연결법 :
- 두 군집 사이의 거리를 각 군집에서 하나씩 관측 값을 뽑았을 떄 나타날 수 있는 거리의 최대값으로 측정
- 같은 군집에 속하는 관측치는 알려진 최대 거리보다 짧으면 군집들의 내부 응집성에 중점을 둔 방법
- 중심 연결법 :
- 두 군집의 중심 간의 거리 측정
- 두 군집이 결합될 때 새로운 군집의 평균은 가중 평균을 토해 구해진다
- 평균 연결법 :
- 모든 항목에 대한 거리 평균을 구하면서 군집화를 하기 때문에 계산량이 불필요하게 많아질 수 있다
- 와드 연결법:
- 군집내의 오차 제곱합에 기초하여 군집을 수행한다
- 보통 두 군집이 합해지면 병합된 군집의 오차 제곱합은 병합 이전 각 군집의 오차제곱합의 합보다 커지게 되는데 그 증가량이 가장 작아지는 방향으로 군집을 형성해 나가는 방법이다.
연속형 변수일 경우 거리 계산 방법

맨해탄 거리

R로 구현
data<-USArrests
?USArrests
str(data)
summary(data)
df_scaled <-scale(data) #표준화
head(df_scaled)
dist_matrix <- dist(df_scaled, method="euclidean") #거리 구해주는 함수
hc<-hclust(dist_matrix) #hierarchical
plot(hc, hang=-1, cex = 0.8, main="Hierarchical Clustering of USArrests")

- 50개 주 이름
- 가장 긴 걸 기준으로 유클리드 계산해서 짧은 것들끼리 묶음
- 높이 값은 거리 값 ⇒ 높이가 길다는 건 거리가 길다는 것 ! ⇒ 성향이 멀다
- 밑으로 내려 갈수록 개인화 ⇒ 개인맞춤 서비스 구현
- 몇개로 군집화 하는게 좋은지 확인할 수 있는 방법
rect.hclust(hc, k = 4, border = "blue")

plot(hc, hang=-1, cex = 0.8, main="Hierarchical Clustering of USArrests")
rect.hclust(hc, k = 4, border = "blue")
#군집 할당 (4개 그룹으로 절단)
groups <- cutree(hc, k = 4)
groups
# 원래 데이터에 군집 결과 추가
final_data <- cbind(data, cluster = groups)
head(final_data)
# 군집별 데이터 개수 확인
table(groups)

EDA 분석
data<-USArrests
?USArrests
str(data)
summary(data)
df_scaled <-scale(data) #표준화
head(df_scaled)
dist_matrix <- dist(df_scaled, method="euclidean") #거리 구해주는 함수
hc<-hclust(dist_matrix) #hierarchical
plot(hc, hang=-1, cex = 0.8, main="Hierarchical Clustering of USArrests")
rect.hclust(hc, k = 4, border = "blue")
#군집 할당 (4개 그룹으로 절단)
groups <- cutree(hc, k = 4)
groups
# 원래 데이터에 군집 결과 추가
final_data <- cbind(data, cluster = groups)
head(final_data)
# 군집별 데이터 개수 확인
table(groups)
#각 그룹별 특성을 EDA를 통해 결론 보고서 작성하기
#k=4
# 2x2 형태로 4개 박스플롯 한 번에 출력
par(mfrow=c(2,2))
boxplot(Murder ~ cluster, data=final_data,
main="Murder by Cluster", xlab="Cluster", ylab="Murder")
boxplot(Assault ~ cluster, data=final_data,
main="Assault by Cluster", xlab="Cluster", ylab="Assault")
boxplot(UrbanPop ~ cluster, data=final_data,
main="UrbanPop by Cluster", xlab="Cluster", ylab="UrbanPop")
boxplot(Rape ~ cluster, data=final_data,
main="Rape by Cluster", xlab="Cluster", ylab="Rape")
par(mfrow=c(1,1))

- cluster 1은 살인체포율이 다른 cluster에 비해 가장 높고, 폭행 체포율과, 강간 체포율 또한 높은 편이다. 다만 도시화는 낮게 이루어져있어, 도시형 범죄라고 보기엔 어렵다.
- cluster 2는 폭행 체포율과 강간 체포율이 다른 cluster에 비해 가장 높고 살인 체포율도 높은 수준으로 나타난다. 도시 인구율 또한 가장 높으므로 이 지역에서는 도시화가 높은 지역에서 강력 범죄가 높게 나타나는 것을 확인할 수 있다.
- cluster 3는 살인체포율과, 폭행체포율, 강간 체포율 모두 중간 수준으로 나타났지만 도시 인구율은 비교적 높게 나타나, cluster 2와 환경은 비슷하지만 범죄 수준은 낮은 지역으로 생각해볼 수 있다.
- cluster 4는 도시 인구율도 낮고, 모든 범죄에 대한 비율이 가장 낮게 나타난다. 이는 가장 안정적인 비도시적 지역으로 예상할 수 있다.
비계층적 군집분석
k-means clustering
- 주어진 데이터를 k개의 클러스터로 묶는 알고리즘
- 각 클러스터와 거리 차이의 분산을 최소화하는 방식
- 절차
- 초기 군집으로 k개의 임의 설정
- 각 자료의 가까운 군집 할당
- 각 군집의 평균으로 중심 이동
- 군집 중심이 변화 없을 때까지 반복

# 1. 데이터 로드 및 전처리
data(iris)
# 수치형 데이터만 선택 (1~4열) 및 표준화
iris_scaled <- scale(iris[, 1:4]) #5번째 필드 뺴고 (명목형이니까)
# 데이터 확인
head(iris_scaled)
# 2. 최적의 k 찾기 시각화
#k = 1,2,3,4....10 각각에 대해 k-means를 돌림
#각 결과에서 tot.withinss(전체 군집 내 제곱합, WSS) 값만 뽑아서
#sapply()로 벡터 형태로 모아서 wss에 저장하는 코드
wss <- sapply(1:10, function(k) {
kmeans(iris_scaled, centers = k, nstart = 20)$tot.withinss
})
plot(1:10, wss, type = "b", pch = 19, frame = FALSE,
xlab = "Number of clusters K",
ylab = "Total within-clusters sum of squares",
main = "Elbow Method for Optimal K")

Scree plot에서 변화가 급격하게 줄어드는 지점을 찾는다
- 집단 간 분산이 크면 → 분류가 잘 된 것
- 집단 내 분산이 작으면 → 분류가 잘 된 것
- 집단 간 , 집단 내 값을 둘 다 준다면 집단 간 = 집단 내 값일 때를 선정하면 됨
실루엣 분석
- 각 군집 간의 거리가 얼마나 효율적으로 분리돼 있는지 나타냄
- 잘 분리
- 다른 군집과의 거리는 떨어져있고
- 동일 군집까리의 데이터는 서로 가깝게 뭉쳐 있음
- 개별 군집은 비슷한 정도의 여유공간을 가지고 떨어져 있다
- 데이터 포인트 실루엣 계수 값 S(i):
- a(i): 같은 클러스터 내부에서의 평균 거리(응집도)
- b(i): 가장 가까운 다른 클러스터와의 평균 거리(분리도)
- =⇒ S(i) = (b(i)-a(i)) / (max(a(i),b(i))
- 결과
- 1에 가까울수록: 내 클러스터에 잘 속하고(작은 a), 다른 클러스터와 잘 분리됨(큰 b)
- 0 근처: 경계에 있음
- 음수: 사실 다른 클러스터가 더 가까움(잘못 배정 가능성)
DBSCAN

- 밀도 기반 군집화의 대표적인 알고리즘
- 특정 공간 내에 데이터 밀도 차이를 기반 알고리즘으로 하고 있어서 복잡한 기하학적 분포도를 가진 데이터 세트에 대해서도 군집화를 자 수행함
iris데이터 셋으로 클러스터 평가
1. Iris 데이터 로드 → DataFrame 생성
2. KMeans(n_clusters=3)로 군집화
3. 각 샘플(150개)에 대해 실루엣 계수 계산
4. 전체 평균 실루엣 점수 계산
5. irisDF에 cluster + silhouette_coeff 붙여서 확인
from sklearn.preprocessing import scale
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
# 실루엣 분석 metric 값을 구하기 위한 API 추가
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
iris = load_iris() #Iris 데이터셋 불러오기
feature_names = ['sepal_length','sepal_width','petal_length','petal_width']
irisDF = pd.DataFrame(data=iris.data, columns=feature_names)
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0).fit(irisDF)
#군집을 3개로 나눔, 초기 중심점을 똑똑하게 잡아서 성능/속도 개선, 반복 최대 횟수=300
irisDF['cluster'] = kmeans.labels_ #→ 각 데이터가 어느 군집(0/1/2)에 속했는지 번호가 들어있음
# iris 의 모든 개별 데이터에 실루엣 계수값을 구함.
score_samples = silhouette_samples(iris.data, irisDF['cluster'])
print('silhouette_samples( ) return 값의 shape' , score_samples.shape)
# irisDF에 실루엣 계수 컬럼 추가
irisDF['silhouette_coeff'] = score_samples
# 모든 데이터의 평균 실루엣 계수값을 구함.
average_score = silhouette_score(iris.data, irisDF['cluster'])
print('붓꽃 데이터셋 Silhouette Analysis Score:{0:.3f}'.format(average_score))
irisDF.head(3)

irisDF.groupby('cluster')['silhouette_coeff'].mean()

클러스터별 평균 실루엣 계수의 시각화를 통한 클러스터 개수 최적화 방법
여러개의 클러스터링 갯수를 List로 입력 받아 각각의 실루엣 계수를 면적으로 시각화한 함수 작성
def visualize_silhouette(cluster_lists, X_features):
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
#silhouette_score: 전체 평균 실루엣 점수 (scalar)
# silhouette_samples: 각 샘플의 실루엣 계수 배열
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import math
# 입력값으로 클러스터링 갯수들을 리스트로 받아서, 각 갯수별로 클러스터링을 적용하고 실루엣 개수를 구함
n_cols = len(cluster_lists)
# plt.subplots()으로 리스트에 기재된 클러스터링 수만큼의 sub figures를 가지는 axs 생성
fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)
# 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 실루엣 개수 시각화
for ind, n_cluster in enumerate(cluster_lists):
# KMeans 클러스터링 수행하고, 실루엣 스코어와 개별 데이터의 실루엣 값 계산.
clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
cluster_labels = clusterer.fit_predict(X_features)
#sil_avg: “이 k에서 평균적으로 군집이 얼마나 잘 분리/응집됐나”
#sil_values: “각 샘플이 얼마나 잘 군집에 속했나”
sil_avg = silhouette_score(X_features, cluster_labels)
sil_values = silhouette_samples(X_features, cluster_labels)
#subplot 축/타이틀 세팅
y_lower = 10
axs[ind].set_title('Number of Cluster : '+ str(n_cluster)+'\n' \
'Silhouette Score :' + str(round(sil_avg,3)) )
axs[ind].set_xlabel("The silhouette coefficient values")
axs[ind].set_ylabel("Cluster label")
axs[ind].set_xlim([-0.1, 1])
axs[ind].set_ylim([0, len(X_features) + (n_cluster + 1) * 10])
axs[ind].set_yticks([]) # Clear the yaxis labels / ticks
axs[ind].set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1])
# 클러스터링 갯수별로 fill_betweenx( )형태의 막대 그래프 표현.
for i in range(n_cluster):
ith_cluster_sil_values = sil_values[cluster_labels==i] #클러스터 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[ind].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, \
facecolor=color, edgecolor=color, alpha=0.7)
axs[ind].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
y_lower = y_upper + 10
axs[ind].axvline(x=sil_avg, color="red", linestyle="--")#세로 점선은 해당 k에서의 평균 실루엣 점수
- sil_avg: “이 k에서 평균적으로 군집이 얼마나 잘 분리/응집됐나”
- sil_values: “각 샘플이 얼마나 잘 군집에 속했나”
진짜 정답 클러스터가 4개(centers=4)”인 데이터를 만든 뒤,
KMeans를 k=2,3,4,5 로 각각 돌려서 실루엣 플롯(면적 그래프)으로 비교
# make_blobs 을 통해 clustering 을 위한 4개의 클러스터 중심의 500개 2차원 데이터 셋 생성
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=500, n_features=2, centers=4, cluster_std=1, \
center_box=(-10.0, 10.0), shuffle=True, random_state=1)
# cluster 개수를 2개, 3개, 4개, 5개 일때의 클러스터별 실루엣 계수 평균값을 시각화
visualize_silhouette([ 2, 3, 4, 5], X)

- 세로 높이 = 그 클러스터에 포함된 데이터 개수
- 가로 길이(오른쪽으로 길수록) = 실루엣 계수가 큰 샘플이 많음 → 클러스터 품질 좋음
좋은 군집의 특징
- 대부분 실루엣 값이 0.5 이상에 많이 분포
- 음수(-) 쪽이 거의 없음
- 클러스터별로 값들이 전반적으로 높음(막대가 오른쪽으로)
1. k=2 (Silhouette 0.705) — 점수는 높은데 “과소 군집”
- 평균 점수는 제일 높게 보임.
- 하지만 실제 정답이 4개인데 2개로 뭉개버린 것이야.
왜 점수가 높게 나올 수 있냐?
- K=2면 멀리 떨어진 클러스터들을 큰 덩어리 두 개로 나누면서
- 덩어리 간 거리가 커지기 때문에 평균 silhouette이 높게 나오기도 함.
(2) k=3 (Silhouette 0.588) — 애매한 타협
- 4개 중 2개가 하나로 합쳐졌을 확률이 큼
- 일부 클러스터에서 실루엣이 낮은 구간이 보임
(3) k=4 (Silhouette 0.651) — 가장 타당
- 정답 데이터 생성 조건(centers=4)과 정확히 일치
- 클러스터별로 실루엣 값이 전반적으로 높은 편
- 분포도 비교적 균형적
(4) k=5 (Silhouette 0.556) — 과대 군집(over-clustering)
- 원래 4개 덩어리를 5개로 쪼개면서
- 한 클러스터가 다른 클러스터와 너무 가까워져서 silhouette 하락
- 일부 클러스터 블록이 얇거나(데이터 수 적음),
- 값이 0 근처로 퍼지는 경향
'LG DX DATA SCHOOL' 카테고리의 다른 글
| 01/27 NLP (0) | 2026.01.27 |
|---|---|
| 01/26 DBSCAN (0) | 2026.01.27 |
| 0122 시계열 분석 (1) | 2026.01.22 |
| 01/20 (1) | 2026.01.21 |
| 01/19 결정트리부터 ... (1) | 2026.01.19 |