머신러닝 - 고객 세그먼테이션

류소리·2022년 10월 21일
0

머신러닝

목록 보기
11/14

[파이썬 머신러닝 완벽 가이드- 개정2판]

고객 세그먼테이션, customer segmentation (p474)

# 데이터 세트 로딩과 데이터 클렌징

import pandas as pd
import datetime
import math
import numpy as np
import matplotlib.pyplot as plt

retail_df = pd.read_excel("/content/drive/MyDrive/book/Online R.xlsx")
retail_df.head(3)

retail_df.info()

  • 데이터는 541,909 x 8로 이루어져 있다.

  • Description, CustomerID는 결측이 존재하며, CustomerID는 약 13만 건 정도의 결측값이 존재한다.

  • 여기선 고객 세그먼테이션을 수행하므로 CustomerID가 결측이면 모두 제거한다.

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()

  • 앞서 설정한 조건으로 데이터를 삭제하여 397,884건만 남겨두었다.
  • 더불어서 Description의 결측값도 모두 삭제되었다.
retail_df["Country"].value_counts()[:5]

  • 주문 국적은 전체 데이터의 대부분이 영국으로 나타나 영국만 남기기로 한다.
retail_df = retail_df[retail_df["Country"]=="United Kingdom"]
print(retail_df.shape)

retail_df["sale_amount"]= retail_df["Quantity"]*retail_df["UnitPrice"]
retail_df["CustomerID"] = retail_df["CustomerID"].astype(int)
print(retail_df["CustomerID"].value_counts().head(5))
print(retail_df.groupby("CustomerID")["sale_amount"].sum().sort_values(ascending = False)[:5])

  • Quantity(주문 제품 건수)와 UnitPrice(제품 단가)를 곱하여 sale_amount(주문 금액)을 생성하였다.
  • CustomerID는 기존 float형을 int형으로 바꿔주었다.
retail_df.groupby(["InvoiceNo","StockCode"])["InvoiceNo"].count().mean()

  • 주문번호 + 상품코드 갯수의 평균은 거의 1로서 주문번호 + 상품코드는 식별자 레벨로 사용 가능하다.
  • 다만 여기선 RFM 기반의 고객 세그먼테이션으로 고객 레벨로 분석을 해야한다.
  • 따라서, 주문번호 + 상품코드 기준 데이터를 고객 기준의 RFM 데이터로 변경한다.
from pandas.core.frame import Frequency
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)

  • RFM 데이터를 만들기 위해 기존 컬럼을 agg()를 활용해서 각각 다른 연산을 수행하였다.
  • Recency의 경우 현재 최근 구입 날짜로 변경해서 한번 더 작업이 필요하다.
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)

  • 지금까지의 가공으로 고객 기준의 RFM 데이터는 3,920 x 4로 이루어져있다.
  • 현재 사용하는 데이터는 2010년 12월 1일 ~ 2011년 12월 9일까지의 데이터이다.
  • 따라서 Recency를 구할 때 오늘까지의 기간은 2011년 12월 10일까지의 기간으로 계산하였다.
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"])
plt.show()

  • cust_df의 R,F,M의 분포를 확인하였다.

  • 모두 치우친 분포로 나타났으며 특히, F와 M은 매우 심하게 치우쳐있다.

  • 이는 이 데이터가 개인 고객과 소매업체의 대규모 주문이 포함되어 있어 나타난 현상이다.

cust_df[["Recency","Frequency","Monetary"]].describe()

  • 각 컬럼들의 평균은 중앙값보다 훨씬 크게 나타나며, 75% 분위수에 비해 최대값이 훨씬 크다.

  • 왜곡이 심한 데이터를 이용해서 군집분석을 진행해보자.

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)))

  • KMeans 군집분석을 진행하였고 피처는 표준화하였다.

  • 실루엣 스코어가 0.592로 제법 좋은 수치로 나타났다.

  • 각 군집별 실루엣 스코어를 확인해보자.

[실루엣 계수 및 군집 시각화 함수는 교재 소스코드를 사용하였다.]

< def visualize_silhouette > : 실루엣 계수 시각화 함수

### 여러개의 클러스터링 갯수를 List로 입력 받아 각각의 클러스터링 결과를 시각화 
def visualize_silhouette(cluster_lists, X_features):     
    # 입력값으로 클러스터링 갯수들을 리스트로 받아서, 각 갯수별로 클러스터링을 적용하고 실루엣 개수를 구함
    n_cols = len(cluster_lists)
   
    # plt.subplots()으로 리스트에 기재된 클러스터링 수만큼의 sub figures를 가지는 axs 생성 
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)
   
    # 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 실루엣 개수 시각화
    for ind, n_cluster in enumerate(cluster_lists):
      
        # KMeans 클러스터링 수행하고, 실루엣 스코어와 개별 데이터의 실루엣 값 계산. 
        clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
        cluster_labels = clusterer.fit_predict(X_features)
     
        sil_avg = silhouette_score(X_features, cluster_labels)
        sil_values = silhouette_samples(X_features, cluster_labels)
    
        y_lower = 10
        axs[ind].set_title('Number of Cluster : '+ str(n_cluster)+'\n' \
                          'Silhouette Score :' + str(round(sil_avg,3)) )
        axs[ind].set_xlabel("The silhouette coefficient values")
        axs[ind].set_ylabel("Cluster label")
        axs[ind].set_xlim([-0.1, 1])
        axs[ind].set_ylim([0, len(X_features) + (n_cluster + 1) * 10])
        axs[ind].set_yticks([])  # Clear the yaxis labels / ticks
        axs[ind].set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1])
       
        # 클러스터링 갯수별로 fill_betweenx( )형태의 막대 그래프 표현. 
        for i in range(n_cluster):
            ith_cluster_sil_values = sil_values[cluster_labels==i]
            ith_cluster_sil_values.sort()
          
            size_cluster_i = ith_cluster_sil_values.shape[0]
            y_upper = y_lower + size_cluster_i
         
            color = cm.nipy_spectral(float(i) / n_cluster)
            axs[ind].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, \
                                facecolor=color, edgecolor=color, alpha=0.7)
            axs[ind].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
            y_lower = y_upper + 10
         
        axs[ind].axvline(x=sil_avg, color="red", linestyle="--")

< def visualize_kmeans_plot_multi > : 군집갯수별 시각화 함수

### 여러개의 클러스터링 갯수를 List로 입력 받아 각각의 클러스터링 결과를 시각화 
def visualize_kmeans_plot_multi(cluster_lists, X_features):
   
    from sklearn.cluster import KMeans
    from sklearn.decomposition import PCA
    import pandas as pd
    import numpy as np
   
    # plt.subplots()으로 리스트에 기재된 클러스터링 만큼의 sub figures를 가지는 axs 생성 
    n_cols = len(cluster_lists)
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)
   
    # 입력 데이터의 FEATURE가 여러개일 경우 2차원 데이터 시각화가 어려우므로 PCA 변환하여 2차원 시각화
    pca = PCA(n_components=2)
    pca_transformed = pca.fit_transform(X_features)
    dataframe = pd.DataFrame(pca_transformed, columns=['PCA1','PCA2'])
   
     # 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 KMeans 클러스터링 수행하고 시각화
    for ind, n_cluster in enumerate(cluster_lists):
      
        # KMeans 클러스터링으로 클러스터링 결과를 dataframe에 저장. 
        clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
        cluster_labels = clusterer.fit_predict(pca_transformed)
        dataframe['cluster']=cluster_labels
      
        unique_labels = np.unique(clusterer.labels_)
        markers=['o', 's', '^', 'x', '*']
    
        # 클러스터링 결과값 별로 scatter plot 으로 시각화
        for label in unique_labels:
            label_df = dataframe[dataframe['cluster']==label]
            if label == -1:
                cluster_legend = 'Noise'
            else :
                cluster_legend = 'Cluster '+str(label)           
            axs[ind].scatter(x=label_df['PCA1'], y=label_df['PCA2'], s=70,\
                        edgecolor='k', marker=markers[label], label=cluster_legend)

        axs[ind].set_title('Number of Cluster : '+ str(n_cluster))    
        axs[ind].legend(loc='upper right')
  
    plt.show()
visualize_silhouette([2,3,4,5],X_features_scaled)
visualize_kmeans_plot_multi([2,3,4,5],X_features_scaled)

  • 군집수가 2일 때는 0번 군집의 실루엣 계수가 대부분 평균 아래이다.
  • 군집수가 3인 경우는 2번 군집, 4인 경우는 2,3번 군집의 데이터 건수가 매우 작다.
  • 이는 앞서 왜곡된 분포를 확인했듯이 소매업체의 대규모 주문이 소수의 군집을 이룬것이다.
  • 이렇게 특이한 데이터 셋을 분리하는 것이 군집화의 목표 중 하나이다.
  • 다만 이 경우 왜곡정도가 너무 심해 굳이 군집분석하지 않아도 충분히 분리 가능하다.
  • 실제 이런 자료를 다루는 도메인이 있다면 이런 결과를 미리 알았을 것이다.
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score ,silhouette_samples

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_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)))

  • 이번엔 왜곡 정도를 낮추기 위해 피처 로그 변환을 적용하였다.
  • 나머지 과정은 동일하게 수행하였을 때 실루엣 스코어는 0.303으로 낮게 나타났다.
visualize_silhouette([2,3,4,5],X_features_scaled)
visualize_kmeans_plot_multi([2,3,4,5],X_features_scaled)  

  • 전체 실루엣 스코어가 감소한 대신 각 군집별 실루엣 스코어가 균일하게 나타났다.
  • 이처럼 왜곡된 데이터 셋은 로그 변환으로 1차 변환 후 적용하면 더 나은 결과가 나오기도 한다.
  • 참고로 위 시각화 코드를 자세히 보면 실루엣 스코어에서의 군집은 입력한 피처를 사용한다.
  • 반면 군집 시각화 함수는 입력한 피처를 받아 PCA 적용 후 군집을 만든다.

자료보충 출처:
https://romg2.github.io/mlguide/01_%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-07.-%EA%B5%B0%EC%A7%91%ED%99%94-%EC%8B%A4%EC%8A%B5/

profile
새싹 빅테이터 개발자

0개의 댓글