项目描述
数据源是来自Kaggle的一个跨国数据集,其中包含2010年12月12日至2011年12月9日期间发生的所有在英国注册的非商店在线零售业务的交易。该公司主要销售独特的全场礼品,并且大部分客户是批发商。分析目的是按照RFM模型对客户进行分级,以用户的实际购买行为数据作为基础,进行用户群体的划分,再基于不同分类信息,分解成不同群体针对运营,从而使企业能更有效的获取客户、使客户更加满意、留住客户成为高价值客户、避免客户流失。
数据一览
数据形状为:542k 行x 8列,8个字段分别为发票号,发票日期,商品码,商品描述,数量,单价,顾客ID,国家。
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
df = pd.read_csv("E:\Anaconda\Jupyter\RFM建模与Kmeans聚类\data.csv")
df.head()
df['Amount']=df['Quantity']*df['UnitPrice']#计算每单的总价,添加Amount列
df.info() #查看整体数据情况
分析思路
R(Recency): 表示客户最近一次交易的时间与统计时间的间隔,R越大,表示客户越久没有发生交易
F(Frequency): 表示用户在定义时间段内的交易次数,F越大,表示客户交易越频繁
M(Monetary): 表示用户在定义时间段内的交易金额,M越大,表示客户价值越高
按照每个指标取值不同分为八类客户,包括重要价值客户、重要发展客户、重要保持客户、重要挽留客户、一般价值客户、一般发展客户、一般保持客户、一般挽留客户等八类用户,没类用户的运营策略如下
客户细分 | 运营策略 |
重要价值客户/高价值客户 最近买了,经常买,买最多 | 倾斜更多资源,VIP服务、个性化服务、附加销售 |
重要发展客户/重点唤回客户 金额高、次数多、最近无交易,需要把他们带回来 | DM营销,提供有用的资源,通过续订或更新的产品赢回他们 |
重要保持客户/重点深耕客户 金额高、最近有交易,频率相对较低,需要重点识别 | 交叉销售,提供会员/忠诚计划推荐其他产品 |
重要挽留客户 做出最大的购买,但是很久没有回来了,可能流失,需要挽留 | 重点联系或拜访,提高留存率 |
一般价值客户/潜力客户 次数多、最近有交易,金额小,需要挖掘 | 向上销售价值更高的产品,要求评论,吸引他们 |
一般发展客户/新客户 最近有交易,交易频率不高,金额小,容易丢失,有推广价值 | 社区活动,提供免费试用,提高客户兴趣,创建品牌知名度 |
一般保持客户/一般维持客户 次数多,金额小,最近无交易,一般维持 | 积分制,分享宝贵的资源,以折扣推荐热门产品/续订 |
一般挽留客户/流失客户 最后一次购买的时间很长,金额小,订单数量少。冬眠客户 | 恢复客户兴趣,否则暂时放弃无价值用户 |
1.数据清洗
df['CustomerID'] = df['CustomerID'].astype(str) #把顾客ID类型转为字符串
#df = df.drop(df[df['InvoiceNo'].str.contains('C')].index) #删除订单号中有'C'的订单 drop()需要通过索引(即.index)来删除这些行,而不能直接删除一个dataframe(表示列)
df['CustomerID'] = df['CustomerID'].fillna('NULL') #给顾客ID缺失的行加上NULL值
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate']) #将订单时间数据类型转为日期
df['Diff']=max(df['InvoiceDate'])-df['InvoiceDate']
df['Diff']=df['Diff'].dt.days #计算订单日期距离所有订单日期最大值的天数
df.drop(['Description'],axis=1,inplace=True) #删除不需要的商品描述特征
df.head()
2.分别计算R、F、M值
#以客户ID分组,求最近消费时间距离时间基线的最小值,得到R值
df_r=df.groupby('CustomerID')['Diff'].min()
df_r.head()
df_r=df_r.reset_index() #reset_index()方法用于 重置DataFrame 的索引,返回一个新的DataFrame,不会修改 df_r本身
df_r=df_r.drop(df_r[df_r['CustomerID']=='NULL'].index)
df_r.head()
#求每位顾客在时间周期内消费次数,得到F值,
df_f_1=df.loc[:,['InvoiceNo','CustomerID','Amount']] #选出求消费金额需要的列
df_f_1=df_f_1.drop(df_f_1[df_f_1['CustomerID']=='NULL'].index) #删除客户ID为空的记录
df_f_1.head()
# 计算每个 CustomerID 的去重订单数
df_f_3 = df_f_1.loc[:,['InvoiceNo','CustomerID']]
df_f = df_f_3.groupby('CustomerID')['InvoiceNo'].nunique().reset_index() #nunique()对订单ID进行去重计算(groupby得到的新表格是没有索引的,所以要reset_index)
df_f.head()
#计算顾客消费金额,得到M值
df_m = df_f_1.groupby('CustomerID')['Amount'].sum().reset_index()
df_m.head()
#合并三张表
df_rf=pd.merge(df_r,df_f,on='CustomerID')
df_rfm=pd.merge(df_rf,df_m,on='CustomerID')
df_rfm.columns = ['CustomerID','R','F','M'] #df_rfm.columns用来重命名 df_rfm 的列名
df_rfm.tail()
3.打标签
# 分级,cut函数把数据离散化,按照指定区间划分数据组,打上标签
R_bins = [0,30,90,180,360,720]
F_bins = [0,10,20,50,100,250] #bins指定分箱区间
M_bins = [0,500,5000,10000,30000,300000]
R_score = pd.cut(df_rfm['R'],R_bins,right = 'False',labels = [5,4,3,2,1],include_lowest = True) #cut()函数用于对数据进行分段(分箱),#labels指定分组名称
F_score = pd.cut(df_rfm['F'],F_bins,right = 'False',labels = [1,2,3,4,5])
M_score = pd.cut(df_rfm['M'],M_bins,right = 'False',labels = [1,2,3,4,5],include_lowest = True)
#连接数据源和标签数据
df_rfm_1 = pd.concat([df_rfm,R_score,F_score,M_score],axis=1)
df_rfm_1.columns = ['CustomerID','R','F','M','R_score','F_score','M_score']
#求每个指标的均值,作为评判标准
R_mean = df_rfm_1['R_score'].astype(float).mean()
F_mean = df_rfm_1['F_score'].astype(float).mean()
M_mean = df_rfm_1['M_score'].astype(float).mean()
df_rfm_1.head()
#分类函数,映射
def rank_column(x, mean_value):
if x > mean_value:
return '高'
else:
return '低'
df_rfm_1['R_rank'] = df_rfm_1['R_score'].apply(rank_column, mean_value=R_mean) #apply()方法会遍历R_score这一列所有值,并把每个值传给rank_column进行处理
df_rfm_1['F_rank'] = df_rfm_1['F_score'].apply(rank_column, mean_value=F_mean)
df_rfm_1['M_rank'] = df_rfm_1['M_score'].apply(rank_column, mean_value=M_mean)
#得到最后的客户分级
df_rfm_1['value'] = df_rfm_1['R_rank'].str[:] + df_rfm_1['F_rank'].str[:] + df_rfm_1['M_rank'].str[:]
df_rfm_1.head()
#按照RFM分值对顾客分类
def trans_value(x):
if x == '高高高':
return '高价值客户'
elif x == '高低高':
return '重点深耕客户'
elif x == '低高高':
return '重点唤回客户'
elif x == '低低高':
return '重点挽留客户'
elif x == '高高低':
return '潜力客户'
elif x == '高低低':
return '新客户'
elif x == '低高低':
return '一般保持客户'
else:
return '流失客户'
df_rfm_1['value_label'] = df_rfm_1['value'].apply(trans_value)
df_rfm_1.head()
4.可视化
#客户类型占比的饼状图
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 适用于 Windows
matplotlib.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
plt.figure(figsize=(6, 6))
df_rfm_1['value_label'].value_counts().plot.pie(autopct='%1.1f%%', startangle=90, colors=['#ff9999','#66b3ff'])
plt.title('客户类型占比')
plt.ylabel('') # 隐藏 y 轴标签
plt.show()
# 每个指标的分布情况箱线图
fig, axes = plt.subplots(1, 3, figsize=(12, 5)) #创建一个1行3列的子图布局,返回fig(整个画布)和axes(子图数组)
for i, col in enumerate(['R', 'F', 'M']):
sns.boxplot(y=df_rfm_1[col], ax=axes[i]) #y=df_rfm_1[col]选择 df_rfm_1 数据框中的某一列(col)作为数据;ax=axes[i]指定绘制在哪个子图上
axes[i].set_title(f"{col} 的箱线图")
plt.tight_layout()
plt.show()
# 客户消费金额分布柱状图
plt.figure(figsize=(8, 5))
sns.barplot(x='value_label', y='M', data=df_rfm_1, estimator=sum, errorbar=None, palette='viridis')
plt.title('客户消费金额分布')
plt.xlabel('客户类型')
plt.ylabel('消费金额')
plt.show()
5.K-means聚类分析
#数据标准化,这里用的是均值-标准差归一化,尽量将数据转化为均值为零,方差为一的数据,形如标准正态分布(高斯分布)
df_cluster = pd.DataFrame(df_rfm_1)
df_cluster_scaled = df_cluster[['R','F','M']] #只选择R、F和M列进行标准化(这三个特征是用于聚类的变量)
scaler = StandardScaler() # 创建一个StandardScaler实例
'''fit_transform会计算出每个特征的均值和标准差,然后对数据进行标准化(减去均值并除以标准差),确保数据的分布符合标准正态分布,标准化后的数据是NumPy数组格式'''
df_cluster_scaled = scaler.fit_transform(df_cluster_scaled) # 使用fit_transform对数据进行标准化
df_cluster_scaled = pd.DataFrame(df_cluster_scaled) # 将结果转换回DataFrame格式,保持列名一致
df_cluster_scaled.columns = ['R','F','M'] # 给标准化后的数据重新命名列名
df_cluster_scaled.head() # 查看标准化后的前五行数据
'''为了确定最佳的聚类数,通常我们使用肘部法,通过绘制不同聚类数的SSD(Sum of Squared Distances,误差平方和)曲线,
找到曲线的“肘部”点,即SSD下降速度显著减缓的地方。通常该点对应的聚类数就是最佳聚类数'''
#测试应该分为几簇,肘部对于的k值为3(曲率最高),故对于这个数据集的聚类而言,最佳聚类数应该选3
ssd = [] #存储每个k值对应的SSD值
range_n_clusters = [2,3,4,5,6,7,8] #设置需要测试的聚类数范围
#循环遍历每个k值,进行KMeans聚类
for num_clusters in range_n_clusters:
kmeans = KMeans(n_clusters = num_clusters,max_iter = 50)
kmeans.fit(df_cluster_scaled)
ssd.append(kmeans.inertia_)
plt.plot(ssd)
range_n_clusters = [2, 3, 4, 5, 6, 7, 8]
for num_clusters in range_n_clusters:
kmeans = KMeans(n_clusters = num_clusters,max_iter = 50, random_state=42, n_init=10) #创建KMeans模型,显式设置 n_init
kmeans.fit(df_cluster_scaled) # 训练KMeans模型
cluster_labels = kmeans.labels_ # 获取聚类标签
silhouette_avg = silhouette_score(df_cluster_scaled,cluster_labels) # 计算轮廓系数
print('For n_clusters = {0},the silhouette score is {1}'.format(num_clusters,silhouette_avg)) # 输出每个聚类数对应的轮廓系数
Kmeans = KMeans(n_clusters=3,max_iter=50)
Kmeans.fit(df_cluster_scaled)
df_cluster['Cluster_ID'] = Kmeans.labels_
cluster = df_cluster[['CustomerID','R','F','M','value_label','Cluster_ID']]
cluster.head()
cluster.to_csv("kmeans_results.csv", index=False, encoding="utf-8-sig")