你还不会用python进行数据分析吗
1、案例背景
用户价值细分是了解用户价值度的重要途径,而销售型公司中对于订单交易尤为关注,因此基于订单交易的价值度模型将更适合运营需求。针对交易数据分析的常用模型是RFM模型,该模型不仅简单、容易理解,且业务落地能力非常强。因此,本节将基于该模型做数据分析和应用。在RFM的结果中,业务部门希望不仅能对用户做分组,还希望能将每个组的用户特征概括和总结出来,这样便于后续精细化运营不同的客户群体,且根据不同群体做定制化或差异性的营销和关怀。
基于业务部门的用户分群需求,我们计划将RFM的3个维度分别作3个区间的离散化,这样出来的用户群体最大有333=27个。如果划分区间过多则不利于用户群体的拆分,区间过少则可能导致每个特征上的用户区分不显著。
从交付结果看,给业务部门做运营的分析结果都要导出成Excel文件,用于做后续分析和二次加工使用。另外,RFM的结果还会供其他模型的建模使用,RFM本身的结果可以作为新的局部性特征,因此数据的输出需要有本地文件和写数据库两种方式。
本节案例选择了4年的订单数据,这样可以从不同的年份对比不同时间下各个分组的绝对值变化情况,方便了解会员的波动。本节案例的输入源数据sales.xlsx和源代码chapter5_code.ipynb,程序输出RFM得分数据写入本地文件sales_rfm_score.xlsx。
2、案例主要应用技术
本案例使用的库包括time、numpy和pandas。在实现RFM组合时,我们使用sklearn的随机森林库来计算RFM的权重,在结果展示的时候主要使用Excel的可视化图表方式。
3、案例数据
案例数据是某企业从2015年到2018年共4年的用户订单抽样数据,数据来源于销售系统。数据在Excel中包含5个sheet,前4个sheet以年份为单位存储为单个sheet中,最后一张会员等级表为用户的等级表。
前4张表的数据概要如下:
- 特征变量数:4
- 数据记录数:30774、41278、50839、81349
- 是否有NA值:有
- 是否有异常值:有
具体数据特征如下:
- 会员ID:每个会员的ID唯一,由纯数字组成。
- 提交日期:订单日提交日期。
- 订单号:订单ID,每个订单的ID唯一,由纯数字组成。
- 订单金额:订单金额,浮点型数据。
会员等级表中是所有会员的会员ID对应会员等级的情况,包括以下两个字段。
- 会员ID:该ID可与前面的订单表中的会员ID关联。
- 会员等级:会员等级以数字区分,数字越大,级别越高。
4、案例过程
(1)导入库
#Python版本3.7.4 #引入各类必须的库,numpy==1.17.2,pandas==0.25.1 import time import numpy as np import pandas as pd #引入机器学习包sklearn,sklearn==0.21.3 from sklearn.ensemble import RandomForestClassifier
这里用到4个库:time、numpy、pandas、sklearn
- time:用来记录插入数据库时的当前日期
- numpy:用来做基本数据处理等
- pandas:有关日期转换、数据格式化处理、主要RFM计算过程等
- sklearn:使用其中的随机森林库
(2)读取数据
#创建sheet_names列表,与表格数据相对应,方便后续处理 sheet_names = ['2015','2016','2017','2018','会员等级'] #利用pandas库中的read_excel()函数,根据每一列的sheet_names,从第一行到最后一行依次读取每一行的数据作为sheet_datas sheet_datas = [pd.read_excel('./sales.xlsx',sheet_name=i) for i in sheet_names]#必须要保证与jupyter在同一个目录下才可以直接read_excel('name.xlsx')
先定义一个列表sheet_names,目的是方便后续制定sheet名称;然后再通过列表推导式配合pandas的read_excel批量读取sales.xlsx中所有的数据到列表中,形成sheet_datas。该过程会比较耗时。
(3)数据审查
- 前N条数据主要查看不同数据列的数据格式,尤其是有特定转换操作之后是否符合源数据文件的格式或得到目标转换要求,以及数据的长度、组成规律、类型等是否与真实数据一致。
- 数据描述性信息主要分析数据分布规律,包括记录数、极值、标准差、分位数结果等,可用于数据集的使用模型、极值的处理等后续计算的辅助判断依据。
- 缺失值信息帮助我们判断数量以及后续应对策略。
- 数据类型用于判断目标类型当前状态,以及后续是否需要做特殊处理。
#对表格数据做显示,以审查一下我们的数据是否有误 for each_name,each_data in zip(sheet_names,sheet_datas):#zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。 print('[data summary for {0:=^50}]'.format(each_name))#显示各年份之间的分割线 print('Overview:','\n',each_data.head(4))# 展示数据前4条 print('DESC:','\n',each_data.describe())# 数据描述性信息 print('NA records',each_data.isnull().any(axis=1).sum()) # 缺失值记录数 print('Dtypes',each_data.dtypes) # 数据类型
[data summary for =======================2015=======================] Overview: 会员ID 订单号 提交日期 订单金额 Unnamed: 4 Unnamed: 5 \ 0 15278002468 3000304681 2015-01-01 499.0 NaN NaN 1 39236378972 3000305791 2015-01-01 2588.0 NaN NaN 2 38722039578 3000641787 2015-01-01 498.0 NaN NaN 3 11049640063 3000798913 2015-01-01 1572.0 NaN NaN Unnamed: 6 Unnamed: 7 Unnamed: 8 Unnamed: 9 0 NaN NaN NaN NaN 1 NaN NaN NaN NaN 2 NaN NaN NaN NaN 3 NaN NaN NaN NaN DESC: 会员ID 订单号 订单金额 Unnamed: 4 Unnamed: 5 \ count 3.077400e+04 3.077400e+04 30774.000000 0.0 0.0 mean 2.918779e+10 4.020414e+09 960.991161 NaN NaN std 1.385333e+10 2.630510e+08 2068.107231 NaN NaN min 2.670000e+02 3.000305e+09 0.500000 NaN NaN 25% 1.944122e+10 3.885510e+09 59.000000 NaN NaN 50% 3.746545e+10 4.117491e+09 139.000000 NaN NaN 75% 3.923593e+10 4.234882e+09 899.000000 NaN NaN max 3.954613e+10 4.282025e+09 111750.000000 NaN NaN Unnamed: 6 Unnamed: 7 Unnamed: 8 Unnamed: 9 count 0.0 0.0 0.0 0.0 mean NaN NaN NaN NaN std NaN NaN NaN NaN min NaN NaN NaN NaN 25% NaN NaN NaN NaN 50% NaN NaN NaN NaN 75% NaN NaN NaN NaN max NaN NaN NaN NaN NA records 30774 Dtypes 会员ID int64 订单号 int64 提交日期 datetime64[ns] 订单金额 float64 Unnamed: 4 float64 Unnamed: 5 float64 Unnamed: 6 float64 Unnamed: 7 float64 Unnamed: 8 float64 Unnamed: 9 float64 dtype: object [data summary for =======================2016=======================] Overview: 会员ID 订单号 提交日期 订单金额 Unnamed: 4 Unnamed: 5 \ 0 39288120141 4282025766 2016-01-01 76.0 NaN NaN 1 39293812118 4282037929 2016-01-01 7599.0 NaN NaN 2 27596340905 4282038740 2016-01-01 802.0 NaN NaN 3 15111475509 4282043819 2016-01-01 65.0 NaN NaN Unnamed: 6 0 NaN 1 NaN 2 NaN 3 NaN DESC: 会员ID 订单号 订单金额 Unnamed: 4 Unnamed: 5 \ count 4.127800e+04 4.127800e+04 41277.000000 0.0 0.0 mean 2.908415e+10 4.313583e+09 957.106694 NaN NaN std 1.389468e+10 1.094572e+07 2478.560036 NaN NaN min 8.100000e+01 4.282026e+09 0.100000 NaN NaN 25% 1.934990e+10 4.309457e+09 59.000000 NaN NaN 50% 3.730339e+10 4.317545e+09 147.000000 NaN NaN 75% 3.923182e+10 4.321132e+09 888.000000 NaN NaN max 3.954554e+10 4.324911e+09 174900.000000 NaN NaN Unnamed: 6 count 0.0 mean NaN std NaN min NaN 25% NaN 50% NaN 75% NaN max NaN NA records 41278 Dtypes 会员ID int64 订单号 int64 提交日期 datetime64[ns] 订单金额 float64 Unnamed: 4 float64 Unnamed: 5 float64 Unnamed: 6 float64 dtype: object [data summary for =======================2017=======================] Overview: 会员ID 订单号 提交日期 订单金额 Unnamed: 4 Unnamed: 5 \ 0 38765290840 4324911135 2017-01-01 1799.0 NaN NaN 1 39305832102 4324911213 2017-01-01 369.0 NaN NaN 2 34190994969 4324911251 2017-01-01 189.0 NaN NaN 3 38986333210 4324911283 2017-01-01 169.0 NaN NaN Unnamed: 6 0 NaN 1 NaN 2 NaN 3 NaN DESC: 会员ID 订单号 订单金额 Unnamed: 4 Unnamed: 5 \ count 5.083900e+04 5.083900e+04 50839.000000 0.0 0.0 mean 2.882368e+10 4.332466e+09 963.587872 NaN NaN std 1.409416e+10 4.404350e+06 2178.727261 NaN NaN min 2.780000e+02 4.324911e+09 0.300000 NaN NaN 25% 1.869274e+10 4.328415e+09 59.000000 NaN NaN 50% 3.688044e+10 4.331989e+09 149.000000 NaN NaN 75% 3.923020e+10 4.337515e+09 898.000000 NaN NaN max 3.954554e+10 4.338764e+09 123609.000000 NaN NaN Unnamed: 6 count 0.0 mean NaN std NaN min NaN 25% NaN 50% NaN 75% NaN max NaN NA records 50839 Dtypes 会员ID int64 订单号 int64 提交日期 datetime64[ns] 订单金额 float64 Unnamed: 4 float64 Unnamed: 5 float64 Unnamed: 6 float64 dtype: object [data summary for =======================2018=======================] Overview: 会员ID 订单号 提交日期 订单金额 Unnamed: 4 Unnamed: 5 \ 0 39229691808 4338764262 2018-01-01 3646.0 NaN NaN 1 39293668916 4338764363 2018-01-01 3999.0 NaN NaN 2 35059646224 4338764376 2018-01-01 10.1 NaN NaN 3 1084397 4338770013 2018-01-01 828.0 NaN NaN Unnamed: 6 0 NaN 1 NaN 2 NaN 3 NaN DESC: 会员ID 订单号 订单金额 Unnamed: 4 Unnamed: 5 \ count 8.134900e+04 8.134900e+04 81348.000000 0.0 0.0 mean 2.902317e+10 4.348372e+09 966.582792 NaN NaN std 1.404116e+10 4.183774e+06 2204.969534 NaN NaN min 2.780000e+02 4.338764e+09 0.000000 NaN NaN 25% 1.902755e+10 4.345654e+09 60.000000 NaN NaN 50% 3.740121e+10 4.349448e+09 149.000000 NaN NaN 75% 3.923380e+10 4.351639e+09 899.000000 NaN NaN max 3.954614e+10 4.354235e+09 174900.000000 NaN NaN Unnamed: 6 count 0.0 mean NaN std NaN min NaN 25% NaN 50% NaN 75% NaN max NaN NA records 81349 Dtypes 会员ID int64 订单号 int64 提交日期 datetime64[ns] 订单金额 float64 Unnamed: 4 float64 Unnamed: 5 float64 Unnamed: 6 float64 dtype: object [data summary for =======================会员等级=======================] Overview: 会员ID 会员等级 Unnamed: 2 Unnamed: 3 0 100090 3 NaN NaN 1 10012905801 1 NaN NaN 2 10012935109 1 NaN NaN 3 10013498043 1 NaN NaN DESC: 会员ID 会员等级 Unnamed: 2 Unnamed: 3 count 1.543850e+05 154385.000000 0.0 0.0 mean 2.980055e+10 2.259701 NaN NaN std 1.365654e+10 1.346408 NaN NaN min 8.100000e+01 1.000000 NaN NaN 25% 2.213894e+10 1.000000 NaN NaN 50% 3.833022e+10 2.000000 NaN NaN 75% 3.927932e+10 3.000000 NaN NaN max 3.954614e+10 5.000000 NaN NaN NA records 154385 Dtypes 会员ID int64 会员等级 int64 Unnamed: 2 float64 Unnamed: 3 float64 dtype: object
使用for循环,配合zip函数来读取sheet_names和sheet_datas中的每个值,然后分别输出以下内容:
- 数据展示:使用数据框的head方法,显示前4条数据
- 描述性信息:使用数据框的describe方法,显示所有的描述性统计结果
- 缺失值记录:使用数据框的isnull().any(axis=1)来判断含有缺失值的记录,然后用sum获取总记录数
- 数据类型:使用数据框的dtypes方法获取所有字段数据类型信息。
输出结果如上面代码cell所示。
通过上述结果我们可以得到如下结论:
- 每个sheet中的数据都能正常读取和识别,无任何错误。
- 日期列(提交日期)已经被自动识别为日期格式,这省去了后期做转换的过程。
- 订单金额的分布是不均匀的,里面有明显的极大值,例如2016年的数据中,最大值为174900,最小值仅为0.1。这样的分布状态,数据会受到极值影响。
- 订单中的最小值竟然包括0、0.1这样的金额,显然不是正常订单。经过与业务方沟通后确认,最大值的订单金额有效,通常是客户一次性购买多个大家电商品;而订单金额为0.1这类是使用优惠券支付的订单,并没有实际意义。除此之外,所有低于1元的订单均有这个问题,因此需要在后续处理中去掉。
- 有的表中存在缺失值记录,但数量不多,因此选择丢弃或填充都可以。
(4)数据预处理。
去除缺失值和异常值:
for ind,each_data in enumerate(sheet_datas[:-1]):#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标 sheet_datas[ind] = each_data.dropna()# 丢弃缺失值记录 sheet_datas[ind] = each_data[each_data['订单金额'] > 1]# 丢弃订单金额<=1的记录 sheet_datas[ind]['max_year_date'] = each_data['提交日期'].max() # 增加一列最大日期值
通过for循环配合enumerate方法,获得每个可迭代元素的索引和具体值。由于处理缺失值和异常值只针对订单数据,因此sheet_datas通过索引实现不包含最后一个对象(即会员等级表)。
- 直接将each_data使用dropna丢弃缺失值后的数据框替代原来sheet_datas中的数据框。
- 使用each_data[each_data[‘订单金额’]>1]来过滤出包含订单金额>1的记录数,然后替换原来sheet_datas中的数据框。
- 最后一行代码的目的是在每个年份的数据中新增一列max_year_date,通过each_data[‘提交日期’].max()获取一年中日期的最大值,这样方便后续针对每年的数据分别做RFM计算,而不是针对4年的数据统一做RFM计算。
汇总所有数据:
# 汇总所有数据 data_merge = pd.concat(sheet_datas[:-1],axis=0)
将4年的数据使用pd.concat方法合并为一个完整数据框data_merge,这样后续的所有计算都能基于数据框进行,而不用写循环代码段对每个年份的数据单独计算。
获取各自年份数据:
data_merge['date_interval'] = data_merge['max_year_date'] - data_merge['提交日期']#计算各自年份的最大日期与每个行的日期的差,得到日期间隔 data_merge['year'] = data_merge['提交日期'].dt.year#增加一段新的字段,为每个记录行发生的年份
第1行代码实现计算各自年份的最大日期与每个行的日期的差,得到日期间隔。
第2行代码实现增加一段新的字段,为每个记录行发生的年份,使用data_merge[‘提交日期’].dt.year实现。
转换日期间隔为数字:
#将日期间隔转化成文字 data_merge['date_interval'] = data_merge['date_interval'].apply(lambda x: x.days)
代码是将data_merge[‘date_interval’]的时间间隔转换为数值型计算对象,这里使用了apply方法。apply方法是对某个pandas对象(数据框或series)实现某个函数的功能。配合lamda实现的是,从data_merge[‘date_interval’]取出每个字段值,然后获得字段值的days结果并返回。
按会员ID做汇总:
#按会员ID做汇总 rfm_gb = data_merge.groupby(['year','会员ID'], as_index=False).agg({'date_interval':'min', #计算最近一次的订单时间 '提交日期':'count', #计算订单的频率 '订单金额':'sum'}) #计算订单的总金额
该代码实现的是基于年份和会员ID,分别做RFM原始值的聚合计算。
这里的分类汇总使用的是groupby方法,以year和会员ID为联合主键,设置as_index=False意味着year和会员ID不作为index列,而是普通的数据框结果列。后面的agg方法实际上是一个“批量”聚合功能的函数,它实现了对data_interval、提交日期、订单金额三列分别以min、count、sum做聚合计算的功能。否则,我们需要分别写3条groupby来实现3个聚合计算。
重命名列名:
#重命名列名 rfm_gb.columns = ['year','会员ID','r','f','m'] rfm_gb.head()#预览并打印
year | 会员ID | r | f | m | |
0 | 2015 | 267 | 197 | 2 | 105.0 |
1 | 2015 | 282 | 251 | 1 | 29.7 |
2 | 2015 | 283 | 340 | 1 | 5398.0 |
3 | 2015 | 343 | 300 | 1 | 118.0 |
4 | 2015 | 525 | 37 | 3 | 213.0 |
使用rfm_gb.columns实现重命名操作,然后输出预览(前5条计算结果)
(5)确定RFM划分区间
在做RFM划分时,基本逻辑是分别对R、F、M做分箱或者离散化操作,然后才能得到得分离散化后的结果。而离散化本身有多种方法可选,由于我们要对数据做RFM离散化,因此需要先看下数据的基本分布状态。
查看数据分布:
#查看数据分布 desc_pd = rfm_gb.iloc[:,2:].describe().T#由于只针对rfm三列,因此使用iloc方法,选择从第3列(索引值为2)开始的字段,调用describe方法 print(desc_pd)
count mean std min 25% 50% 75% max r 148591.0 165.524043 101.988472 0.0 79.0 156.0 255.0 365.0 f 148591.0 1.365002 2.626953 1.0 1.0 1.0 1.0 130.0 m 148591.0 1323.741329 3753.906883 1.5 69.0 189.0 1199.0 206251.8
由于只针对rfm三列,因此使用iloc方法,选择从第3列(索引值为2)开始的字段,调用describe方法,后面的T做转置的目的,是为了更加直观的展示。
从基本概要可以看出,汇总后的数据总共有14万条,r和m的数据分布相对较为离散,表现在min、25%、50%、75%和max的数据没有特别集中;而从f(购买频率)可以看出,大部分用户的分布都趋近于1,表现是从min到75%的分段值都是1,且mean(均值)为1.365。
离散化的方法比较多,我们应根据自身应用特点和场景进行选择。这里我们将选择25%和75%作为区间划分的2个边界值。
有一个问题在于,r和m本身能较好的区分用户特征,但f则无法区分(大量的用户只有1个订单)。针对该问题我们与业务部门进行了沟通,结论是由于行业属性(大家电)的原因,用户发生复购确实很少,1年购买1次是比较普遍(其中包含新客户以及老客户在当年的第1次购买),因此划分时可以使用2和5来作为边界:选择2是因为一般的业务部门认为当年购买2次及2次以上就可以被定义为复购用户(而非累计订单的数量计算复购用户)。选择5是因为业务部门认为普通用户购买5次已经是非常高的次数,超过该次数就属于非常高价值的用户群体。可以说,这个值是基于业务经验和日常数据报表而获得的。
设置区间边界:
# 定义区间边界 r_bins = [-1,79,255,365] # 注意起始边界小于最小值 f_bins = [0,2,5,130] m_bins = [0,69,1199,206252]
基于上述分析,得到区间边界的基本原则如下:
中间2个边界值:r和m是分别通过25%和75%的值来获取的,f是业务与数据部门定义的。最小值边界:比各个维度的最小值小即可。
最大值边界:大于等于各个维度的最大值即可。
上面的原则中,中间2个边界值以及最大边界值比较容易理解,最小值边界为什么要小于各个维度的最小值呢?这是由于,在边界上的数据归属有一个基本原则,要么属于区间左侧,要么属于区间右侧。例如,f_bins中的2处于边界上,要么属于左侧区间,要么属于右侧区间。在后续使用pd.cut方法中,对于自定义边界,实行的是左开右闭的原则,即数据属于右侧区间,f_bins中的2就属于右侧区间。
这种原则会带来一个问题——最左侧的值是无法划分为任何区间的。因此,在定义最小值时,一定要将最小值的边界值定义得比数据框中的最小值要小。否则,当数据中出现与最小值边界值相同甚至更小的值时,数据就无法被划分在正常目标的区间内。
例如,有一列数[1,2,3,4,5],假如数据划分的区间边界是[1,3,5],即划分为2份。其中的2、3倍划分到(1,3]区间中,4、5被分到(3,5]区间中,而1则无法划分到任何一个正常区间内。
(6)计算RFM因子权重
在计算RFM组合得分时,我们可以直接将结果组合为一个新的分组,例如322/132。但在加权求和得到一个新的RFM得分指标时,则必须要确定一个权重值。如何确定RFM三个维度的权重呢?
这里面提供一个思路。其实在每个公司中,涉及到会员数据时,一般都会有会员体系,而会员体系中有一个维度,是衡量会员价值度高低的,这个维度是——会员等级。在指定会员等级时,各个公司都已经综合考虑到了多种与公司整体利益相关的因素,设置各种会员权益都与会员等级有关。例如,免运费门槛、优惠券使用、特殊商品优惠价格、会员活动和营销等。因此我们可以基于会员等级来确定RFM三者的权重,基本思路是,建立一个rfm三个维度与会员等级的分类模型,然后通过模型输出维度的权重。
匹配会员等级和RFM得分:
# 匹配会员等级和rfm得分 rfm_merge = pd.merge(rfm_gb,sheet_datas[-1],on='会员ID',how='inner')#使用merge方法合并两个数据框,关联主键是会员ID,匹配方式是内部匹配
将会员的订单数据与等级数据匹配,使用merge方法合并两个数据框,关联主键是会员ID,匹配方式是内部匹配。
通过RF获得RFM因子得分:
# rf获得rfm因子得分 clf = RandomForestClassifier()#调用随机森林分类起模型 clf = clf.fit(rfm_merge[['r','f','m']],rfm_merge['会员等级'])#使用随机森林分类器模型在我们的数据上进行拟合 weights = clf.feature_importances_ #返回'r','f','m'三部分权重 print('feature importance:',weights)
feature importance: [0.41293053 0.00598281 0.58108667] • 1
上述过程非常简单,先建立rf模型对象,然后将rfm三列作为特征,将会员等级作为目标输入模型中进行训练,最后通过模型的feature_importances_获得权重信息。结果如上。
由上述结果可知,在这3个维度中,用户的等级首先侧重于会员的价值贡献度(实际订单的贡献),其次是新近程度,最后是频次。这种逻辑与很多公司的整体会员等级一致。例如某国内电商的会员等级,是基于历史累计订单金额进行升级,如果最近一段时间(例如1年内)没有购物,则会降低会员等级。
(7)RFM计算过程
RFM分箱得分:
# RFM分箱得分 rfm_gb['r_score'] = pd.cut(rfm_gb['r'], r_bins, labels=[i for i in range(len(r_bins)-1,0,-1)])# 计算R得分 rfm_gb['f_score'] = pd.cut(rfm_gb['f'], f_bins, labels=[i+1 for i in range(len(f_bins)-1)])# 计算F得分 rfm_gb['m_score'] = pd.cut(rfm_gb['m'], m_bins, labels=[i+1 for i in range(len(m_bins)-1)])# 计算M得分
每个rfm的过程使用了pd.cut方法,基于自定义的边界区间进行划分,labels用来显示每个离散化后的具体值。F和M的规则是值越大,等级越高;而R的规则是值越小,等级越高。因此关于R的labels的规则与F和M相反。在labels指定时需要注意,4个区间的结果是划分为3份,因此labels的数量上通过减1实现边界数量与区间数量的平衡,而i+1则实现了区间从1开始,而不是0。
计算总得分:
方法1 加权得分
#方法一:加权得分 rfm_gb = rfm_gb.apply(np.int32) # cate转数值 rfm_gb['rfm_score'] = rfm_gb['r_score'] * weights[0] + rfm_gb['f_score'] * weights[1] + rfm_gb['m_score'] * weights[2]
代码中,用rfm_gb方法将每个值转换为np.int32类型,否则上面pd.cut的实现结果是类别型结果。然后将rfm三列分别乘以权重,得到新的rfm加权得分。
方法2 RFM组合
#方法二:RFM组合 rfm_gb['r_score'] = rfm_gb['r_score'].astype(np.str)#3列使用astype方法将数值型转换为字符串类型,然后使用pandas的字符串处理库中str的cat方法做字符串合并 rfm_gb['f_score'] = rfm_gb['f_score'].astype(np.str) rfm_gb['m_score'] = rfm_gb['m_score'].astype(np.str) rfm_gb['rfm_group'] = rfm_gb['r_score'].str.cat(rfm_gb['f_score']).str.cat(rfm_gb['m_score'])
这种方式是传统的做会员分组的方式。目标是将3列作为字符串组合为新的分组。
代码中,现针对3列使用astype方法将数值型转换为字符串类型,然后使用pandas的字符串处理库中str的cat方法做字符串合并,该方法可以将右侧的数据合并到左侧,再连续使用两个str.cat方法得到总的R、F、M字符串组合。
关 于 p a n d a s 的 s t r 方 法 关于pandas的str方法关于pandas的str方法
这里str库中cat方法,用于字符串对象合并,语法如下:
参数介绍:
others:要合并的另外一个对象(右侧对象),如果为空,则将左侧对象进行组合。
sep:合并的分隔符,默认为空,可自定义,例如“,”、“;”等。
na_rep:如果遇到NA(缺失值)时如何处理,默认为忽略。
需要注意的是:该方法用于对series做组合,而不能是数据框,适用于一维数据或者字符串。
举例:将左侧对象进行组合
输入输出如下。
import pandas as pd pd.Series(['a', 'b', 'c']).str.cat(['A', 'B', 'C'], sep = ';' ) '运行 运行
0 a;A 1 b;B 2 c;C dtype: object
(8)保存RFM结果到Excel
#保存RFM结果到excel rfm_gb.to_excel('sales_rfm_score1.xlsx')# 保存数据为Excel
使用数据框的to_excel方法导出到文件。结果如图所示。
image.png[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3tWENeL-1630238926681)(attachment:image.png)]
5、案例数据结论
(1)基于图形的交互式分析
重点人群分布:先通过Excel柱形图做简单分析,在整个分组中,212群体的用户是相对集中且变化最大的。
通过图XX1可以发现,从2016年到2017年用户群体数量变化不大,但到2018年增长了近一倍。因此,这部分人群将作为重点分析人群。
重点分组分布:除了212人群外,图XX1还显示了312、213、211及112人群都在各个年份占据很大数量,虽然各自规模不大,但组合起来的总量超过212本身。因此,后期也要重点做分析。将阈值设置为2000以上,如图XX2中显示,很多分组非常少的人群减少了很多,说明人群数量比较少的组别非常多。
(2)基于RFM分组结果的分析
通过RFM分组的Excel结果数据,我们将更进一步确定要分析的主要目标群体。
我们打开导出的sales_rfm_score.xlsx文件,然后建立数据透视表。
步骤为:
单击Excel顶部菜单栏的“插入–数据透视表”,在弹窗的窗口中单击“确定”按钮,如图所示
在新建的sheet视图中,进行如下设置:
- 将rfm_group拖入数据透视表字段区域中的“行”区块
- 将会员ID拖入数据透视表字段区域中的“值”区块
- 单击会员ID项
- 在弹出的窗口中选择“值字段设置”
- 在弹窗中选择“计算类型”为“计数”
- 单击“确定”按钮
到此,数据就以RFM分组为主题,以用户数量作为分类汇总了。
鼠标左键单击“计数项:会员ID”中的任意数值,单击顶部的排序功能,按从大到小排序。
排序后发现,会员在所有分组的分布是不均匀的,我们需要按百分数进行汇总显示,这样可以知道每个分组的占比。在透视表中的“计数项:会员ID”列单击任意数值,然后单击右键,在弹出的菜单中选择“值显示方式”,从右侧菜单中选择“父行汇总的百分比”。
设置完成后,我们对分区用户量进行累计求和,最后发现前9个分组的用户数量占比接近96%,如图所示
因此,我们需要把分析重点,放在这9组人群上。
(3)RFM用户特征分析
经过上面的分析,我们得到了要分析的重点客户群体。可根据用户的量级分为两类:
第1类是用户群体占比超过10%的群体;
第2类是占比在个位数的群体。
这两类人由于量级不同,因此需要分别有针对性的策略场景。
除此以外,我们还会增加第3类人群,虽然从用户量级上偏小,但是单个人的价值度非常高。
第1类人群:占比超过10%的群体。
由于这类人群基数大,必须采取批量操作和运营的方式落地运营策略,一般需要通过系统或产品实现,而不能主要依赖于人工。
212:可发展的一般性群体。这类群体购买新进度和订单金额一般,且购买频率低,考虑到其最大的群体基础,以及在新进度和订单金额上都还可以,因此可采取常规性的礼品兑换和赠送购物社区活动、签到、免运费等手段,维持并提升其消费状态。
211:可发展的低价值群体。这类群体相对于212群体,在订单金额上表现略差,因此在211群体策略的基础上,可以增加与订单相关的刺激措施,例如组合商品优惠券,发送积分购买商品等。
312:有潜力的一般性群体。这类群体购买新进度高,说明最近一次购买发生在很短时间之前,群体对于公司尚有比较熟悉的接触渠道和认知状态,购物频率低,说明对网站的忠诚度一般,订单金额处于中等层级,说明其还具有可提升的空间,因此可以借助其最近购买的商品,为其定制一些与上次购买相关的商品,通过向上销售等策略提升购买频次和订单金额。
112:可挽回的一般性群体。这类群体购买新进度较低,说明距离上次购买时间较长,很可能用户已经处于沉默或预流失流失阶段;购买频率低,说明对网站的忠诚度一般,订单金额处于中等层级,说明其还可能具有可提升的空间,因此对这部分群体的策略,首先是通过多种方式(例如邮件短信等)触达客户并挽回,然后通过针对流失客户的专享优惠(例如流失用户专享优惠券)措施促进其消费,在此过程中可增加接触频次和刺激力度的方式,增加用户的回访、复购以及订单价值回报。
213:可发展的高价值群体。这类人群发展的重点是提升购物频率,因此可指定不同的活动或事件来触达用户,促进其回访和购买,例如不同的节日活动,每周新品推送,高价值客户专享商品等。
第2类人群:占比为1%~10%的群体,这部分人群数量适中,在落地时无论是产品还是人工都可接入。
311:有潜力的低价值群体。这部分用户与211群体类似,但在购物新进度上更好,因此对其可采取相同的策略。除此以外,在这类群体的最近接触渠道上,可以增加营销或广告资源投入,通过这些渠道再次将客户引入网站完成消费。
111:这是一类在各个维度上都比较差的客户群体,一般情况下会在其他各个群体策略和管理都落地后才考虑他们。主要策略是先通过多种策略挽回客户,然后为客户推送与其类似的其他群体,或者当前热销的商品或折扣非常大的商品。在刺激消费时,可根据其消费水平、品类等情况,有针对性的设置商品暴露条件,先在优惠券及优惠商品的综合刺激下,使其实现消费,再考虑消费频率以及订单金额的提升。
313:有潜力的高价值群体。这类群体的消费新进度高且订单金额高,但购买频率低,因此只要提升其购买频次,用户群体的贡献价值就会倍增。提升购买频率上,除了在其最近一次的接触渠道上增加曝光外,与最近一次渠道相关的其他关联访问渠道也要考虑增加营销资源,另外213中的策略也要组合应用其中。
113:可挽回的高价值群体,这类群体与112群体类似,但订单金额贡献更高,因此除了应用112中的策略外,可增加部分人工的参与来挽回这些高价值客户,例如线下访谈,客户电话沟通等。
第3类群体:占比非常少,但却是非常重要的群体。
333:绝对忠诚的高价值群体。虽然用户绝对数量只有355,但由于其各方面表现非常突出,因此可以倾斜更多的资源,例如设计VIP服务、专享服务、绿色通道等。另外针对这部分人群的高价值附加服务的推荐,也是提升其价值的重点策略。
233、223、133:一般性的高价值群体。这类群体的主要着手点是提升新近购买度,及促进其实现最近一次的购买,可通过电话、客户拜访、线下访谈、微信、电子邮件等方式,直接建立用户挽回通道,以挽回这部分高价值用户。
322、323、332:有潜力的普通群体。这类群体最近刚完成购买,需要提升的是购买频次及购买金额。因此可通过交叉销售、个性化推荐、向上销售、组合优惠券、打包商品销售等策略,提升其单次购买的订单金额,及促进其重复购买。
6、案例应用和部署
针对上述得到的分析结论,会员部门采取了一下措施:
分别针对3类群体,按照公司实际运营需求和当前目标,制定了不同的群体落地的排期。
录入数据库的RFM得分数据已经应用到其他数据模型中,成为建模输入的关键维度特征之一。
7、案例注意点
本案例中有一下几点需要特别关注的:
- 不同品类、行业对于RFM的依赖度是有差异的,即使是一个公司,在不同的发展阶段和周期下,3个维度的优先级上也有调整。就一般性的经验而言,大家电等消费周期较长的行业,R和M会更重要一些;快消品等消费周期短且快的行业,更看重R和F。具体还是要根据当前运营需求与业务部门沟通。
- 对R、F、M区间的划分是一个离散化的过程,具体需要划分为几个区间,需要与业务方确认。本案例是划分为3个区间,这样的结果对于业务分析而言,略有点多。这意味着业务方需要制定十几套甚至更多的策略。如果业务方要求简略,也可以划分为2个区间,这样分出来的组别最多有8组,策略制定会更加简单。但是,具体是划分为2个还是3个,取决于当前业务方有多少资源可以投入到这个事情中来。
- R、F、M的权重打分,除了案例中提到的建模方法外,结合业务经验的专家打分法也是常用的思路。例如,结合AHP层次分析法进行打分,这样出来的权重结果会更加科学、严谨。
- 虽然订单数据库中的数据质量相对较高,但可能由于数据采集、数据库同步、ETL、查询、误操作等问题,还是会导致NA值的出现,而NA值的处理非常重要。
- R、F、M三个维度的处理(包括计算、离散化、组合、转换)之前都需要注意期数据类型和格式,尤其是有关时间项的转换操作应提前完成。
8、案例延伸思考
利用RFM模型划分用户群体并做价值度分析,是统计分析非常基础且有效的方法。该模型几乎用不到任何专业的统计分析和挖掘只是,只需要具有基本的数据清洗、处理和转换技能即可完成,因此几乎是各个企业都会用到的模型。更重要的是,该模型原理简单,业务方理解和应用起来非常容易入手,可大大提高部署落地的可能性。
结合本节案例,有一下几个值得思考的问题:
(1)按照R、F、M三个维度进行穷尽,应该有333=27中可能性,但是为什么结果只有26种?
(2)不同周期下,R、F、M权重会发生改变,那么运营结果是否有可比性和连续性?
(3)RFM模型可以作为模型分析方法,也可以作为数据预处理方法,基于不同的维度,通过计算组合得分或加权得分的方式获得新的数据,这是一种数据降维的有效方法。使用组合得分方法和加权得分方法得到的两种降维方法,在后续应用中,有什么不同呢?