CD店铺消费者消费数据分析

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
  • 首先当然要把该import的都全部Import进来。

plt.rcParams['font.family'] = ['Arial Unicode MS'] 
  • 为了待会儿画图正常显示中文。因为我是MAC系统,所以代码跟WIN的可能有些不同。

pd.__version__
  • 我pd的版本,是最新安装的,0.24.0

columns=['user_id','order_dt','order_products','order_amount']
df = pd.read_csv('CDNOW_master.txt',names=columns,sep='\s+')
#如果数据本身以空格做分割的话,这里sep要用\s+
  • 导入数据,并把列头分别命名为:'user_id','order_dt','order_products','order_amount'

df.order_dt=pd.to_datetime(df.order_dt,format='%Y%m%d')
  • 由于原数据时间那一项是‘19970101’这样的形式的,因此要用pd.to_datetime()把格式换成yyyy-mm-dd的格式,进行后续的分析时才更加方便。

df['month']=df.order_dt.values.astype('datetime64[M]')
#后续要解决的问题都需要以月为单位来计算,因此要多加一条column:"month",以月为单位。

df.head(10)
  • 看下表头前十行大概长啥样。

df.describe()
  • 先给这些数据做一些简单的统计分析。可以看出:大部分订单只消费了少量商品(平均2.4),用户的消费金额比较稳定,平均消费35元,中位数25元。有一定的极值干扰。



接下来就开始进行进一步的分析。

首先:1.进行用户按月消费趋势的分析。分别有:

  • (1)每月消费的总金额
  • (2)每月的消费次数
  • (3)每月的产品购买数
  • (4)每月的消费人数
grouped_month=df.groupby('month') 
  • 由于是按月消费的分析,因此要按月分组。

grouped_month.order_amount.sum().plot()#(1)每月消费的总金额
  • 利用.sum()函数把order_amount那一列按月分组的总和求出来,并且画图,得到下列图片:

grouped_month.order_products.count().plot()#(2)每月的消费次数
  • 利用.count()函数把总消费次数按月分组的总和求出来(这里用order_products或者order_amount其实都一样)。画图,并且得到下列图片:

grouped_month.order_products.sum().plot()#(3)每月的产品购买数
  • 每月产品总购买数的画图:

grouped_month.user_id.count().plot()#(4)每月消费人数
  • 每月消费人数的画图:

  • 由以上四图可知,每月消费的总金额、消费次数、产品购买数以及消费人数,都在前三个月上升达到高峰,随后在3到4月份时巨幅下降,4月份之后开始稳定上下波动,甚至有轻微下降趋势。



2.用户个体消费分析

  • (1)用户消费金额、产品购买数的描述统计
  • (2)用户消费金额和产品购买数的散点图
  • (3)用户消费金额的分布图
  • (4)用户产品购买数的分布图
  • (5)用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额

grouped_user=df.groupby("user_id")
  • 用户个体消费分析,就要根据user_id来分组了。

grouped_user.sum().describe() #(1)用户消费金额、产品购买数的描述统计。
  • 由上表可知:
  • ①用户平均每个人购买7张CD,但中位数是3,说明有小部分用户购买了大量CD。
  • ②用户平均消费106元,中位值有43,判断同上,有极值干扰。

  • 由于散点图对极值比较敏感,可以做简单的过滤,因此:
grouped_user.sum().plot.scatter(x='order_amount',y='order_products',c='b')
  • 因此可得:
  • 由上图可知,数据主要集中在order_amount小于3000,order_products小于200。因此超出这两个范围的都视为极值。

grouped_user.sum().query('order_amount<3000').query('order_products<200').plot.scatter(x='order_amount',y='order_products',c='b')
(2)用户消费金额和产品购买数的散点图
  • 再缩小范围做一幅图,可以更加清晰地看到,绝大部分的数据集中在order_amount小于1000,order_products小于100。:

  • 用户消费金额的分布图
grouped_user.sum().query('order_amount<1000').order_amount.plot.hist(bins=50)
#(3)用户消费金额的分布图(根据上面散点图,选择数据较为集中的部分进行分析。)

  • 用户消费产品数的分布图
grouped_user.sum().query('order_products<100').order_products.plot.hist(bins=50)
#(4)用户消费产品数的分布图(根据上面散点图,选择数据较为集中的部分进行分析)

  • 用户累计消费金额占比:利用cumsum() 计算轴向元素累加和,再除以sum,得到累计比例。
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())

user_cumsum.reset_index().order_amount.plot()
  • 按用户消费金额进行升序排列,由图可知,排名前50%的用户只贡献了15%左右的消费额度。而排名最后3570的用户就贡献了60%的消费额度。



3.用户消费行为:

  • (1)用户第一次消费(首购)
  • (2)用户最后一次消费
  • (3)新老客户消费比:
    • ① 多少用户仅消费了一次?② 每月新客占比。
  • (4)用户分层:
    • ① RFM。② 新、活跃、回流、流失。
  • (5)用户购买周期(按订单)
    • ① 用户消费周期描述。② 用户消费周期分布。
  • (6)用户生命周期(按第一次&最后一次消费)
    • ① 用户生命周期描述。② 用户生命周期分布。

  • (1)用户第一次消费(首购):
grouped_user.min().order_dt.value_counts().plot(figsize=(10,4))
#第一次购买的时间,就是时间最小,所以用.min()。然后用value_counts()汇总起来。 
  • 由图可知,所有用户的首购都集中在97年1月1日至97年3月29日,2月上旬以前首购客户数量一直稳定上升。在2月10日左右首购客户的数量产生剧烈波动,在2月下旬开始稳定,然后一直到3月下旬都呈现稳定下跌的状态。

  • (2)用户最后一次消费:
grouped_user.max().order_dt.value_counts().plot()
#最后一次消费,日期最大。
  • 用户最后一次购买的分布比第一次分布广,大部分最后一次购买集中在前三个月,说明有很多用户购买了第一次之后就不再购买了。而且随着时间的递增,最后一次购买也在递增,说明消费呈现流失上升的状况。

  • (3)新老客户比:
    • ① 有多少客户只消费了一次?
      用count对grouped_user的order_dt进行统计,=1的就是只消费一次客户。
(grouped_user.order_dt.count()==1).value_counts()
  • 接近一半的客户是只消费了一次的。

  • ② 每月新客占比。
user_life=grouped_user.order_dt.agg(['min']).sort_values('min')
user_life['month']=user_life['min'].values.astype('datetime64[M]')
  • 新建一个dataframe,user_life,以min为首购日期。在user_life那里添加一列month,也是首购日期,不过是以月为单位。
user_life.groupby('month').month.describe()
  • 由上表可知每个月份的首购客户数量:
    1月份新客占比:7846/(7846+8476+7248)=33.29%
    2月份新客占比:8476/(7846+8476+7248)=35.96%
    3月份新客占比:7248/(7846+8476+7248)=30.75%

  • (4)用户分层:
    ①RFM模型分析(最近一次消费、消费频率、消费金额)。
rfm=df.pivot_table(index='user_id',
                          values=['order_products','order_amount','order_dt'],
                          aggfunc={'order_dt':'max',
                                  'order_amount':'sum',
                                  'order_products':'count'})
rfm.head()

利用透视表功能,把user_id作为索引,根据order_amount和order_dt的值,算出每个用户最大购买日期(最后一次消费的日期)、总消费金额,以及总消费次数(这里用 'order_products':'count' 或者 'order_amout':'count'都一样,因为算的是次数而不是购买的产品数。每次消费买的产品数都可能不一样。)然后我们可以看下rfm表大概长啥样:



rfm["R"]=(rfm.order_dt.max()-rfm.order_dt)/np.timedelta64(1,'D')
  • R,Recency,计算最近一次消费距离至今多少天(因为数据比较久远,所以用order_dt里面最大的一项作为今天)
rfm=rfm.rename(columns={'order_products':'F','order_amount':'M'})
  • F,Frequency,消费频率,这里为总消费次数;M,Monetary,为总金额。

  • 然后把按照r、f、m这三个维度取平均,每个维度都把高于平均值的标记为1,低于平均值得标记为0.
    • 这样就有以下8种用户:
      111 重要价值客户
      011 重要保持客户
      101 重要挽留客户
      001 重要发展客户
      110 一般价值客户
      010 一般保持客户
      100 一般挽留客户
      000 一般发展客户
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' : '重要挽留客户',         
         '110' : '一般价值客户',
         '001' : '重要发展客户',
         '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添加一列客户标示。
rfm.head(10)

rfm_grouped=rfm.groupby('label') 
rfm_grouped.sum()
  • 可以看到不同维度客户的占比

pd.Series(rfm_grouped['label'].count(),name='series')
  • 可以看到不同客户的总数。
pd.Series(rfm_grouped['label'].count(),name='series').plot.pie()
  • 画成饼图更加直观。
  • 从RFM分层可知,本数据集的大部分用户为一般挽留客户,其次是重要保持客户。

② 用户生命周期:新、活跃、回流、流失(不活跃)

  • 新:首次消费在最新时间。
  • 活跃:持续消费。
  • 回流:曾今有过消费,隔了超过一个月后再次消费。
  • 流失:曾今有过消费,到最新时间为止还没有消费过。
    本例直接按一个月作为间隔。
pivoted_counts = df.pivot_table(index='user_id', # 使用数据透视,将每个用户每月消费次数计算出来
                               columns='month', # 如果没有数据的话(比如1号客户1997年2月1日没有数据),数据透视功能也会把它变成NaN。
                               values='order_dt',# 因此要价格fillna(0)把空值变成0。
                               aggfunc='count').fillna(0)
pivoted_counts.head()
  • 透视表大概长这个样子。
  • 由于本次案例是需要计算每个月是否有消费,具体消费多少次不重要,因此可以把大于0的都当做1:
purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
  • 由于很多的首次消费是在2月或者3月,所以在写函数判断的时候需要考虑在内,并不是所有的首次消费都在一月:
def active_status(data):
    status=[]
    for i in range(18): #共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()
purchase_sta=purchase.apply(lambda x:pd.Series(active_status(x),index=indexs),axis=1)
purchase_sta.head()
  • 可得到一张不同用户在不同月份的不同状态(new=新、active=活跃、return=回流、unactive=流失),unreg相当于未注册,指这个用户在这个月及以前从未购买过产品,主要为了统计起来更加方便而加进去。
purchase_sta.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T
  • 把unreg替换成NaN,再用fillna(0)把空值填为0。然后转置,把月份作为索引行,状态作为列,得到如下的表:
purchase_sta.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T.plot.area(figsize=(10,4))
  • 作出非堆积效果图:
  • 由图可知,到了在前三个月,新用户增加的数量非常大。从三月一号开始,用户开始快速流失。到后面的几个月流失用户基本占绝大比例。

(5)用户购买周期(按订单)
  • 用户消费周期描述
  • 用户消费周期分布
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
  • 算出不同用户每次消费相隔多少时间
order_diff.describe()
  • 可以看到每次消费相隔时间大致的分布。
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
  • 把分布图作出来:
  • 由上图以及描述可知:

订单周期呈指数分布。
用户的平均购买周期是68天。
绝大部分用户的购买周期都低于100天。


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

① 用户消费周期描述

(user_life['max']-user_life['min']).describe()

② 用户消费周期分布

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



4.复购率和回购率分析

  • (1) 复购率(自然月内,购买多次的用户占比)
  • (2)回购率(曾经购买过的用户在某一时期内的再次购买的占比)
  • (1)复购率:当月多次购买(超过一次)
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x == 0 else 0)
  • "1"表示消费次数大于1;"0"表示只消费过一次;"NaN"表示没买过,在.count中就不会计算在名额内。求复购率就计算消费次数大于1的占比就行了。
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
  • 由上图可知,复购率在前三个月高速增长,最终在大概四月份的时候稳定在20%左右。

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

推荐阅读更多精彩内容

  • CD消费分析自己已模仿练习了很多遍,也总结过2篇文章,但任然存在很多问题,主要原因是自己对业务不了解、对panda...
    夜希辰阅读 19,692评论 23 62
  • 用python分析用户消费行为,数据来源CDNow网站的用户购买明细。一共有用户ID, 购买日期, 购买数量和购买...
    _hahaha阅读 4,858评论 1 9
  • 一朵花凋谢,冬天在我脸上描画了一辈子的霜雪 动物冬眠闭着眼睛感受我身体里的寒冷仿佛我也学会了理解影子和世界的关系 ...
    万里西风烈阅读 861评论 10 38
  • 回忆过去的自己,无知、幼稚、年少轻狂;看看现在的自己,却不知如何评价,但将来的你回想现在,一定是段美好、充满青春气...
    木程舟阅读 140评论 0 0
  • “这不是我们少主的命值多少钱的问题,而是您认为您的能力值多少钱的问题,不知道您觉得自己价值多少呢?”凌霄轻描淡写道...
    唐仙缘阅读 142评论 0 1