python数据分析

库版本

库版本

详情

用户在CD网站上的消费记录。本次分析,通过这份数据分析用户的消费情况以及趋势,个体消费情况,以及回购率和复购率。

导入数据

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#显示图形(自动生成画布,只在jupyter notebook中有效)
%matplotlib inline 
columns=['user_id','order_dt','order_products','order_amount']
df=pd.read_table(r'C:/Users/12585/Desktop/CDNOW_master.txt',names=columns,sep='\s+')
  • user_id:用户ID
  • order_dt:购买日期
  • order_prodects:购买产品数
  • order_amount:购买金额

查询df内容

df内容

各种统计数据

df.describe()
统计数据

其中,count代表总共有多少数据,mean代表平均值,std代表标准差,min代表最小值,max代表最大值。

观察到,order_dt中日期处理成pandas中可以计算的日期格式,如下:

df['order_dt']=pd.to_datetime(df.order_dt,format='%Y%m%d')
#新增一个字段,将order_dt.values的日期转化成当月的第一天
df['month']=df.order_dt.values.astype('datetime64[M]')

再次查询此时df内容


df内容

进行用户消费趋势的分析(按月)

  • 每月的消费总金额
  • 每月的消费次数
  • 每月的产品购买量
  • 每月的消费人数

每月的消费总金额

# 按月归类
grouped_month=df.groupby('month')
#每个月消费金额
order_month_amount=grouped_month.order_amount.sum()
#显示部分数据
order_month_amount.head()
消费总金额
#图形采用折线的方式
plt.style.use('ggplot')
#绘制order_month_amount数据的折线图,横轴代表月份,纵轴代表每个月的总金额
order_month_amount.plot()
月销售额趋势图

说明:1997年年初的两个月作用销量陡增,接着断崖式下降,再趋于平稳。但是在接下来这一年的年初却未曾出现这种情况,推测这种销售情况可能由于某种特殊的原因导致的,且不容易复现。

每月的消费次数

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

说明:用户消费次数在前两个月较多,后暴跌,从四月份开始逐渐平稳。

每月的产品购买量

grouped_month.order_products.sum().plot()
产品购买量

说明:产品购买量的趋势同消费额和用户消费次数成正相关。

每月的消费人数(去除重复消费的用户,得到每个月的用户数)

# 这里 x 其实是一个数组,是每月分组的用户id,去重后求数组的 len 长度就代表每月的用户数
df.groupby('month').user_id.apply(lambda x:len(x.drop_duplicates())).plot()
每月消费人数

和每月用户消费次数对比可知,顾客多次消费的人数不多,大多数都是一一个月来购买一次,具体什么原因导致有待后面的分析。

用户个体消费分析

即个体的消费情况分析。

  • 用户消费金额,消费次数的统计特征
  • 用户消费金额和消费次数的散点图
  • 用户消费金额的分布图
  • 用户消费次数的分布图
  • 用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)

用户消费金额,消费次数的统计特征

#按照用户分组
grouped_user=df.groupby('user_id')
#计算单个用户消费额
grouped_user.sum().describe()
用户消费统计

说明:用户金额平均数在106左右,方差std较大,说明个体用户消费金额波动比较大。订单量的平均值为7单左右,方差std为17左右,波动相对而言较小。

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

grouped_user.sum().plot.scatter(x='order_amount',y='order_products')
消费额和购买量的关系图

说明:从图中可知,消费次数和消费额成正相关。大部分客户消费在4000以下,购买数量在200以下。

用户消费金额的分布图

# 直方图,分为20块
grouped_user.sum().order_amount.plot.hist(bins=100)
顾客消费情况

说明:从图中可知,大部分消费额都是比较集中,除去少部分异常值。大部分的消费额都在一千以内,客户购买力较小。下面除去较少的个别客户,得到更细致的更普遍的客户消费额分布趋势。

# 直方图,分为20块,过滤
grouped_user.sum().query('order_products<100').order_amount.plot.hist(bins=20)
顾客消费情况

用户消费次数的分布图

grouped_user.sum().query('order_amount<2000').order_products.plot.hist(bins=20)
顾客购买量直方图

说明:从消费额和消费次数的离散图中可知,存在少数异常值干扰用户消费次数,所以设置一个过滤条件,从离散图中可知,消费额小于2000的状态较普遍。

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

# cumsum 是求累加值
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
# 这里 reset_index() 是为了得到一个自然数的行标签,表示的就是人数,下面的图就可以看出来多少个少占多少百分比
user_cumsum.reset_index().order_amount.plot()
消费额占比

说明 :从图中可知,百分之五十的用户贡献了百分之十五的消费额,消费额前五千的消费总额占据了消费额的百分之六十。

用户消费行为

  • 用户第一次消费(首购)
  • 用户最后一次消费
  • 新老客户消费比
    -- 多少用户仅消费一次
  • 用户分层
    -- RFM模型
    -- 新、老、活跃、回流、流失
  • 用户购买周期(按订单)
    -- 用户消费周期描述
    -- 用户消费周期分布
  • 用户生命周期(按第一次和最后一次消费) -用户生命周期描述
    -- 用户生命周期分布

用户第一次消费(首购)

# 得到最小的日期,然后统计一下各个日期的个数
grouped_user.order_dt.min().value_counts().plot()
user_id

用户最后一次消费

# 得到最大的日期,然后统计一下各个日期的个数,得到最后一次消费的情况,用户流失
grouped_user.order_dt.max().value_counts().plot()
最后一次消费日期统计

新老客户消费比

首先获得第一次和最后一次消费日期。
#新老客消费比
# 得到第一次和最后yc次消费情况,如果 min、max 日期相同,说明只消费了一次
user_life=grouped_user.order_dt.agg(['min','max'])
user_life.head()
首购以及最后一次消费日期
消费一次的客户数量。
(user_life['min']=user_life['max']).value_counts()
一次消费情况

说明:一半用户只消费了一次。

用户分层

RFM模型
# 画 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.head()
原始数据
# 最后日期减去消费日期,此处最后消费日期针对所有用户。
rfm['R']= -(rfm.order_dt - rfm.order_dt.max())/np.timedelta64(1,'D')
# 重命名,也就是 R:最后一次消费距今天数,F:消费总金额 ,M:消费总产品数
# R :消费时间  F:消费金额  M:消费频次
rfm.rename(columns={'order_products':"M",'order_amount':'F'},inplace=True)
rfm.head()
处理后数据

)

def rfm_func(x):
    level=x.apply(lambda x:'1' if x>=0 else '0')
    # level 的类型是 series,index 是 R、F、M
#     print(type(level))
#     print(level.index)
    label=level.R + level.F + level.M
    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']='g'
rfm.loc[~(rfm.label=='重要价值客户'),'color']='r'
rfm.plot.scatter('F','R',c=rfm.color)
消费金额和消费次数散点图
rfm.groupby('label').sum()
RFM模型
用户生命周期 新客,活跃,回流,流失(一段时间不消费,或者不活跃)
# 数据透视, userid为索引,月为列,求每月的消费次数,这里填充了
pivoted_counts=df.pivot_table(index='user_id',
                             columns='month',
                             values='order_dt',
                             aggfunc='count').fillna(0)
pivoted_counts.head()
用户月消费次数统计
# 转变一下消费,有消费为1,没有消费为0
df_purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
df_purchase.tail()
是否消费统计
# 这里由于进行数据透视,填充了一些 null 值为0,而实际可能用户在当月根本就没有注册,
#这样会误导第一次消费数据的统计,所以写一个函数来处理
def active_status(data):
    status=[]
    # 数据一共有18个月份,每次输入一行数据,这样进行逐月判断
    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
                    

若本月没有消费,这里只是和上个月判断是否注册,有缺陷,可以判断是否存在就可以了

  • 若之前是未注册,则依旧为未注册
  • 若之前有消费,则为流失/不活跃
  • 其他情况,为未注册
    若本月有消费
  • 若是第一次消费,则为新用户
  • 如果之前有过消费,则上个月为不活跃,则为回流
  • 如果上个月为未注册,则为新用户
  • 初次之外,为活跃
    return:回流 new:新客 unreg:未注册 active:活跃
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
统计每月各类用户数量
# 又把null 值填充为0
purchase_stats_ct.fillna(0).T
# 绘制了一个面积图,蓝色是注册用户,一开始有后面没有用户进来
purchase_stats_ct.fillna(0).T.plot.area()
各类用户图形表示

复购率和回购率分析

复购率

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

回购率

  • 曾经购买过的用户在某一时期的再次购买的占比(可能是在三个月内)
#查看每个用户每月购买次数
pivoted_counts.head(10)
消费情况
# 区分一个,和一个以上的情况,以便于计算复购率,大于1为1,等于0为NaN,其它为0
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()
消费情况处理
# 复购人数/总消费人数(不会计算nan值)
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
复购率
# 需要使用函数来判断是否回购:当月消费过的用户下个月也消费了叫做回购,这个定义可以改变
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
# 一行行的传递过去
purchase_b=df_purchase.apply(lambda x:pd.Series(purchase_back(x),index=df_purchase.columns),axis=1)
purchase_b.head()
回购情况
# 求得回购率,回购的次数处于总购买次数
(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))
回购率
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容