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'아이티윌_데이터 분석 55기 > 강의내용 필기_분석(Python)' 카테고리의 다른 글
| #14 14일차_웹크롤링 실습 (0) | 2026.07.09 |
|---|---|
| #13 13일차_웹크롤링 (0) | 2026.07.08 |
| #10 회귀모형과 패널티 모형, 부스팅 이론 (0) | 2026.06.18 |
| #8 클래스 불균등 처리 (0) | 2026.06.15 |
| #7 7일차_독립성 검정, 적합도 검정, 클래스 불균등 처리 (0) | 2026.06.11 |