任务5.2 聚类

一、 数据读取

In [1]:

import pandas as pd
import numpy as np 
import matplotlib.pyplot as plt
import matplotlib as mpl
from pyecharts.charts import *
from pyecharts import options as opts

import warnings
warnings.filterwarnings('ignore') #忽略警告信息
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False

In [3]:

df = pd.read_excel('order2021kmeans.xlsx')
df.head()

Out[3]:

订单顺序编号订单号用户名商品编号订单金额付款金额渠道编号平台类型下单时间付款时间是否退款
08sys-2021-306447069user-104863PR000499499.41480.42渠道1微信公众号2021-01-01 01:05:502021-01-01 01:06:17
111sys-2021-417411381user-181957PR000483279.53279.53渠道1APP2021-01-01 01:36:172021-01-01 01:36:56
261sys-2021-313655292user-282453PR0001541658.951653.91渠道1微信公众号2021-01-01 12:01:042021-01-01 12:03:20
378sys-2021-311884106user-167776PR000215343.25337.12渠道1APP2021-01-01 12:47:022021-01-01 12:47:21
481sys-2021-375273222user-138024PR000515329.04329.04渠道1APP2021-01-01 12:50:232021-01-01 12:50:50

二、数据预处理

In [4]:

#查看一下数据的整体信息以及缺失值和重复值
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 104557 entries, 0 to 104556
Data columns (total 11 columns):
 #   Column  Non-Null Count   Dtype         
---  ------  --------------   -----         
 0   订单顺序编号  104557 non-null  int64         
 1   订单号     104557 non-null  object        
 2   用户名     104557 non-null  object        
 3   商品编号    104557 non-null  object        
 4   订单金额    104557 non-null  float64       
 5   付款金额    104557 non-null  float64       
 6   渠道编号    104549 non-null  object        
 7   平台类型    104557 non-null  object        
 8   下单时间    104557 non-null  datetime64[ns]
 9   付款时间    104557 non-null  datetime64[ns]
 10  是否退款    104557 non-null  object        
dtypes: datetime64[ns](2), float64(2), int64(1), object(6)
memory usage: 8.8+ MB

In [5]:

# 删除重复值
df.duplicated().sum()

Out[5]:

0

In [7]:

df.columns = df.columns.str.strip()

In [8]:

#查看数据分布
df.describe().T

Out[8]:

countmeanstdmin25%50%75%max
订单顺序编号104557.052279.00000030183.1503851.0026140.0052279.0078418.00104557.000000
订单金额104557.01049.6815211054.4099686.10432.04679.321248.2828465.250000
付款金额104557.01167.4942252174.024855-12.47383.66641.231252.6383270.053829

In [9]:

#去除退款用户数据

data = df[df['是否退款']=='否']
data.head()

Out[9]:

订单顺序编号订单号用户名商品编号订单金额付款金额渠道编号平台类型下单时间付款时间是否退款
08sys-2021-306447069user-104863PR000499499.41480.42渠道1微信公众号2021-01-01 01:05:502021-01-01 01:06:17
111sys-2021-417411381user-181957PR000483279.53279.53渠道1APP2021-01-01 01:36:172021-01-01 01:36:56
261sys-2021-313655292user-282453PR0001541658.951653.91渠道1微信公众号2021-01-01 12:01:042021-01-01 12:03:20
378sys-2021-311884106user-167776PR000215343.25337.12渠道1APP2021-01-01 12:47:022021-01-01 12:47:21
481sys-2021-375273222user-138024PR000515329.04329.04渠道1APP2021-01-01 12:50:232021-01-01 12:50:50

In [10]:

# 查看异常值
len(data[data['订单金额']<0])#订单金额小于0元则为异常值

Out[10]:

0

In [11]:

# 查看异常值
len(data[data['付款金额']<0])#付款金额小于0元则为异常值

Out[11]:

5

In [13]:

#付款金额存在负值的情况,处理办法是将其转化为正值
data['付款金额'] = data['付款金额'].abs()
len(data[data['付款金额']<0])

Out[13]:

0

三、数据可视化分析

查看按渠道划分的收益

In [20]:

#统计不同渠道付款总额,并降序,转换成DF数据
channel_revenue=data.groupby('渠道编号')['付款金额'].sum().sort_values(ascending=False).reset_index()
#channel_revenue

#绘制柱状图
plt.figure(figsize=(12,4))
plt.title("不同渠道的总收益",fontsize=16)
plt.bar(channel_revenue['渠道编号'],channel_revenue['付款金额'])
plt.xlabel("渠道编号")
plt.ylabel("渠道收益")
plt.xticks(rotation=45)
plt.tight_layout()#自动调整子图布局
plt.show()

查看按月份划分的收益

In [25]:

#数据准备
data['付款月份'] = data['付款时间'].dt.month  #提取月份
#data['付款月份'] = [i.month for i in data['付款时间']]
data['付款月份名称'] = data['付款时间'].dt.month_name() #提取月份名称
#按月份统计总收益
month_revenue = data.groupby(['付款月份', '付款月份名称'])['付款金额'].sum().reset_index()

#绘制柱状图
plt.figure(figsize=(12, 4))
plt.title("Total Revenue by month", fontsize=16)

plt.bar(month_revenue["付款月份名称"],month_revenue["付款金额"])
plt.ylabel("Total Revenue", fontsize=12)
plt.xticks(rotation=45, fontsize=10)
plt.yticks(fontsize=10)
plt.tight_layout()
plt.show()

查看按每天小时划分的收益

In [27]:

#数据准备
data['付款小时'] =data['付款时间'].dt.hour #获取时间
data['付款天数'] = data['付款时间'].dt.day #获取天数
data['付款天数名称'] = data['付款时间'].dt.day_name()#获取时间名字
hourly_sales =data.groupby(['付款天数名称','付款小时'])['付款金额'].sum().reset_index()
hourly_sales = hourly_sales.rename(columns={'付款金额': 'TotalValue'}) 
hourly_sales

Out[27]:

付款天数名称付款小时TotalValue
0Friday03.208984e+05
1Friday11.104411e+05
2Friday29.343783e+04
3Friday31.139179e+04
4Friday45.626410e+03
............
163Wednesday191.769271e+06
164Wednesday202.354979e+06
165Wednesday211.699454e+06
166Wednesday221.063404e+06
167Wednesday235.837316e+05

168 rows × 3 columns

In [32]:

# 创建一个空列表来存储所有切分后的DataFrame
split_dfs = []
 
# 计算需要切分的组数
num_groups = len(hourly_sales) // 24
 
for i in range(num_groups):
    # 每次迭代选取24行
    start_index = i * 24
    end_index = start_index + 24
    split_df = hourly_sales.iloc[start_index:end_index]
    split_dfs.append(split_df)
 

#查看第一个切分后的DataFrame
print(split_dfs[0])
    付款天数名称  付款小时    TotalValue
0   Friday     0  3.208984e+05
1   Friday     1  1.104411e+05
2   Friday     2  9.343783e+04
3   Friday     3  1.139179e+04
4   Friday     4  5.626410e+03
5   Friday     5  5.533420e+03
6   Friday     6  1.560106e+04
7   Friday     7  6.796755e+04
8   Friday     8  1.060996e+05
9   Friday     9  2.194146e+05
10  Friday    10  3.375344e+05
11  Friday    11  6.278208e+05
12  Friday    12  1.014062e+06
13  Friday    13  1.410802e+06
14  Friday    14  1.232234e+06
15  Friday    15  7.496987e+05
16  Friday    16  7.528851e+05
17  Friday    17  8.270167e+05
18  Friday    18  1.153808e+06
19  Friday    19  2.028809e+06
20  Friday    20  2.508399e+06
21  Friday    21  1.794541e+06
22  Friday    22  1.083644e+06
23  Friday    23  5.885448e+05

In [54]:

# 创建折线图并绘制
name=hourly_sales['付款天数名称'].unique()
line=(
    Line()
    .add_xaxis(split_dfs[0]['付款小时'].astype(str).tolist())  # X轴为每个点的索引
    .set_global_opts(
        title_opts={"text":"每日每小时收益总额"},
        legend_opts=opts.LegendOpts( #图例配置
            is_show=True,  # 是否显示图例
            orient='vertical',#垂直显示
            pos_top='5%',  # 图例位置,例如顶部5%
            pos_right='5%'  # 图例位置,例如右侧5%
            )
    )
)
for i in range(num_groups): 
    line.add_yaxis(name[i],split_dfs[i]['TotalValue'].tolist(),label_opts=opts.LabelOpts(is_show=False))


line.render_notebook()

Out[54]:

可见每天的收益总额差不多,在晚上20时~22时收益最高,说明此时人们拥有更多的时间和欲望购物

消费群体画像LRFM_基于K-means聚类分析

开始构建LRFM模型,其中

L:客户生命周期,表示客户最后一次购买与第一次购买的时间之差,该指标可以揭示客户与品牌或超市之间的长期关系以及客户的忠诚度

R:最近一次消费 (Recency):天。表示用户最近是否活跃,时间越新鲜越好,若R时间过去太久可能用户已流失

F:消费频率 (Frequency):频率越高 说明用户忠诚度越高

M:消费金额 (Monetary):金额越大说明用户为重要用户

一般的价值模型只有RFM,关于引入L的进一步含义:'客户生命周期'越长,说明客户与商家之间的关系越持久,一般意味着客户对产品或者商家较高的满意度和信任,且存在较高的复购概率。

通过对不同生命周期客户群体的划分,更长周期的客户提供增值服务,较短周期的客户加强营销推广,能够进一步的优化营销策略,进而实现更大的商业价值。

In [56]:

#构建L指标:
L = (data.groupby('用户名')['付款时间'].max() - data.groupby('用户名')['付款时间'].min()).dt.days.reset_index()

#构建R指标:
data['付款时间']=pd.to_datetime(data['付款时间']) 
#计算数据集中最大日期与每条记录的事务日期之间的时间差,做时间间隔diff
max_date = max(data['付款时间'])
data['diff']= max_date - data['付款时间']
data['diff']=data['diff'].dt.days
R = data.groupby('用户名')['diff'].min().reset_index()

#注意,字符串需要'',不论英汉。对用户名分组,计算至今天数的最小值,进行索引重置,将series变为dataframe,并且保留原列
F = data.groupby('用户名')['订单号'].count().reset_index()

M = data.groupby('用户名')['付款金额'].sum().reset_index()

#进行合并:LRFMdata = L.merge(R,on='用户名') 因为on的字节都一样,所以直接省略掉
LRFMdata = L.merge(R).merge(F).merge(M)
LRFMdata

Out[56]:

用户名付款时间diff订单号付款金额
0user-10000007911770.81
1user-10000302211511.59
2user-1000060471443.55
3user-100007035112162.14
4user-10000804514879.94
..................
71259user-299980277742719.77
71260user-299983041706.80
71261user-2999892075021637.47
71262user-29999203641440.17
71263user-29999502761350.87

71264 rows × 5 columns

In [57]:

#对columns进行重命名:
LRFMdata.rename(columns={
    '付款时间':'L',
    'diff':'R',
    '订单号':'F',
    '付款金额':'M'
},inplace=True)
LRFMdata

Out[57]:

用户名LRFM
0user-10000007911770.81
1user-10000302211511.59
2user-1000060471443.55
3user-100007035112162.14
4user-10000804514879.94
..................
71259user-299980277742719.77
71260user-299983041706.80
71261user-2999892075021637.47
71262user-29999203641440.17
71263user-29999502761350.87

71264 rows × 5 columns

聚类分析

In [58]:

#1.数据标准化处理:防止特征值数据差异过大,结果偏好

#开始准备进行模型聚类;再备份一下数据,防止出现意外:
model_data = LRFMdata.copy()

# 数据标准化
from sklearn.preprocessing import MinMaxScaler  
sacle_matrix = model_data.iloc[:, 1:5]  # 获得要转换的矩阵
model_scaler = MinMaxScaler() #构建最大最小标准化模型
data_scaled = model_scaler.fit_transform(sacle_matrix) #模拟计算
print(data_scaled.round(3))
[[0.    0.217 0.    0.021]
 [0.    0.607 0.    0.006]
 [0.    0.129 0.    0.005]
 ...
 [0.57  0.137 0.167 0.02 ]
 [0.    1.    0.    0.005]
 [0.    0.758 0.    0.004]]

In [59]:

#2.构建K-means模型

from sklearn.cluster import KMeans
#定义SSE列表,用来存放不同K值下的SSE:误差平方和
SSE = []
#定义候选K值
for i in range(1,10):
    kmeans = KMeans(n_clusters = i,random_state = 10)
    kmeans.fit(data_scaled)
    SSE.append(kmeans.inertia_)          # 样本到质心的距离平方和
#使用手肘法看K值
plt.plot(range(1,10),SSE,marker = 'o')   #表示在每个K值的位置上画一个圆形标记。
plt.show()

如图,拐点在3处拟合优化程度更好,选择3与4的范围通过轮廓系数进一步优化选择

In [60]:

#用轮廓系数调优找到最优K值并进行kmeans模型聚类:(直接进行轮廓系数的优选运行比较慢,
#可以首先考虑手肘法看K值,然后进一步再选择拟合程度好的拐点进行轮廓系数分析)
from sklearn.cluster import KMeans  
from sklearn.metrics import silhouette_score

score_list = list()  # 用来存储每个K下模型的平局轮廓系数
silhouette_int = -1  # 初始化的平均轮廓系数阀值

for n_clusters in range(3, 5): 
    model_kmeans = KMeans(n_clusters=n_clusters)                # 建立聚类模型对象
    labels_tmp = model_kmeans.fit_predict(data_scaled)          # 训练聚类模型
    silhouette_tmp = silhouette_score(data_scaled, labels_tmp)  # 得到每个K下的平均轮廓系数
    if silhouette_tmp > silhouette_int:                         # 如果平均轮廓系数更高
        best_k = n_clusters                                     # 保存
        silhouette_int = silhouette_tmp  
        best_kmeans = model_kmeans  
        cluster_labels_k = labels_tmp 
    score_list.append([n_clusters, silhouette_tmp])             # 将每次K及其得分追加到列表
    
print('{:*^60}'.format('K值对应的轮廓系数:'))
print(np.array(score_list))  
print('优K值:{0} \n轮廓系数是:{1}'.format(best_k, silhouette_int))
#显然轮廓系数越大越好,组内聚合和组间离别得效果
*************************K值对应的轮廓系数:*************************
[[3.         0.529391  ]
 [4.         0.48905871]]
优K值:3 
轮廓系数是:0.5293910039364125

In [61]:

# 获得训练集下的标签信息,并与原数据dataframe进行concat合并(为后续最后的客户细分做索引保留)
cluster_labels = pd.DataFrame(cluster_labels_k, columns=['Cluster_Id'])  
merge_data = pd.concat((model_data, cluster_labels), axis=1)
merge_data.head()

Out[61]:

用户名LRFMCluster_Id
0user-10000007911770.810
1user-10000302211511.591
2user-1000060471443.550
3user-100007035112162.141
4user-10000804514879.940

In [62]:

#进一步对聚类后的数据进行统计分析:选取只与画像有关的列
merged_rfm = merge_data[['M','F','R','L','Cluster_Id']]

# 统计LRFM各指标的均值和各标签的总人数
mean_lrfm = merged_rfm.groupby('Cluster_Id').mean().reset_index()  # Compute the mean for each Cluster_Id
mean_lrfm['count'] = merged_rfm['Cluster_Id'].value_counts().values  # Add the count of each Cluster_Id
mean_lrfm

Out[62]:

Cluster_IdMFRLcount
001281.7274341.12341780.2910595.03069232289
111317.3531931.096880251.0970174.67939029263
222680.9187092.30683771.253089181.2257009712

绘制客户特征雷达图

In [63]:

r = pd.DataFrame(best_kmeans.cluster_centers_)  # 聚类中心
labels = np.array(['M', 'F', 'R', 'L'])
labels = np.concatenate((labels, [labels[0]]))  # 闭合标签
# 聚类数量
N = len(r) + 1  # 聚类数量,加1以闭合雷达图
angles = np.linspace(0, 2 * np.pi, N, endpoint=False)  # 均匀分布的角度
data = pd.concat([r, r.iloc[:, 0]], axis=1)  # 聚类中心数据并闭合图形
angles = np.concatenate((angles, [angles[0]]))  # 闭合角度

# 创建雷达图
fig = plt.figure(figsize=(8, 8))  # 增加图形尺寸
ax = fig.add_subplot(111, polar=True)
colors = ['#FF69B4', '#87CEEB', '#800080', '#32CD32', '#FFD700']  
line_styles = ['-', '--', '-.', ':', '-']
# 绘制每个聚类的雷达图
for i in range(len(r)):
    ax.plot(angles, data.loc[i, :], linewidth=2, linestyle=line_styles[i % len(line_styles)], label=f"客户群 {i}", color=colors[i % len(colors)])  # 添加样式和颜色
    ax.fill(angles, data.loc[i, :], color=colors[i % len(colors)], alpha=0.25)  # 为每个聚类区域添加填充色
ax.set_thetagrids(angles * 180 / np.pi, labels, fontsize=12)  # 增加字体大小
ax.grid(True, color='gray', linestyle='-', linewidth=0.5, alpha=0.5)  # 调整网格线的透明度
plt.title(u'客户特征雷达图', fontsize=16, pad=20)
plt.legend(loc='lower right', fontsize=12)
plt.show()

从雷达图可以看出,客户群体2的指标都很低,偏向于F指标。客户群体1偏向F指标,客户群体0偏向FM指标

因此可以根据箱线图和雷达图将消费群体划分为三个类群:

L高,R高,F低,M高 重要客户

L低,R低,F高,M低 潜在客户

L低,R低,F低,M低 一般客户

重要客户:已经有较长的消费历史,最近有光顾,表明他们对产品或服务有持续兴趣。光顾频次不高,但是消费金额很高,表明每次购买时倾向于选择高价值商品。

策略:

1.针对这类客户,可以通过忠诚度计划、VIP会员奖励等方式进一步增强他们的忠诚感,确保他们继续光顾。

2.基于他们的消费历史和高额消费,可以推荐相关的高价值产品,增加他们的购买频次。

3.为这些重要客户提供专属的优惠、折扣或客户服务(例如专属客服、生日礼包等),增加他们的满意度和粘性。

4.尽管他们的光顾频次不高,可以通过定期的邮件营销或电话跟进,提醒特定产品的新品或促销活动。

潜在客户:客户生命周期较短,最近光顾时间较久,可能代表他们已经有一段时间没有与品牌互动,虽然频繁光顾,但每次的消费金额较低,可能更多是购买低价商品。

策略:

1.通过推送促销活动、优惠券或折扣,刺激这类客户回归并进行复购。可以提供一些小额产品或折扣,以促使他们增加单次消费金额。

2.提供一些会员特权(如积分、专属折扣等),鼓励他们在未来增加购买频率并提升消费金额。

一般客户:客户生命周期较短,最近一次光顾的时间很久,可能表示他们已经离开或失去兴趣。每次消费金额也较低,整体贡献的收入不高。

策略:

1.提供试用、限时免费体验等活动,降低他们尝试新产品的门槛,从而提高其消费意愿。

2.优化网站或应用的购买流程,减少购物障碍,提升他们的购买体验,激励他们消费更多。

3.定期与这类客户保持联系,提供产品更新信息、活动通知等,以增强品牌记忆并促使他们增加购买频次。

总结:

重要客户:聚焦于增强忠诚度、提升复购率并提供个性化服务,最大化其长期价值。

潜在客户:通过优惠刺激其回归并增加消费,提升他们的终生价值。

一般客户:通过唤醒活动、优惠和个性化推荐,提高客户的活跃度和消费金额。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值