아이티윌_데이터 분석 55기/강의내용 필기_분석(Python)

#12 12일차_군집분석, 연관분석

ecosso 2026. 6. 19. 16:10

01 군집분석

 -01 특징

 -02 종류

 -03 평가척도

 

02 연관분석

 


 

실루엣 계수는 군집이 잘 묶였나, 그렇지 않은가를 보여줄 뿐이지 정말로 군집 내의 특성을 명확하게 반영하였는지를 확인하기 위해서는 다른 검증을 사용해야 한다.

군집분석의 평가척도는 비즈니스적으로 A,B,C...의 성격을 가진다까지는 말해주지 않는다.

따라서 각 군집의 특성을 들여다보았을 때, 

 

 

01 군집분석

 -01 특징

 - 비지도 학습

 - 거리를 기반으로 유사한 데이터끼리 하나의 군집을 형성하여 데이터를 여러 군집으로 나누는 과정

  -> 세그먼트 이해 (변수 선택, 통계적 검증 (군집 이질성 -> f검정, 실루엣 계수 등), 비즈니스적 검증 -> 수치 비교)

 - 이상치에 민감

 - 스케일에 민감


 -02 종류

 1) 계층적 군집분석

 - 한 번 데이터가 묶이기 시작하면 한 군집에 속한 데이터는 다른 군집으로 이동할 수 없음

 - 덴드로그램 시각화 목적으로도 사용

 - 군집과의 거리 정의가 중요 ( 최단거리법, 최대거리법, 평균거리법, 와드연결법)

 - 군집의 수가 정해져있지 않음

 

 2) 비계층적 군집분석

 - 대표적으로 kmeans가 있음

 - 초기 시드값을 기준으로 군집을 형성

 - 데이터들끼리의 발생 가능한 모든 거리를 모두 계산, 가장 작은 거리를 구함

 - 그 다음으로 군집과의 거리를 계산함으로써 각 데이터가 인근 군집에 포함될지, 새로운 군집을 형성해야할지에 대한 판단을 진행

 - 최단거리법, 최대거리법, 평균거리법, 와드연결법

 

 - 군집 형성 과정이 유동적

 > 초기 k개의 랜덤 seed로부터 가장 가까운 데이터를 군집에 포함시켜나가며 군집이 확장

 > 확장된 군집에 의해 군집 중심이 이동되면서 이동된 중심으로부터 가까운 데이터들을 새로 흡수

 > 이미 다른 군집에 포함된 데이터도 군집 이동 가능

 > 군집의 수가 사전에 정해져야 함

 

 3) 혼합분포군집

 - 모든 데이터들이 군집의 수만큼 특정 분포를 따른다는 가정

 - 각 분포의 모수를 최대우도추정법으로 추정 (경험적 추정방식 : E-M Step)

 

 4) DBSCAN

 - 비선형적인 군집의특성을 군집화하는 기법

 - 반경(e)과 min_sample 선택이 중요 -> 군집이 매우 달라짐

 - 군집의 수가 정해져있지 않음

 

 5) SOM

 - 인공신경망 구조의 군집분석 기법

 - 고차원 데이터를 저차원 (2D) 공간에 매핑하여 격자 (unit)의 이동 패턴을 확인하여 군집형성과정 유도

 - 맵 크기 (nXm) 결정 필요


 -03 평가척도

 1) 덴드로그램 : K를 결정 (꾼집분석 ㅗ기에 전반적인 군집의 분포, 대략적인 k를 결정하기 위해 사용)

 2) 총분산 분해

 TOTAL_SS = WITHIN_SS (군집내) + BETWEEN_SS (군집간)

 

좋은 군집일수록 군집내분산 작아짐

좋은 군집일수록 군집간분산 커짐

 

 2) 실루엣 계수 : 서로 다른 군집 방식 비교

 2-1) 개별 실루엣 계수

 - 각 데이터포인트마다의 실루엣 계수

 - -1 ~ 1 범위

 

 2-2) 전체 실루엣 계수

 - 개별 실루엣 계수 평균

 - -1 ~ 1 범위

 - 클수록 좋은 군집


[ 예제 - IRIS DATA를 사용한 군집분석 (계층적 군집분석) ] 

# 1. 데이터 로딩
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data

# 2. 스케일링
from sklearn.preprocessing import MinMaxScaler
m_sc = MinMaxScaler()
X_sc = m_sc.fit_transform(X)

# 3. 군집분석 수행
from scipy.cluster.hierarchy import linkage, dendrogram

linkage(y,                  # data
        method = 'single')  # 군집과의 거리 계산방식 (single, complete, average, centroid, ward)

m1 = linkage(X_sc, method = 'single')
m2 = linkage(X_sc, method = 'complete')
m3 = linkage(X_sc, method = 'average')
m4 = linkage(X_sc, method = 'centroid')
m5 = linkage(X_sc, method = 'ward')

# 덴드로그램 시각화
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2,3)

dendrogram(m1, ax = ax[0,0])
dendrogram(m2, ax = ax[0,1])
dendrogram(m3, ax = ax[0,2])
dendrogram(m4, ax = ax[1,0])
dendrogram(m5, ax = ax[1,1])

 

ax[0,0].set_title('single')
ax[0,1].set_title('complete')
ax[0,2].set_title('average')
ax[1,0].set_title('centroid')
ax[1,1].set_title('ward')

 

# 4. 평가 (실루엣 계수)
# 1) 군집 번호
from scipy.cluster.hierarchy import fcluster

fcluster(Z,             # 계층군집분석 결과
         t,             # 기준점
         criterion = )  # 기준

cut_tree = fcluster(m2, t=3, criterion = 'maxclust')


# 2) 실루엣 계수
from sklearn.metrics import silhouette_samples, silhouette_score
silhouette_samples(X,       # 데이터
                   labels)  # 군집번호

# 개별 실루엣 계수
silhouette_samples(X_sc, cut_tree)

silhouette_samples(X_sc, cut_tree)
Out[28]: 
array([ 0.79758641,  0.71667716,  0.76766945,  0.73216636,  0.79101112,
        0.66770433,  0.7641607 ,  0.79908154,  0.64911243,  0.74541483,
        0.74004099,  0.78818592,  0.71691495,  0.67143325,  0.62167514,
        0.52051174,  0.68344413,  0.79063956,  0.64620307,  0.74398456,
        0.73655725,  0.74668801,  0.75377396,  0.70174517,  0.76139223,
        0.7014976 ,  0.76391801,  0.78653144,  0.78599589,  0.75966523,
        0.74202472,  0.71854073,  0.66744545,  0.61861683,  0.74796511,
        0.77071759,  0.73611627,  0.78294471,  0.68349437,  0.79439794,
        0.79344999,  0.40509026,  0.72623556,  0.69076026,  0.69874295,
        0.70452895,  0.74606591,  0.75653331,  0.75467126,  0.7911558 ,
        0.00912203,  0.25478351, -0.03649107,  0.54431883,  0.35063847,
        0.57706479,  0.13838806,  0.28035257,  0.35649448,  0.54286027,
        0.33819327,  0.50081739,  0.49660395,  0.48850059,  0.55193755,
        0.2395936 ,  0.49780828,  0.56609238,  0.42218847,  0.57694197,
        0.16938276,  0.55344961,  0.40056834,  0.52177779,  0.45835982,
        0.3214728 ,  0.21787717,  0.09498951,  0.50188391,  0.47941191,
        0.54236725,  0.48812408,  0.59088545,  0.39772246,  0.48683316,
        0.22294882,  0.12352162,  0.46340165,  0.54638603,  0.57818658,
        0.57858254,  0.46907656,  0.59141639,  0.3137466 ,  0.59433047,
        0.54673412,  0.57454327,  0.51504013,  0.29967915,  0.59152009,
        0.48499252,  0.25372546,  0.57803583,  0.11355493,  0.51474447,
        0.52940387,  0.42784055,  0.458337  , -0.15982397,  0.53060823,
        0.36745244, -0.0950845 ,  0.53884593,  0.25186524, -0.13207173,
        0.47468449,  0.25463922,  0.46448253,  0.43946377,  0.40800858,
        0.6068679 ,  0.23581599,  0.46660522,  0.19224271,  0.55230788,
        0.48921978,  0.2313365 ,  0.16340388,  0.34231682,  0.33403197,
        0.46432366,  0.43288713,  0.38014849,  0.33416199,  0.3406227 ,
        0.53442888,  0.46821943,  0.23810177,  0.23399904,  0.55480885,
        0.57060357,  0.53131219,  0.25372546,  0.60496143,  0.55863308,
        0.50773856,  0.13879821,  0.34724011,  0.40850541,  0.19500212])

 

score1.mean()

score1.mean()
Out[30]: np.float64(0.5035159025156312)

 

# 전체 실루엣 계수
silhouette_samples(X_sc, cut_tree)

# 군집분석 수행(kmeans)
from sklearn.cluster import KMeans
KMeans(n_clusters=8,        # 군집수
       init = 'k-means++')  # 초기값(seed) 결정 방식 (k-means++ : 임의의 공간이 아닌 실제 데이터 중 k개 만큼을 랜덤하게 seed로 결정하는 방식)

kmeans = KMeans(3, random_state = 0)
kmeans.fit(X_sc)

kmeans.fit(X_sc)
C:\Users\green\anaconda3\Lib\site-packages\sklearn\cluster\_kmeans.py:1419: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=1.
  warnings.warn(
Out[34]: KMeans(n_clusters=3, random_state=0)

환경설정에 따른 메모리누수가 발생할 수 있다는 경고로 무시해도 된다.

 

kmeans.labels_                              # 군집번호

import pandas as pd
pd.Series(kmeans.labels_).value_counts()   # 군집간 크기

pd.Series(kmeans.labels_).value_counts()   # 군집간 크기
Out[50]: 
2    52
1    50
0    48
Name: count, dtype: int64

 

# 군집평가
# 1) WCSS (Within Cluster Sum of Squre) : 군집내 제곱합
kmeans.inertia_  # 7.1227

# 2) 실루엣계수
from sklearn.metrics import silhouette_score
silhouette_samples(X_sc, kmeans.labels_)

# k 의 수 결정
# 실루엣계수보다는 between_ss 혹은 wcss로 결정하는 것이 좋다.
wcss = []; sil = []
for k in range(2,11):
    kmeans = KMeans(k, random_state = 0)
    labels = kmeans.fit_predict(X_sc)
    wcss.append(kmeans.inertia_)
    sil.append(silhouette_score(X_sc, labels))

fig, ax = plt.subplots(1,2)
ax[0].plot(range(2,11), wcss, marker ='o')
ax[0].set_title('Elbow point(WCSS)')
ax[0].set_xlabel('k')
ax[0].set_ylabel('WCSS')

ax[1].plot(range(2,11), wcss, marker ='o')
ax[1].set_title('Elbow point(SILHOUETTE)')
ax[1].set_xlabel('k')
ax[1].set_ylabel('SILHOUETTE')


[ 예제 - iris data를 사용한 군집분석 (SOM) ]

# 1. 데이터 로딩
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data

# 2. 스케일링
from sklearn.preprocessing import MinMaxScaler
m_sc = MinMaxScaler()
X_sc = m_sc.fit_transform(X)

# 3. 군집분석 (SOM)
# pip install skearn_som

SOM(m=3,                    # grid 크기 (m X n)
    n=3,                    # grid 크기 -> unit 수가 결정됨
    dim=3,                  # 설명변수의 수 (임의로 변경 불가)
    lr=1,                   # 학습률 (클수록 최적의 해를 결정하는 보폭이 커짐)
    max_iter=3000,          # 최대반복수
    random_state=None)      # seed 값 설정

c1 = 0.9X1 + 0.7X2 + 0.3X3 + -0.X1
c2 = 0.8X1 + 0.3X2 - 0.1X3 + 0.8X1

som1 = SOM(dim = 4)
labels = som1.fit_predict(X_sc) # 9개의 군집으로 결과 도출

 

# 4. 평가
# 실루엣 계수
from sklearn.metrics import silhouette_score
silhouette_score(X_sc, labels)

silhouette_score(X_sc, labels)
Out[54]: 0.34639599498457263

[ 예제 - 기하학적 분포의 특성을 갖는 군집형성 과정 ]

# 내가 가지고 있는 데이터의 분류가 잘 되지 않을 때에는 비선형적인 관계를 가지고 있을 가능성이 있다.
# 따라서 이럴 때 DBSCAN의 사용을 고려할 필요가 있다.
# DBSCAN 자체를 모든 데이터에 항상 무조건적으로 수행할 필요는 없다.

 

# 1. 데이터 로딩
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data

# 2. 스케일링
from sklearn.preprocessing import MinMaxScaler
m_sc = MinMaxScaler()
X_sc = m_sc.fit_transform(X)

# 3. 군집분석(SOM)
# pip install sklearn_som
from sklearn_som.som import SOM

SOM(m=3,                           # grid 크기(mXn)
    n=3,                           # grid 크기 -> unit수 결정됨
    dim=3,                         # 설명변수 수
    lr=1,                          # 학습률(클수록 최적의 해를 결정하는 보폭이 커짐)
    max_iter=3000,                 # 최대반복수
    random_state=None)    


som1 = SOM(dim=4)
labels = som1.fit_predict(X_sc)    # 9개의 군집으로 결과 도출


# 4. 평가
# 실루엣계수
from sklearn.metrics import silhouette_score
silhouette_score(X_sc, labels)     # 0.2453






# =============================================================================
# 예제 - 기하학적 분포의 특성을 갖는 군집형성 과정(kmeans vs dbscan)
# =============================================================================
# 1. 데이터 로딩
from sklearn.datasets import make_circles
X, y = make_circles(n_samples=750, factor=0.3, noise=0.1, random_state=0)

# 2. 스케일링
from sklearn.preprocessing import StandardScaler
X_sc = StandardScaler().fit_transform(X)

# 3. 군집분석
# 1) kmeans 
from sklearn.cluster import KMeans
kmeans_group = KMeans(2).fit_predict(X_sc)

# 2) dbscan
from sklearn.cluster import dbscan
_, dbscan_group = dbscan(X_sc, eps=0.4, min_samples=4)


# 4. 시각화
import matplotlib.pyplot as plt

plt.rc('font', family='Malgun Gothic')
plt.rc('axes', unicode_minus = False)

fig, ax = plt.subplots(1,3)

# 원본
ax[0].scatter(X_sc[:,0], X_sc[:,1], c=y)
ax[0].set_title('원본산점도')

# kmeans
ax[1].scatter(X_sc[:,0], X_sc[:,1], c=kmeans_group)
ax[1].set_title('KMeans 산점도')

# dbscan
ax[2].scatter(X_sc[:,0], X_sc[:,1], c=dbscan_group)
ax[2].set_title('DBSCAN 산점도')


02 연관분석

 - 연관분석 = 장바구니 분석 = 서열 분석

 - 비지도 학습

 - 상품간의 연관규칙을 발견하는 모델링 기법

 - 활용 : 번들상품, 진열 위치 변경, 할인쿠폰, 추천상품노출

 - 카테고리 정의가 매우 중요! (국물라면 -> 계란 vs 라면 -> 계란)

 

** 구매 데이터

주문번호 상품번호 수량
1000 맥주(1)
1000 우유(2)
1000 소세지(3)
1000 수박(4)

 

** 장바구니 데이터

주문번호 상품번호
1000 [1,2,3,4]
1001 [4,10,7]

 

** 트랜젝션 데이터

  1 2 3 4 7 10
1000 T T T T F F
1000 F F F T T T

 

** 평가척도

1) 지지도

 - 추천 연관 강도를 얼마나 지지할 수 있는지에 대한 척도

 - 너무 소수를 위한 정책을 만들지 않기 위해 적절한 지지도 이상의 연관성만 필터링하여 분석

 

2) 신뢰도

 - 두 상품의 연관 강도

 - 조건부 확률 (조건 -> 반응)

 

 - 신뢰도 P (B|A) = B 구매수 / A 구매수

                           = P(A와 B의 교집합) / P(A)

3) 향상도

 - 두 상품의 연관 강도가 우연히 강해지는 확률을 제외한 값

 - 향상도 = P(B|A) / P(B)

Lift > 1 두 상품의 구매가 경향이 있다

Lift = 1 : 두 상품 구매가 독립적

Lift < 1 : 우연에 의한 구매증가로 볼 수 있음

 

[ 예제 - 장바구니 연관 분석 ] 

# 1. 데이터 로딩

# 1. 데이터 로딩
vlist = [['오징어','치즈','맥주'],
         ['소주','맥주','라면'],
         ['라면','김치','계란'],
         ['맥주','소세지'],
         ['오징어','맥주']]

# 2. 장바구니 데이터로 변경
(생략 : 1에서 진행하였음)

# 3. 트랜젝션 데이터 변경
from mlxtend.preprocessing import TransactionEncoder
TransactionEncoder().fit_transform(vlist)

TransactionEncoder().fit_transform(vlist)
Out[3]: 
array([[False, False, False,  True, False, False,  True,  True],
       [False, False,  True,  True, False,  True, False, False],
       [ True,  True,  True, False, False, False, False, False],
       [False, False, False,  True,  True, False, False, False],
       [False, False, False,  True, False, False,  True, False]])

 

m_tr = TransactionEncoder()
tlist = m_tr.fit_transform(vlist)
m_tr.columns_  # 트랜잭션 데이터 컬럼

import pandas as pd
df_tr = pd.DataFrame(tlist, columns = m_tr.columns_)

df_tr 
Out[10]: 
      계란     김치     라면     맥주    소세지     소주    오징어     치즈
0  False  False  False   True  False  False   True   True
1  False  False   True   True  False   True  False  False
2   True   True   True  False  False  False  False  False
3  False  False  False   True   True  False  False  False
4  False  False  False   True  False  False   True  False

 

# 4. 연관분석
# pip install mlxtend
# matplotlib 패키지가 사용중인 경우 설치 에러가 발생한다.
# 이 경우 spyder의 재시작이 필요하다.

from mlxtend.frequent_patterns import association_rules, apriori
m_ap = apriori(df_tr, min_support = 0.1, use_colnames = True)

apriori(df_tr, min_support = 0.1, use_colnames = True)
Out[12]: 
    support       itemsets
0       0.2           (계란)
1       0.2           (김치)
2       0.4           (라면)
3       0.8           (맥주)
4       0.2          (소세지)
5       0.2           (소주)
6       0.4          (오징어)
7       0.2           (치즈)
8       0.2       (김치, 계란)
9       0.2       (계란, 라면)
10      0.2       (김치, 라면)
11      0.2       (맥주, 라면)
12      0.2       (라면, 소주)
13      0.2      (맥주, 소세지)
14      0.2       (맥주, 소주)
15      0.4      (맥주, 오징어)
16      0.2       (맥주, 치즈)
17      0.2      (치즈, 오징어)
18      0.2   (김치, 계란, 라면)
19      0.2   (맥주, 라면, 소주)
20      0.2  (맥주, 치즈, 오징어)

 

result = association_rules(m_ap, metric = 'lift')
result[['antecedents', 'consequents', 'support', 'confidence', 'lift']]

result[['antecedents', 'consequents', 'support', 'confidence', 'lift']]
Out[17]: 
   antecedents consequents  support  confidence  lift
0         (김치)        (계란)      0.2        1.00  5.00
1         (계란)        (김치)      0.2        1.00  5.00
2         (계란)        (라면)      0.2        1.00  2.50
3         (라면)        (계란)      0.2        0.50  2.50
4         (김치)        (라면)      0.2        1.00  2.50
5         (라면)        (김치)      0.2        0.50  2.50
6         (라면)        (소주)      0.2        0.50  2.50
7         (소주)        (라면)      0.2        1.00  2.50
8         (맥주)       (소세지)      0.2        0.25  1.25
9        (소세지)        (맥주)      0.2        1.00  1.25
10        (맥주)        (소주)      0.2        0.25  1.25
11        (소주)        (맥주)      0.2        1.00  1.25
12        (맥주)       (오징어)      0.4        0.50  1.25
13       (오징어)        (맥주)      0.4        1.00  1.25
14        (맥주)        (치즈)      0.2        0.25  1.25
15        (치즈)        (맥주)      0.2        1.00  1.25
16        (치즈)       (오징어)      0.2        1.00  2.50
17       (오징어)        (치즈)      0.2        0.50  2.50
18    (김치, 계란)        (라면)      0.2        1.00  2.50
19    (김치, 라면)        (계란)      0.2        1.00  5.00
20    (계란, 라면)        (김치)      0.2        1.00  5.00
21        (김치)    (계란, 라면)      0.2        1.00  5.00
22        (계란)    (김치, 라면)      0.2        1.00  5.00
23        (라면)    (김치, 계란)      0.2        0.50  2.50
24    (맥주, 라면)        (소주)      0.2        1.00  5.00
25    (맥주, 소주)        (라면)      0.2        1.00  2.50
26    (라면, 소주)        (맥주)      0.2        1.00  1.25
27        (맥주)    (라면, 소주)      0.2        0.25  1.25
28        (라면)    (맥주, 소주)      0.2        0.50  2.50
29        (소주)    (맥주, 라면)      0.2        1.00  5.00
30    (맥주, 치즈)       (오징어)      0.2        1.00  2.50
31   (맥주, 오징어)        (치즈)      0.2        0.50  2.50
32   (치즈, 오징어)        (맥주)      0.2        1.00  1.25
33        (맥주)   (치즈, 오징어)      0.2        0.25  1.25
34        (치즈)   (맥주, 오징어)      0.2        1.00  2.50
35       (오징어)    (맥주, 치즈)      0.2        0.50  2.50