用户与订单之间的关系_数据分析项目:用户消费行为分析

数据分析项目:用户消费行为分析

项目背景: 为了平台创造出更多利润,并且能够合理的投放广告,使用网站18个月销售数据进行分析,根据复购率,回购率,高额消费用户等指标以及消费模型进行针对性的客户管理与维护:

数据来源: CDNoW网站用户购买记录,通过以下字段利用python进行数据分析

数据获取: 提取码:zvum

数据字段:

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

数据分析流程:(思维导图展示)

262f6bd8610bcd281d525849bbd3ed8f.png

一.数据准备

  • 导入常用python库
# 导入pandas、numpy和matplotlib
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 让所作图形立即呈现的设置
%matplotlib inline
# 中文字符和正负号正常显示的设置
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
  • 读取数据并观察
# 源数据共有四个字段,为其定义英文字段
columns = ['user_id','order_dt','order_products','order_amount']
# 用s+匹配任意空白符
df=pd.read_csv('CDNOW_master.txt',names=columns,sep='s+')
# 查看前五行数据
df.head()
# 了解数据基本信息
df.info()

b0da0f4240b1904849d946b73c10d1ef.png
查看前五行数据
  • user_ud:用户ID
  • order_dt: 购买日期
  • order_products: 购买产品数
  • order_amount: 购买金额

dfab160a0549d129a686b214e7dcce47.png
数据概况

数据中无缺失值,无需对缺失值处理,但是购买日期的数据类型是int,为方便后面数据处理,在这先将其转化成datetime格式

#pd.to_datetime将数据字段转化成时间格式
df['order_dt'] = pd.to_datetime(df.order_dt,format='%Y%m%d')
# 下面按照月份分析,所以需添加一个字段month,用它来表示订单日期所在的月份,格式为月份的第一天
df['month']=df.order_dt.values.astype('datetime64[M]')
# 处理后,查看数据,此时数据时间的格式
df.info()
#前五行数据
df.head()

cacf6ea8afb7afc3371c79e421e6179e.png
查看时间列的格式

5bc40e1821f4ebb2a7e3529766b32b29.png
查看新增列后的数据前五行
# 通过对用户ID分组,并进行求和,再进行描述性统计
df.groupby

2e855b4cafdeeee2d1f4f6cbd9e6fec1.png

观察数据,从用户ID看,每位用户平均购买7张CD,最多的用户购买了1033张,属于狂热用户了(观察异常值)。用户的平均消费金额(客单价)106元,标准差是240,结合分位数和最大值看,平均值略大于第三分位数,肯定存在小部分的高额消费用户。

二.数据分析

1.按月分析用户消费趋势

#按月分组
grouped_month = df.groupby('month')
#制定可视化图分析不同维度用户消费趋势
fig = plt.figure(figsize = (8,12))
ax1 = fig.add_subplot(4,1,1)
ax2 = fig.add_subplot(4,1,2)
ax3 = fig.add_subplot(4,1,3)
ax4 = fig.add_subplot(4,1,4)

# 按月金额数(图1)
ax1.plot(grouped_month.order_amount.sum(),c='blue')
ax1.set_title('每月消费总金额变化趋势')

# 按月的订单数(图2)
ax2.plot(grouped_month.user_id.count(),c='blue')
ax2.set_title('每月订单数变化趋势')

# 按月产品数(图3)
ax3.plot(grouped_month.order_products.sum(),c='blue')
ax3.set_title('每月消费产品购买量')

# 每月用户人数  这里需要去重,有些用户重复购买(图4)
ax4.plot(grouped_month.user_id.apply(lambda x:len(x.drop_duplicates())),c='blue')
ax4.set_title('每月用户数变化趋势')
# 另一种去重方式:df.groupby(['month','user_id']).count().reset_index()

#每月用户消费的平均金额(图5)
((grouped_month.order_amount.sum())/(grouped_month.user_id.apply(lambda x: len(x.drop_duplicates())))).plot()
plt.title('每月用户消费的平均金额')

#每月用户消费的平均次数(图6)
((grouped_month.user_id.count())/(grouped_month.user_id.apply(lambda x : len(x.drop_duplicates())))).plot()
plt.title('每月用户消费的平均次数')
  • 按月进行统计,下面通过折线图分别查看数据变化趋势
  • 建议代码和图表一一对应展示,结论紧随,这样不仅美观且方便阅览

a639bd92ca91fad36db73e6c527bc008.png

b1de956b75201c228c7c6a4d00180be3.png

92fe4a79431c0001955ea20cbac37980.png

图一: 消费金额在前三个月达最高峰,后期消费金额较为平稳,有轻微下降趋势

图二: 前三个月订单数在10000笔左右,后续月份的订单数在2500左右

图三: 产品购买量 呈现早期购买量多 后期下降趋势

图四: 每月消费人数小小于每月的消费次数(订单数),但是区别不大,前三个月每月的消费人数在8000-10000之间,后续月份平均2000左右,一样是前期消费人数多,后期平稳下降趋势

出现这种状况,假设问题是出现在用户身上,早期时间段的用户有异常值,或者由于各类促销营销,由于只有消费数据,无法进一步进行判断

图五:前三个月用户平均消费在40元左右,后续月份用户平均消费金额相比前三个月有一些提高,用户平均消费金额在区间[45,57.5]之间

图六:前三个月用户消费的平均次数逐渐增加,后续月份用户的平均消费次数在1.35次左右

#数据透视查看,按月分别对用户购买金额求和,订单数求和,用户人数计数
df.pivot_table(index='month',
                values = ['order_products','order_amount','user_id'],
                aggfunc = {'order_products':'sum',
                           'order_amount':'sum',
                           'user_id':'count'})

f1191af3b5496aaf4f6bc8fc78ef636b.png

2 .用户个体消费分析

  • 用户消费金额,消费次数描述统计
grouped_user = df.groupby('user_id')
grouped_user.sum().describe()

8ab730ebf2416842239ff9dfbf17f432.png

用户平均购买量7张CD,标准差17,波动比较大,但是中位值只有3,说明小部分用户购买了大量的CD

用户平均消费为106元,中位值为43,也有极值干扰

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

# 每笔订单消费金额与消费商品数的散点图(可考虑做直方图,进一步直观的考察数据的分布情况)
df.plot.scatter

8634de52dcf3fbc34847cb798db50df4.png

绘制每笔订单的散点图。从图中观察,订单消费金额和订单商品量呈规律性,订单金额在(0-400)及产品数在(0-40)的订单较多。订单的极值较少,超出1000的就几个。

#每位用户的消费金额与消费商品数散点图,并用query过滤掉订单金额大于4000的订单,减小极值干扰
grouped_user.sum().query('order_amount < 4000').plot.scatter(x='order_amount',y='order_products')

baaecbe09d9d6abe8b9dd0bc6469d77d.png

绘制用户的散点图,用户也比较健康,而且规律性比订单更强。因为这是CD网站的销售数据,商品比较单一,金额和商品量的关系也因此呈线性,没几个离群点。消费能力特别强的用户有,但是数量不多

  • 用户消费金额的分布图(符合二八法则)
grouped_user.sum().order_amount.plot.hist(bins=20)
# bins=20,就是分成20块,最高金额是14000,每个项就是700

558b2d9f7b52b13c73c6872637c6541e.png

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

# 过滤掉商品数大于100的订单,减小极值影响
grouped_user.sum().query('order_products<100').order_amount.hist(bins=40)
plt.title('消费金额分布图')

19a783183e3ea028e48aebd7c9771afc.png
# 计算过滤后数据的描述统计
grouped_user.sum().query('order_products<100').describe()

92b59fa0305e64390e36af4201cfdc73.png

使用切比雪夫定理过滤掉异常值,计算95%的数据的分布情况 95%的数据在[mean-2std,mean+2std]

通过计算可知,95%的消费在区间[0,856.5]元之间

  • 用户消费次数的分布图
plt.figure(figsize=(12,5))
grouped_user.count().query('order_products<100').order_amount.hist(bins=40)
plt.title('消费次数分布图')

d24e731ea8d1eb92d27852197da51e52.png

从直方图看,大部分用户的消费能力确实不高,大多只消费了一次或两次,高消费用户在图上几乎看不到,这也确实符合消费行为的行业规律,即“二八法则”

下面通过计算累计消费金额和累计消费商品数百分比来分析用户累计消费金额的占比

  • 用户累计消费金额占比
#按ID分组,并求和,再对订单金额进行排序(默认从小到大),最后通过匿名函数对每一行进行累计求和占比
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x: x.cumsum()/x.sum())
plt.subplot(211)
user_cumsum.reset_index().order_amount.plot(figsize=(8,8))
plt.title('消费金额累计百分比')
plt.subplot(212)
user_cumsum.reset_index().order_products.plot()
plt.title('消费商品数累计百分比')

2b437c6faf056d31eefa8facb1099129.png

由上图可知,7570位用户贡献了消费额的80%,即32%的用户贡献了80%的消费金额

同样地,8250位用户贡献了消费商品数量的80%,即34%的用户贡献了消费商品数的80%

该结果符合消费行业规律——“二八法则”

3. 用户消费行为分析

  • 第一次购买时间分布
# 计算第一次购买时间分布
df['order_dt'].groupby(df['user_id']).min().value_counts().plot(figsize=(12,5))
#等同于grouped_user.min().order_dt.value_counts().plot(figsize=(12,5))
# 2月发生较大下跌  渠道发生变化,或者其他  可以做一些假设

ad38cd4436930d6e0f6954431f0e0cbc.png
#按月统计购买次数
grouped_user.min().month.value_counts()

96691e551f2bd1314ed1ecf3b10b0ea4.png

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

  • 最后一次购买时间
grouped_user.max().month.value_counts().plot()
plt.title('最后一次消费时间分布')

f36566f8895c2f0b6f097e3af7249422.png

用户最后一次购买的分布比第一次购买分布广

大部分最后一次购买在前三个月,说明很多用户购买一次后就不再进行购买

随着时间递增,最后一次购买数在递增,消费呈线性流失上升的情况

  • 新老客户消费比

a.多少用户只消费一次

# 计算只消费了一次的用户人数(结果为:11908)
one_consumed=grouped_user.count().query('order_dt==1').order_dt.count()
# 计算总的消费人数(结果为:23570)
all_consumed=grouped_user.count().order_dt.count()
#计算多少用户只消费一次
one_consumed/all_consumed=50%

由上可知,有一半的用户只消费了一次

b.计算每月新客占比并作出其百分比折线图:

# 按月份和用户ID分组
grouped_month_user=df.groupby(['month','user_id'])
# 用当月用户订单日期最小值与用户订单日期最小值联结
tmp=grouped_month_user.order_dt.agg(['min']).join(grouped_user.order_dt.min())
# 判断用户当月订单日期最小值是否与用户订单日期最小值相等,新建字段new,new代表老客户
tmp['new']=(tmp['min']==tmp.order_dt)
# 重置索引列,并按月分组,作新客占比折线图
tmp.reset_index().groupby('month').new.apply(lambda x: x.sum()/x.count()).plot()
plt.title('新客占比百分比')

de9000906309c2da45597a4b32dd128a.png

可以看出,只有前三个月的新客占比不为零,后续月份新客占比百分比为零,这说明只有前三个月有新用户的增加,后续月份消费的用户是前三个月加入的老客户,并没有新客户的加入

用户分层

RFM模型

#  RFM  
rfm = data.pivot_table(index = 'user_id',
                     values = ['order_products','order_amount','order_dt'],
                     aggfunc = {'order_dt':'max',
                                'order_amount':'sum',
                                'order_products':'sum'})
rfm.head()

9fcd82b77e5af09c42e2e238768c6731.png
  • R:消费最后一次消费时间的度量,数值越小越好
  • F:消费的总商品数,数值越大越好
  • M:消费的总金额,数值越大越好
# 计算每位用户最后一次消费时间与全部用户最后一次消费时间的差值
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.head()

8dcdd87e6c2f99051b8485dedb34f9ee.png
# /np.timedelta64(1,'D')  消除单位   进行重命名
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.head()

4823028b61e7f968ac7fa321f84bb40d.png
#应用匿名函数,判断每一行值与平均值大小关系
rfm[['R','F','M']].apply(lambda x:x-x.mean()).head()

8bdb32968342b5184762d775d066a088.png
# 客户层次的定义,RFM得分可根据业务定义打分,也可以通过K-means聚类模型,得出不同相似程度的数据集,并且根据每一个数据集的特点进行客户定义
def rfm_func(x):
    level = x.apply(lambda x:'1' if x>= 0 else '0')
  # 字符串拼接
  # 111,R>0,是距离平均消费时间要久,R越大 说明没有消费时间越久  ,F >0 M>0,消费次数和金额也是较高的,重要价值客户,依次类推
    label = level.R + level.F + level.M
    d = {
        '111':'重要价值客户', 
        '011':'重要保持客户',
        '101':'重要挽留客户',
        '001':'重要发展客户',
        '110':'一般价值客户',
        '010':'一般保持客户',
        '100':'一般挽留客户',
        '000':'一般发展客户'
    }
    result = d[label]
    return result
# x - x.mean() (具体真实情况可以修改,不一定需要用均值)   切比雪夫也可以 > 200 极值人工处理掉
rfm['label'] = rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
rfm.head()

08385a460ecf9c8b72f1481a0a66d797.png
# 计算每层客户R、F、M的和
rfm.groupby('label').sum()

1db2e5f827af8b072de3132562833db6.png

可以看出,重要保持客户对于消费总金额的占比远大于其他客户的占比,这说明绝大部分收益是由重要保持客户贡献的,只要能保证这部分客户不流失和增加,那么公司收益将得到有力保障

rfm.groupby('label').count()

f72926fb70beca23c83f8ca71f1ae4a1.png
# 各类类型用户占比
use_c = rfm.groupby('label').count()
plt.axis('equal')
labels = ['一般价值客户','一般保持客户','一般发展客户','一般挽留客户','重要价值客户','重要保持客户','重要发展客户','重要挽留客户']
plt.pie(use_c['M'],
       autopct='%3.1f%%',
        labels = labels,
        pctdistance=0.9,
       labeldistance = 1.2,
       radius=3,
       startangle = 15)

8c63a31d499f546248876c2d7ec3c8e6.png

从RFM分层可知,大部分用户为重要保持客户,但是这是由于极值的影响,所以RFM的划分应该以业务微赚

  • 尽量用小部分的用户覆盖大部分的额度
  • 不要为了数据好看划分等级
  • 极值会拉均值
  • 根据数据可以和业务相结合,如何提升一些重要的指标

用户状态分层

pivoted_counts = df.pivot_table(index = 'user_id',
                                  columns = 'month',
                                  values = 'order_dt',
                                  aggfunc = 'count').fillna(0)
# pivoted_counts.head()
pivoted_counts.head()
# 按月份进行对比,1月份哪些是购买的,再去对比二月份哪些是购买的

74a19c527d8847efbcdb81d51f4b5509.png
消费过的为1 ,没消费过的为0
data_purchase = pivoted_counts.applymap(lambda x: 1 if x > 0 else 0)

将用户状态分为unreg(未注册)、new(新客)、active(活跃用户)return(回流用户)和unactive(不活跃用户)

编写思路:

  • 若本月没有消费
    • 若之前是未注册,则依旧为未注册
    • 若之前有消费,则为流失/不活跃
    • 其他情况,为未注册
  • 若本月有消费
    • 若是第一次消费,则为新用户
    • 若之前有过消费,则上个月为不活跃,则为回流
    • 若上个月为未注册,则为新用户
    • 除此之外,为活跃
#用户状态
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')
# 这里需要对返回的值进行转换,将列表转为Series
    return pd.Series(status, index = pivoted_counts.columns)

purchase_stats = df_purchase.apply(active_status,axis=1)
# df.rename(columns={ df.columns[2]: "new name" }, inplace=True)
# purchase_stats.rename(columns={purchase_stats.columns:data_purchase.columns})
purchase_stats.head()
# 未注册不希望参与处理 设置为空值

19fa9d5b27d44e9090575e0e86a429f8.png
purchase_status_ct = purchase_stats.replace('unreg',np.NaN).apply(lambda x : pd.value_counts(x))
purchase_status_ct

#将上面透视表转置
purchase_stats_ct.fillna(0).T.head()

1048cd8e978fa9bee403ff8b81cb686f.png
#作用户状态分层面积图
purchase_stats_ct=purchase_status_ct.fillna(0).T
purchase_stats_ct.plot.area(figsize=(12,5))
plt.title('用户分层')

a4cef5d6732eac0fa21fef90b5456d8c.png

由上可知不同客户活跃状态,根据业务需求采取不同的运营策略,如拉新,引流,促活,召回等

用户购买周期(按订单)

#计算用户相邻订单日期的差值,其中shift()函数是指将数据进行移动,默认axis=0

fd748f40262853e493b6a1505162dd28.png
order_diff.describe()

75f05fffd5332d812de6da5b8c46a737.png
#作用户消费周期分布:
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
plt.title('用户消费周期分布')

aa8feaa6b84655ab6943f5d2c3a85a2c.png
  • 用户的生命周期受只购买一次的用户影响比较厉害(可以排除)
  • 用户均消费134天,中位数仅0天,明显受到只购买一次用户的影响明显
#usertime_diff代表用户消费首次订单与最后一次订单间隔时间
usertime_diff=df['order_dt'].groupby(df['user_id']).max()-df['order_dt'].groupby(df['user_id']).min()
usertime_diff=usertime_diff.reset_index()
usertime_diff=usertime_diff['order_dt']/np.timedelta64(1,'D')
usertime_diff[usertime_diff>0].hist(bins=40)
plt.title('用户生命周期分布')

3a692aff1a5479546da2a3b5ce6d9fc0.png

这是双峰趋势图。部分质量差的用户,虽然消费了两次,但是仍旧无法持续,在用户首次消费30天内应该尽量引导。少部分用户集中在50天~300天,属于普通型的生命周期,高质量用户的生命周期,集中在400天以后,这已经属于忠诚用户了

6.复购率和回购率分析

复购率:自然月内,购买多次的用户占比

回购率:曾今购买过的用户在某一时期内的再次购买占比

注:指标的定义和标准是根据不同公司的业务形态而变化

  • 复购率
# 作透视表,计算客户每个月的消费次数
pivoted_counts=df.pivot_table(index='user_id',
                              columns='month',
                              values='order_dt',
                              aggfunc='count').fillna(0)
pivoted_counts.head()

bd9a193ec6db4f3f422ce16b4246bd75.png

以上透视表记录了每位用户每月消费次数的记录,是一份消费明细表

# 购买大于1次的 赋值为1 ,然后小于等于1 的 如果是购买次数是0,则赋值为空,否则 就是购买一次,赋值为0
purchase_r = pivoted_counts.applymap(lambda x: 1 if x > 1 else np.NaN if x == 0 else 0)
purchase_r.head()

a2f22d11fe1945b72ede7c3dd05ea848.png
#计算复购率
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
plt.title('复购率')

8e9e19dc7d67e232fcabdd75bd5708a4.png

复购率稳定在20%左右,前三个月因为有大量新用户涌入,而这些用户只购买了一次,所以导致复购率降低,后续月份用户数量比较稳定,所以复购率也稳定在21%左右,即后续月份每月有大概21%的用户会在一个月内消费两次以上回购率

# 回购率 只需要0  1 代表
data_purchase.head()

681da46c85ee6e4f48e649bd7260e702.png

定义一个函数,将消费两次以上记为1,消费一次记为0,没有消费记为空值:

def purchase_back(data):
    status = []
    for i in range(17):
        if data[i] == 1: # 本月进行过消费
            if data[i+1] == 1: # 下一月是否进行消费
                status.append(1) #消费为1 回购了
            if data[i+1] == 0:
                status.append(0) # 未消费则为0 没有回购
        else:
            status.append(np.NaN) # 之前没消费则不计
    status.append(np.NaN) # 最后一个月没有判断需要补上
    return pd.Series(status,df_purchase.columns)
#对透视表应用函数purchase_back:
purchase_b = df_purchase.apply(purchase_back, axis =1)
purchase_b.head()

fc1961ae97f57dd65e2ad4eb6bb8b392.png

计算回购率:

(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))
plt.title('回购率')
  • 0 表示上个月购买了,下个月没有进行消费,则是没有回购 ,
  • 1代表当月消费过次月依旧消费,表示回购了
  • NAN表示当月没有消费(不进行计算)

355784c7583bdaa7b2621e21e30f45ac.png
  • 前三个月因为有大量的新用户涌入,但是超过一半的人只消费了一次,所以前三个月回购率比较低,后续月份用户人数比较稳定,回购率也比较稳定,稳定在30%左右,即当月消费人数中有30%左右的用户会在下一个月再次消费
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值