CD用户消费行为分析-知识点补充

背景:

用户消费行为分析学习材料

数据:txt格式. 百度云 提取码:9tkg

问题引入点:秦璐—如何七周成为数据分析师:用Python分析用户消费行为

思路整理分析:CDNow网站用户消费行为分析——Python篇

关键点:

1.项目背景:分析CDNow网站的用户购买明细来分析该网站的用户消费行为,使运营部门在营销时更加具有针对性,从而节省成本,提升效率。

2.导入数据与清洗

导库:pandas 、numpy、matplotlib.pyplot、datetime

描述性统计 df.describe() 数据信息(缺省值) df.info() 类型转换

3.消费行为特征

用户总体消费趋势分析——月份

用户消费周期分析——用户ID

购买周期(按订单),周期(按第一次&最后一次消费)

用户分层——价值(RFM模型)/ 活跃程度(新用户、活跃用户、不活跃用户、回流用户)

用户质量分析 消费时长,复购率, 回购率, 留存率,贡献率

具体练习程序:

基本语法补充;https://www.cnblogs.com/nxld/p/6058591.html Python中的pandas模块进行数据分析

#导入常用的库
import pandas as pd 
import numpy as np
import  matplotlib.pyplot as plt
from datetime import datetime
% matplotlib inline
‘%’内置的命令,jupyter专有的定义,比如在pycharm不常用到,inline意思是做图之后可以在html页面的单元格进行显示
plt.style.use('ggplot')  #更改设计风格,使用自带的形式进行美化,这是一个r语言的风格
#导入源数据
columns = ['用户ID','购买日期','订单商品数','订单金额']
df = pd.read_table("D:/BaiduNetdiskDownload/CDNOW_master.txt",names = columns,sep = '\s+')
df.head()

如果不做数据清洗,很难进行下一步的数据分析,为了进行数据格式的转化,pandas里面有三种比较常用的方法

  1. astype()强制转化数据类型
    带有特殊符号的object是不能直接通过astype("flaot)方法进行转化的,如果数据是纯净的数据,可以转化为数字.astype基本也就是两种用作,数字转化为单纯字符串,单纯数字的字符串转化为数字,含有其他的非数字的字符串是不能通过astype进行转化的
  2. 通过创建自定义的函数进行数据转化
    例如: convert the string number to a float

_ 去除$ - 去除逗号, - 转化为浮点数类型.

//////////////////////////////////////////////////////////////////////

 def convert_currency(var):
 new_value = var.replace(",","").replace("$","")
 return float(new_value)   
 又例如  
 .apply(lambda x: x.replace(",","").replace("$","")).astype("float64")
  再如利用np.where() function 将Active 列转化为布尔值。
  1. pandas提供的to_nueric()以及to_datetime() 使用pd.to_numeric可以将其它形式的数据转换成整数型的数据
    利用pd.to_datatime()将年月日进行合并 pd.to_datetime(df[['Month', 'Day', 'Year']])
df.describe()
image.png
#索引,数据类型和内存信息
df.info()
image.png
df.dtypes
image.png
# 将购买日期列进行数据类型转换
df['购买日期'] = pd.to_datetime(df.购买日期,format = '%Y%m%d') #Y四位数的日期部分,y表示两位数的日期部分
df['月份'] = df.购买日期.values.astype('datetime64[M]') 
df.head()
image.png
#解决中文显示参数设置
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
# 设置图的大小,添加子图
plt.figure(figsize=(25,15))

# 每月的总销售额
plt.subplot(221)
df.groupby('月份')['订单金额'].sum().plot(fontsize=24) 
plt.title('总销售额',fontsize=24) 
 
#每月的消费次数
plt.subplot(222)
df.groupby('月份')['购买日期'].count().plot(fontsize=24)
plt.title('消费次数',fontsize=24) 

#每月的销量
plt.subplot(223)
df.groupby('月份')['订单商品数'].sum().plot(fontsize=24)
plt.title('总销量',fontsize=24)

#每月的消费人数
plt.subplot(224)
df.groupby('月份')['用户ID'].apply(lambda x:len(x.unique())).plot(fontsize=24)
plt.title('消费人数',fontsize=24)
plt.tight_layout() # 设置子图的间距
plt.show()
image.png
# 根据用户id进行分组
group_user = df.groupby('用户ID').sum()
group_user.describe()
image.png
#查询条件:订单金额 < 4000
group_user.query('订单金额 < 4000').plot.scatter(x='订单金额',y='订单商品数')
image.png
group_user.订单金额. plot.hist(bins = 20)
#bins = 20,就是分成20块,最高金额是14000,每个项就是700
image.png
group_user.query("订单金额< 800")["订单金额"].plot.hist(bins=40)
image.png
group_user.query('订单商品数 < 100').订单商品数.hist(bins = 25)
image.png
#每个用户的每次购买时间间隔
order_diff = df.groupby('用户ID').apply(lambda x:x['购买日期'] - x['购买日期'].shift())
order_diff.head(10)

[注]注意表的所选数据要一致 同步更新,in[*]好像是没处理完,结果没显示出来。
shift 用法 https://www.cnblogs.com/iamxyq/p/6283334.html shift函数是对数据进行移动的操作
函数原型:DataFrame.shift(periods=1, freq=None, axis=0) 正值下移
参数 periods:类型为int,表示移动的幅度,可以是正数,也可以是负数,默认值是1,1就表示移动一次,注意这里移动的都是数据,而索引是不移动的,移动之后没有对应值的,就赋值为NaN。
freq: DateOffset, timedelta, or time rule string,可选参数,默认值为None,只适用于时间序列,如果这个参数存在,那么会按照参数值移动时间索引,而数据值没有发生变化。
axis:{0, 1, ‘index’, ‘columns’},表示移动的方向,如果是0或者’index’表示上下移动,如果是1或者’columns’,则会左右移动。

image.png

order_diff.describe()
image.png
plt.figure(figsize=(15,5))
plt.hist((order_diff / np.timedelta64(1, 'D')).dropna(), bins = 50)
plt.xlabel('消费周期',fontsize=24)
plt.ylabel('频数',fontsize=24)
plt.title('用户消费周期分布图',fontsize=24)

dropna用法:如果是Series,则返回一个仅含非空数据和索引值的Series,默认丢弃含有缺失值的行;处理DataFrame对象,默认滤除所有包含NaN,传入how=‘all’滤除全为NaN的行,传入axis=1滤除列(0-行),传入thresh=n保留至少有n个非NaN数据的行。
https://blog.csdn.net/weixin_38168620/article/details/79596798?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
timedelta64其实就是能将时间转换为是以年,月或者日,小时等为基础单位。在已有的日期时间上可以与之加减运算,较为方便,不需要自己将两个运算的时间转换一致。

image.png

orderdt_min=df.groupby('用户ID').购买日期.min()#第一次消费
orderdt_max=df.groupby('用户ID').购买日期.max()#最后一次消费
id_ordercycle=orderdt_max-orderdt_min
id_ordercycle.head()
image.png
id_ordercycle.describe()
image.png
((orderdt_max-orderdt_min)/np.timedelta64(1,'D')).hist(bins=15)

因为数据类型是timedelta时间,无法直接作出直方图,所以先换算成数值。换算的方式直接除timedelta函数即可,np.timedelta64(1, ‘D’),D表示天,1表示1天,作为单位使用的。因为max-min已经表示为天了,两者相除就是周期


image.png
#计算所有消费过两次以上的老客的生命周期
life_time = (orderdt_max - orderdt_min).reset_index()
life_time.head(10)
image.png
#用户生命周期分布图
plt.figure(figsize=(10,5))
life_time['life_time'] = life_time.购买日期 / np.timedelta64(1,'D')
life_time[life_time.life_time > 0].life_time.hist(bins = 150, figsize = (15,10))
#判断语句,筛选数据(life_time.hist,数据里还有user_id)
image.png
#去掉0天生命周期的用户之后的用户生命周期各描述值
life_time[life_time.life_time>0].购买日期.describe()
image.png
参考《Pandas透视表(pivot_table)详解》一文,链接地址:http://python.jobbole.com/81212/
  pivot_table(data,values=None,
  index=None,
columns=None,
  aggfunc='mean',
  fill_value=None,
  margins=False,
  dropna=True,
 margins_name='All')
rfm = df.pivot_table(index = '用户ID',
                     values = ['订单金额','购买日期','订单商品数'],
                     aggfunc = {'订单金额':'sum',
                                '购买日期':'max',
                                '订单商品数':'sum'})
rfm.head()
image.png
# 日期的最大值与当前日期的差值为R
rfm['R'] = (rfm['购买日期'].max() - rfm['购买日期']) / np.timedelta64(1,'D')
rfm.rename(columns = {'订单金额':'M',
                     '订单商品数':'F'},
          inplace=True)
rfm.head()
image.png
# 构建rfm模型公式
def get_rfm(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()) / x.std()).apply(get_rfm,axis=1)# 先计算出平均值差值与std差的比值,再用自定义函数
rfm.head()
image.png
#求和
rfm.groupby('label').sum()
image.png
rfm.groupby('label').size().sort_values()  

排序 https://www.jianshu.com/p/bea8c73582e1

image.png

rfm.groupby('label').sum().sort_values(by ="M")
image.png
#将用户消费数据进行数据透视:
#用户活跃程度分层
#将用户消费数据进行数据透视:
df1 = df.pivot_table(index = "用户ID",
                     columns = "月份",
                     values = '购买日期',
                     aggfunc = 'count').fillna(0)
df1.head() 
image.png

1.dataframe和series中都有apply方法,dataframe中的apply方法需要指定axis参数,是对一行或者一列进行操作,比如求一列的最大值与最小值之差等;series中的apply方法与map方法一样,都是作用于单个元素,所以不具有axis参数;更不能求最大值与最小值之差,因为apply到的是单个元素,单个元素时没有最大值、最小值一说的;
2. dataframe中的apply()函数是自动作用于每一列/行,不是单独某一列或者某一行,否则就成了series了
3.series中的map和apply函数自动作用于每一个元素,不是单独的某个元素,否则直接指定元素进行相应操作好啦,不用map和apply函数了
https://www.cnblogs.com/ironan-liu/p/11458546.html

df2 = df1.applymap(lambda x:1 if x>0 else 0)
df2.tail()
image.png
#可得到一张不同用户在不同月份的不同状态(new=新、active=活跃、return=回流、unactive=流失),
# unreg相当于未注册,指这个用户在这个月及以前从未购买过产品,主要为了统计起来更加方便而加进去。
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['月份'].sort_values().astype('str').unique()
df3=df2.apply(lambda x:pd.Series(active_status(x),index=indexs),axis=1)
u =df3.reset_index()
df3.head()
image.png
df4=df3.apply(lambda x:pd.value_counts(x)).fillna(0).T
df4
image.png
#把unreg替换成NaN,再用fillna(0)把空值填为0。然后转置,把月份作为索引行,状态作为列,得到如下的表 
df4=df3.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T
df4
image.png
u =df4.reset_index()#  (in/re)set_ index 用法 https://blog.csdn.net/jingyi130705008/article/details/78162758
u.head()
image.png
#非堆积图效果 stackplot https://blog.csdn.net/qq_35189715/article/details/96108580
labels = u[['active','new','return','unactive']].columns
plt.figure(figsize=(15,5))
plt.stackplot(u['index'].astype(str).apply(lambda x:x[:-3]), u['active'],u['new'],u['return'],u['unactive'], labels=labels)
plt.xlabel('月份')
plt.ylabel('消费人数')
plt.title('每月的消费人数')
plt.legend(loc = 'upper left');
image.png

python之matplotlib和pandas绘图 1.https://blog.csdn.net/layman2016/article/details/79538889?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase
2.https://blog.csdn.net/genome_denovo/article/details/78322628?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
3.https://blog.csdn.net/qq_40195360/article/details/103710930

df5=df4.apply(lambda x:x/x.sum(),axis=1)#每一层用户占总用户的比例
df5
image.png
a = df.groupby('用户ID')['购买日期'].agg(['min','max']).reset_index() 
#agg函数通常用于调用groupby()函数之后,对数据做一些聚合操作,包括sum,min,max以及其他一些聚合函数,
# 通常在调用完agg函数后需要reset_index,因pandas会默认将groupby()的列也做为index传到结果中
a.head()
image.png
new_old = (a['min'] == a['max']).value_counts().values
plt.pie(x = new_old,
       autopct = '%.1f%%',
       shadow = True,
       explode = [0.08,0],
       textprops = {'fontsize' : 11})
plt.axis('equal') 
plt.legend(['仅消费一次','多次消费'])
image.png
#每个用户在每月的订单数
#pivot_table透视表
pivoted_df=df.pivot_table(index='用户ID',columns='月份',values='购买日期',aggfunc='count').fillna(0)#某些用户在某月没有消费过,用nan表示,这里用0填充
pivoted_df.head()
image.png
#转换:消费2次以上记为1,消费1次记为0,消费0次记为NAN  
#applymap针对dataframe所有数据
pivoted_df_transf=pivoted_df.applymap(lambda x: 1 if x>1 else np.nan if x==0 else 0)
#lambda 之后是该匿名函数的一个或多个参数(用英文逗号分隔),然后是一个英文冒号 :  大于1是1,等于0是nan,其他是0 
pivoted_df_transf.head()
image.png
#count统计所有非空数据个数表示总消费用户数,sum计算非0数据的和表示消费两次以上的用户数
df_duplicate =pd.DataFrame(pivoted_df_transf.sum()/pivoted_df_transf.count()).reset_index()
df_duplicate.columns = ['Date', 'DuplicatedRate']
df_duplicate['Date'] = df_duplicate.Date.astype(str).apply(lambda x:x[:-3])

plt.figure(figsize = (15,6))
plt.plot(df_duplicate.Date, df_duplicate.DuplicatedRate)
plt.xlabel('时间', fontsize=24)
plt.ylabel('复购率',fontsize=24)
# plt.ylim(0,1)
plt.title('复购率的变化',fontsize=24)
image.png
# unique()是以 数组形式(numpy.ndarray)返回列的所有唯一值(特征的所有唯一值)
# nunique() Return number of unique elements in the object.即返回的是唯一值的个数
#回购率
#每个用户每个月平均消费金额
pivoted_money=df.pivot_table(index='用户ID',columns='月份',values='订单金额',
                             aggfunc='mean').fillna(0)  # pd.pivot_table 参数   https://www.jianshu.com/p/d6782830fe62  

columns_month=df.月份.sort_values().astype('str').unique()
pivoted_money.columns=columns_month
pivoted_money.head()
image.png
#将有消费的记为1,没有消费的记为0
pivoted_purchase=pivoted_money.applymap(lambda x:1 if x>0 else 0)
pivoted_purchase.head()
image.png
# 判断
def purchase_return(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)
        else:
            status.append(np.nan)
    status.append(np.nan)
    return pd.Series(status, index=columns_month) #status
pivoted_purchase_return=pivoted_purchase.apply(purchase_return,axis=1) #axis=1表示计算方向在行的方向上,左右运算
pivoted_purchase_return.head()
image.png
df_purchase = (pivoted_purchase_return.sum() / pivoted_purchase_return.count()).reset_index()
df_purchase.columns = ['Date', 'PurchaseRate']
df_purchase['Date'] = df_purchase.Date.astype(str).apply(lambda x:x[:-3])# [:-3]去掉倒数3个,此处对应  -01
plt.figure(figsize = (15,5))
plt.plot(df_purchase.Date, df_purchase.PurchaseRate)
plt.xlabel('时间', fontsize=24)
plt.ylabel('回购率', fontsize=24)
plt.title('回购率的变化', fontsize=24)
image.png
#分析留存率
#新建一个对象,并增加用户第一次消费时间的列,merge将两个dataframe合并
data_t=df[['用户ID','购买日期','订单商品数','订单金额']]
user_purchase_retention=pd.merge(left=data_t,right=orderdt_min.reset_index(),
how='inner',on='用户ID',suffixes=('','_min'))#当列冲突时,即有多个列名称相同时,需要使用on=来指定哪一个列作为key,配合suffixes指定冲突列名;可以使用suffixes=自己指定后缀
#jupyter入门之pandas    https://blog.csdn.net/weixin_42042680/article/details/80684415
user_purchase_retention.head(5)
image.png
#每一次消费时间与第一次消费时间间隔
user_purchase_retention['order_date_diff']=user_purchase_retention['购买日期']-user_purchase_retention['购买日期_min']
#将timedelta转换为数值型
user_purchase_retention['date_diff']=user_purchase_retention.order_date_diff.apply(lambda x:x/np.timedelta64(1,'D'))
user_purchase_retention.head(5)
image.png
#将时间间隔分桶(0-3)(3-7)等
bin=[0,3,7,15,30,60,90,180,365]
user_purchase_retention['date_diff_bin']=pd.cut(user_purchase_retention.date_diff,bins=bin)
user_purchase_retention.head(10)
image.png
#用户第一次消费之后,后续各时间段的消费总额
pivoted_retention=user_purchase_retention.pivot_table(index='用户ID',
columns='date_diff_bin',values='订单金额',aggfunc=sum,dropna=False)
pivoted_retention.head()
image.png
pivoted_retention.mean()#各时间段的平均消费额
image.png
#1代表有消费,0代表没有
pivoted_retention_trans=pivoted_retention.applymap(lambda x:1 if x>0 else 0)
pivoted_retention_trans
image.png

#每笔订单离第一笔订单的时间间隔
(pivoted_retention_trans.sum()/pivoted_retention_trans.count()).plot.bar(figsize=(10,5))
plt.xlabel('消费时间间隔')
plt.title('留存率')
image.png
#先将用户消费金额按升序排列,逐行计算用户累计金额,最后一行是总消费金额
user_money=df.groupby('用户ID').订单金额.sum().sort_values().reset_index()
user_money['money_cumsum']=user_money.订单金额.cumsum()
money_total=user_money.money_cumsum.max()#python cumsum函数 累加和  https://blog.csdn.net/banana1006034246/article/details/78841461
#转行成百分比
user_money['prop']=user_money.apply(lambda x:x.money_cumsum/money_total,axis=1)#apply用在每个行上
user_money.tail()
user_money.prop.plot()
plt.xlabel('用户ID', fontsize=24)
plt.ylabel('比率', fontsize=24)
plt.title('用户累计销售额贡献比', fontsize=24);
image.png

你可能感兴趣的:(CD用户消费行为分析-知识点补充)