CD网站用户消费行为分析

一、简介

对国外某一CD网站的用户购买数据进行分析,主要围绕整体消费情况和用户消费行为进行分析,找出高价值用户人群,了解用户留存以及流失等情况,为平台指定策略提供数据方面的支持和建议。

二、流程

1、数据清洗
2、用户消费趋势分析:观察用户月消费额、月消费趋势以及总体消费人数的变动情况
3、用户个体消费分析:关注用户消费金额以及消费次数的情况
4、用户消费行为分析:新老客比例,首购及末购情况,RFM用户分层等
5、复购率、回购率分析

三、具体分析过程

1、数据清洗

导入数据包,同时设置好列标签

import pandas as pd
import numpy as np

columns = ['user_id', 'order_dt', 'order_products', 'order_amount']
df = pd.read_table('CDNOW_master.txt', names = columns, sep='\s+')
df
image.png

可以发现该数据集总共有69659行*4列数据,4张列标签分别是
user_id:用户id
order_dt:下单日期
order_products:购买商品数量
order_amount:购买金额

df.info()
image.png

order_dt字段为数值型,需要将其更改为日期型,同时加入month列,作为备用

# 转换为日期格式
df['order_dt'] = pd.to_datetime(df.order_dt, format="%Y%m%d")
df['month'] = df.order_dt.values.astype('datetime64[M]')
df.info()
image.png
  • pd.to_datetime可以将特定字符串或数字转换为时间格式,其中format参数用于匹配

  • 例如19970701,%Y匹配前面4位数字,如果y小写就只匹配2位数字97

  • 同理,%m匹配月份07,%d匹配日期01。另外,小时是%h,分钟是%M

df.describe()
image.png

再来简单观察数据集统计情况,可以发现大部分订单只消费了少量商品(平均2.4个),同时用户消费金额比较稳定,平均消费35.89元,但是中位数在25.98元,最大值有1286元,说明存在极值干扰的情况

2、用户消费趋势的分析

1)月消费金额

先对消费金额按月进行统计

grouped_month = df.groupby('month')
order_month_amount = grouped_month.order_amount.sum()
order_month_amount.head()
image.png

利用matplotlib进行图表展示

# 加载数据可视化包
import matplotlib.pyplot as plt
# 可视化结果显示在页面
%matplotlib inline
# 更改设计风格
plt.style.use('ggplot')
order_month_amount.plot()
image.png

可以发现,顾客消费金额在前三月达到了顶峰,之后出现了明显的回落,但是能保持在较稳定的水平

2)月订单数量

grouped_month.user_id.count().plot()
image.png

在订单数量方面,其走势也和消费金额大体一致,前三月在11000左右,后期回落到了2500左右的水平

3)月消费人数

df.groupby('month').user_id.apply(lambda x: len(x.drop_duplicates())).plot()
image.png

用drop duplicates对user_id字段进行去重,从而观察每月消费人数情况,可以发现前三月消费人数在8000到10000之间,后续月份消费人数大幅回落,平均消费人数不足2000

3、用户个体消费分析

1)用户消费描述统计

grouped_user = df.groupby('user_id')
grouped_user.sum().describe()
image.png

用户平均购买了7张CD,但是中位数只有3,说明有小部分用户购买了大量CD
用户平均消费金额106元,中位数为43,同样存在极值的干扰

grouped_user.sum().query('order_amount < 4000').plot.scatter(x = 'order_amount', y = 'order_products')
image.png

利用散点图对极值敏感的特点,可以直观发现极值的存在

2)用户消费金额分布情况

# hist为直方图,bins为分组,此处分为20组
grouped_user.sum().order_amount.plot.hist(bins = 20)
image.png

从直方图可知,用户消费金额绝大部分呈现集中趋势,小部分异常值干扰了判断,可以使用过滤操作来排除异常

# 加入过滤条件
grouped_user.sum().query('order_products < 92').order_amount.hist(bins = 20)
image.png

使用切比雪夫定理来过滤异常值,计算95%数字的情况(order_products平均值为7,标准差为17,7+5*17=92)

3)用户累计消费占比情况

# cumsum求累加值
user_cumsum = grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum() / x.sum())
user_cumsum.reset_index().order_amount.plot()
image.png

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

4、用户消费行为分析

1)用户首购及末购情况

# 得到最小日期,再统计各个日期的个数
grouped_user.min().order_dt.value_counts().plot()
image.png

可以发现,用户第一次购买集中分布在前三个月,其中在2月11日至2月25日有一次剧烈波动

grouped_user.max().order_dt.value_counts().plot()
image.png

而在最后一次购买方面,日期分布明显要比首购宽广,但是大量最后一次购买行为集中在前三个月,说明有大量用户只购买了一次之后便不再购买。
随着时间递增,最后一次购买数量也在递增,消费呈现流失上升的状况

2)用户生命周期

user_life = grouped_user.order_dt.agg(['min', 'max'])
user_life.head()
image.png
(user_life['min']  == user_life['max']).value_counts()
image.png

可以发现有一半用户只消费了一次

(user_life['max'] - user_life['min']).describe()
image.png
((user_life['max'] - user_life['min']) /np.timedelta64(1, 'D')).hist(bins = 20)
image.png

可以看到用户生命周期受极值的影响非常厉害,中位数仅有0天,但是平均首购与末购相隔天数为134天,而且图表中数据也集中于0天上

u_1 = ((user_life['max'] - user_life['min']) /np.timedelta64(1, 'D'))
u_1[u_1 > 0].hist(bins = 40)
image.png

将首购与末购相隔天数大于0的单独挑出来进行观察,可以发现有接近1200名用户两次消费间隔天数约在25天内,间隔400到500天的也有很大一部分

3)用户分层——RFM模型

# 先对原始数据进行透视
rfm = df.pivot_table(index='user_id',
                    values=['order_products', 'order_amount', 'order_dt'],
                    aggfunc={'order_products':'sum',
                            'order_amount':'sum',
                            'order_dt':'max'
                            })
rfm.head()
image.png
 # 将最早时间与最晚时间差转换为浮点数
rfm['R'] = -(rfm.order_dt - rfm.order_dt.max()) / np.timedelta64(1,'D')
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')
    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)
image.png
rfm.groupby('label').sum()
image.png

从RFM分层可知,大量用户为重要保持客户,但这是受到极值的影响,RFM划分标准应以业务为准

  • 尽量用小部分用户覆盖大部分额度
  • 不用为了数据好看划分等级
rfm.loc[rfm.label == '重要价值客户', 'color'] = 'b'
rfm.loc[~(rfm.label == '重要价值客户'), 'color'] = 'r'
rfm.plot.scatter('F', 'R', c = rfm.color)
image.png

4)用户分层——新老客、活跃度

# 数据透视,user_id为索引,月为列,求每月消费次数
pivoted_counts = df.pivot_table(index = 'user_id',
                               columns = 'month',
                                values = 'order_dt',
                               aggfunc = 'count').fillna(0)
pivoted_counts.head()
image.png
# 转变维度,有消费记录为1,没有消费记录为0
df_purchase = pivoted_counts.applymap(lambda x: 1 if x > 0 else 0)
df_purchase.tail()
image.png
# 由于进行数据透视,将一些null值填充为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')
    return status

若本月没有消费

  • 若之前是未注册,则依旧为未注册
  • 若之前有消费,则为流失/不活跃
  • 其他,为未注册

若本月有消费

  • 若是第一次消费,则为新用户
  • 若之前有消费,上个月为不活跃,则为回流
  • 若上个月未注册,则为新用户
  • 其他,为活跃用户
indexs=df['month'].sort_values().astype('str').unique()  #astype 的区别
purchase_stats = df_purchase.apply(lambda x:pd.Series(active_status(x),index=indexs),axis=1)
purchase_stats.head(5)
image.png
# 把未注册的记录替换为空值,这样count计算时不会被计算到
purchase_stats_ct = purchase_stats.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
purchase_stats_ct
image.png

可以发现活跃用户在减少,非活跃用户不断增加

# T转置,同时求出所有用户占比
purchase_stats_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis = 1) 
image.png
# 绘制面积图
purchase_stats_ct.fillna(0).T.plot.area()
image.png

由上图可知每月不同消费状态的人群变化情况

5)用户购买周期

# 计算两个订单的时间间隔
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
order_diff.head(10)
image.png
order_diff.describe()
image.png
# 去除单元值并作图
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
image.png

可以发现,用户的购买周期具有以下几个特点:

  • 订单周期呈指数型分布
  • 用户平均购买周期是68天
  • 大部分用户购买周期低于100天

5、复购率和回购率分析

  • 复购率:自然月内购买两次以上的用户占比
  • 回购率:某一时期内曾经购买过的用户再次购买的占比
# 复购率
pivoted_counts.head(10)
image.png
# 区分一次和一次以上的情况,以便计算复购率(大于1为1,等于1为0,等于0为NaN)
purchase_r=pivoted_counts.applymap(lambda x:1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()
image.png
# 复购人数/总消费人数
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
image.png

可以发现,复购率大体稳定在20%左右,前三个月大量只购买一次的新用户的涌入,导致复购率较低

# 回购率,知道本月是否有消费记录即可
df_purchase.head()
image.png
# 使用函数来定义回购率,当月已消费的用户下月也有消费记录的计入回购
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
    """语句中的是 第一位与第二位相比 输出结构放第一位, 循环17次,17/18的判断结束后,输出的status仅17个,需补上最后一个"""
    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.head()
image.png
# 求得回购率
(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))
image.png

可以发现绝大部分用户购买一次后不再购买,老用户回购率在30%左右

四、总结

1、cd网站在前三个月涌入了绝大多数的新用户,月订单数量在11000左右,月消费人数在8000到10000之间,之后均回落至2000左右的水平
2、50%的用户仅贡献了15%的消费额度,消费排名前5000的用户就贡献了60%的消费额,符合二八法则。同时该网站用户平均购买了7张cd,中位数只有3,说明有小部分用户购买了大量CD,同样也符合二八法则
3、根据用户首购、末购情况,可以发现用户的受次购买集中分布在前三个月,且购买一次后便不再购买,该部分用户占比50%(符合2中提及的50%用户仅贡献15%的消费额度)
4、根据用户分层RFM模型以及新老客、活跃度情况的分析,大量用户为重要保持客户,绝大多数新用户集中在前三个月,之后大部分转为非活跃用户
5、按用户购买周期分析,用户平均购买周期为68天,大部分用户购买周期集中在100天内
6、从复购率和回购率的角度来看,该cd网站的复购率答题稳定在20%左右,回购率在30%左右

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