
: 특정 공간 내에 데이터 밀도 차이를 기반 알고리즘으로 하고 있어 복합적 기하학적 분포도를 가진 데이터 세트에 대해서도 군집화를 잘 수행한다
- 응집도 높음 : 집단 내의 분산 값은 낮아야 함
- 결합도 낮음 : 집단 간의 분산 값은 높아야 함
파라미터
- 입실론 주변 영역(epsilon) : 개별데이터를 중심으로 입실론 반경을 가지는 원형의 영역
- 최소 데이터 개수(min points) : 개별 데이터의 입실론 주변 영역에 포함되는 타 데이터의 개수
- 입실론 주변 영역 내에 포함되는 최소 데이터개수를 충족시키는지 여부에 따라 정의되는 데이터
- 핵심포인트 : 주변 영역 내에 최소 데이터 개수 이상의 타 데이터를 가지고 있을 경우 해당 데이터
- 이웃포인트 : 주변 영역 내에 위치한 타 데이터
- 경계 포인트 : 주변 영역 내에 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않지만 핵심 포인트를 이웃포인트로 가지고 있는 데이터
- 잡음 포인트 : 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않으며, 핵심 포인트도 이웃 포인트로 가지고 있지 않는 데이터
from sklearn.cluster import DBSCAN
dbscan = DBSCAN(eps=0.6, min_samples=8, metric='euclidean')
dbscan_labels = dbscan.fit_predict(iris.data)
irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target
iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)

-1 : 클러스터링을 하지 못하고 버려진 것
입실론 크기 : 0.6 → 0.8로 증가
from sklearn.cluster import DBSCAN
dbscan = DBSCAN(eps=0.8, min_samples=8, metric='euclidean')
dbscan_labels = dbscan.fit_predict(iris.data)
irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target
iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)
visualize_cluster_plot(dbscan, irisDF, 'dbscan_cluster', iscenter=False)

클러스터링 실패 수 감소

make_circles()
from sklearn.datasets import make_circles # 데이터 분포가 circle처럼 나옴 (임의의 분포 만들기)
X, y = make_circles(n_samples=1000, shuffle=True, noise=0.05, random_state=0, factor=0.5)
clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y
visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)
x, y축 → 두개의 조건
from sklearn.datasets import make_circles : 데이터 분포가 circle처럼 나옴 (임의의 분포 만들기)
factor=0.5 : 동그라미 2개 = 안에 만들어지는 원이 바깥 원의 1/2 크기
clusterDF['target'] = y : 데이터 프레임으로 만듦

⇒ 비선형 ! !
# KMeans로 make_circles( ) 데이터 셋을 클러스터링 수행.
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2, max_iter=1000, random_state=0)
kmeans_labels = kmeans.fit_predict(X) #훈련 + 예측
clusterDF['kmeans_cluster'] = kmeans_labels
clusterDF.head()
#visualize_cluster_plot(kmeans, clusterDF, 'kmeans_cluster', iscenter=True)


- 선형경계
- 그치만 의미있는 군집은 아님
군집화 실습 (중요)
import pandas as pd
import datetime
import math
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
retail_df = pd.read_excel(io='Online Retail.xlsx')
retail_df.head(3)
- invoice : 주문번호 & stockcode: 제품코드 기반 -> 주문량, 주문일자, 제품단가, 주문 고객 번호, 주문 고객 국가 등등 ...

데이터 세트의 전체 건수, 칼럼 타입, NULL 개수 확인
retail_df.info()

- 전체 데이터에 비해 customerID의 null 값이 너무 많다 (13만 5천건)
- => 사전 정제 작업 필요
- Null 데이터 제거 : 고객 세그멘테이션을 진행할 때 고객 식별 번호가 없는 데이터는 필요 없음
데이터 정제
retail_df = retail_df[retail_df['Quantity']>0]
retail_df = retail_df[retail_df['UnitPrice']>0]
retail_df = retail_df[retail_df['CustomerID'].notnull()]
print(retail_df.shape)
retail_df.isnull().sum()

null 값은 칼럼에 더이상 존재 x
retail_df['Country'].value_counts()[:5]

영국 데이터가 대다수 → 이 데이터를 통해 분석 진행 → 다른 국가의 데이터 제외
retail_df = retail_df[retail_df['Country']=='United Kingdom']
print(retail_df.shape)

RFM 기반으로 데이터 가공
- Recency : 가장 최근 상품 구입 일에서 오늘까지의 기간
- Frequency : 상품 구매 횟수
- Monetary value : 총 구매 금액
retail_df['sale_amount'] = retail_df['Quantity'] * retail_df['UnitPrice']
retail_df['CustomerID'] = retail_df['CustomerID'].astype(int)
- UnitPrice * Quantity → 주문 금액 데이터 만들기
- CustomerNo : float → int
#top5 주문 건수 & 주문 금액을 가진 고객데이터 추출
print(retail_df['CustomerID'].value_counts().head(5))
print(retail_df.groupby('CustomerID')['sale_amount'].sum().sort_values(ascending=False)[:5])

- 몇몇 특정 고객이 많은 주문 건수와 줒문 금액을 가지고 있음
- 주문 번호 + 상품 코드 베레의 식별자
- ⇒ invoiceNo + StockCode로 groupby → 유일한 식별자로 만들자!
# DataFrame의 groupby() 의 multiple 연산을 위해 agg() 이용
# Recency는 InvoiceDate 컬럼의 max() 에서 데이터 가공
# Frequency는 InvoiceNo 컬럼의 count() , Monetary value는 sale_amount 컬럼의 sum()
aggregations = {
'InvoiceDate': 'max',
'InvoiceNo': 'count',
'sale_amount':'sum'
}
cust_df = retail_df.groupby('CustomerID').agg(aggregations)
# groupby된 결과 컬럼값을 Recency, Frequency, Monetary로 변경
cust_df = cust_df.rename(columns = {'InvoiceDate':'Recency',
'InvoiceNo':'Frequency',
'sale_amount':'Monetary'
}
)
cust_df = cust_df.reset_index()
cust_df.head(3)

- frequency : 고객별 주문 건수 = customerID로 groupby()해서 Invoice notice의 count()로 구함
- Recency : customerID로 groupby() → InvoiceDate 컬럼의 max() 에서 데이터 가공
- monetary value: 고객별 주문 금액 = customerID로 groupby() → sale_amount의 sum()
import datetime as dt
cust_df['Recency'] = dt.datetime(2011,12,10) - cust_df['Recency']
cust_df['Recency'] = cust_df['Recency'].apply(lambda x: x.days+1)
print('cust_df 로우와 컬럼 건수는 ',cust_df.shape)
cust_df.head(3)
recency = 고객이 가장 최근에 주문한 날짜를 기반으로 하기 때문 → 오늘날짜 - 최근 주문 일자

RFM 분석에 필요한 Recency, Frequency, Monetary 모두 생성
fig, (ax1,ax2,ax3) = plt.subplots(figsize=(12,4), nrows=1, ncols=3)
ax1.set_title('Recency Histogram')
ax1.hist(cust_df['Recency'])
ax2.set_title('Frequency Histogram')
ax2.hist(cust_df['Frequency'])
ax3.set_title('Monetary Histogram')
ax3.hist(cust_df['Monetary'])


- Recency, Frequency, Monetary → 모두 왜곡된 데이터 값 분포도
- 특히 Frequency, Monetary → 왜곡정도 심함
cust_df[['Recency','Frequency','Monetary']].describe()

- 왜곡정도 확인해보기
왜곡 정도가 높은 데이텅 세트 → k - 평균 군집 적용하면 중심의 개수 증가 시키더라도 변별력이 떨어지는 군집화 수행됨
standardScaler로 평균과 표준편차 재조정 한 뒤 → k-평균 적용
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
X_features = cust_df[['Recency','Frequency','Monetary']].values
X_features_scaled = StandardScaler().fit_transform(X_features)
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict(X_features_scaled)
cust_df['cluster_label'] = labels
print('실루엣 스코어는 : {0:.3f}'.format(silhouette_score(X_features_scaled,labels)))

- 군집 3개→ 전체 군집의 평균 실루엣 계수 = 실루엣 스코어 = 0.576 → 안정적
군집의 개수 2~5개까지 변화 → 개별 군집의 실루엣 계수 & 데이터 구성 확인해보기
visualize_silhouette([2,3,4,5],X_features_scaled)
visualize_kmeans_plot_multi([2,3,4,5],X_features_scaled)


그룹이 전체적으로 잘 안된 ?
평균적으로 지수가 넘어가야되는데 그룹을 나눌수록 양극화 되어 망해가는 중인거임
→ 스케일링 실패인거네
⇒ 가운데로 정상화 시킬 수 있게 스케일링 필요
### Log 변환을 통해 데이터 변환
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
# Recency, Frequecny, Monetary 컬럼에 np.log1p() 로 Log Transformation
cust_df['Recency_log'] = np.log1p(cust_df['Recency'])
cust_df['Frequency_log'] = np.log1p(cust_df['Frequency'])
cust_df['Monetary_log'] = np.log1p(cust_df['Monetary'])
# Log Transformation 데이터에 StandardScaler 적용
X_features = cust_df[['Recency_log','Frequency_log','Monetary_log']].values
X_features_scaled = StandardScaler().fit_transform(X_features)
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict(X_features_scaled)
cust_df['cluster_label'] = labels
print('실루엣 스코어는 : {0:.3f}'.format(silhouette_score(X_features_scaled,labels)))

로그를 취해 스케일링을 해줬는데 ⇒ 실루엣 스코어가 더 낮아짐
절대적으로 로그가 항상 긍정적으로 작용하는 것은 아니다
frequency, monetary가 이미 너무너무 극단적이라 복구가 안됨
visualize_silhouette([2,3,4,5],X_features_scaled)
visualize_kmeans_plot_multi([2,3,4,5],X_features_scaled)

마이너스 값이 나오면 안되는데 지금 number of cluster 3~5 그림 보면 마이너스 값을 나타내는 그래프가 존재
⇒ 신뢰도 안 좋음
이럴 땐 RFM에는 안 됨 → EDA로 고객 분석해서 분류하는게 더 좋음

1️⃣ 실루엣 계수란?
각 데이터 포인트가 자신의 군집에 얼마나 잘 속해 있고, 다른 군집과는 얼마나 잘 분리되어 있는지를 나타내는 지표
2️⃣ 실루엣 계수 계산식
각 데이터 포인트 iii에 대해:
- a(i)
- → 같은 군집 내 다른 점들과의 평균 거리 (응집도)
- b(i)
- → 가장 가까운 다른 군집과의 평균 거리 (분리도)

3️⃣ 실루엣 값의 범위와 의미
값 의미
| 1에 가까움 | 매우 잘 분리됨 (이상적) |
| 0 근처 | 군집 경계에 위치 |
| 음수(-) | 잘못된 군집에 할당됨 |
4️⃣ 전체 실루엣 스코어란?
모든 데이터 포인트의 실루엣 계수 평균값
silhouette_score(X, labels)
5️⃣ 실루엣 스코어 해석 기준
평균 실루엣 해석
| > 0.7 | 매우 명확한 군집 구조 |
| 0.5 ~ 0.7 | 합리적인 군집 |
| 0.25 ~ 0.5 | 약한 군집 구조 |
| < 0.25 | 군집 구조 불명확 |
연관성 규칙
군집화 연관 추천 → 비지도 학습
월마트에서 진행
연관규칙 분석
- 장바구니 분석, 서열 분석이라고 불림
- 장바구니 분석 : 장바구니에 무엇이 같이 들어있는지에 대한 분석
- 서열 분석 : A를 산 다음에 B를 산다
연관 규칙 지표 평가 방법
- 지지도 : P(A∩B)
- 전체 거래 중 항목 A와 항목 B를 동시에 포함하는 거래의 비율
- 품목A와 품목B를 포함하는 거래수 / 전체 거래 수
- 신뢰도 (Confidence) : (P(A∩B) / P(A))
- 항목 A를 포함한 거래 중에서 항목 A와 항목B가 같이 포함될 확률 (연관성의 정도 파악)
- 지지도(Support) / P(A)
- 향상도 (Lift) : (P(A | B) / P(A)) = P(A∩B) / P(A)*P(B)
- A가 구매되지 않았을 때 품목 B의 구매확률에 비해 A가 구매됐을 때 품목 B의 구매확률의 증가비
- 연관규칙 A→B는 A와 B의 구매가 서로 관련이 없는 경우에 향상도가 1이 됨
- 신뢰도 / P(B)
⇒ 지지도는 빈도, 신뢰도는 조건부 확률, 향상도는 독립 대비 연관성의 강도
'LG DX DATA SCHOOL' 카테고리의 다른 글
| 0129 목요일 추천시스템 (0) | 2026.01.30 |
|---|---|
| 01/27 NLP (0) | 2026.01.27 |
| 0123 군집분석 (0) | 2026.01.23 |
| 0122 시계열 분석 (1) | 2026.01.22 |
| 01/20 (1) | 2026.01.21 |