캐글 링크
https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce
https://www.kaggle.com/code/drindeng/detailed-rfm-analysis-and-k-means-clustering
데이터 전처리
- 라이브러리 임포트
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
- 고객 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개의 클러스터로 나뉘는 것이 가장 적합하다는 것을 의미합니다.
- 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로 설정하여 약간 투명하게 표시됩니다.
'[코드쉐도잉] > 산업' 카테고리의 다른 글
[DAU, Funnel] 어느 단계에서 이탈이 발생하는가? (0) | 2025.01.14 |
---|---|
[CRM] e-commerce (0) | 2025.01.10 |
[LTV] E-Commerce predicting customer lifetime value (0) | 2025.01.06 |
[NETFLIX] Visualize (4) | 2024.11.28 |