온라인 판매 데이터를 기반으로 고객 세그멘테이션을 군집화 기반으로 수행해 보겠습니다.
하기 페이지에서 Data Folder를 클릭 후 엑셀 데이터 DataSet을 다운로드합니다. 이 데이터는 541만 Row의 데이터로 RFM Segmentation 실습을 하기에 좋습니다.
archive.ics.uci.edu/ml/datasets/online+retail#
Data Loding & Organization
우선 하기와 같이 엑셀을 로딩하기 위해서는 xlrd Library를 설치해야 합니다. 'pip install xlrd'로Library를설치한후실행합니다.
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
import pandas as pd
import datetime
import math
import numpy as np
import matplotlib.pyplot as plt
import Common_Module.Common_Module as CM
retailDF = pd.read_excel(io='C:\\Users\\HANA\\PycharmProjects\\HANATOUR\\ML_Learn\\OnlineRetail.xlsx')
print(retailDF.info())
retailDF = retailDF[retailDF['Quantity']>0]
retailDF = retailDF[retailDF['UnitPrice']>0]
retailDF = retailDF[retailDF['CustomerID'].notnull()]
print(retailDF.shape)
print(retailDF.isnull().sum())
print(retailDF['Country'].value_counts()[:5])
retailDF = retailDF[retailDF['Country']=='United Kingdom']
print(retailDF.shape)
'retailDF.info()'를 통해 데이터 세트의 전체 건수, 칼럼 타입, Null 개수를 확인하고, 총 54만 건 중 CustomerID가 Null인 값을 제외하고 데이터의 대부분을 차지하는 United Kingdom만 데이타 Set로 사용합니다.
#print(retailDF.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 541909 entries, 0 to 541908
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 InvoiceNo 541909 non-null object
1 StockCode 541909 non-null object
2 Description 540455 non-null object
3 Quantity 541909 non-null int64
4 InvoiceDate 541909 non-null datetime64[ns]
5 UnitPrice 541909 non-null float64
6 CustomerID 406829 non-null float64
7 Country 541909 non-null object
dtypes: datetime64[ns](1), float64(2), int64(1), object(4)
memory usage: 33.1+ MB
None
#print(retailDF.shape)
(397884, 8)
#print(retailDF.isnull().sum())
InvoiceNo 0
StockCode 0
Description 0
Quantity 0
InvoiceDate 0
UnitPrice 0
CustomerID 0
Country 0
dtype: int64
#print(retailDF['Country'].value_counts()[:5])
United Kingdom 354321
Germany 9040
France 8341
EIRE 7236
Spain 2484
Name: Country, dtype: int64
#print(retailDF.shape)
(354321, 8)
RFM DataSet 만들기
실제 사용할 RFM Data 정보를 만들게 됩니다. 하기 데이터를 이해하기 위해서는 lambda와 agg()의 사용법을 알아야 합니다.
retailDF['sale_amount'] = retailDF['Quantity'] * retailDF['UnitPrice']
retailDF['CustomerID'] = retailDF['CustomerID'].astype(int)
print(retailDF['CustomerID'].value_counts().head())
print(retailDF.groupby('CustomerID')['sale_amount'].sum().sort_values(ascending=False)[:5])
aggregations = {
'InvoiceDate': 'max',
'InvoiceNo': 'count',
'sale_amount': 'sum'
}
cust_df = retailDF.groupby('CustomerID').agg(aggregations)
cust_df = cust_df.rename(columns={'InvoiceDate':'Recency', 'InvoiceNo':'Frequency', 'sale_amount':'Monetary'})
cust_df = cust_df.reset_index()
cust_df['Recency'] = datetime.datetime(2021,10,2) - cust_df['Recency']
cust_df['Recency'] = cust_df['Recency'].apply(lambda x:x.days+1)
print(cust_df)
Row를 가장 많이 가지고 있는 CustomerID와 Sales_Amount가 가장 높은 순으로 정렬해 보다. 그 후 RFM값을 정리해서 보여준다.
#print(retailDF['CustomerID'].value_counts().head())
17841 7847
14096 5111
12748 4595
14606 2700
15311 2379
Name: CustomerID, dtype: int64
#print(retailDF.groupby('CustomerID')['sale_amount'].sum().sort_values(ascending=False)[:5])
CustomerID
18102 259657.30
17450 194550.79
16446 168472.50
17511 91062.38
16029 81024.84
Name: sale_amount, dtype: float64
#print(cust_df)
CustomerID Recency Frequency Monetary
0 12346 3910 1 77183.60
1 12747 3587 103 4196.01
2 12748 3585 4595 33719.73
3 12749 3588 199 4090.88
4 12820 3588 59 942.34
... ... ... ... ...
3915 18280 3862 10 180.60
3916 18281 3765 7 80.82
3917 18282 3592 12 178.05
3918 18283 3588 756 2094.88
3919 18287 3627 70 1837.28
[3920 rows x 4 columns]
다음은 RFM데이터에 대한 분포를 확인하기 위한 Desc와 분포도 그래프를 확인하면 데이터의 왜곡 정도가 매우 심하다는 것을 알 수 있습니다. 왜곡 정도가 매우 높은 데이타 세트에 K-Means Cluster를 적용하면 중심의 개수를 증가시키더라도 변별력이 떨어지는 군집화가 수행됩니다.
print(cust_df[['Recency','Frequency', 'Monetary']].describe())
fig, (ax1, ax2, ax3) = plt.subplots(figsize=(12,4), nrows=1, ncols=3)
ax1.set_title('Recency')
ax1.hist(cust_df['Recency'])
ax2.set_title('Frequency')
ax2.hist(cust_df['Frequency'])
ax3.set_title('Monetary')
ax3.hist(cust_df['Monetary'])
plt.show()
#print(cust_df[['Recency','Frequency', 'Monetary']].describe())
Recency Frequency Monetary
count 3920.000000 3920.000000 3920.000000
mean 3676.742092 90.388010 1864.385601
std 99.533485 217.808385 7482.817477
min 3585.000000 1.000000 3.750000
25% 3602.000000 17.000000 300.280000
50% 3635.000000 41.000000 652.280000
75% 3727.000000 99.250000 1576.585000
max 3958.000000 7847.000000 259657.300000
K-Means Cluster 적용
먼저 데이터 세트를 StandardScaler로 평균과 표준편차를 재조정 한 뒤에 K-Means Cluster를 수행해 보겠습니다. 결과를 보면 Silhoutte Score값이 0.59로 나쁘지 않습니다. 하지만 초기 데이터 자체가 왜곡이 매우 심해 Cluster를 높이는 것이 의미가 없어 보입니다.
X_feature = cust_df[['Recency', 'Frequency', 'Monetary']].values
X_feature_scaled = StandardScaler().fit_transform(X_feature)
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict((X_feature_scaled))
cust_df['cluster_label'] = labels
print(silhouette_score(X_feature_scaled, labels))
CM.visualize_silhouette([2,3,4,5], X_feature_scaled)
#print(silhouette_score(X_feature_scaled, labels))
0.5924537813510862
데이터 세트의 왜곡 정도를 낮추기 위해 가장 자주 사용되는 방법은 데이터 값에 로그(Log)를 적용하는 로그 변환입니다. 실루엣 스코어는 이전보다 낮은 0.3이 되었지만 절대치는 중요하지 않습니다. 어떻게 개별 군집이 더 균일하게 나뉠 수 있는지가 더 중요합니다.
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']))
X_feature_log = cust_df[['Recency_log', 'Frequency_log', 'Monetary_log']].values
X_feature_scaled_log = StandardScaler().fit_transform(X_feature_log)
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict((X_feature_scaled_log))
cust_df['cluster_label_log'] = labels
print(silhouette_score(X_feature_scaled_log, labels))
CM.visualize_silhouette([2,3,4,5], X_feature_scaled_log)
#print(silhouette_score(X_feature_scaled_log, labels))
0.38063927452834523
'ML with SckitLearn' 카테고리의 다른 글
train_test_split()의 사용과 교차 검증 cross_val_score 이용하기 (0) | 2020.10.18 |
---|---|
선형 회귀 분석 - LinearRegression 및 보스턴 주택 가격 회귀 구현 (0) | 2020.10.18 |
머신러닝에서의 회귀 분석 : 소개, 단순선형회귀, 경사 하강법 (0) | 2020.10.17 |
StandardScaler : 피처스케일링 정규화 (1) | 2020.10.03 |
[DBSCAN] 밀도기반 군집화 Cluster Algorithm (0) | 2020.10.01 |
Estimator의 이해와 fit(), predict(), accuracy_score() Method (0) | 2020.09.30 |
[GMM] Gaussian Mixture Model 개요에 대해 (0) | 2020.09.29 |
실루엣 분석(Silhouette Analysis) : Clustering 적절성 분석 (0) | 2020.09.27 |