LG DX DATA SCHOOL

0123 군집분석

getfeelingsfrom 2026. 1. 23. 15:58

 

군집분석의 종류

군집화 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