CD网站-用户行为分析

数据源:数据来源于网上,是用户在一家CD网站上的消费记录。
链接:https://pan.baidu.com/s/1s1Ep6x5K6bJMLmpb8I_CWg
提取码:am5t

分析维度如下:

  • 用户消费趋势分析
  • 用户个体消费分析
  • 用户消费行为分析
  • 复购率和回购率的分析

数据集的读入和观察

import pandas as pd
import numpy as np
#用户id,购买日期,购买产品数,购买金额
columns = ["user_id","order_dt","order_products","order_amount"]
df = pd.read_table('CDNOW_master.txt',names = columns,sep = '\s+') 
#此处用s+,是因为我们有多个字符,是由空格进行分割的 

df.info()    #此处我们看到购买日期的类型是int型,说明我们后面需要进行类型转换   
image.png

各字段含义说明:

  • user_id:用户ID
  • order_dt:购买日期
  • order_products:购买产品数
  • order_amount:购买金额
    我们要进行按月的分析,所以需要增加一个字段描述月份。
#转换购买日期的数据类型以及增加一个字段
df['order_dt'] = pd.to_datetime(df.order_dt,format = '%Y%m%d')
df['month'] = df.order_dt.values.astype('datetime64[M]')
df.head()

查看数据的整体分布情况


image.png

大部分订单只是消费了少量商品(平均2.4),有一定极值干扰
用户的消费金额比较稳定,平均消费是35元,中位数在25元,仍然有一定极值干扰

用户消费趋势的分析

#每月消费总金额
grouped_month = df.groupby('month')        #按月进行分组
order_month_amount = grouped_month.order_amount.sum()          #分组后对购买金额求和

import matplotlib.pyplot as plt                  #加载可视化展示包
%matplotlib inline          #魔法函数,将可视化展示出来
plt.style.use('ggplot')            #更改设计风格
order_month_amount.plot()

#每月消费次数
grouped_month.user_id.count().plot()

#每月消费订单量
grouped_month.order_products.sum().plot()

#每月消费客户数
df.groupby('month').user_id.apply(lambda x:len(x.drop_duplicates())).plot()

每月消费总金额趋势.png

每月消费次数趋势.png

每月消费订单量趋势.png

每月消费客户数趋势.png

从上面的图表可以看到,消费金额、消费次数、订单量和客户数在前三个月呈上升趋势,且在3月时达到峰值;
4月开始过山车式猛降,随后几个月都趋于稳定,起伏并不大,总体趋势有轻微下降;
每月消费客户数低于每月消费订单量,但差异并不显著;
前三月每月的消费人数在8000-10000之间,后续月份,平均消费人数在2000人不到;
由此可见平台应该要采取一定措施拉新、留存了;

#每月客单价趋势
#对‘月份’及‘用户id’进行分组求和>>去掉表中的索引打平>>再按月份进行分组>>对每月的消费金额求平均
plt.style.use('ggplot')
df.groupby(['month','user_id']).sum().reset_index().groupby('month')['order_amount'].mean().plot()

#每月用户平均消费次数的趋势
user_c=df.groupby(['month','user_id']).count().reset_index()
user_c['count']=user_c['order_products']
user_c.loc[:,['month','user_id','count']].groupby('month')['count'].mean().plot()

#每月件单价走势
df_jiandanj = df.groupby('month').order_amount.sum()/df.groupby('month').order_products.sum()
df_jiandanj.plot()
客单价趋势.png

月平均消费次数走势.png

件单价走势.png
  • 客单价:
    ①前三个月的客单价最低,根据之前看到消费金额等数据却是最高的,基本可以判定是因为客户数的新增来高销售额;
    ②从4月开始客单价开始提升但存在轻微起伏,可见后面的客户逐渐开始认可产品,并愿意为之付费;
  • 月消费次数:
    总的消费次数都在1-2次之间,但前三月最低,可见大部分客户都只在最初消费了一次;
  • 件单价分析:
    可以看到前三个月件单价是最高的,但随着时间的推移,件单价开始下降,充分说明价格不是导致客户流失的因素;
    用户个体消费分析
#用户消费金额、消费次数的描述统计
grouped_user = df.groupby('user_id')           #以用户进行分组
grouped_user.sum().describe()
image.png

用户平均购买了7张CD,但是中位值只有3,说明小部分用户购买了大量的CD;
用户平均消费了106元,中位值有43,判断同上,有极值干扰;

#消费金额和消费次数的散点图
grouped_user.sum().plot.scatter(x = 'order_amount',y = 'order_products')
#设置过滤条件,更好的展示两者关系
grouped_user.sum().query('order_amount<4000').plot.scatter(x = 'order_amount',y = 'order_products')
初始散点关系.png

微调之后散点关系.png

因为我们的数据都是针对CD的消费做的分析,不难看出,销售金额与销售数量是线性关系;

#用户消费金额的分布图
plt.style.use('ggplot')
grouped_user.sum().order_amount.plot.hist(bins = 20)
#设置过滤条件
grouped_user.sum().query('order_products < 100').order_products.hist(bins = 20)
消费金额初始分布.png

消费金额剔除极值后分布.png

从直方图可知,用户消费金额,绝大部分呈现集中趋势,小部分异常值干扰了判断,所以我们将极值剔除。

#用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)
user_cumsum = grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum() / x.sum())
plt.style.use('ggplot')
user_cumsum.reset_index().order_amount.plot()
plt.title('用户消费金额占比')
plt.xlabel('用户数量')
plt.ylabel('消费金额占比')
image.png

按用户消费金额进行升序排列,由图可知50%的用户仅贡献了15%的消费额度,而排名前500的消费用户就贡献了60%的消费额度,符合消费数据的二八法则。
用户消费行为分析

#用户第一次消费(首购)的趋势图
plt.style.use('ggplot')
grouped_user.min().order_dt.value_counts().plot(figsize = (10,4))
plt.title('用户首购趋势')
plt.ylabel('人数')

#用户最后一次购买趋势
plt.style.use('ggplot')
grouped_user.max().order_dt.value_counts().plot(figsize = (10,4))
plt.title('用户最后一次购买趋势')
plt.ylabel('人数')
image.png

image.png

用户第一次购买的分布,主要集中在前三个月;
其中,在2月15日左右有一次剧烈的波动,具体原因可能与公司运营活动相关;

用户最后一次购买的分布比首次购买分布广;
大部分最后一次购买,主要集中在前三个月,说明有很多用户购买了一次后就不再进行购买;
随着时间的递增,最后一次购买的客户数也在递增,消费呈现流失上升的状况;

#新老客户的消费占比,多少用户仅消费了一次
#将用户首次消费和最后一次消费的时间求出来
user_life = grouped_user.order_dt.agg({'min','max'})
#求出消费一次的用户数
(user_life['min'] == user_life['max']).value_counts()
image.png

下面我们来看重中之重:怎么应用RFM模型对客户进行分层管理运营

rfm = df.pivot_table(index = 'user_id',
                    values = {'order_products','order_amount','order_dt'},
                    aggfunc = {'order_dt':'max',
                               'order_amount':'sum',
                               'order_products':'sum'})
rfm['R'] = -(rfm.order_dt - rfm.order_dt.max()) / np.timedelta64(1,'D')
rfm.rename(columns = {'order_products':'F','order_amount':'M'},inplace = True)
#给每条数据新增标签
def rfm_func(x):
    level = x.apply(lambda x:'1' if x>= 0 else '0')
    label = level.R + level.F + level.M
    d = {
        '111':'重要价值客户',
        '011':'重要保持客户',
        '101':'重要挽留客户',
        '001':'重要发展客户',
        '110':'一般价值客户',
        '010':'一般保持客户',
        '100':'一般挽留客户',
        '000':'一般发展客户'
    }
    result = d[label]
    return result

rfm['label'] = rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis = 1)

rfm.loc[rfm.label=='重要价值客户','color'] = 'g'
rfm.loc[~(rfm.label == '重要价值客户'),'color'] = 'r'
plt.style.use('ggplot')
rfm.plot.scatter('F','R',c=rfm.color)

#查看不同组别客户的详细数据
rfm.groupby('label').sum()

image.png

image.png

从RFM分层可知,大部分用户为重要保持客户,但由于极值的影响,所以RFM的划分标准应该以业务为准:
尽量用小部分的用户覆盖大部分的消费金额,这部分可以作为平台的重点运营客户
不要为了数据好看而去划分出更多的等级;

#新、活跃、回流、流失/不活跃
#按照用户id和月份对数据进行透视,便于看用户的生命周期
pivoted_counts = df.pivot_table(index = 'user_id',
                               columns = 'month',
                               values = 'order_dt',
                               aggfunc = 'count').fillna(0)
#生命周期只看是否有消费,所以我们将数据优化一下,使其只有0,1
df_purchase = pivoted_counts.applymap(lambda x:1 if x>0 else 0)

#定义各种类型的用户,其实这个未注册是可以省略的
def active_status(data):
    status=[]
    for i in range(18):
        #若本月没有消费
        if data[i] ==0:
            if len(status) > 0:
                if status[i-1] == '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')
    data.iloc[0:]=status
    return data

本月没有消费:
若之前是未注册,则依旧为未注册
若之前有消费,则为流失/不活跃
其他情况,是为未注册

本月有消费:
若是第一次消费,则为新用户
如果之前有过消费,上月为不活跃,则为回流
如果之前为未注册,则为新用户
除此之外,为活跃

#将类型函数应用到数据中
purchase_stats = df_purchase.apply(active_status,axis = 1)
#将未注册的用户调整为缺失值,便于对其他类型进行计数
purchase_stats_ct = purchase_stats.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
plt.style.use('ggplot')
purchase_stats_ct.fillna(0).T.plot.area()    #制作面积图,可以更直观的观察走势
plt.title('每月用户活动状态')
plt.ylabel('用户人数')

#查看每个月各类用户的占比
purchase_stats_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis = 1)

image.png

image.png

由上面的图表我们看到,每月的用户消费状态都在变化,且都属于前三个月达到峰值,随后逐渐下降
活跃用户:持续消费的用户,对应的是消费运营的质量;
回流用户:之前不消费本月才消费,对应的是唤回运营;
不活跃用户:对应的是流失;

#求出每月不同状态用户的个数
pivot_user_status=purchase_stats.apply(lambda x:x.value_counts()).fillna(0).T.drop('unreg',axis=1)
(pivot_user_status['return']/pivot_user_status['unactive'].shift()).plot()
plt.title('每月回流用户占比')
plt.ylabel('用户占比')
image.png

每月回流用户占比:指的是上个月不活跃的用户在本月重新活跃的用户占比;
从图可知,用户的整体活跃度在不断下降,用户正在流失。

#用户购买周期(按订单)
#计算每个用户的每笔订单的周期
order_diff = grouped_user.apply(lambda x:x.order_dt - x.order_dt.shift())
#购买周期描述
order_diff.describe()

#购买周期分布图
plt.style.use('ggplot')
(order_diff / np.timedelta64(1,'D')).hist(bins = 20)
plt.title('用户购买周期分布')
plt.xlabel('时间天数')
plt.ylabel('频次')
image.png

image.png

再次证明,只消费一次的用户有很多。

#用户生命周期(按第一次&最后一次消费)
#查看用户生命周期的描述
(user_life['max'] - user_life['min']).describe()

plt.style.use('ggplot')
((user_life['max'] - user_life['min']) / np.timedelta64(1,'D')).hist(bins = 40)

#排除只消费一次的数据
user_life['a0']=(user_life['max']-user_life['min'])/np.timedelta64(1,'D')
plt.style.use('ggplot')
user_life.loc[user_life['a0']>0]['a0'].hist(bins=50)

image.png

image.png

image.png

复购率和回购率的分析
复购率:自然月内,购买多次的用户占比;
回购率(类似于留存率):例如本月购买的用户有100个,到下月仍然购买的有30个,那么回购率则为30%
曾经购买过的用户在某一时期内的再次购买占比;

##求复购,消费次数大于1的则说明是复购,其余不是
purchase_r = pivoted_counts.applymap(lambda x:1 if x > 1 else np.NaN if x == 0 else 0)
plt.style.use('ggplot')
(purchase_r.sum() / purchase_r.count()).plot(figsize = (10,4))
复购率.png

复购率稳定在20%左右,前三个月因为有大量的新用户涌入,而这批用户只购买了一次,所以导致复购率并不高

#回购率的定义
def purchase_back(data):
    status = []
    for i in range(17):
        if data[i] == 1:
            if data[i+1] == 1:
                status.append(1)
            if data[i+1] == 0:
                status.append(0)
        else:
            status.append(np.NaN)
    status.append(np.NaN)    #最后这边是因为我们的数据截止到6月,没有7月的数据进行计算,所以定义为空值
    data.iloc[0:]=status
    return data

本月有消费,下月有消费,为1;
本月有消费,下月无消费,为0;
本月无消费,则为空值。

purchase_b = df_purchase.apply(purchase_back,axis = 1)
plt.style.use('ggplot')
(purchase_b.sum() / purchase_b.count()).plot(figsize = (10,4))
image.png

由此可见,回购率一直在30%左右上下波动。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335