CD案例分析总结

本次案例的数据来源于CDNOW,是美国的一家网上唱片公司,数据描述的是这家公司网站上的用户消费记录,分析目标旨在分析用户消费行为,建立RFM模型,分析复购率、回购率等关键指标。数据字典如下:

  • user_id:用户ID
  • order_dt:购买日期
  • order_products:购买产品数
  • order_amount:购买金额

数据集包含69659条记录,共有4个字段,时间范围从19970101-19980630之间,下面是数据集整理统计描述:


整体描述.png

一、用户消费趋势分析(按月)

分别统计每月的消费总金额、消费次数、产品购买量、消费人数

grouped_month_info = grouped_month[['order_amount','user_id','order_products']].agg({'order_amount':sum, 'user_id': 'count', 'order_products':sum}).reset_index()
grouped_month_info.rename(columns = {'order_amount':'消费金额', 'user_id': '消费次数', 'order_products': '产品购买量'}, inplace=True)
grouped_month['user_id'].unique().map(len)
grouped_month_info['消费人次'] = grouped_month['user_id'].unique().map(len)

月产品购买量.png

由上图可知,消费金额在前三个月达到最高峰,后续消费较为稳定,有轻微下降趋势


月消费金额.png

由上图可知,产品购买量在前三个月达到最高峰,后续消费较为稳定,有轻微下降趋势


月消费人次和消费次数.png

前三个月消费订单人数在10000笔左右,后续月份的平均消费人数则在2500人,每月消费人数低于每月消费次数,但差异不大。

二、用户个体消费分析

1、用户消费金额,消费次数的描述统计

grouped_user=df.groupby('user_id').sum().describe()
out:
order_products  order_amount
count   23570.000000    23570.000000
mean    7.122656    106.080426
std 16.983531   240.925195
min 1.000000    0.000000
25% 1.000000    19.970000
50% 3.000000    43.395000
75% 7.000000    106.475000
max 1033.000000 13990.930000

结果如下:
用户平均购买了7张,但是中位值只有3,说明小部分用户购买了大量货物,用户平均消费106元,中位值有43,判断同上,这说明也有极值干扰。

2、用户消费金额和消费次数的散点图

用户消费金额,消费次数.png

为排除极值干扰,order_amount筛选<4000,由散点图可知,用户消费金额与产品购买量几乎成线性关系,购买的商品越多,消费金额就越大。

3、用户消费金额和频次的分布图

grouped_user_sum_order_amount = grouped_user.sum().order_amount #每个用户的金额
grouped_user_sum_order_amount_lst = [i for i in range(0,int(grouped_user_sum_order_amount.max())+50,50)]
grouped_user_sum_order_amount = pd.cut(grouped_user_sum_order_amount, bins=grouped_user_sum_order_amount_lst,labels = grouped_user_sum_order_amount_lst[1:])
用户消费金额频次.png

从直方图可知,用户消费金额,绝大部分呈现集中趋势,小部分异常值干扰了判断,可以使用过滤操作排除异常, 这里我们使用切比雪夫定理过滤掉异常值,因为切比雪夫定理说明,95%的数据都分布在5个标准差之内,剩下5%的极值就不要了。如下:

order_amount (mean = 106 ,std = 241) mean+5std = 1311

4、用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)

# cumsum 是求累加值
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
累积消费金额曲线.png

按照用户消费金额进行升序排序,由图可以知道50%的用户仅贡献了11%的消费额度,而排名前5000的用户就贡献了60%的消费额度。

三、用户消费行为

1、用户第一次消费(首购)

grouped_user_min = grouped_user.min().order_dt.value_counts().reset_index().rename(columns={'index':'first_date'})
grouped_user_min['first_date'] =grouped_user_min['first_date'].astype(str)
首购分布.png

用户第一次购买分布集中在前三个月。其中,在2月9日到2月25日之间有剧烈的波动。

2、用户最后一次消费

grouped_user_max = grouped_user.max().order_dt.value_counts().reset_index().rename(columns={'index':'last_date'})
grouped_user_max['last_date'] =grouped_user_max['last_date'].astype(str)
最后一次购买分布.png

上图呈现断崖式下跌很正常,可以理解为用户流失比例基本一致,一开始用户迅猛增长数量比较多流失的也比较多,后面没有用户,用户最后一次购买的分布比第一次分布广,大部分最后一次购买,集中在前三个月,说明很多用户购买了一次后就不再进行购买。随着时间的递增,最后一次购买数量也在递增,消费呈现流失上升的状况(这也是正常,随着时间的增长,可能因为运营没有跟上或者用户忠诚度下降了)

3、新老客户消费比

  • 多少用户仅消费一次
#新老客消费比
# 得到第一次和最后yc次消费情况,如果 min、max 日期相同,说明只消费了一次
user_life=grouped_user.order_dt.agg(['min','max'])
# 统计只消费了一次的用户
(user_life['min']==user_life['max']).value_counts()
消费了一次.png

结论得出大概有一半的用户,只消费了一次

  • 每月新客占比
# 按照month、userid分组,第一次和最后一次消费日期
user_life_month=df.groupby(['month','user_id']).order_dt.agg(['min','max']).reset_index()
# 新增is_new字段,用于标记新用户
user_life_month['is_new']=(user_life_month['min']==user_life_month['max'])
# 再次按month分组,计算新用户占比
user_life_month_pct=user_life_month.groupby('month').is_new.apply(lambda x:x.value_counts()/x.count()).reset_index()
# level_1为True的作图
user_life_month_pct[user_life_month_pct.level_1].plot(x='month',y='is_new')

新客占比.png

上图看出1997年1月新用户占比高达90%以上,后续有所下降,并逐渐趋于平稳,1997年4月到1998年6月维持在81%左右,1998年6月以后无新用户。

4、用户分层

  • RFM模型
    R:最后一次消费距今天数,F:消费总金额 ,M:消费总产品数
    为得到最近一次消费,一般是计算 today 距离最近一次消费,这里因为时间太久远,就随便用的max值,数值越大就越久远,分子得到的是一些天数类似 545 days(因为是时间格式相减),再除以一个单位,就不会有单位了,只留下数值,如下:
# 重命名,也就是 R:最后一次消费距今天数
rfm['R']= -(rfm.order_dt - rfm.order_dt.max())/np.timedelta64(1,'D')
#  F:消费金额  M:消费频次
rfm.rename(columns={'order_products':"F",'order_amount':'M'},inplace=True)
# RFM模型
def rfm_func(x):
    level=x.apply(lambda x:'1' if x>=0 else '0')
    # level 的类型是 series,index 是 R、F、M
    #print(level)
    #print(level.index)
    label=level.R + level.F + level.M
    #print(label)
    d={
        # R 为1 表示离均值较远即时间很久,F为1 表示 消费金额比较多,M 为1 表示消费频次比较多,所以是重要价值客户
        '111':'重要价值客户',
        '011':'重要保持客户',
        '101':'重要发展客户',
        '001':'重要挽留客户',
        '110':'一般价值客户',
        '010':'一般保持客户',
        '100':'一般发展客户',
        '000':'一般挽留客户',
    }
    result=d[label]
    return result

# 注意这里是要一行行的传递进来,所以 axis=1,传递一行得到一个 111,然后匹配返回一个值
rfm['label']=rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
# 归结为两类方便得出客户标签
rfm.loc[rfm.label=='重要价值客户','color']='重要价值客户'
rfm.loc[~(rfm.label=='重要价值客户'),'color']='非重要价值客户'
客户标签分布.png

紫色部分是重要价值客户,消费频次较高。

rfm.groupby('label').sum()
 M  F   R
label           
一般价值客户  7181.28     650     36295.0
一般保持客户  19937.45    1712    29448.0
一般发展客户  438291.81   29346   6951815.0
一般挽留客户  196971.23   13977   591108.0
重要价值客户  167080.83   11121   358363.0
重要保持客户  1592039.62  107789  517267.0
重要发展客户  33028.40    1263    114482.0
重要挽留客户  45785.01    2023    56636.0

从RFM 分层可知,大部分用户是重要保持客户,但是这是由于极值的影响,所以 RFM 的划分标准应该以业务为准,也可以通过切比雪夫去除极值后求均值,并且 RFM 的各个划分标准可以都不一样.

  • 新、老、活跃、回流、流失
# 数据透视, userid为索引,月为列,求每月的消费次数,这里填充了
pivoted_counts=df.pivot_table(index='user_id',
                             columns='month',
                             values='order_dt',
                             aggfunc='count').fillna(0)
# 转变一下消费,有消费为1,没有消费为0
df_purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
df_purchase.tail()
# 这里由于进行数据透视,填充了一些 null 值为0,而实际可能用户在当月根本就没有注册,
#这样会误导第一次消费数据的统计,所以写一个函数来处理
# 上面填充了一些null值为0,而实际可能用户在当月根本就没有注册,这样会误导第一次消费数据的统计,所以写一个函数来处理
def active_status(data):
    status=[]
    # 数据一共有18个月份,每次输入一行数据,也就是一个user_id的信息,进行逐月判断
    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')
    return status

关于active_status的说明:

return:回流 new:新客 unreg:未注册 active:活跃
若本月没有消费,这里只是和上个月判断是否注册,有一定的缺陷,应该判断是否存在就可以了

若之前有数据,是未注册,则依旧为未注册
若之前有数据,不是未注册,则为流失/不活跃
若之前没有数据,为未注册
若本月有消费

若是第一次消费,则为新用户
若之前有过消费,上个月为不活跃,则为回流
若之前有过消费,上个月为未注册,则为新用户
若之前有过消费,其他情况为活跃

purchase_stats=df_purchase.apply(lambda x: pd.Series(active_status(x),index=df_purchase.columns),axis=1)
purchase_stats.head()
# 这里把未注册的替换为空值,这样 count 计算时不会计算到
# 得到每个月的用户分布
purchase_stats_ct=purchase_stats.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
purchase_stats_ct
returnratee=purchase_stats_ct.apply(lambda x:x/x.sum(),axis=0)
purchase_stats_ct_info = purchase_stats_ct.fillna(0).T

用户分层.png

前三个月有大量新用户涌入,新用户占比很高,而后面几个月不活跃用户占比非常高,普遍在90%以上。活跃用户,持续消费的用户,对应的是消费运营的质量。回流用户,之前不消费,本月才消费,对应的是唤回运营。不活跃用户,对应的是流失。

5、用户购买周期(按订单)

  • 用户消费周期描述
# 用户购买周期(按订单)
# 计算相邻两个订单的时间间隔,shift 函数是对数据进行错位,所有数据会往下平移一下,所以可以
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
order_diff.head(10)
购买周期.png

描述性统计:


统计.png

6、用户生命周期(按第一次和最后一次消费)

  • 用户生命周期描述


    描述.png
  • 用户生命周期分布


    生命周期1.png

    用户平均生命周期134天,但中位数仅0天。用户的生命周期分布受只购买一次的用户(用户生命周期0天)影响比较严重,可以排除,得到:


    生命周期2.png

    用户平均生命周期276天,中位数302天。

7、复购率

复购率:自然月内,购买多次的用户占比(即,购买了两次以上)

# 区分一个,和一个以上的情况,以便于计算复购率,大于1为1,等于0 为NaN,等于1为0
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()
purchase_r_reshop = (purchase_r.sum()/purchase_r.count()).reset_index(name = 'reshop')
复购率.png

复购率稳定在20%所有,前一个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致复购率降低

8、回购率

回购率:曾经购买过的用户在某一时期的再次购买的占比(可能是在三个月内)

# 需要使用函数来判断是否回购:当月消费过的用户下个月也消费了叫做回购,这个定义可以改变
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)
    # 第18个月补充NaN,因为没有下个月的数据了
    status.append(np.NaN)
    return status

indexs=df['month'].sort_values().astype('str').unique()
purchase_b = df_purchase.apply(lambda x :pd.Series(purchase_back(x),index = indexs),axis =1)
# 回购率,回购的次数/总购买次数
purchase_b_backshop = purchase_b.sum()/purchase_b.count()
回购率.png

回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致回购率较低。

四、小结

1、用户消费趋势(每月)方面,前3个月有大量新用户涌入,消费金额、消费订单数、产品购买量均达到高峰,后续每月较为稳定。前3个月消费次数都在10000笔左右,后续月份的平均2500;前3个月产品购买量达到20000甚至以上,后续月份平均7000;前3个月消费人数在8000-10000之间,后续月份平均2000不到。

2、用户个体消费方面大概符合二八法则,小部分用户购买了大量的CD,拉高了平均消费金额。用户消费金额集中在0100元,有大约17000名用户。用户购买量集中在05个,有大约16000名用户。50%的用户仅贡献了15%的消费额度,15%的用户贡献了60%的消费额度。

3、用户消费行为方面,首购和最后一次购买的时间,集中在前三个月,说明很多用户购买了一次后就不再进行购买。而且最后一次购买的用户数量也在随时间递增,消费呈现流失上升的状况。

4、从整体消费记录来看,有一半的用户,只消费了一次。从每月新用户占比来看,1997年1月新用户占比高达90%以上,后续有所下降,1997年4月到1998年6月维持在81%左右,1998年6月以后无新用户。

5、从RFM模型来看,在8种客户中,重要保持客户的消费频次和消费金额最高,人数排在第二位;而一般发展客户消费频次和消费金额排第二位,人数却是最多。

6、从用户分层情况来看,新用户从第4月份以后没有新增;活跃用户有所下降;回流用户数量趋于稳定,每月1000多。流失/不活跃用户,数量非常多,基本上每月都在20000以上。

7、用户购买周期方面,平均购买周期是68天,最小值0天,最大值533天。绝大部分用户的购买周期都低于100天。

8、用户生命周期方面,由于只购买一次的用户(生命周期为0天)占了接近一半,排除这部分用户的影响之后,用户平均生命周期276天,中位数302天。

9、复购率和回购率方面,复购率稳定在20%左右,回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致复购率和回购率都比较低。

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