본문 바로가기
[코드쉐도잉]/산업

[RFM & K-means] Brazilian E-Commerce Public Dataset by Olist

by 에디터 윤슬 2025. 1. 6.

캐글 링크

https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce

 

Brazilian E-Commerce Public Dataset by Olist

100,000 Orders with product, customer and reviews info

www.kaggle.com

 

https://www.kaggle.com/code/drindeng/detailed-rfm-analysis-and-k-means-clustering

 

Detailed RFM Analysis and K-Means Clustering

Explore and run machine learning code with Kaggle Notebooks | Using data from Brazilian E-Commerce Public Dataset by Olist

www.kaggle.com

 

 

데이터 전처리

  • 라이브러리 임포트
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
warnings.filterwarnings("ignore")

import datetime as dt

customers_df= pd.read_csv('olist_customers_dataset.csv')
geolocation_df= pd.read_csv("olist_geolocation_dataset.csv")
items_df= pd.read_csv('olist_order_items_dataset.csv')
payments_df= pd.read_csv('olist_order_payments_dataset.csv')
reviews_df= pd.read_csv('olist_order_reviews_dataset.csv')
orders_df= pd.read_csv('olist_orders_dataset.csv')
products_df= pd.read_csv('olist_products_dataset.csv')
sellers_df= pd.read_csv('olist_sellers_dataset.csv')
category_translation_df= pd.read_csv('product_category_name_translation.csv')

 

  • NULL 값 및 중복값 확인
# 데이터프레임 리스트와 이름 정의
datasets = [customers_df, geolocation_df, items_df, payments_df, reviews_df, orders_df, products_df, sellers_df, category_translation_df]
titles = ["customers", "geolocation", "items", "payments", "reviews", "orders", "products", "sellers", "category_translation"]

	•	`datasets`: 분석할 데이터프레임들을 리스트로 저장.
	•	`titles`: 각 데이터프레임의 이름을 저장한 리스트.

# `data_summary` 데이터프레임 생성:
data_summary = pd.DataFrame({},)

	•	데이터프레임의 요약 정보를 정리한 새로운 데이터프레임을 생성합니다.
    
# 요약 정보 컬럼 생성
data_summary['datasets']= titles
data_summary['columns'] = [', '.join([col for col, null in data.isnull().sum().items() ]) for data in datasets]

	•	`datasets`: 데이터프레임의 이름.
	•	`columns`: 각 데이터프레임의 컬럼 이름을 쉼표로 구분하여 나열.
    
# 각 데이터프레임의 총 행 수
data_summary['total_rows']= [data.shape[0] for data in datasets]

# 각 데이터프레임의 총 열 수
data_summary['total_cols']= [data.shape[1] for data in datasets]

# 중복된 행의 개수
data_summary['total_duplicate']= [len(data[data.duplicated()]) for data in datasets]

# 결측값의 총 개수
data_summary['total_null']= [data.isnull().sum().sum() for data in datasets]

# 결측값이 존재하는 컬럼 이름을 쉼표로 구분하여 나열
data_summary['null_cols'] = [', '.join([col for col, null in data.isnull().sum().items() if null > 0]) for data in datasets]

# 시각적 스타일링
data_summary.style.background_gradient(cmap='YlGnBu')

 

 

  • 결측값 및 중복행 제거
# 결측값 제거
for i in datasets:
    i.dropna(inplace=True)
    
# 중복행 제거    
for i in datasets:
    i.drop(i[i.duplicated()].index, axis=0, inplace=True)

 

  • 잘 지워졌는지 확인
data_summary = pd.DataFrame({},)
data_summary['datasets']= titles
data_summary['columns'] = [', '.join([col for col, null in data.isnull().sum().items() ]) for data in datasets]
data_summary['total_rows']= [data.shape[0] for data in datasets]
data_summary['total_cols']= [data.shape[1] for data in datasets]
data_summary['total_duplicate']= [len(data[data.duplicated()]) for data in datasets]
data_summary['total_null']= [data.isnull().sum().sum() for data in datasets]
data_summary['null_cols'] = [', '.join([col for col, null in data.isnull().sum().items() if null > 0]) for data in datasets]
data_summary.style.background_gradient(cmap='YlGnBu')

 

 

  • 데이터셋 info 확인
for i in datasets:
    i.info()
    print("*"*50)
    
    
>>>

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 99441 entries, 0 to 99440

Data columns (total 5 columns):

 #   Column                    Non-Null Count  Dtype 

---  ------                    --------------  ----- 

 0   customer_id               99441 non-null  object

 1   customer_unique_id        99441 non-null  object

 2   customer_zip_code_prefix  99441 non-null  int64 

 3   customer_city             99441 non-null  object

 4   customer_state            99441 non-null  object

dtypes: int64(1), object(4)

memory usage: 3.8+ MB

**************************************************

<class 'pandas.core.frame.DataFrame'>

Int64Index: 738332 entries, 0 to 1000161

Data columns (total 5 columns):

 #   Column                       Non-Null Count   Dtype  

---  ------                       --------------   -----  

 0   geolocation_zip_code_prefix  738332 non-null  int64  

 1   geolocation_lat              738332 non-null  float64

 2   geolocation_lng              738332 non-null  float64

 3   geolocation_city             738332 non-null  object 

 4   geolocation_state            738332 non-null  object 

dtypes: float64(2), int64(1), object(2)

memory usage: 33.8+ MB

**************************************************

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 112650 entries, 0 to 112649

Data columns (total 7 columns):

 #   Column               Non-Null Count   Dtype  

---  ------               --------------   -----  

 0   order_id             112650 non-null  object 

 1   order_item_id        112650 non-null  int64  

 2   product_id           112650 non-null  object 

 3   seller_id            112650 non-null  object 

 4   shipping_limit_date  112650 non-null  object 

 5   price                112650 non-null  float64

 6   freight_value        112650 non-null  float64

dtypes: float64(2), int64(1), object(4)

memory usage: 6.0+ MB

**************************************************

 

 

  • merge all dataframe
merged_df= pd.merge(customers_df, orders_df, on="customer_id")
merged_df= merged_df.merge(reviews_df, on="order_id")
merged_df= merged_df.merge(items_df, on="order_id")
merged_df= merged_df.merge(products_df, on="product_id")
merged_df= merged_df.merge(payments_df, on="order_id")
merged_df= merged_df.merge(sellers_df, on='seller_id')
merged_df= merged_df.merge(category_translation_df, on='product_category_name')
merged_df.shape

>>> (11578, 40)

 

 

RFM Analysis(recency, frequency, monetary)

  • 마지막 시간을 데이터 기준 2일 후로 설정
present_day = merged_df['order_purchase_timestamp'].max() + dt.timedelta(days=2)
present_day

>>> Timestamp('2018-08-31 14:18:28')

print("Latest date in dataset: ", merged_df['order_purchase_timestamp'].max())
>>> Latest date in dataset:  2018-08-29 14:18:28

 

  • Recency 값 정의
recency_df= pd.DataFrame(merged_df.groupby(by='customer_unique_id', as_index=False)['order_purchase_timestamp'].max())
recency_df

 

recency_df['Recency']= recency_df['order_purchase_timestamp'].apply(lambda x: (present_day - x).days)
recency_df

 

  • Frequency 값 정의
frequency_df = pd.DataFrame(merged_df.groupby(["customer_unique_id"]).agg({"order_id":"nunique"}).reset_index())
frequency_df.rename(columns={"order_id":"Frequency"}, inplace=True)
frequency_df

 

  • Monetary 값 정의
monetary_df = merged_df.groupby('customer_unique_id', as_index=False)['payment_value'].sum()
monetary_df.columns = ['customer_unique_id', 'Monetary']
monetary_df.head()

 

  • RFM merge
RF_df = recency_df.merge(frequency_df, on='customer_unique_id')
RFM_df = RF_df.merge(monetary_df, on='customer_unique_id').drop(columns='order_purchase_timestamp')
RFM_df.head()

 

RFM_df.describe().T

  • RFM 시각화
plt.figure(figsize=(12, 10))
plt.subplot(3, 1, 1); sns.distplot(RFM_df['Recency'])
plt.subplot(3, 1, 2); sns.distplot(RFM_df['Frequency'])
plt.subplot(3, 1, 3); sns.distplot(RFM_df['Monetary'])
plt.show()

 

 

  • 이상치 탐지
for i in ["Recency", "Frequency", "Monetary"]:
    plt.figure()
    plt.tight_layout()
    plt.gca().set(xlabel= i, ylabel='Frequency')
    plt.boxplot(RFM_df[i])

  • +/- 5% 경계에 속하는 이상값을 확인
# 함수 설정

def num_outlier(df_in, col_name):
    q1 = df_in[col_name].quantile(0.05)
    q3 = df_in[col_name].quantile(0.95)
    iqr = q3-q1 #Interquartile range
    fence_low  = q1-1.5*iqr
    fence_high = q3+1.5*iqr
    outliers_df= df_in.loc[(df_in[col_name] < fence_low) | (df_in[col_name] > fence_high)]
    return print("Number of outliers in {} column: ".format(col_name), len(outliers_df)), print("Indexes: ", outliers_df.index)
    
# 실행
for i in ["Recency", "Frequency", "Monetary"]:
    num_outlier(RFM_df, i)
    print("*"*40)
    
Number of outliers in Recency column:  3

Indexes:  Int64Index([828, 2463, 2476], dtype='int64')

****************************************

Number of outliers in Frequency column:  99

Indexes:  Int64Index([  40,   96,  110,  138,  185,  238,  332,  335,  373,  508,  551,

             703,  874, 1093, 1234, 1333, 1428, 1500, 1739, 1786, 2146, 2156,

            2312, 2397, 2402, 2829, 2852, 2899, 2925, 2971, 3084, 3117, 3166,

            3668, 3740, 3824, 3849, 3928, 3938, 4102, 4113, 4221, 4235, 4292,

            4332, 4412, 4442, 4524, 4590, 4681, 4689, 4858, 4912, 5079, 5382,

            5412, 5500, 5672, 5763, 5900, 5945, 6005, 6083, 6090, 6108, 6172,

            6198, 6274, 6389, 6455, 6591, 6694, 6774, 6792, 7160, 7222, 7541,

            7590, 7651, 7763, 7807, 7982, 8085, 8151, 8257, 8417, 8428, 8609,

            8719, 8768, 8786, 8864, 8906, 8951, 8996, 9074, 9185, 9186, 9279],

           dtype='int64')

****************************************

Number of outliers in Monetary column:  100

Indexes:  Int64Index([  64,   92,   95,  110,  206,  223,  228,  329,  338,  684,  773,

             803,  923,  932,  982, 1038, 1072, 1168, 1281, 1312, 1389, 1506,

            1820, 1952, 2025, 2055, 2061, 2253, 2271, 2290, 2508, 2531, 2581,

            2608, 2680, 2697, 2799, 2855, 2871, 2907, 3308, 3325, 3574, 3605,

            3648, 3650, 3853, 4016, 4106, 4203, 4313, 4340, 4777, 4829, 4913,

            4921, 4984, 5050, 5093, 5094, 5108, 5224, 5249, 5325, 5329, 5362,

            5372, 5380, 5434, 5496, 5563, 5723, 5752, 5784, 5910, 5972, 6093,

            6104, 6379, 6575, 6590, 6909, 6971, 7065, 7228, 7314, 7315, 7391,

            7685, 8000, 8429, 8457, 8470, 8694, 8797, 8842, 9066, 9169, 9246,

            9317],

           dtype='int64')

****************************************

 

  • 이상값 제거
def remove_outlier(df_in, col_name):
    q1 = df_in[col_name].quantile(0.05)
    q3 = df_in[col_name].quantile(0.95)
    iqr = q3-q1     
    fence_low  = q1-1.5*iqr
    fence_high = q3+1.5*iqr
    index_outliers= df_in.loc[(df_in[col_name] < fence_low) | (df_in[col_name] > fence_high)].index
    df_in= pd.DataFrame(df_in.drop(index_outliers.to_list(), axis=0, inplace=True))
    print("Outliers in the {} column have been removed".format(col_name))
    return df_in
    
    
for i in ["Recency", "Monetary"]:
    remove_outlier(RFM_df, i)
    print("*"*40)
    
    
Outliers in the Recency column have been removed

****************************************

Outliers in the Monetary column have been removed

****************************************

 

Creating RFM Segments

RFM_df2= RFM_df.copy()
RFM_df2= RFM_df2.set_index('customer_unique_id')
RFM_df2

 

  • RFM 점수 계산
RFM_df2["recency_score"]  = pd.qcut(RFM_df2['Recency'], 5, labels=[5, 4, 3, 2, 1])
RFM_df2["frequency_score"]= pd.qcut(RFM_df2['Frequency'].rank(method="first"), 5, labels=[1, 2, 3, 4, 5])
RFM_df2["monetary_score"] = pd.qcut(RFM_df2['Monetary'], 5, labels=[1, 2, 3, 4, 5])

•`pd.qcut()`:
	•데이터를 동일한 크기의 5개 구간으로 나눕니다(5분위).
	•`labels=`: 각 분위에 대해 점수를 부여합니다.
	•`Recency`가 작을수록 높은 점수(`5`), 클수록 낮은 점수(`1`).
		•	예제 데이터 결과:
		•	`Recency = 10`: 가장 최근에 구매 → 점수 `5`.
		•	`Recency = 50`: 가장 오래 전에 구매 → 점수 `1`.
        
•	`pd.qcut()`:
	•데이터를 동일한 크기의 5개 구간으로 나눕니다.
	•`rank(method="first")`:
	•`Frequency` 값을 순위로 변환합니다.
	•순위는 값이 같을 경우 데이터의 순서에 따라 결정됩니다.
	•점수 부여:
	•`labels=`: 빈도가 작을수록 낮은 점수(`1`), 클수록 높은 점수(`5`).
		•	예제 데이터 결과:
		•	`Frequency = 1`: 가장 적은 구매 횟수 → 점수 `1`.
		•	`Frequency = 5`: 가장 많은 구매 횟수 → 점수 `5`.
        
•`pd.qcut()`:
	•데이터를 동일한 크기의 5개 구간으로 나눕니다.
	•점수 부여:
	•`labels=`: 지출 금액이 적을수록 낮은 점수(`1`), 많을수록 높은 점수(`5`).
		•	예제 데이터 결과:
		•	`Monetary = 100`: 가장 적은 금액 → 점수 `1`.
		•	`Monetary = 500`: 가장 많은 금액 → 점수 `5`.

 

  • RFM scores 계산
RFM_df2['RFM_SCORE'] = RFM_df2.recency_score.astype(str)+ RFM_df2.frequency_score.astype(str) + RFM_df2.monetary_score.astype(str)
RFM_df2

	•	`recency_score`, `frequency_score`, `monetary_score`:
	•	각각의 점수는 1~5 사이의 값으로 되어 있습니다.
	•	이 값을 문자열(`str`)로 변환하여 결합합니다.
	•	`+` 연산자:
	•	문자열을 이어붙이는 역할을 합니다.
	•	예를 들어, `recency_score=5`, `frequency_score=4`, `monetary_score=3`이면, 결합 결과는 `"543"`이 됩니다.
	•	결과적으로, 각 고객은 세 자리 숫자로 된 고유한 RFM 점수를 갖게 됩니다.

 


Source: https://documentation.bloomreach.com/engagement/docs/rfm-segmentation

 

Enhance customer loyalty and revenue with RFM segmentation

Learn how to use RFM segmentation to gain insights into customer loyalty and revenue. Discover best practices and deployment steps.

documentation.bloomreach.com

 

  • 고객 segmentaion
seg_map= {
    r'111|112|121|131|141|151': 'Lost customers',
    r'332|322|233|232|223|222|132|123|122|212|211': 'Hibernating customers', 
    r'155|154|144|214|215|115|114|113': 'Cannot Lose Them',
    r'255|254|245|244|253|252|243|242|235|234|225|224|153|152|145|143|142|135|134|133|125|124': 'At Risk',
    r'331|321|312|221|213|231|241|251': 'About To Sleep',
    r'535|534|443|434|343|334|325|324': 'Need Attention',
    r'525|524|523|522|521|515|514|513|425|424|413|414|415|315|314|313': 'Promising',
    r'512|511|422|421|412|411|311': 'New Customers',
    r'553|551|552|541|542|533|532|531|452|451|442|441|431|453|433|432|423|353|352|351|342|341|333|323': 'Potential Loyalist',
    r'543|444|435|355|354|345|344|335': 'Loyal',
    r'555|554|544|545|454|455|445': 'Champions'
}

 

RFM_df2['Segment'] = RFM_df2['recency_score'].astype(str) + RFM_df2['frequency_score'].astype(str) + RFM_df2['monetary_score'].astype(str)
RFM_df2['Segment'] = RFM_df2['Segment'].replace(seg_map, regex=True)
RFM_df2.head()

 

  • RFM Stats 확인
RFMStats = RFM_df2[["Segment", "Recency", "Frequency", "Monetary"]].groupby("Segment").agg(['mean','median', 'min', 'max', 'count'])
RFMStats

 

  • RFM ratio 확인
RFMStats['Ratio']= (100*RFMStats['Monetary']["count"]/RFMStats['Monetary']["count"].sum()).round(2)
RFMStats

 

  • RFM 시각화
plt.figure(figsize=(20,8))
#plt.rc('font', size=20)
per= sns.barplot(x=RFMStats['Ratio'], y=RFMStats.index, data=RFMStats, palette="viridis")
sns.despine(bottom = True, left = True)
for i, v in enumerate(RFMStats['Ratio']):
    per.text(v, i+.20,"  {:.2f}".format(v)+"%", color='black', ha="left")
per.set_ylabel('Segments', fontsize=25)
per.set(xticks=[])
plt.title('Distribution of Segments', fontsize=35)
plt.show()

 

  • 트리맵 시각화
# !pip3 install squarify
import squarify

# Treemap by recency/frequency
plt.figure(figsize=(15,8))
plt.rc('font', size=15)
squarify.plot(sizes=RFMStats["Recency"]["count"], label=RFMStats.index, 
              color=["red","orange","blue", "forestgreen", "yellow", "purple", "cornsilk","royalblue", "pink", "brown"], alpha=.55)
plt.suptitle("Recency and Frequency Grid", fontsize=25);


	•	`sizes=RFMStats"Recency""count"`:
	•	트리맵에서 각 사각형의 크기를 결정하는 값입니다.
	•	여기서는 `RFMStats"Recency""count"`, 즉 Recency(최근성) 데이터를 기준으로 각 사각형의 크기를 설정합니다.
	•	예: Recency 값이 높은 고객 그룹은 더 큰 사각형으로 표시됩니다.
	•	`label=RFMStats.index`:
	•	각 사각형에 표시할 라벨(텍스트)입니다.
	•	여기서는 `RFMStats.index`, 즉 RFM 그룹 이름이 라벨로 사용됩니다.
	•	`color=...`:
	•	사각형의 색상을 지정합니다. 여러 색상 리스트를 제공하여 그룹별로 다른 색상을 적용합니다.
	•	예: 빨강(`red`), 주황(`orange`), 파랑(`blue`) 등 다양한 색상이 사용됩니다.
	•	`alpha=.55`:
	•	색상의 투명도를 설정합니다. 값은 0~1 사이로, 여기서는 `0.55`로 설정하여 약간 투명하게 표시.

 

 

Clustering with K-Means

RFM_df3= RFM_df2.drop(["recency_score", "frequency_score", "monetary_score", "RFM_SCORE", "Segment"], axis=1)
RFM_df3

 

  • 왜도(skewness) 확인 및 분포 시각화
#check if data is skewed
from scipy import stats #library
def check_skew(df, column):
    skew = stats.skew(df[column])
    skewtest = stats.skewtest(df[column])
    plt.title('Distribution of ' + column)
    sns.distplot(df[column])
    plt.show()
    print("{}'s: Skew: {}, : {}".format(column, skew, skewtest))
    return
    
    
for col in RFM_df3.columns:
    check_skew(RFM_df3, col)
    
    
	•	`scipy.stats`: 왜도 계산 및 왜도 검정(`skewtest`)을 위한 라이브러리.
	•	`matplotlib.pyplot`: 데이터 시각화를 위한 플롯팅 라이브러리.
	•	`seaborn`: 히스토그램과 커널 밀도 추정(KDE)을 포함한 고급 시각화 도구.
    
주요 작업:
	1.	왜도 계산 (`stats.skew`):
		•`stats.skew(dfcolumn)`: 데이터의 왜도를 계산합니다.
		•왜도의 값:
		: 오른쪽으로 긴 꼬리를 가진 분포(양의 왜도).
		: 왼쪽으로 긴 꼬리를 가진 분포(음의 왜도).
		: 대칭적인 분포(정규분포와 유사).
	2.	왜도 검정 (`stats.skewtest`):
		•`stats.skewtest(dfcolumn)`: 데이터가 대칭인지 여부를 검정합니다.
		•결과:
		•`statistic`: 검정 통계량.
		•`pvalue`: 귀무가설(데이터가 대칭적이다)을 기각할 확률.
		•: 귀무가설 기각 → 데이터는 비대칭적이다.
		•: 귀무가설 채택 → 데이터는 대칭적이다.

 

 

  • 왜곡도 값이 1보다 크거나 -1보다 작으면 분포가 매우 왜곡되어 있음을 나타냅니다.
  • 0.5와 1 또는 -0.5와 -1 사이의 값은 적당히 기울어져 있습니다. 
  • 0.5에서 0.5 사이의 값은 분포가 상당히 대칭임을 나타냅니다.
  • Frequency 및 Monetary 열은 매우 편향되어 있으므로 로그 변환을 적용해야 합니다.
RFM_log= RFM_df3.copy()
for i in RFM_log.columns[1:]:
    RFM_log[i] = np.log10(RFM_log[i])
    
RFM_log

 

 

  • 로그 변환 이후 재확인
for col in RFM_log.columns:
    check_skew(RFM_log, col)

 

 

  • standardscaler 진행
from sklearn.preprocessing import StandardScaler
scaler= StandardScaler()

RFM_log_scaled= scaler.fit_transform(RFM_log)
RFM_log_scaled_df= pd.DataFrame(RFM_log_scaled)
RFM_log_scaled_df.columns = ['recency', 'frequency', 'monetary']
RFM_log_scaled_df.head()

  • K-Means 클러스터링
from sklearn.cluster import KMeans
from yellowbrick.cluster import KElbowVisualizer

k_means = KMeans()
elbow = KElbowVisualizer(k_means, k=(2, 20))
elbow.fit(RFM_log_scaled_df)
elbow.show()


	•	`KElbowVisualizer`:
	•	Elbow Method를 구현한 Yellowbrick의 도구입니다.
	•	`k=(2, 20)`: 클러스터 수()를 2에서 20까지 테스트합니다.
	•	기본적으로 “왜곡(distortion)” 메트릭을 사용하여 각 에 대한 클러스터 품질을 평가합니다.
	•	왜곡은 각 데이터 포인트와 해당 클러스터 중심 사이의 거리 제곱합(Within-Cluster Sum of Squares, WCSS)을 계산합니다.
    
    
    •	`elbow.fit(RFM_log_scaled_df)`:
	•	RFM 데이터를 기반으로 K-Means 모델을 여러 값의 에 대해 학습시킵니다.
	•	`RFM_log_scaled_df`: 로그 변환 및 스케일링된 RFM 데이터프레임입니다.
	•	로그 변환과 스케일링은 데이터의 분포를 정규화하고, 클러스터링 성능을 향상시키기 위한 전처리 단계입니다.
	•	`elbow.show()`:
	•	결과를 시각화합니다. 그래프에는 다음이 표시됩니다:
	•	X축: 클러스터 수().
	•	Y축: 왜곡(WCSS).
	•	그래프에 “팔꿈치(elbow)” 지점이 표시되며, 최적의  값으로 추천됩니다.
    
Elbow Method란?
엘보우 방법은 K-Means에서 최적의 클러스터 수를 찾기 위한 방법입니다:
	1.	각  값에 대해 WCSS(Within-Cluster Sum of Squares)를 계산합니다.
	2.	가 증가할수록 WCSS는 감소하지만, 특정 지점 이후로는 감소율이 급격히 줄어듭니다.
	3.	이 “팔꿈치 지점(elbow point)“이 최적의  값으로 간주됩니다.
결과 해석
	1.	그래프 설명:
	•	X축: 클러스터 수() 범위 (2~20).
	•	Y축: 왜곡(WCSS).
	•	그래프는 보통 아래로 완만하게 감소하는 곡선 형태를 가집니다.
	•	팔꿈치 지점에서 WCSS 감소율이 급격히 줄어들며, 이는 최적의  값을 나타냅니다.
	2.	최적의 :
	•	Yellowbrick은 팔꿈치 지점을 자동으로 감지하고, 해당 위치에 점선과 텍스트로 표시합니다.
	•	예를 들어, 팔꿈치가 에서 발생하면 이는 데이터가 5개의 클러스터로 나뉘는 것이 가장 적합하다는 것을 의미합니다.

The optimal K value for Elbow was found to be 6.

 

  • k-means 모델 적용
kmeans= KMeans(n_clusters=elbow.elbow_value_)
kmeans.fit(RFM_log_scaled_df)

RFM_log_scaled_df['Cluster']= kmeans.labels_
RFM_log_scaled_df.head()

# kmeans = KMeans(n_clusters=elbow.elbow_value_)
•	`KMeans(n_clusters=...)`:
	•	K-Means 클러스터링 모델을 생성합니다.
	•	`n_clusters=elbow.elbow_value_`: Elbow Method에서 찾은 최적의 클러스터 수를 사용합니다.
	•	`elbow.elbow_value_`: Yellowbrick의 `KElbowVisualizer`가 계산한 최적의 클러스터 수입니다. 예를 들어, 최적의 클러스터 수가 5라면 `n_clusters=5`로 설정됩니다.
    
    
# kmeans.fit(RFM_log_scaled_df)
•	`fit()`:
	•	K-Means 알고리즘을 사용하여 데이터를 군집화합니다.
	•	입력 데이터: `RFM_log_scaled_df`.
	•	이 데이터는 로그 변환 및 스케일링된 RFM 데이터로, 클러스터링 전에 전처리가 완료된 상태입니다.
	•	학습 과정:
		1.	초기 클러스터 중심(centroids)을 무작위로 설정.
		2.	각 데이터 포인트를 가장 가까운 클러스터 중심에 할당.
		3.	각 클러스터 중심을 재계산.
		4.	위 과정을 반복하여 클러스터 중심이 수렴할 때까지 진행.
        
        
# RFM_log_scaled_df['Cluster'] = kmeans.labels_
•	`kmeans.labels_`:
	•	K-Means 알고리즘이 학습 과정에서 각 데이터 포인트에 할당한 클러스터 레이블입니다.
	•	레이블 값은 `0`, `1`, …, `n_clusters-1`로 구성됩니다. 예를 들어, 클러스터 수가 5라면 레이블은 `` 중 하나로 할당됩니다.
•	`RFM_log_scaled_df'Cluster' = ...`:
	•	원본 데이터프레임(`RFM_log_scaled_df`)에 새로운 열(`Cluster`)을 추가하여 각 데이터 포인트의 클러스터 레이블을 저장합니다.

 

 

  • 군집 시각화
# Function to visualize clusters
def rfm_clusters_stat(df):
    df_new = df.groupby(['Cluster']).agg({
            'Recency'  : ['mean','median', 'min', 'max'],
            'Frequency': ['mean','median', 'min', 'max'],
            'Monetary' : ['mean','median', 'min', 'max', 'count']
        }).round(0)

    return df_new
    
    
RFM_df4= RFM_df3.copy()
RFM_df4['Cluster'] = kmeans.labels_
RFM_df4

 

rfm_clusters_stat(RFM_df4).style.background_gradient(cmap='YlGnBu')

 

  • 대부분의 고객이 한동안 주문을 하지 않았습니다.
  • 대부분의 고객이 평균적으로 한 번만 주문했기 때문에 빈도 특성은 모델에 많은 것을 추가하지 않았습니다.

 

  • 시각화
RFM_stats= pd.DataFrame(rfm_clusters_stat(RFM_df4))

# Visualize Segments
plt.figure(figsize=(10, 6))
squarify.plot(sizes=RFM_stats["Monetary"]["count"], label=RFM_stats.index, color=["b","g","r","m","c", "y"], alpha=0.7)
plt.suptitle("Segments of Customers", fontsize=25)


•	`sizes=RFM_stats"Monetary""count"`:
•	각 클러스터의 고객 수(`count`)를 기준으로 사각형의 크기를 설정합니다.
•	고객 수가 많을수록 더 큰 사각형으로 표시됩니다.
•	`label=RFM_stats.index`:
•	각 사각형에 표시할 라벨(클러스터 이름)입니다.
•	`RFM_stats.index`는 클러스터 번호(예: `0`, `1`, `2`, …)입니다.
•	`color="b", "g", "r", "m", "c", "y"`:
•	사각형의 색상을 지정합니다. 여기서는 파랑(`b`), 초록(`g`), 빨강(`r`), 자주색(`m`), 청록색(`c`), 노랑(`y`)이 사용됩니다.
•	`alpha=0.7`:
•	색상의 투명도를 설정합니다. 값은 0~1 사이로, 여기서는 0.7로 설정하여 약간 투명하게 표시됩니다.