电商交易规律、RFM分类

目的:
1 查看交易状况,分析销量下降的原因,交易额分解,顾客来源分析
2 顾客购买行为:复购、回购、留存分析、消费者生命周期、顾客消费能力差异
3 订单特征:下单时间、地区特征、价格特征
4 顾客分类

import pandas as pd
import numpy as np
from pyecharts.globals import CurrentConfig, OnlineHostType
CurrentConfig.ONLINE_HOST = OnlineHostType.NOTEBOOK_HOST

一 数据查看

d = pd.read_excel('数据.xlsx')
d.head()

在这里插入图片描述

d.info()
# 没有空值,数据类型正确

在这里插入图片描述

d.describe()
# 大部分 没有邮费,购买数量平均为1.5个,标准差1.48,大部分是1-2个;平均金额126,中位数115,右偏
# 订单特点是:小额订单较多,订单数量多为1-2件,邮费大部分为0

在这里插入图片描述

添加月份列和星期列

d = d.set_index('付款日期')
d['month'] = d.index.to_period('M')
d['付款日期'] = d.index
d['week'] = d.index.weekday
print('共 %s 周' % d.index.isocalendar().week.max())

共 26 周

二 销售波动分析

在这里插入图片描述
交易额波动原因分析:
购买周期为70天,交易额变化与之不符,交易额与订单量的变化最为一致,分析影响订单量的原因。将顾客分为新老客户、新顾客/活跃/不活跃/回流,发现新顾客、不活跃占比高,因此可断定用户流失情况严重。

交易成功订单

data = d[d['订单状态'] == '交易成功']
print('交易成功的订单数量为:',len(data))
print('\n统计日期从%s到%s' %(data['付款日期'].min(), data['付款日期'].max()),
     '\n消费者数量:',data['买家昵称'].nunique(),
     '\n总销售额:', data['实付金额'].sum(),
     '\n总销售量:', data['购买数量'].sum(),
     '\n总订单量:', len(data),
      '\n单笔订单平均销售额:', round(data['实付金额'].mean(), 2),
      '\n单笔订单平均购买件数:', round(data['购买数量'].mean(),2),
     '\n客单价:', round(data['实付金额'].sum()/data['买家昵称'].nunique(),2),
     '\n人均消费件数:', round(data['购买数量'].sum()/data['买家昵称'].nunique(),2)
     )

在这里插入图片描述
销售额变化趋势:1-4月连续下滑,4-6月缓慢上升
销售额 = 订单量 x 客单价 x 连带率

1 是否与购买周期有关(不符)

t12 = data.groupby('买家昵称')['付款日期'].agg(['min','max'])
t12['life'] = (t12['max'] -  t12['min']).dt.days
print('用户的平均购买周期为%s天'%round(t12['life'].mean(),2))
# 排除新客,统计消费两次及以上的
t12 = t12[t12['life']>0]
print('两次及以上购买的客户的平均购买周期为%s天' % round(t12['life'].mean(),2))
t12_ = t12['life'].value_counts().sort_index()
t12_[:5]

用户的平均购买周期为4.86天
两次及以上购买的客户的平均购买周期为71.31天
1 41
2 41
3 34
4 37
5 34

l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis([str(i) for i in t12_.index.tolist()])
l.add_yaxis('', t12_.tolist(),is_smooth=True,symbol='pin',symbol_size=10)
​
l.set_global_opts(
    title_opts=opts.TitleOpts(title='用户购买周期分布'), 
    xaxis_opts=opts.AxisOpts(name='天数',boundary_gap=False),
    yaxis_opts=opts.AxisOpts(name='频率'),
    tooltip_opts=opts.TooltipOpts(trigger='axis')
    )
l.render_notebook()

在这里插入图片描述

print('异常值右边界为:',t12.mean()+3*t12.std())

异常值右边界为: 40.81493910221057

t12[t12['life']==60]['min'].dt.date.value_counts()

2019-02-17 46
2019-02-19 14
2019-02-18 3
2019-03-20 1
2019-02-03 1
2019-02-20 1
2019-02-16 1
2019-02-09 1
2019-04-29 1
2019-03-27 1
Name: min, dtype: int64

t12[t12['life']==60]['max'].dt.date.value_counts()

2019-04-18 46
2019-04-20 14
2019-04-19 3
2019-04-21 2
2019-05-19 1
2019-05-27 1
2019-04-04 1
2019-06-28 1
2019-04-10 1
Name: max, dtype: int64

2 是否与订单量、客单价、连带率有关(与订单量有关)

t1 = data.groupby('month')[['实付金额','购买数量']].agg(['sum','mean'])
t1

在这里插入图片描述

from pyecharts.charts import Line
from pyecharts import options as opts
​
line = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
line.add_xaxis(t1.index.strftime('%Y-%m').tolist())
line.add_yaxis('销售额', (t1['实付金额']['sum']/10000).tolist(), yaxis_index=0)
line.add_yaxis('销量', (t1['购买数量']['sum']/10000).tolist(), yaxis_index=1)
line.add_yaxis('订单量', data.groupby('month')['month'].count().tolist(), yaxis_index=2)
line.extend_axis(yaxis=opts.AxisOpts(type_='value', name='销量\n(万)', position='left', offset=50))
line.extend_axis(yaxis=opts.AxisOpts(type_='value', name='订单量', position='right'))
​
line.set_global_opts(
    title_opts=opts.TitleOpts(title='各月总销售额、总销售量、订单量', pos_left=250),
    xaxis_opts=opts.AxisOpts(boundary_gap=False, axistick_opts=opts.AxisTickOpts(is_align_with_label=True)),
    yaxis_opts=opts.AxisOpts(name='总销售额\n(万)'),
    tooltip_opts=opts.TooltipOpts(trigger='axis'),
    legend_opts=opts.LegendOpts(orient='vertical', pos_right=150))
line.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
line.render_notebook()

在这里插入图片描述

cust_month = data.groupby('month')['买家昵称'].apply(lambda x: x.nunique())

line = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
line.add_xaxis(t1.index.strftime('%Y-%m').tolist())
line.add_yaxis('每单平均销售额', round(t1['实付金额']['mean'],0).tolist(), yaxis_index=0, symbol='pin',symbol_size=20)
line.add_yaxis('每单平均销量', round(t1['购买数量']['mean'],1).tolist(), yaxis_index=1, symbol='pin',symbol_size=20)
line.add_yaxis('客单价',  round(t1['实付金额']['sum']/cust_month,2), yaxis_index=2)
line.add_yaxis('连带率', round(t1['购买数量']['sum']/cust_month,2), yaxis_index=3)

line.extend_axis(yaxis=opts.AxisOpts(type_='value', name='平均\n购买量', position='left', offset=50, max_=1.6, min_=1.4))
line.extend_axis(yaxis=opts.AxisOpts(type_='value', name='客单价', position='right', min_=100))
line.extend_axis(yaxis=opts.AxisOpts(type_='value', name='连带率', position='right', offset=50, min_=1))

line.set_global_opts(
    title_opts=opts.TitleOpts(title='各月平均销售额、销售量、客单价、连带率', pos_left=200),
    xaxis_opts=opts.AxisOpts(boundary_gap=False, axistick_opts=opts.AxisTickOpts(is_align_with_label=True)),
    yaxis_opts=opts.AxisOpts(name='平均\n销售额', min_=100),
    tooltip_opts=opts.TooltipOpts(trigger='axis'),
    legend_opts=opts.LegendOpts(orient='vertical', pos_right=100)

)
line.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
line.render_notebook()

在这里插入图片描述
与销售额变动最为一致的是订单量,客单价和连带率虽有变化,但波动范围较小,下面继续拆分销售额

3 销售额拆解(新老客差别在订单数)

销售额,按新老顾客分类,计算顾客数、客单价、件单价和连带率,件单价*连带率=客单价
在这里插入图片描述

t7 = data.pivot_table(index='买家昵称',columns='month',values='购买数量',aggfunc='count')
t7 = t7.applymap(lambda x: 1 if x>0 else 0)  # 各月消费过的客户记为1
data['顾客类别'] = 0
# 标记新顾客和老顾客
users = []
for m in t7.columns:  
    curr_users = t7.index[t7[m]==1]  # 本月购买者
    new_users = curr_users[[u not in users for u in curr_users]]  # 本月购买的没在之前的购买者中
    users.extend(new_users)
    data['顾客类别'][data['month']==m] = np.where(data['买家昵称'].isin(new_users),'new','old')
unique = lambda x : x.nunique()
sales = data.groupby(['month','顾客类别']).agg({'实付金额':'sum', '购买数量':'sum','买家昵称':unique})
sales.columns = ['销售额', '销量', '顾客数']
sales['件单价'] = round(sales['销售额'] / sales['销量'],1)
sales['连带率'] = round(sales['销量']/ sales['顾客数'],1)
sales['客单价'] = round(sales['件单价'] * sales['连带率'],1)
sales.reset_index(inplace=True)
sales

在这里插入图片描述

b = Bar(init_opts=opts.InitOpts(height='350px', width='850px'))

b.add_xaxis(t1.index.strftime('%Y-%m').tolist()[1:])

for col,i,j in zip(['销售额', '顾客数'],['stack0','stcak1'],[0,1]):
    for c in ['new','old']:
        b.add_yaxis(c+'_'+col, sales[sales['顾客类别']==c][col].tolist(),gap=0, stack=i,yaxis_index=j,label_opts=opts.LabelOpts(is_show=False))
b.extend_axis(yaxis=opts.AxisOpts(name='顾客数',position='left',offset=50))
b.extend_axis(yaxis=opts.AxisOpts(name='客单价',position='right'))
b.set_global_opts(
    title_opts=opts.TitleOpts(title='销售额=顾客数*客单价', pos_left=250),
    yaxis_opts=opts.AxisOpts(name='销售额', min_=100),
    tooltip_opts=opts.TooltipOpts(trigger='axis'),
    legend_opts=opts.LegendOpts(pos_bottom=0)

)
l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis(t1.index.strftime('%Y-%m').tolist()[1:])
for c in ['new','old']:
    l.add_yaxis(c+'-客单价', sales[sales['顾客类别']==c]['客单价'].tolist(), yaxis_index=2,label_opts=opts.LabelOpts(is_show=False))

b.overlap(l).render_notebook()

在这里插入图片描述

l = Line(init_opts=opts.InitOpts(height='350px', width='950px'))

l.add_xaxis(t1.index.strftime('%Y-%m').tolist()[1:])

for col,i in zip(['件单价', '连带率'],[1,2]):
    for c in ['new','old']:
        l.add_yaxis(c+'_'+col, sales[sales['顾客类别']==c][col].tolist(),yaxis_index=i,label_opts=opts.LabelOpts(is_show=False))
l.extend_axis(yaxis=opts.AxisOpts(name='件单价',position='right'))
l.extend_axis(yaxis=opts.AxisOpts(type_='value',name='连带率',position='right',offset=50,max_=4))
l.set_global_opts(
    title_opts=opts.TitleOpts(title='客单价=件单价*连带率', pos_left=300),
    yaxis_opts=opts.AxisOpts(name='客单价', min_=100),
    tooltip_opts=opts.TooltipOpts(trigger='axis'),
    legend_opts=opts.LegendOpts(pos_bottom=0)

)
b =Bar(init_opts=opts.InitOpts(height='350px', width='850px'))
b.add_xaxis(t1.index.strftime('%Y-%m').tolist()[1:])
for c in ['new','old']:
    b.add_yaxis(c+'-客单价', sales[sales['顾客类别']==c]['客单价'].tolist(),gap=0, yaxis_index=0,label_opts=opts.LabelOpts(is_show=False))


l.overlap(b).render_notebook()

在这里插入图片描述
新客户占比较高,在5-6月,老客户占比提升,老客户又可分为活跃与回流,下面分析每月客户的构成,重点查看老客户中的回流和活跃占比情况

4 每月客户的构成(老客的活跃与回流)

新用户、活跃用户、不活跃用户、回流用户 回流占比:回流用户在总用户中的占比;活跃占比:活跃用户在总用户中的占比

新用户:当前时期有消费,并且是第一次消费或之前时期没有消费
活跃用户:当前时期有消费,并且在上个时期也消费了
不活跃用户:当前时期没有消费,但在之前时期有过消费
回流用户:当前时期有消费,但在之前时期有过消费,但上个时期没有消费

​分析:
如果本月消费了:new/active/return
是首月 new
不是首月 上个月是unactive return
不是首月 上个月是unreg new 表示一直没有购买
不是首月 上个月是其它(new/return) active # 表示本月和上月都购买了

如果本月没有消费:unreg/unactive
是首月 unreg
不是首月 上个月是unreg unreg
不是首月 上个月不是unreg unactive

def active_status(users):
    status = []
    for i in range(len(dates)):
        if users[i] == 0:  # 若本月没有消费
            if len(status) > 0:  # 并且不是首月
                if status[i-1] == 'unreg':  # 并且上个月是 unreg
                    status.append('unreg')
                else:
                    status.append('unactive')
            else:
                status.append('unreg')
        else: # 若本月消费
            if len(status) == 0:  # 是首月
                status.append('new')
            else:  # 不是首月
                if status[i-1] == 'unactive':
                    status.append('return')
                elif status[i-1] == 'unreg':
                    status.append('new')
                else:
                    status.append('active')
    return status
​
t10 = t7.apply(active_status,axis=1)
# 'unreg'不计数,它是一直没有购买的标记
t10 = t10.replace('unreg',np.nan).apply(pd.value_counts).fillna(0)
t10

在这里插入图片描述

t10_ = round(100*(t10/t10.sum()).loc[['active','return']],2)
t10_

在这里插入图片描述

l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis(t10.columns.strftime('%Y-%m').tolist())
for state in t10.index:
    l.add_yaxis(state, t10.loc[state].tolist(), stack='stack',is_smooth=True,
                areastyle_opts=opts.AreaStyleOpts(opacity=0.8))
l.add_yaxis('活跃占比', t10_.loc['active'].tolist(), yaxis_index=1, symbol='pin',symbol_size=10)
l.add_yaxis('回流占比', t10_.loc['return'].tolist(), yaxis_index=1,symbol='pin',symbol_size=10)
l.extend_axis(yaxis=opts.AxisOpts(name='活跃/回流占比(%)'))
l.set_global_opts(
    title_opts=opts.TitleOpts(title='各类型用户数量及比例',subtitle='新用户、活跃用户、不活跃用户、回流用户',pos_left='300px'),
    xaxis_opts=opts.AxisOpts(boundary_gap=False),
    yaxis_opts=opts.AxisOpts(name='用户数量'),
    tooltip_opts=opts.TooltipOpts(trigger='axis'),
    legend_opts=opts.LegendOpts(pos_bottom='10px')
                 )
l.set_colors(['#006699','#3399CC','#0099CC','#66CCFF','#FF6600','#990000'])
l.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
l.render_notebook()

在这里插入图片描述

结论1:

    两次及以上购买的客户的平均购买周期为71.31天,占统计期的40%,在60天和156天出现异常值,和两次购买间隔分布的结果一致,查看原始数据,在2-17号下单的顾客,又在4-18日下单的。在1-11号下单的,在6-16号又下了单,根据顾客数量判断不是刷单行为。

1 交易状况
各指标变化情况如下:
(1)每单购买量:平均1.52件,70%为1件,19%为2件;1月份最高为1.6,此后一直平稳在1.5;
(2)连带率:平均为1.66件,且半年间保持同一水平;
(3)每单的平均销售额和客单价:平均消费额度126.34元,客单价138.13元;持续上升至3月份,随后逐渐下降;
(4)销售额和销量:从1月持续下降至4月,此后开始回升;

** 2 交易额变化分析**:
通过购买周期的计算,发现销售额的变化并非受购买周期的影响
(1) 内部因素:
将销售额按新客和老客区分,并将销售额分解为顾客数件单价连带率,通过图可知各月的顾客数中新顾客占比在90%左右,是主要购买力。此外的老顾客中,回流用户占比偏大(2月份不能算),回流占比呈波动上升,在6月份达到最高为1.99%,而活跃用户占比仅在开始的2月份偏高,为1.11%,此后下滑,维持在0.2%左右;
3-5月新客客单价均较1-2月份高,因此客单价不是导致3-4月销售额下降的原因。在客单价的拆解过程中,新顾客的件单价呈上升趋势,优于老顾客的件单价,但连带率却低于老顾客,最终导致3-4月的老客的客单价高于新客客单价。
因此3-4月销售额过低的原因在于:1月年货节的促销提前透支了销售额。店铺销售额过分依赖新顾客,在3-4月份,新客大量减少,且新顾客连带率要低于老顾客导致其客单价较低,由此导致销售额大幅下降
5-6月销售额升高的原因在于:猜测可能是由于对老客采取了唤回措施,使得老客户回流
解决措施:
增加老顾客比例:老顾客通常忠诚度更高,其连带率高于新顾客,但其占比极低,可以从商品、服务、店铺设计等方面查找原因
提高新顾客的连带率:新顾客是销售额的主要贡献者,新顾客连带率稳定在1.5左右,可考虑在销量较高的一些商品上推出组合套餐等方式,提高购买量。
(2) 外部因素:
整体经济变化、行业变化、竞争对手等。

三 顾客交易行为

上述分析推断用户流失情况严重,由于数据所限,无法计算流失率,但可以通过留存反映。
1 留存 /各月顾客构成(新增/留存)
2 以月为时期的复购率、回购率;距离首单复购间隔天数和金额;两次购买间隔平均天数分布
3 用户数与销售额曲线

1 留存 /各月顾客构成(新增/留存)

months = [str(i) for i in data.month.unique()]
liucun = []
for i in range(len(months)):
    users = []
    if i >0:       
        users_past = data.loc[:dates[i-1], '买家昵称'].unique()  # 之前的购买者
        users_current = data.loc[dates[i], '买家昵称'].unique()  # 本月购买者
        new_users = users_current[[u not in users_past for u in users_current]]  # 本月新增
        users.append(len(new_users))
    else:  # 首月的话,新增为首月本身客户数
        new_users = data.loc[dates[0], '买家昵称'].unique()
        users.append(len(new_users))
        
    if i < len(dates)-1:
        for j in range(i+1,len(dates)): 
            users_next = data.loc[dates[j], '买家昵称'].unique()  # 后面月份的购买者
            maintains = new_users[[u in users_next for u in new_users]]  # 留存的
            users.append(len(maintains)) 
    liucun.append(users)  # liucun.append([np.nan]*(6-len(users))+users)  liucun2的格式
liucun = pd.DataFrame(liucun,columns=['新增顾客']+(['+%s'%str(i+1) for i in range(len(dates)-1)]),index=dates)
# liucun2 = pd.DataFrame(liucun,columns=dates,index=dates, )
liucun
# 新增的用户在下月的留存量很少,用户会沉寂3-4个月,如1月的留存顾客在6月份又活跃

在这里插入图片描述

liucun.div(liucun['新增顾客'], axis=0)[1:]  # 留存占新增的比例

在这里插入图片描述

liucun2 = []
for i in [5,4,3,2,1,0]:
    liucun2.append(np.diagonal(liucun.values[:,::-1], i).tolist())
liucun2 = pd.DataFrame(liucun2, index=liucun.index, columns=liucun.index)
liucun2  # 每一行为该月顾客组成(来自哪个月的或新增)

在这里插入图片描述

for i in range(1,6):
    print('%s新顾客占比:%s%%' %(months[i],round(100*liucun2.iloc[i,i]/liucun2.loc[months[i]].sum(),2)))

在这里插入图片描述

b = Bar(init_opts=opts.InitOpts(height='350px', width='600px'))
b.add_xaxis(liucun2.index.tolist())
for col in liucun2.columns:
    b.add_yaxis('%s顾客'%col, liucun2[col].tolist(), stack='stack')
​
b.set_global_opts(
    title_opts=opts.TitleOpts(title='客户留存情况',subtitle='每月客户来源'),
    legend_opts=opts.LegendOpts(orient='vertical',pos_right=20)
)
b.set_colors(['#006699','#3399CC','#0099CC','#66CCFF','#99CCFF','#CCCCFF'])
b.render_notebook()

在这里插入图片描述

2 复购率、回购率

复购率:在某窗口时期内消费两次及以上的用户在该时期总消费用户中的比例
回购率:在某窗口时期内消费过的客户在下一个时期依然消费的比例
留存率:每月新增用户,在未来月份中的留存比例

复购率

t6 = data.pivot_table(index='买家昵称',columns='month',values='购买数量',aggfunc='count')
t6 = t6.applymap(lambda x: 1 if x>=2 else 0 if x == 1 else np.nan)  # 购买>=2为1,=1为0,其余为空
fugou = round(100*(t6.sum(axis=0)/t6.count()),2)  # sum count 函数,不计空值

回购率

t7 = data.pivot_table(index='买家昵称',columns='month',values='购买数量',aggfunc='count')
t7 = t7.applymap(lambda x: 1 if x>0 else 0)  # 各月消费过的客户记为1


# 每月顾客是否回购1,未回购0,未购买np.nan
def huigou(user):
    status = []
    for i in range(len(user)-1):
        if user[i] == 1:
            if user[i+1] == 1:  # 如果本月和下月都购买了
                status.append(1)
            if user[i+1] == 0: # 如果本月购买了,但下月没购买
                status.append(0)
        else:  # 如果本月没购买
            status.append(np.nan)
    status.append(np.nan)  # 长度填充至一致,最后一个月不用计算回购率
    return status
​
huigou = t7.apply(huigou,axis=1)  # 应用在每行上
huigou = round(100*(huigou.sum()/huigou.count()),2)
# from pyecharts.charts import Line
l = Line(init_opts=opts.InitOpts(height='350px', width='600px'))
l.add_xaxis(fugou.index.strftime('%Y-%m').tolist())
l.add_yaxis('复购率', fugou.tolist(), is_smooth=True, symbol='pin',symbol_size=15)
l.add_yaxis('回购率', huigou.tolist(), is_smooth=True, symbol='pin',symbol_size=15)
l.set_global_opts(
    title_opts=opts.TitleOpts(title='各月复购及回购率'),
    xaxis_opts=opts.AxisOpts(boundary_gap=False),
    tooltip_opts=opts.TooltipOpts(trigger='axis')
)
l.set_series_opts(
    markpoint_opts=opts.MarkPointOpts(
        data=[opts.MarkPointItem(type_='max'), opts.MarkPointItem(type_='min')]
    )
                 )
l.render_notebook()

在这里插入图片描述

距离首单复购间隔天数和金额

# 计算距离首单间隔天数并分段
first_buy = data.groupby('买家昵称')[['付款日期']].min()
fugou2 = pd.merge(data[['买家昵称','购买数量','实付金额']],first_buy,
                       left_on='买家昵称',right_index=True,how='inner')
fugou2['order_diff'] = (fugou2.index-fugou2['付款日期']).dt.days
fugou2['order_diff_bin'] = pd.cut(fugou2['order_diff'],bins=[0,3,7,15,30,60,90,fugou2['order_diff'].max()])# 平均消费额度和间隔天数
t8 = fugou2.pivot_table(index='买家昵称',columns='order_diff_bin',values='实付金额',aggfunc='sum')
t8 = t8.agg(['mean','count'])
t8

在这里插入图片描述

b = Bar(init_opts=opts.InitOpts(height='350px', width='850px'))
b.add_xaxis(['(0, 3]', '(3, 7]', '(7, 15]', '(15, 30]', '(30, 60]', '(60, 90]', '(90, 173]'])
b.add_yaxis('平均消费额度', round(t8.loc['mean'],2).tolist(), yaxis_index=0, label_opts=opts.LabelOpts(is_show=False))
b.add_yaxis('频率', t8.loc['count'].tolist(), yaxis_index=1, label_opts=opts.LabelOpts(is_show=False))
​
b.extend_axis(yaxis=opts.AxisOpts(type_='value', name='顾客数', position='right'))
b.set_global_opts(
    title_opts=opts.TitleOpts(title='距离首单复购间隔天数分布和平均消费额度'),
    yaxis_opts=opts.AxisOpts(name='平均消费额度'),
)
b.render_notebook()

在这里插入图片描述

两次购买间隔平均天数分布

def diff_g(g):
    g = g.sort_index()
    return g['order_diff'].diff()  # 时间间隔
last_diff = fugou2.groupby('买家昵称').apply(diff_g)
print('顾客消费平均间隔为:%s天'%round(last_diff.mean(),2))
t9 = last_diff.value_counts().sort_index()[1:]

顾客消费平均间隔为:52.08天

l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis([str(i) for i in t9.index.tolist()])
l.add_yaxis('', t9.tolist(), is_smooth=True, symbol='pin',symbol_size=10)
​
l.set_global_opts(
    title_opts=opts.TitleOpts(title='两次购买间隔天数分布'),
    xaxis_opts=opts.AxisOpts(name='间隔天数',boundary_gap=False,axistick_opts=opts.AxisTickOpts(is_align_with_label=True)),
    yaxis_opts=opts.AxisOpts(name='订单数'),
    tooltip_opts=opts.TooltipOpts(trigger='axis')
)
l.set_series_opts(
    markpoint_opts=opts.MarkPointOpts(
        data=[opts.MarkPointItem(type_='max'), opts.MarkPointItem(type_='min')]
    )
                 ) 
l.render_notebook()

在这里插入图片描述

找到60天后再次购买的顾客

last_diff[last_diff==60].reset_index()['付款日期'].dt.date.value_counts().sort_index()

在这里插入图片描述

yichang = fugou2[(fugou2.index.strftime('%Y-%m-%d')=='2019-04-18') & (fugou2['买家昵称'].isin(last_diff[last_diff==60].reset_index()['买家昵称']))]
print('买家数量:', yichang['买家昵称'].nunique(),'订单数:', len(yichang))
# 60的:4-18日很多订单,并且几乎都在2-17号下了首单;156的:在1-11号、6-16号下了单

买家数量: 49 订单数: 54

3 用户数与销售额曲线

t11 = data.groupby('买家昵称')[['实付金额']].sum().sort_values(by='实付金额',ascending=False).reset_index()
t11['cumsum'] = t11['实付金额'].cumsum()
t11['prop'] = round(100*t11['cumsum']/t11['cumsum'].max(),2)  # 比例
x = t11[t11['prop']<=80].index.argmax()+1  # 80%所对的用户数
print('前{}位({}%)客户贡献了80%的销售额'.format(x,round(100*x/len(t11),2)))
t11[:3]

在这里插入图片描述

l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis(list(range(1,len(t11)+1)))
l.add_yaxis('', list(np.linspace(0,100,len(t11))), color='k',symbol='none')
l.add_yaxis('', t11['prop'].tolist(),symbol='none')
l.set_global_opts(
    title_opts=opts.TitleOpts(title='用户数与销售额曲线'), 
    xaxis_opts=opts.AxisOpts(name='用户数'),yaxis_opts=opts.AxisOpts(name='累计销售额占比'),
    )
l.set_series_opts(
    label_opts=opts.LabelOpts(is_show=False),
    markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(coord=[14624,80],value=80)])
)
l.render_notebook()

在这里插入图片描述

结论2:

复购、回购、留存
(1)以月为窗口期的复购率在2%-4.7%之间,1月份用户重复购买行为较多,复购率达4.6%,此后逐渐降低,至5-6月份稳定在2.68%;
(2)回购率在0.98%-2.2%之间,2-3月最低,5月份的回购率最高,其次是1月份,顾客在下月重复购买次数较高
(3)复购率高于回购率。即用户的重复购买行为通常在本月内发生,很少发生的下月,但在5月份,复购率和回购率差距最小,可能是6月份的年中促销使得一部分顾客选择推迟购买时间。
(4)新用户增加量在2月和6月较高;新顾客首月留存率偏低,在0.8%-1.5%之间;

其它
(1)顾客两次购买的时间间隔平均为52天,在两个星期内再次购买的人占30%
(2)顾客的消费能力存在差异但比较小,前57.5%的客户贡献了全部销售额的80%

建议
(1)根据下单高峰期,设计商品上下架时间
(2)根据复购、回购及下单时间间隔,建议在购买的1天、7天及14天后推送消息,提升老顾客比例
店铺主要问题是难以留下新客,老顾客的活跃度也较低,需要从商品价格、店铺设计、物流、售后等方面找出原因。

四 顾客分类

找到有流失迹象的客户。
使用KMeans方法分别对每个特征进行聚类,根据聚类结果中各类均值大小作为各类得分的依据,得到r_score,f_score,m_score,之后再将分值分为大小两类,根据类别组合对客户打标签。

客户标签如下所示:
R F M 客户类型
大 大 大 重要价值用户
小 大 大 重要价值流失预警客户
大 小 大 重要发展用户
小 小 大 高消费唤回客户

大 大 小 消费潜力客户
小 大 小 一般保持客户
大 小 小 一般发展客户
小 小 小 流失客户

rfm = data.groupby('买家昵称')[['付款日期','实付金额']].agg({'付款日期':'max','实付金额':'sum'})
rfm['r'] = (pd.to_datetime('2019-07-01') -rfm['付款日期'] ).dt.days
​
# F:这里统计有多少天下了单,而不是下了几单
f = data.groupby('买家昵称').apply(lambda x: len(pd.unique(x.index))).reset_index()
rfm = pd.merge(rfm,f,right_on='买家昵称',left_index=True,how='inner')
rfm.columns = ['付款日期','m','r','买家昵称','f']
rfm = rfm[['r','f','m']]
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

error = []
for col in ['r','f','m']:
    e = []
    for i in range(1, 10):
        model = KMeans(n_clusters=i)
        model.fit(rfm[col].values.reshape(-1, 1))
        e.append(model.inertia_)
    error.append(e)

pd.DataFrame(error,columns=range(1, 10), index=['r','f','m']).T.plot.line(figsize=(18,6), subplots=True, sharey=False,layout=(1,3))
# n_cluster分别为4,4,6

在这里插入图片描述

for col,n_cluster in zip(['r','f','m'], [4, 4, 6]):
    model = KMeans(n_clusters=n_cluster)
    model.fit(rfm[col].values.reshape(-1, 1))
    rfm[col+'_s'] = model.labels_

rfm.head()

在这里插入图片描述

def get_score(rfm, col_s, col, ascending):
    t = rfm.groupby(col_s)[col].mean().sort_values(ascending=ascending).reset_index()
    t[col+'_s3'] = list(range(1, len(t)+1))
    rfm = pd.merge(rfm, t[[col_s,col+'_s3']], on=col_s)
    rfm.drop(columns=[col_s], inplace=True)
    return rfm
rfm = get_score(rfm, 'r_s', 'r', False)
rfm = get_score(rfm, 'f_s', 'f', True)
rfm = get_score(rfm, 'm_s', 'm', True)
rfm.head()

在这里插入图片描述

level3 = rfm[['r_s3','f_s3','m_s3']].apply(lambda x: x-x.mean()).applymap(lambda x:1 if x>=0 else 0)
label3 = (level3['r_s3'] * 100) + (level3['f_s3'] * 10) + (level3['m_s3'] * 1)
rfm['label3'] = label3.map(d2)
p3 = rfm['label3'].value_counts()
p3

在这里插入图片描述

r3= rfm.groupby('label3').mean()
r3 = (r3 - r3.min()) / (r3.max() - r3.min())
rfm_radar(r3)

在这里插入图片描述

哪类顾客消费金额高

# 横轴为F,纵轴为R,颜色深浅表示金额大小,
c1 = rfm.groupby(['f_s3','r_s3'])[['m']].mean().unstack(level=0).sort_index(ascending=False)
c1

在这里插入图片描述

h = HeatMap(init_opts=opts.InitOpts(height='350px', width='550px'))
h.add_xaxis([1,2,3,4])
h.add_yaxis('', c1.index.tolist(), [[i,j,round(c1.iloc[j,i],2)]for i in range(c1.shape[0]) for j in range(c1.shape[1])])
h.set_global_opts(title_opts={'text':'方法三RF得分下的金额'},visualmap_opts=opts.VisualMapOpts(min_=100,max_=600,pos_right=0,pos_top=50),
                 xaxis_opts=opts.AxisOpts(name='F得分'),yaxis_opts=opts.AxisOpts(name='R得分'))
h.render_notebook()

在这里插入图片描述

c2 = rfm.groupby('label3')[['r','f','m']].agg({'r':'mean','f':'mean','m':['mean','sum','count']})
c2.columns = ['r平均','f平均','m平均','消费总金额','人数']
c2['人数占比'] = round(100*c2['人数']/c2['人数'].sum(),2)
c2 = c2.sort_values(by='消费总金额',ascending=False)
c2

在这里插入图片描述

五 订单时间、地点、价格分布

1 用户下单时间段

异常值查看

查看销售规律,获得一天之内和一周之内的销售规律,在此之前需要剔除异常值,防止因极大值拉高,这些异常值可能出现在节假日、店庆等一系列活动中。

abnormal = data.groupby(data['付款日期'].dt.date)['实付金额'].sum()
l = Line(init_opts=opts.InitOpts(height='450px', width='950px'))
l.add_xaxis(abnormal.index.tolist())
l.add_yaxis('', abnormal.tolist())
l.set_global_opts(tooltip_opts=opts.TooltipOpts(trigger='axis'),datazoom_opts=opts.DataZoomOpts())
l.set_series_opts(markline_opts=opts.MarkLineOpts(data=[{'yAxis':60000}]),label_opts=opts.LabelOpts(position='end'))
l.render_notebook()

在这里插入图片描述

m = abnormal.mean()
std = round(abnormal.std(),2)
print('均值为%s,方差为%s,三倍标准差内为%s-%s' %(m, std,m-std, m+std))
# 极大值日期
abnormal.index[abnormal>60000]

均值为19507.25,方差为40497.5,三倍标准差内为-20990.25-60004.75
Index([2019-01-11, 2019-02-17, 2019-02-18, 2019-02-19, 2019-04-18, 2019-06-16], dtype=‘object’, name=‘付款日期’)

selected_date = abnormal.index[abnormal<60000]
data3 = data[data['付款日期'].dt.date.isin(selected_date)]
r1 = data3.groupby('week')['买家昵称'].count()
r1

week
0 2235
1 2324
2 2879
3 2683
4 2800
5 3007
6 2323
Name: 买家昵称, dtype: int64

周下单时间

l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis(['周一','周二','周三','周四','周五','周六','周日'])
l.add_yaxis('', r1.tolist())
l.set_global_opts(title_opts=opts.TitleOpts(title='周销售规律'))
l.render_notebook()

在这里插入图片描述

一天内下单时间

# 每30min统计一次
def f(g):
    time_30min = g['购买数量'].resample('30min',closed='left',label='left').sum()  # 每30分钟为一组求和    
    delta = pd.Timedelta('30min')  # 一组30分钟
    time_30min.rename(index=lambda t: t.strftime('%H:%M') + '-' + (t+delta).strftime('%H:%M'), inplace=True)
    return time_30min

minutes30 = data3.groupby(data3.index.date).apply(f).sum(level=1)  
# 按天分组,每天计算30min内销量,结果为层次索引的dataframe,再根据内层索引分组,将相同30min内的数据合并
l = Line(init_opts=opts.InitOpts(height='350px', width='850px'))
l.add_xaxis(minutes30.index.tolist())
l.add_yaxis('', minutes30.tolist(), is_smooth=True, symbol='pin',symbol_size=15,)
l.set_global_opts(
    title_opts=opts.TitleOpts(title='天销售规律'),
    xaxis_opts=opts.AxisOpts(boundary_gap=False),
    tooltip_opts=opts.TooltipOpts(trigger='axis')
)
l.set_series_opts(
    markpoint_opts=opts.MarkPointOpts(
        data=[opts.MarkPointItem(type_='max'), opts.MarkPointItem(type_='min')]
    )
                 )
l.render_notebook()
# 下单时间在凌晨,上午10:00-11:00较高

在这里插入图片描述

2 各月每笔订单的金额、数量分布

data['实付金额'].describe()

在这里插入图片描述

data['金额分组'] = pd.cut(data['实付金额'], bins=[0, 100, 150, 200, 300, data['实付金额'].max()],
                      labels=['100以下', '100-150','150-200','200-300','300以上'])
data['num'] = 1
t2 = data.pivot_table(index='month',columns='金额分组', values='num', aggfunc='count')
t2

在这里插入图片描述

t3 = data.pivot_table(index='month',columns='购买数量', values='num', aggfunc='count')
t3['5件及以上'] = t3.loc[:,5:].sum(1)  # 合计5件及以上的
t3 = t3.iloc[:,[0,1,2,3,-1]]
t3

在这里插入图片描述

from pyecharts.charts import Bar,Grid
bar1 = Bar()
bar1.add_xaxis(t2.columns.tolist())
for i in t2.index.strftime('%Y-%m').tolist():
    bar1.add_yaxis(i, t2.loc[i].tolist(), label_opts=opts.LabelOpts(is_show=False))
bar1.set_global_opts(
    title_opts=opts.TitleOpts(title='单笔订单金额分布'),
    yaxis_opts=opts.AxisOpts(name='订单数量'),
    legend_opts=opts.LegendOpts(orient='vertical', pos_right=0)
                   )
bar1.set_colors(['#006699','#3399CC','#0099CC','#66CCFF','#99CCFF','#CCCCFF'])
bar2 = Bar()
bar2.add_xaxis(t3.columns.tolist())
for i in t3.index.strftime('%Y-%m').tolist():
    bar2.add_yaxis(i, t3.loc[i].tolist(),xaxis_index=1, yaxis_index=1, label_opts=opts.LabelOpts(is_show=False))
bar2.set_global_opts(
    title_opts=opts.TitleOpts(title='单笔订单购买件数分布', pos_top="55%"),
    xaxis_opts=opts.AxisOpts(position='top'),
    yaxis_opts=opts.AxisOpts(name='订单数量', is_inverse=True),
    legend_opts=opts.LegendOpts(orient='vertical', pos_right=0),
    tooltip_opts=opts.TooltipOpts(trigger='axis')
                   )
bar2.set_colors(['#006699','#3399CC','#0099CC','#66CCFF','#99CCFF','#CCCCFF'])
grid = Grid(init_opts=opts.InitOpts(height='450px', width='850px'))
grid.add(chart=bar1,grid_opts=opts.GridOpts(pos_left=50, pos_right=50, height="35%"))
grid.add(chart=bar2,grid_opts=opts.GridOpts(pos_left=50, pos_right=50, pos_top='65%', height="35%"))
​
grid.render_notebook()
# 较多100元以下,300以上的稀少,其它价格区间的占20%左右(t2['100-150']/t2.sum(1));购买数量大部分为1-2件

在这里插入图片描述

3 不同地区的订单量、价格偏好

t4 = data.pivot_table(index='省份',columns='金额分组', values='num', aggfunc='count').fillna(0)
order = t4.sum(1)
t4 = round(t4.div(t4.sum(1),0),3)  # 横向占比
t4.head()

在这里插入图片描述

总订单量前12的省份订单量

前12个省份订单量占据总订单量的80%以上

(order.sort_values(ascending=False).cumsum()>= order.sum()*0.8).values.argmax()

11

from pyecharts.charts import Pie

data_pair = [[i, order.loc[i]] for i in order.sort_values(ascending=False)[:12].index]
p = Pie(init_opts=opts.InitOpts(height='350px', width='750px'))
p.add('',data_pair, radius=['90%','40%'],center=['30%','50%'], label_opts=opts.LabelOpts(position='inside'))
p.set_global_opts(
    title_opts=opts.TitleOpts(title='总订单量前12的省份'),
    legend_opts=opts.LegendOpts(orient='vertical',pos_right=50),
    tooltip_opts=opts.TooltipOpts(trigger='item', formatter='{b}:{c}({d}%)'),
                 )
p.render_notebook()

在这里插入图片描述

前12省份价格偏好和价位订单差距

t4_ = t4.loc[order.sort_values(ascending=False)[:12].index]
from pyecharts.charts import HeatMap
h = HeatMap(init_opts=opts.InitOpts(height='450px', width='850px'))
h.add_xaxis(t4_.index.tolist())
h.add_yaxis('', t4_.columns.tolist(), value=[[i,j,t4_.iloc[i,j]] for i in range(t4_.shape[0]) for j in range(t4_.shape[1])])
h.set_global_opts(title_opts=opts.TitleOpts(title='前12省份价格偏好'), visualmap_opts=opts.VisualMapOpts(max_=0.6,pos_right=0))
h.render_notebook()

在这里插入图片描述

前12省份价位订单差距排名

相邻价格区间的占比差距求和

t5 = pd.Series([0],index=t4_.index)
for i in range(t4_.shape[1]-2):  # 300元以上的不计
    b = abs(t4_.iloc[:, i]-t4_.iloc[:,i+1])
    t5 = t5+b
t5 = t5.sort_values(ascending=False)
t5

在这里插入图片描述

结论3:

下单时间:顾客下单时间在周六、周三较高;一天之内在下午下单量高于上午,10-11点最高;
价格分布:总体来看,每笔订单的金额46.9%为100元以下,除300元以上外,其它每个价格区间占20%左右。其中,1月份100元以下订单占比最高,达56%,其次是6月份,为46%;
地区
(1)前12个省份(38.7%)订单量占据总订单量的80%以上,其中订单最多的为上海,占20%;
(2)各地区在价格方面的偏好较为一致,100元以下的订单量占据各省订单量的50%左右,且订单量越大的地区对低价格区间越偏好,导致价位订单量之间的差距相对更大;
(3)可根据地区订单差异提前分配库存。

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页