✳️ 고객 세그멘테이션
고객 세그멘테이션(Customer Segmentation)은 다양한 기준으로 고객을 분류하는 기법
CRM(고객 관계 관리, Customer Relationship Management) 분야가 비지도 학습이 가장 많이 사용돼서 해당 분야 데이터로 실습해보자!
데이터는 RFM을 기반으로 가공할 것이다.
💟 RFM의 개념
- Recency(R) : 가장 최근 구입 일에서 오늘까지의 시간
- Frequency(F) : 상품 구매 횟수
- Monetary value(M) : 총 구매 금액
✳️ 실습하기
1️⃣ 데이터 불러오기
# 엑셀 파일을 불러오기 위해 패키지 설치
! pip install openpyxl
retail_df = pd.read_excel('C:/Users/82109/OneDrive/문서/ML/Online Retail.xlsx')
retail_df.head()
retail_df.info()
- 컬럼 정보
- InvoiceNO : 6자리의 주문번호(취소된 주문은 c 로 시작)
- StockCode : 5자리의 제품 코드
- Description : 제품 이름(설명)
- Quantity : 주문 수량
- InvoiceDate : 주문 일자, 날짜 자료형
- UnitPrice : 제품 단가
- CustomerID : 5자리의 고객 번호
- Country : 국가명
2️⃣ EDA
- 결측치 확인
# 결측치 확인
retail_df.isnull().sum()
CustomerID의 결측치가 매우 많다..! 일단 저 컬럼의 결측치를 처리 해야한다.
- 기술 통계
retail_df.describe(include='all')
수량과 가격에 - (minus)값이 있다. 취소된 주문건으로 여겨진다.
cond1 = retail_df['Quantity'] < 0
retail_df[cond1]
수량이 0이하인 것을 봤을 때 InvoiceNo가 C로 시작하는데 이게 취소건을 의미하는 것이라고 한다.
3️⃣ 데이터 전처리
- CustomerID 결측치인 것들 삭제
- 취소건 삭제
InvoiceNo(C로 시작)
UnitPrice(음수 존재), Quantity(음수 존재) 음수 데이터 삭제 - 영국 데이터만 이용(국가에 영국이 최빈값)
- 결측치 제거
cond_cust = (retail_df['CustomerID'].notnull())
retail_df[cond_cust].isnull().sum()
- 취소건에 대한 데이터 삭제
# InvoiceNo가 C로 시작하지 않는 것
cond_invo = (retail_df['InvoiceNo'].astype(str).str[0] != 'C')
# 음수데이터 제거
cond_minus = (retail_df['Quantity'] > 0) & (retail_df['UnitPrice'] > 0)
retail_df_2 = retail_df[cond_cust & cond_invo & cond_minus]
retail_df_2.info()
데이터가 5만개대에서 3만개대로 변경됨
- 영국 데이터만 가져오기
cond_uk = (retail_df_2['Country'] == 'United Kingdom')
retail_df_2 = retail_df_2[cond_uk]
4️⃣ RFM 기반 데이터 가공
- Monetary(M)
- 왜 컬럼명을 Monetary라고 안했냐면 추후에 rfm_df를 따로 만들어서 거기에 추가할 것이기 때문에 지금 데이터셋에는 다른 이름인 Amt(Amount)로 저장하였다.
# Monetary value(M) : 총 구매 금액
retail_df_2['Amt'] = retail_df_2['Quantity'] * retail_df_2['UnitPrice']
# 정수로 변환
retail_df_2['Amt'] = retail_df_2['Amt'].astype('int')
- Recency(R)
- Recency(R) : 최근 구입일에서 오늘까지의 시간 -> describe()를 통해 가장 최근 구매한 사람의 날짜를 max행에서 확인 함.
- 2011-12-09가 가장 마지막 구매인 것으로 나타나서 (2012-11-10) - (구매일자)를 해줘야 함.
retail_df_2.describe(include='all')
- Recency(R)를 구하기 위한 날짜 데이터 가공
- 2011-12-10 기준으로 각 날짜를 빼고 + 1
- 추후 CustomerID 기준으로 Priod의 최소 Priod를 구하면 그것이 Recency
- 예를 들면 1번 고객의 경우 100일전 구매, 50일전 구매, 5일전 구매 라는 결과가 나왔으면 5일전(최소 Priod) 구매한 날짜가 Recency
import datetime as dt
retail_df_2['Period'] = (dt.datetime(2011,12,10) - retail_df_2['InvoiceDate']).apply(lambda x: x.days + 1)
- 데이터 재확인
- 만들어둔 것들이 데이터프레임에 들어가 있는지 확인
retail_df_2.head(3)
- 새로운 데이터셋 생성
rfm_df = retail_df_2.groupby('CustomerID').agg({
'Period' : 'min',
'InvoiceNo' : 'count', # 상품구매 횟수여서 count로 빈도 계산
'Amt' : 'sum' # 고객아이디별로 총 금액 계산
})
# 컬럼 이름 예쁘게 바꿀거얌
rfm_df.columns = ['Recency', 'Frequency', 'Monetary']
rfm_df
원하던 형태의 새로운 데이터프레임 완셩!✨
5️⃣ 데이터 분포 확인
- 데이터의 분포를 확인해보면 데이터가 굉장히 치우쳐져 있기 때문에 학습하기 어려울 것으로 나타났다.
- 그래서 정규화가 필요함!
sns.histplot(rfm_df['Recency'])
sns.histplot(rfm_df['Frequency'])
sns.histplot(rfm_df['Monetary'])
6️⃣ 데이터 정규화
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_features = sc.fit_transform(rfm_df[['Recency', 'Frequency', 'Monetary']])
7️⃣ 모델 수립 및 평가
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# 모델 훈련 및 예측
kmeans = KMeans(n_clusters = 3, random_state = 42)
labels = kmeans.fit_predict(X_features)
rfm_df['label'] = labels
# 평가
silhouette_score(X_features, labels)
나쁘지 않은 수치이지만 시각화를 통해 정말 괜찮은지 알아보자
- 시각화하여 알아보기 (튜터님께서 미리 주신 kmeans_visual의 visualize_silhouette를 이용함)
from kmeans_visual import visualize_silhouette
# 첫 번째 전달인자는 cluster 개수, 두 번째 전달인자로는 변수를 받게 설계해두심
visualize_silhouette([2, 3, 4, 5, 6], X_features)
- 해석
- cluster가 2개일 때, sihouette_score : 0.567
- cluster가 3개일 때, sihouette_score : 0.593
- cluster가 4개일 때, sihouette_score : 0.596
- cluster가 5개일 때, sihouette_score : 0.617
- cluster가 6개일 때, sihouette_score : 0.594
🤔 생각
cluster가 5개일 때가 점수가 가장 높으니 clsuter를 5개로 해야할까? Nooooop!
그래프를 보면 군집이 5개로 잘 나뉘어서 표현이 되어야 했는데 2, 3, 4가 몰려 있는 형태로 좋은 형태를 갖추고 있지 않았다.
이 경우에는 스케일링을 했으 값이 너무 치우친 상황이므로 추가적으로 변환이 필요하다.
8️⃣ 추가 전처리
- log scale은 numpy를 이용하였다.
# log 스케일을 통한 추가 전처리
import numpy as np
rfm_df['Recency_log'] = np.log1p(rfm_df['Recency'])
rfm_df['Frequency_log'] = np.log1p(rfm_df['Frequency'])
rfm_df['Monetary_log'] = np.log1p(rfm_df['Monetary'])
9️⃣ 재정규화와 모델 수립 및 평가
# X_features도 재할당
X_features2 = rfm_df[['Recency_log', 'Frequency_log', 'Monetary_log']]
# 재정규화
sc2 = StandardScaler()
# 훈련 및 적용
X_features2_sc = sc2.fit_transform(X_features2)
# 시각화
visualize_silhouette([2, 3, 4, 5, 6], X_features2_sc)
🤔 생각
로그 스케일을 해주니 cluster가 잘 쪼개져 있었다.
그러나 이 중 어떤 cluster를 가져와서 써야할지는 모르는 일이다.
적절한 cluster 개수를 찾는 것은 해당 도메인에 걸맞는 기준을 가지고 기술통계 확인, 데이터 확인 하면서 판단해봐야 한다.
'📒 Today I Learn > 🤖 Machine Learning' 카테고리의 다른 글
[딥러닝] 간단한 실습 (0) | 2024.08.16 |
---|---|
[딥러닝] 딥러닝 이론 (0) | 2024.08.15 |
[머신러닝 심화] 비지도 학습 이론 (0) | 2024.08.14 |
[머신러닝 심화] 분류와 회귀 모델링 심화 실습 (0) | 2024.08.13 |
[머신러닝 심화] 분류와 회귀 모델링 심화 이론 (0) | 2024.08.13 |