LG DX DATA SCHOOL

01/26 DBSCAN

getfeelingsfrom 2026. 1. 27. 08:53

: 특정 공간 내에 데이터 밀도 차이를 기반 알고리즘으로 하고 있어 복합적 기하학적 분포도를 가진 데이터 세트에 대해서도 군집화를 잘 수행한다

  • 응집도 높음 : 집단 내의 분산 값은 낮아야 함
  • 결합도 낮음 : 집단 간의 분산 값은 높아야 함

파라미터

  • 입실론 주변 영역(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