利用Python进行用户消费行为分析(CDNOW_master)
用户消费行为的分析报告
想必大家对于CD用户消费者行为的分析已经见得多了,这里就不再一一叙述,这里主要是作为我的一个小练习,来提高自己处理业务的能力。
项目需求如下:
(1)用户消费趋势分析
- 每月的消费总金额
- 每月的消费次数
- 每月的产品购买量
- 每月的消费人数
(2)用户个体消费行为分析
- 用户消费金额和消费总数的描述统计
- 用户消费金额和消费总数的散点图
- 用户消费金额和消费总数的分布图
- 用户累计消费金额的占比
(3)用户消费行为分析
- 用户第一次消费时间(用户首次购买产品的时间)
- 用户最后一次消费时间
- 新老客消费占比
- 用户分层(RFM模型)
获取数据
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
%matplotlib inline
columns = ['user_id','order_dt','order_products','order_amount']
CDNOW = pd.read_table('./CDNOW_master.txt',sep='\s+',names=columns) # \s+多个空格进行分割,+是空格匹配
CDNOW.head()
user_id | order_dt | order_products | order_amount | |
---|---|---|---|---|
0 | 1 | 19970101 | 1 | 11.77 |
1 | 2 | 19970112 | 1 | 12.00 |
2 | 2 | 19970112 | 5 | 77.00 |
3 | 3 | 19970102 | 2 | 20.76 |
4 | 3 | 19970330 | 2 | 20.76 |
数据来源CDNow网站的用户购买明细,主要包括以下几个字段:
- user_id 用户ID
- order_dt 购买日期
- order_products 购买产品数
- order_amount 购买金额
数据处理
- 检查数据是否有空值
# CDNOW.info()
CDNOW.isnull().any()
user_id False
order_dt False
order_products False
order_amount False
dtype: bool
用CDNOW.info()可以发现order_dt的类型为int型,需要转换为日期类型
CDNOW['order_dt'] = pd.to_datetime(CDNOW['order_dt'],format='%Y%m%d')
添加一列month,方便后面进行分析
CDNOW['month'] = CDNOW['order_dt'].values.astype('datetime64[M]')
CDNOW.head()
user_id | order_dt | order_products | order_amount | month | |
---|---|---|---|---|---|
0 | 14048 | 1997-02-24 | 1 | 11.77 | 1997-03-01 |
查看数据描述
print(CDNOW.mode())
CDNOW.describe()
user_id order_dt order_products order_amount month
0 14048 1997-02-24 1 11.77 1997-03-01
user_id | order_products | order_amount | |
---|---|---|---|
count | 69659.000000 | 69659.000000 | 69659.000000 |
mean | 11470.854592 | 2.410040 | 35.893648 |
std | 6819.904848 | 2.333924 | 36.281942 |
min | 1.000000 | 1.000000 | 0.000000 |
25% | 5506.000000 | 1.000000 | 14.490000 |
50% | 11410.000000 | 2.000000 | 25.980000 |
75% | 17273.000000 | 3.000000 | 43.700000 |
max | 23570.000000 | 99.000000 | 1286.010000 |
- 从用户购买的商品数来看,平均每位用户购买2.4个商品,标准差在2.3左右,证明有一定的偏差,而中位数为2,3/4分位数为3,说明了大多数的订单量都不多,购买商品的最大值为99。(中位数<均值,结合众数可以发现,用户购买的商品数分布是属于右偏分布的,均值>中位数>众数)。
- 从用户购买的金额来看,每个用户贡献的金额为36,而最大值达到了1286,证明用户的购买金额也是服从长尾分布的,符合二八原则,即20%的用户贡献了80%的交易额。
数据分析
进行用户消费趋势的分析(按月)
该部分内容主要是从用户每月的消费总金额、每月的消费次数、每月的产品购买量以及每月的消费人数进行分析。
每月的消费总金额
按月分组后,对order_amount进行求和,可以得到每月的消费总金额
group_month = CDNOW.groupby('month') # 按月分组
group_month_amount = group_month['order_amount'].sum()
plt.style.use('ggplot')
group_month_amount.plot()
plt.xlabel('月份')
plt.ylabel('消费金额')
plt.title('每月的消费金额折线图')
从上图可以看出,消费金额在1997年的前三个月达到了高峰。其中在3月份,销售额达到了最大值,为393155.27元,之后消费金额呈下降趋势,并不断趋于平稳的状态。
每月的消费次数
按月分组后,对order_product进行计数,可以得到每月的商品消费次数
group_month_order = group_month['order_products'].count()
group_month_count.plot(color='yellow')
plt.xlabel('月份')
plt.ylabel('消费次数')
plt.title('每月的消费次数')
从上图可以看出,消费次数在1997年的前三个月达到了最大值。其中在3月份达到了最大值,消费次数为 11598次,平均消费次数在10000左右,而后续的月份平均消费次数在2500左右。
每月的商品购买量
按月分组后,对order_product进行求和,可以得到每月的商品购买量。
group_month_product = group_month['order_products'].sum()
group_month_product.plot(color='green')
plt.xlabel('月份')
plt.ylabel('商品购买量')
plt.title('每月的商品购买量')
和前面的分析一样,商品购买量的最大值出现在了1997年的前三个月中,其中最大值达到了26159,其中不断下降并趋于平稳状态。1997年的前三个月出现购买量剧增的情况,可能与当时的外部环境、内部环境等多方面因素都有一定的联系,这里没能拿到进一步的数据,无法详细的进行分析。
每月的消费人数
使用nunique()去重
# group_userid_count = df.groupby(['month','user_id']).count().reset_index()
# group_userid_count = group_month['user_id'].nunique()
CDNOW.groupby('month').user_id.nunique().plot(color='blue')
plt.xlabel('月份')
plt.ylabel('消费人数')
plt.title('每月的消费人数')
从上图来看,每月的消费人数在1997年的前三个月出现了最大值,而后出现平稳的下降趋势,由于其他的数据,暂时还没有办法分析出前三个月出现最大值的原因。
使用数据透视表进行求取
也可以使用数据透视表进行求和、计数等操作
CDNOW.pivot_table(
index='month',
values= ['user_id','order_products','order_amount'],
aggfunc={
'order_products':'sum',
'order_amount':'sum',
'user_id':'count' }
).head()
order_amount | order_products | user_id | |
---|---|---|---|
month | |||
1997-01-01 | 299060.17 | 19416 | 8928 |
1997-02-01 | 379590.03 | 24921 | 11272 |
1997-03-01 | 393155.27 | 26159 | 11598 |
1997-04-01 | 142824.49 | 9729 | 3781 |
1997-05-01 | 107933.30 | 7275 | 2895 |
用户个体消费行为分析
这部分内容主要从用户消费金额和消费总数的描述统计、用户消费金额和消费总数的散点图、用户消费金额和消费总数的分布图(二八法则)、用户累计消费金额的占比这几个方面进行分析。
用户消费金额和消费总数的描述统计
根据用户的ID进行分组,分别对每个用户购买的商品数、消费金额进行求和。
group_user = CDNOW.groupby('user_id')
group_user.sum().describe()
order_products | order_amount | |
---|---|---|
count | 23570.000000 | 23570.000000 |
mean | 7.122656 | 106.080426 |
std | 16.983531 | 240.925195 |
min | 1.000000 | 0.000000 |
25% | 1.000000 | 19.970000 |
50% | 3.000000 | 43.395000 |
75% | 7.000000 | 106.475000 |
max | 1033.000000 | 13990.930000 |
每位用户平均购买了7个产品,但是中位数只有3,结合众数来看,说明用户的消费次数时符合右偏分布的,数据中存在极大值(小部分用户购买了大量的产品),把平均值往右边拉,提高了平均值。
每位用户的平均消费金额为106元,中位数值为43,和用户的商品购买量一样,符合右偏分布,说明了存在极值的干扰,拉高了平均消费金额。
用户消费金额和消费总数的散点图
# group_user.sum().plot.scatter(x = 'order_amount',y='order_products')
plt.scatter(group_user.sum()['order_amount'],group_user.sum()['order_products'],c='blue')
plt.xlabel('消费金额')
plt.ylabel('消费总数')
plt.title('用户消费金额和消费总数的散点图')
从上图的散点图中可以明显的看出,数据中存在少数极值,这些极值可能会影响到分析的结果,故需要把极值过滤掉。
group_user_sum = group_user.sum().query('order_amount<4000')
plt.scatter(group_user_sum['order_amount'],group_user_sum['order_products'],c='red')
plt.xlabel('消费金额')
plt.ylabel('消费总数')
plt.title('用户消费金额和消费总数的散点图')
从这张散点图中可以看出,用户的消费总数和用户的消费总金额存在某种线性关系。
用户消费金额和消费总数的分布图(二八法则)
# bins =(group_user.sum()['order_amount'].max()-group_user.sum()['order_amount'].min()) / len(group_user.sum()['order_amount'])
plt.figure(figsize=(16,9))
plt.subplot(2,2,1)
plt.hist(group_user.sum()['order_amount'],bins=20,color='green')
plt.xlabel('order_amount')
plt.ylabel('frequency')
plt.title('用户消费金额的分布图')
plt.subplot(2,2,2)
plt.hist(group_user.sum()['order_products'],bins=20,color='red')
plt.xlabel('order_products')
plt.ylabel('frequency')
plt.title('用户消费次数的分布图')
从上述直方图可以看出,用户消费金额,绝大部分是呈现集中趋势,小部分极值干扰了判断,故需要过滤掉极值
plt.figure(figsize=(16,9))
plt.subplot(2,2,1)
group_user_sum = group_user.sum().query('order_amount<4000')
plt.hist(group_user_sum['order_amount'],bins=40,color='green')
plt.xlabel('order_amount')
plt.ylabel('frequency')
plt.title('用户消费金额的分布图')
plt.subplot(2,2,2)
group_user_sum = group_user.sum().query('order_products<100')
plt.hist(group_user_sum['order_amount'],bins=40,color='red')
plt.xlabel('order_products')
plt.ylabel('frequency')
plt.title('用户消费总数的分布图')
从上面两个图中,可以明显的看出,用户消费金额的分布和用户消费总数的分布都是符合右偏分布的,和前面的描述性统计结果一致。
用户累计消费金额的占比
累积消费金额的占比是使用每次累积得到的消费金额除以总的消费金额。
# user_cumsum = group_user.sum().sort_values('order_amount').apply(lamda x: x.cumsum()/x.sum())
user_cumsum =group_user.sum().sort_values('order_amount').cumsum()/group_user.sum().sort_values('order_amount').sum()
user_cumsum.reset_index().head()
user_id | order_products | order_amount | |
---|---|---|---|
0 | 10175 | 0.000006 | 0.0 |
1 | 4559 | 0.000012 | 0.0 |
2 | 1948 | 0.000018 | 0.0 |
3 | 925 | 0.000024 | 0.0 |
4 | 10798 | 0.000030 | 0.0 |
order_amount出现0的情况可能是因为商品有不同的优惠方式,所以出现购买金额出现0的情况
plt.plot(range(0,23570),user_cumsum['order_amount'],color='blue')
plt.xlabel('消费人数')
plt.ylabel('用户累计消费金额占比')
plt.title('用户累计消费金额的占比图')
按照用户消费金额进行升序排列,并进行累计求和,可以发现:50%的用户贡献了15%的消费额度,而排名前5000的用户就贡献了60%的消费额(看头部用户)。
用户消费行为分析
用户消费行为分析可以从用户第一次消费时间、用户最后一次消费时间、新老客消费比、用户分层、用户购买周期、用户生命周期进行分析。
用户第一次消费时间(用户首次购买产品的时间)
先对用户进行分组,然后取用户的最小购买时间。
group_user = CDNOW.groupby('user_id')
group_user.min()['order_dt'].value_counts().plot() # value_counts()根据日期进行计数
plt.xlabel('用户第一次消费时间')
plt.ylabel('用户数')
用户的第一次购买时间,集中在前面三个月。其中,在2月11日-2月25日之间有两次剧烈的波动。
由于没有具体的数据,无法分析引起数据发生的变化,但是我们可以做出假设:
- 渠道发生了变化?
- 周期性变化?
- 等等
用户最后一次消费时间
group_user.max().order_dt.value_counts().plot(color='blue')
plt.xlabel('用户最后一次消费时间')
plt.ylabel('用户数')
用户最后一次购买的分布比用户第一次购买的分布更广,大部分用户最后一次后买,集中在前三个月,说明了有很多新用户购买了一次之后就不再进行购买了。
随着时间的递增,最后一次购买也在递增,消费呈现流失上升的状况。
新老客消费比
- 多少用户仅消费了一次?
user_life = group_user['order_dt'].agg(['min','max'])
user_life.head()
min | max | |
---|---|---|
user_id | ||
1 | 1997-01-01 | 1997-01-01 |
2 | 1997-01-12 | 1997-01-12 |
3 | 1997-01-02 | 1998-05-28 |
4 | 1997-01-01 | 1997-12-12 |
5 | 1997-01-01 | 1998-01-03 |
先计算出用户的首次消费时间和最后一次消费时间,如果首次消费时间=最后一次消费时间,则证明该用户只消费了一次。比如说用户ID为1的用户,他的首次消费时间为1997年1月1日,而他的最后一次消费时间也是1997年1月1日,说明这位用户仅消费了一次。
(user_life['max'] == user_life['min']).value_counts()
True 12054
False 11516
dtype: int64
结果分析:有超过一半的用户只消费了一次
- 新客占比?
group_month_user = CDNOW.groupby(['month','user_id'])
user_life_month = group_month_user['order_dt'].agg(['min','max'])
(user_life_month['min'] == user_life_month['max']).value_counts()
True 46727
False 8652
dtype: int64
从上面的分析结果可以看出,新客为46727人,而老客人数为8652。
用户分层
(1)RFM模型
可以通过用户进行细分,区别出低价值用户、高价值用户,针对不同的用户群体开展不同的个性化服务,将有限的资源合理地分配给不同价值的客户,实现效益最大化。
- R:recency最近一次消费时间,理论上R值越小,价值越高;
- F:frequency最近一次消费频率,消费频率越高意味着这部分用户对产品的满意度越高,用户粘性比较好,忠诚度也高;
- M:Montary最近一段时间消费的金额,符合二八原则
# 建立数据透视表
rfm = CDNOW.pivot_table(
index='user_id',
values= ['order_products','order_amount','order_dt'],
aggfunc={
'order_products': 'count',
'order_amount': 'sum',
'order_dt': 'max'
})
# 计算R是使用当前时间-最近一次时间,但是由于这个数据比较老,隔的时间比较久远,故用最大时间来代替
rfm['R'] = (rfm['order_dt'].max() - rfm['order_dt']) / np.timedelta64(1,'D') # np.timedelta64(1,'D')是为了消除单位
rfm.rename(columns={
'order_amount':'M','order_products':'F'},inplace=True)
rfm.head()
M | order_dt | F | R | |
---|---|---|---|---|
user_id | ||||
1 | 11.77 | 1997-01-01 | 1 | 545.0 |
2 | 89.00 | 1997-01-12 | 2 | 534.0 |
3 | 156.46 | 1998-05-28 | 6 | 33.0 |
4 | 100.50 | 1997-12-12 | 4 | 200.0 |
5 | 385.61 | 1998-01-03 | 11 | 178.0 |
rfm1 = rfm[['R','F','M']].apply(lambda x: x-x.mean()) # 根据R、F、M用当前值减去每一列的均值
def rfm_func(x):
level = x.apply(lambda x:'1' if x>= 1 else '0') # 根据计算的RFM的数值,如果大于1,则标记为1,如果小于1则标记为0
label = level['R'] + level['F'] +level['M'] # 根据上述的标记,将标记合并则得到每位用户的标签,如用户ID为1的用户的标签为100
# 根据各个用户标签,将用户进行分群
d = {
'111':'重要价值客户',
'011':'重要保持客户',
'101':'重要发展客户',
'001':'重要挽留客户',
'110':'一般价值客户',
'010':'一般保持客户',
'100':'一般发展客户',
'000':'一般挽留客户'
}
result = d[label] # 将用户标签对应到具体的用户分群中
return result
rfm['label'] = rfm1.apply(rfm_func,axis=1)
# 针对客户类别进行分组
rfm.groupby('label').sum()
M | F | R | |
---|---|---|---|
label | |||
一般价值客户 | 9061.26 | 515 | 51893.0 |
一般保持客户 | 74247.25 | 4243 | 130603.0 |
一般发展客户 | 437939.13 | 16860 | 6940318.0 |
一般挽留客户 | 145929.29 | 6320 | 499073.0 |
重要价值客户 | 66774.84 | 1345 | 106737.0 |
重要保持客户 | 1496321.20 | 36839 | 420516.0 |
重要发展客户 | 130311.05 | 1421 | 354279.0 |
重要挽留客户 | 139731.61 | 2116 | 151995.0 |
rfm.loc[rfm['label']=='重要价值客户','color'] = 'g' # 设置颜色
rfm.loc[~(rfm['label']=='重要价值客户'),'color'] = 'r'
plt.scatter(rfm['R'],rfm['F'],c=rfm.color)
plt.xlabel('R')
plt.ylabel('F')
从RFM分层可知,大部分用户为重要保持客户,但是这是由于极值的影响,所以RFM的划分标准应该以业务为准
- 尽量用小部分的用户覆盖大部分的额度
- 不用为了数据好看划分等级。