在当今竞争激烈的商业环境中,深入了解客户行为和特征对于企业的成功至关重要。通过构建客户画像,企业可以更好地制定营销策略,优化客户体验,并提高客户满意度和忠诚度。本文将通过一个实际案例,展示如何利用LRFM模型和K-means聚类分析,对客户群体进行细分,并提出针对性的营销策略。

一、 数据准备与预处理

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
df = pd.read_excel('order2021kmeans.xlsx')
df.head()
订单顺序编号 订单号 用户名 商品编号 订单金额 付款金额 渠道编号 平台类型 下单时间 付款时间 是否退款
0 8 sys-2021-306447069 user-104863 PR000499 499.41 480.42 渠道1 微信公众号 2021-01-01 01:05:50 2021-01-01 01:06:17
1 11 sys-2021-417411381 user-181957 PR000483 279.53 279.53 渠道1 APP 2021-01-01 01:36:17 2021-01-01 01:36:56
2 61 sys-2021-313655292 user-282453 PR000154 1658.95 1653.91 渠道1 微信公众号 2021-01-01 12:01:04 2021-01-01 12:03:20
3 78 sys-2021-311884106 user-167776 PR000215 343.25 337.12 渠道1 APP 2021-01-01 12:47:02 2021-01-01 12:47:21
4 81 sys-2021-375273222 user-138024 PR000515 329.04 329.04 渠道1 APP 2021-01-01 12:50:23 2021-01-01 12:50:50
#查看一下数据的整体信息以及缺失值和重复值
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
# 删除重复值
df.duplicated().sum()
0
df.columns = df.columns.str.strip()
#查看数据分布
df.describe().T
count mean std min 25% 50% 75% max
订单顺序编号 104557.0 52279.000000 30183.150385 1.00 26140.00 52279.00 78418.00 104557.000000
订单金额 104557.0 1049.681521 1054.409968 6.10 432.04 679.32 1248.28 28465.250000
付款金额 104557.0 1167.494225 2174.024855 -12.47 383.66 641.23 1252.63 83270.053829
#去除退款用户数据

data = df[df['是否退款']=='否']
data.head()
订单顺序编号 订单号 用户名 商品编号 订单金额 付款金额 渠道编号 平台类型 下单时间 付款时间 是否退款
0 8 sys-2021-306447069 user-104863 PR000499 499.41 480.42 渠道1 微信公众号 2021-01-01 01:05:50 2021-01-01 01:06:17
1 11 sys-2021-417411381 user-181957 PR000483 279.53 279.53 渠道1 APP 2021-01-01 01:36:17 2021-01-01 01:36:56
2 61 sys-2021-313655292 user-282453 PR000154 1658.95 1653.91 渠道1 微信公众号 2021-01-01 12:01:04 2021-01-01 12:03:20
3 78 sys-2021-311884106 user-167776 PR000215 343.25 337.12 渠道1 APP 2021-01-01 12:47:02 2021-01-01 12:47:21
4 81 sys-2021-375273222 user-138024 PR000515 329.04 329.04 渠道1 APP 2021-01-01 12:50:23 2021-01-01 12:50:50
# 查看异常值
len(data[data['订单金额']<0])#订单金额小于0元则为异常值
0
# 查看异常值
len(data[data['付款金额']<0])#付款金额小于0元则为异常值
5
#付款金额存在负值的情况,处理办法是将其转化为正值
data['付款金额'] = data['付款金额'].abs()
len(data[data['付款金额']<0])
0

在开始分析之前,我们需要对数据进行读取和预处理。本次分析的数据集包含2021年的订单信息,涉及订单金额、付款金额、下单时间、付款时间等多个字段。通过pandas库读取数据后,我们对数据进行了初步的检查,发现数据中存在一些缺失值和异常值。例如,部分订单的付款金额为负值,这在实际业务中是不合理的。因此,我们对这些异常值进行了处理,将其转换为正值。
此外,我们还删除了重复值,并对数据进行了清洗和整理。最终,我们得到了一个干净、完整且可用于分析的数据集。

二、数据可视化分析

查看按渠道划分的收益

#统计不同渠道付款总额,并降序,转换成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()

在这里插入图片描述

查看按月份划分的收益

#数据准备
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()

在这里插入图片描述

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

#数据准备
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
付款天数名称 付款小时 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
... ... ... ...
163 Wednesday 19 1.769271e+06
164 Wednesday 20 2.354979e+06
165 Wednesday 21 1.699454e+06
166 Wednesday 22 1.063404e+06
167 Wednesday 23 5.837316e+05

168 rows × 3 columns

# 创建一个空列表来存储所有切分后的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
# 创建折线图并绘制
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()
    <div id="69c5ee2a78fb4b2394ab0f8d81e21e53" style="width:900px; height:500px;"></div>

在数据预处理完成后,我们通过可视化分析,对数据进行了初步的探索。首先,我们统计了不同渠道的付款总额,并绘制了柱状图。从图中可以看出,不同渠道的收益存在明显差异,这为企业的渠道优化提供了依据。
接着,我们按月份和每天的小时划分,分别统计了各时间段的收益情况。通过柱状图和折线图的展示,我们可以清晰地看到收益在不同时间段的变化趋势。例如,某些月份或某些时间段的收益明显高于其他时段,这可能与季节性因素或消费者的购物习惯有关。

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

为了更深入地了解客户群体,我们构建了LRFM模型。LRFM模型是一种常用的客户价值分析模型,它通过以下四个指标来衡量客户的价值:

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

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

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

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

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

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

#构建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
用户名 付款时间 diff 订单号 付款金额
0 user-100000 0 79 1 1770.81
1 user-100003 0 221 1 511.59
2 user-100006 0 47 1 443.55
3 user-100007 0 351 1 2162.14
4 user-100008 0 45 1 4879.94
... ... ... ... ... ...
71259 user-299980 277 74 2 719.77
71260 user-299983 0 4 1 706.80
71261 user-299989 207 50 2 1637.47
71262 user-299992 0 364 1 440.17
71263 user-299995 0 276 1 350.87

71264 rows × 5 columns

#对columns进行重命名:
LRFMdata.rename(columns={
    '付款时间':'L',
    'diff':'R',
    '订单号':'F',
    '付款金额':'M'
},inplace=True)
LRFMdata
用户名 L R F M
0 user-100000 0 79 1 1770.81
1 user-100003 0 221 1 511.59
2 user-100006 0 47 1 443.55
3 user-100007 0 351 1 2162.14
4 user-100008 0 45 1 4879.94
... ... ... ... ... ...
71259 user-299980 277 74 2 719.77
71260 user-299983 0 4 1 706.80
71261 user-299989 207 50 2 1637.47
71262 user-299992 0 364 1 440.17
71263 user-299995 0 276 1 350.87

71264 rows × 5 columns

## 聚类分析
#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]]
#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()

在这里插入图片描述

#用轮廓系数调优找到最优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.5293922 ]
 [4.         0.48968044]]
优K值:3 
轮廓系数是:0.52939220004291
# 获得训练集下的标签信息,并与原数据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()
用户名 L R F M Cluster_Id
0 user-100000 0 79 1 1770.81 2
1 user-100003 0 221 1 511.59 0
2 user-100006 0 47 1 443.55 2
3 user-100007 0 351 1 2162.14 0
4 user-100008 0 45 1 4879.94 2
#进一步对聚类后的数据进行统计分析:选取只与画像有关的列
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
Cluster_Id M F R L count
0 0 1317.201285 1.096815 251.099344 4.676748 32288
1 1 2681.425225 2.306877 71.265596 181.205683 29262
2 2 1281.627152 1.123389 80.291037 5.028184 9714

在LRFM模型的基础上,我们进一步利用K-means聚类算法对客户群体进行了细分。通过手肘法和轮廓系数法,我们确定了最优的聚类数量为3。

绘制客户特征雷达图

为了更直观地展示不同客户群体的特征,我们绘制了雷达图。从雷达图中可以看出,不同客户群体在LRFM四个指标上的差异,进一步验证了聚类结果的合理性。

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指标。

聚类结果显示,客户群体可以分为以下三类:

重要客户:这类客户具有较长的生命周期,最近也有消费行为,虽然消费频率不高,但每次消费金额较高。他们对企业的价值较大,是企业的核心客户群体。

潜在客户:这类客户生命周期较短,但消费频率较高,消费金额较低。他们可能是新客户或偶尔购买的客户,具有较大的开发潜力。

一般客户:这类客户生命周期较短,最近一次消费时间较久,消费频率和金额都较低。他们对企业的贡献相对较小,但仍有提升空间。

营销策略建议

根据客户群体的特征,我们提出了以下针对性的营销策略:

重要客户

1.增强忠诚度:通过提供专属的VIP会员服务、生日优惠、积分兑换等方式,进一步增强重要客户的忠诚度。

2.个性化推荐:基于他们的消费历史和偏好,推荐高价值的相关产品,提高客户的购买频次和金额。

3.专属服务:为重要客户提供专属客服、优先配送等服务,提升他们的购物体验和满意度。

潜在客户

1.促销刺激:通过推送限时折扣、优惠券、满减活动等方式,刺激潜在客户进行复购,提高他们的消费金额。

2.会员特权:鼓励潜在客户注册成为会员,享受积分、专属折扣等特权,增加他们的粘性。

3.产品推荐:根据潜在客户的消费行为和偏好,推荐适合他们的产品,引导他们进行更多消费。

一般客户

1.唤醒活动:通过发送产品试用、限时免费体验等活动,唤醒一般客户的购买意愿,提高他们的活跃度。

2.优化体验:优化购物流程,减少购物流程中的障碍,提升客户的购物体验,鼓励他们增加消费。

3.定期沟通:通过邮件、短信等方式,定期向一般客户推送产品更新信息、促销活动等,保持品牌记忆,促使他们增加购买频次。

总结:

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

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

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


结论
通过LRFM模型和K-means聚类分析,我们成功地对客户群体进行了细分,并揭示了不同客户群体的特征和价值。基于这些分析结果,企业可以制定更加精准的营销策略,提升客户满意度和忠诚度,从而实现更高的商业价值。在实际应用中,企业可以根据自身的业务特点和客户需求,灵活调整和优化这些策略,以达到最佳的营销效果。
未来展望
本次分析仅基于2021年的数据,未来可以进一步扩展数据范围,结合更多年份的数据进行分析,以更全面地了解客户行为的变化趋势。此外,还可以引入更多的客户特征和行为数据,如客户年龄、性别、地域、购买品类等,构建更复杂的客户画像模型,为企业提供更精准的决策支持。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐