用户消费行为分析报告
一、项目背景:
该项目对购买某网站电子产品的用户消费数据进行分析(2020年1月至2020年11月),研究用户的基本属性、消费特征、价值度与活跃度、复购率和回购率情况,为提升销售额以及营销的精准度提供一些思路。
二、数据来源:
https://www.kesci.com/mw/dataset/601e971ab233440015800bc7/file
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] =False #显示符号
from PIL import Image
from IPython.display import display
# 读取PNG图片
image = Image.open('字段解释.png')
# 获取当前图片大小
width, height = image.size
# 设置图片尺寸
new_width = int(width * 0.5) # 调整为原来宽度的0.5
new_height = int(height * 0.5) # 调整为原来高度的0.5
# 调整图片尺寸
resized_image = image.resize((new_width, new_height))
# 显示调整后的图片
display(resized_image)
三、分析框架
image = Image.open('用户消费行为分析.png')
width, height = image.size
new_width = int(width * 0.5) # 调整为原来宽度的0.5
new_height = int(height * 0.5) # 调整为原来高度的0.5
resized_image = image.resize((new_width, new_height))
display(resized_image)
四、结论先行
1.整体趋势:2020年1至4月份销售额和订单量较少且稳定,4月份开始至8月份持续增长,9-11月份虽有下降但也处于比1-4月份高的水平,可能与五一小长假、暑假、开学季和国庆小长假有关;
2.用户属性:
1)30-35岁和40-45岁的男性数量以及45-50岁的女性数量较突出→多向这类用户进行推送,促活;
2)15-20岁、40-45岁、45-50岁这三个年龄段的用户消费金额较高→多推送一些高价位商品;
3)广东、上海、北京的销售额、订单数和消费人数均遥遥领先,同时虽然湖南消费人数较少,但订单数和销售额均排第四,很有潜力→这4个城市可加大营销力度;
4)品牌销量TOP3:samsung、apple、ava,产品类别销量TOP3:智能手机、笔记本、冰箱,可将这些商品作为主推产品,进行引流;且对于销量好的产品,需重点关注库存情况,避免缺货;对于销量差的产品,需及时进行清仓处理;
3.用户整体消费特征:
1)五一小长假、暑假、开学季、十一小长假的销售额、订单量都不错,尤其是8月份的销售额全年最高,对于这些关键节点,需提前准备好各类资源,运营计划、库存、物流、客服资源等;
2)大部分用户会在13-20时下单,以及周末的订单量较高,对于这些时间节点,需保障好网站的运行稳定性;
4.用户个人消费特征:
1)消费次数和消费金额高度相关,因此可通过提高消费次数,来提高消费金额;
2)共93804位用户,14.7%的用户贡献了将近60%的销售额,需对这部分用户重点维护,制定对应的营销方案;
5.用户消费周期
1)用户购买周期均值为9.6天,小部分用户购买周期在50天以上,对于这部分用户可在其消费后9天左右通过赠送优惠券等方式来刺激召回;
2)用户生命周期均值为32天,可在用户消费后第30天左右对其进行引导,促使其再次消费,延长生命周期;75%分位数的用户生命周期为42天,生命周期>42天的用户属于核心用户,需根据其特点推出针对性的营销活动,引导其持续消费;
6.用户价值度分析
通过RFM模型分析,发现一般发展用户数量最多,其次是重要保持用户和一般挽留用户;
针对重要价值用户,需重点维护,提供VIP服务,给予专享权益、定期福利等活动;
针对重要发展用户,消费频次较低,需要提升他们的活跃度,不断优化产品和服务,制定对应策略来提升他们的购买频率;
针对重要保持用户,应主动与用户保持联系,可通过定期邮件/短信推送,与用户保持联系与互动;
针对重要挽留用户,需重点防止流失,可主动联系用户调查具体原因,想办法挽回;
针对一般价值用户,可通过发放不同形式优惠券,引导用户多次消费,增加消费金额;
针对一般发展用户,这类用户可能属于新用户,可提供新手福利,并在各种节假日活动时及时提醒;
针对一般保持用户,这类用户是预流失用户,前期活跃过,后期发现产品、服务、奖励等无法满足预期需求,不再复购,需对其做好利益和情感的触达;
针对一般挽留用户,这类用户可能已流失一段时间,不易挽回,在预算有限的情况下,暂且考虑放弃。
五、数据清洗
删除无用列、转换数据类型、修改并增加时间相关列、删除重复值、缺失值处理和异常值处理;
#数据读入
df = pd.read_csv(r'C:\Users\13779\Desktop\电子产品销售分析.csv')
df.head()
Unnamed: 0 | event_time | order_id | product_id | category_id | category_code | brand | price | user_id | age | sex | local | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 2020-04-24 11:50:39 UTC | 2294359932054536986 | 1515966223509089906 | 2.268105e+18 | electronics.tablet | samsung | 162.01 | 1.515916e+18 | 24.0 | 女 | 海南 |
1 | 1 | 2020-04-24 11:50:39 UTC | 2294359932054536986 | 1515966223509089906 | 2.268105e+18 | electronics.tablet | samsung | 162.01 | 1.515916e+18 | 24.0 | 女 | 海南 |
2 | 2 | 2020-04-24 14:37:43 UTC | 2294444024058086220 | 2273948319057183658 | 2.268105e+18 | electronics.audio.headphone | huawei | 77.52 | 1.515916e+18 | 38.0 | 女 | 北京 |
3 | 3 | 2020-04-24 14:37:43 UTC | 2294444024058086220 | 2273948319057183658 | 2.268105e+18 | electronics.audio.headphone | huawei | 77.52 | 1.515916e+18 | 38.0 | 女 | 北京 |
4 | 4 | 2020-04-24 19:16:21 UTC | 2294584263154074236 | 2273948316817424439 | 2.268105e+18 | NaN | karcher | 217.57 | 1.515916e+18 | 32.0 | 女 | 广东 |
df.info()
#数据量:564169,原始字段:12
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 564169 entries, 0 to 564168
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Unnamed: 0 564169 non-null int64
1 event_time 564169 non-null object
2 order_id 564169 non-null int64
3 product_id 564169 non-null int64
4 category_id 564169 non-null float64
5 category_code 434799 non-null object
6 brand 536945 non-null object
7 price 564169 non-null float64
8 user_id 564169 non-null float64
9 age 564169 non-null float64
10 sex 564169 non-null object
11 local 564169 non-null object
dtypes: float64(4), int64(3), object(5)
memory usage: 51.7+ MB
#1.删除无用的Unnamed: 0列
del df['Unnamed: 0']
#2.转换数据类型(否则以科学计数法的形式展示)
df['order_id'] = df['order_id'].astype(object)
df['product_id'] = df['product_id'].astype(object)
df['category_id'] = df['category_id'].astype(object)
df['user_id'] = df['user_id'].astype(object)
df['age'] = df['age'].astype(int)
df.head()
event_time | order_id | product_id | category_id | category_code | brand | price | user_id | age | sex | local | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2020-04-24 11:50:39 UTC | 2294359932054536986 | 1515966223509089906 | 2268105426648171520.0 | electronics.tablet | samsung | 162.01 | 1515915625441993984.0 | 24 | 女 | 海南 |
1 | 2020-04-24 11:50:39 UTC | 2294359932054536986 | 1515966223509089906 | 2268105426648171520.0 | electronics.tablet | samsung | 162.01 | 1515915625441993984.0 | 24 | 女 | 海南 |
2 | 2020-04-24 14:37:43 UTC | 2294444024058086220 | 2273948319057183658 | 2268105430162997248.0 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424.0 | 38 | 女 | 北京 |
3 | 2020-04-24 14:37:43 UTC | 2294444024058086220 | 2273948319057183658 | 2268105430162997248.0 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424.0 | 38 | 女 | 北京 |
4 | 2020-04-24 19:16:21 UTC | 2294584263154074236 | 2273948316817424439 | 2268105471367840000.0 | NaN | karcher | 217.57 | 1515915625443148032.0 | 32 | 女 | 广东 |
#2.转换数据类型(经上述处理,category_id和user_id保留了一位小数,利用apply和round函数进一步调整)
df['category_id'] = df['category_id'].astype(object).apply(lambda x: round(x))
df['user_id'] = df['user_id'].astype(object).apply(lambda x: round(x))
df.head()
event_time | order_id | product_id | category_id | category_code | brand | price | user_id | age | sex | local | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2020-04-24 11:50:39 UTC | 2294359932054536986 | 1515966223509089906 | 2268105426648171520 | electronics.tablet | samsung | 162.01 | 1515915625441993984 | 24 | 女 | 海南 |
1 | 2020-04-24 11:50:39 UTC | 2294359932054536986 | 1515966223509089906 | 2268105426648171520 | electronics.tablet | samsung | 162.01 | 1515915625441993984 | 24 | 女 | 海南 |
2 | 2020-04-24 14:37:43 UTC | 2294444024058086220 | 2273948319057183658 | 2268105430162997248 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424 | 38 | 女 | 北京 |
3 | 2020-04-24 14:37:43 UTC | 2294444024058086220 | 2273948319057183658 | 2268105430162997248 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424 | 38 | 女 | 北京 |
4 | 2020-04-24 19:16:21 UTC | 2294584263154074236 | 2273948316817424439 | 2268105471367840000 | NaN | karcher | 217.57 | 1515915625443148032 | 32 | 女 | 广东 |
#3.修改并增加时间相关列(将UTC时间修改为北京时间)
from datetime import datetime, timedelta
df['event_time'] = df['event_time'].apply(lambda x:str(x)[:20])
df['event_time'] = pd.to_datetime(df['event_time']) + timedelta(hours=8)
#3.修改并增加时间相关列(增加month/day/hour/dayofweek列)4
df['date'] = df['event_time'].dt.date
df['month'] = df['event_time'].dt.month
df['day'] = df['event_time'].dt.day
df['hour'] = df['event_time'].dt.hour
df['dayofweek'] = df['event_time'].dt.day_name()
df.head()
event_time | order_id | product_id | category_id | category_code | brand | price | user_id | age | sex | local | date | month | day | hour | dayofweek | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2020-04-24 19:50:39 | 2294359932054536986 | 1515966223509089906 | 2268105426648171520 | electronics.tablet | samsung | 162.01 | 1515915625441993984 | 24 | 女 | 海南 | 2020-04-24 | 4 | 24 | 19 | Friday |
1 | 2020-04-24 19:50:39 | 2294359932054536986 | 1515966223509089906 | 2268105426648171520 | electronics.tablet | samsung | 162.01 | 1515915625441993984 | 24 | 女 | 海南 | 2020-04-24 | 4 | 24 | 19 | Friday |
2 | 2020-04-24 22:37:43 | 2294444024058086220 | 2273948319057183658 | 2268105430162997248 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424 | 38 | 女 | 北京 | 2020-04-24 | 4 | 24 | 22 | Friday |
3 | 2020-04-24 22:37:43 | 2294444024058086220 | 2273948319057183658 | 2268105430162997248 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424 | 38 | 女 | 北京 | 2020-04-24 | 4 | 24 | 22 | Friday |
4 | 2020-04-25 03:16:21 | 2294584263154074236 | 2273948316817424439 | 2268105471367840000 | NaN | karcher | 217.57 | 1515915625443148032 | 32 | 女 | 广东 | 2020-04-25 | 4 | 25 | 3 | Saturday |
#4.检查并删除重复值
df.duplicated().sum()
df.drop_duplicates(inplace=True)
#共有674条重复记录
#5.缺失值处理
df.isnull().sum()
event_time 0
order_id 0
product_id 0
category_id 0
category_code 129221
brand 27184
price 0
user_id 0
age 0
sex 0
local 0
date 0
month 0
day 0
hour 0
dayofweek 0
dtype: int64
#5.缺失值处理(category_code存在129370个缺失值,brand存在27224个缺失值,但这些数据的缺失并不影响分析,用’others’填充)
df['category_code'].fillna('others',inplace=True)
df['brand'].fillna('others',inplace=True)
df.isnull().sum()
event_time 0
order_id 0
product_id 0
category_id 0
category_code 0
brand 0
price 0
user_id 0
age 0
sex 0
local 0
date 0
month 0
day 0
hour 0
dayofweek 0
dtype: int64
#6.异常值处理(price为0)
(df['price'] == 0).sum()
#共存在39行price为0的数据,剔除这部分数据
df = df[df['price'] > 0]
#6.异常值处理(年份为1970年的数据)
df['event_time'].min()
df = df[df['event_time'].dt.year != 1970]
#重置索引
df = df.reset_index().drop('index', axis=1)
#处理后的数据预览
df.head()
event_time | order_id | product_id | category_id | category_code | brand | price | user_id | age | sex | local | date | month | day | hour | dayofweek | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2020-04-24 19:50:39 | 2294359932054536986 | 1515966223509089906 | 2268105426648171520 | electronics.tablet | samsung | 162.01 | 1515915625441993984 | 24 | 女 | 海南 | 2020-04-24 | 4 | 24 | 19 | Friday |
1 | 2020-04-24 22:37:43 | 2294444024058086220 | 2273948319057183658 | 2268105430162997248 | electronics.audio.headphone | huawei | 77.52 | 1515915625447879424 | 38 | 女 | 北京 | 2020-04-24 | 4 | 24 | 22 | Friday |
2 | 2020-04-25 03:16:21 | 2294584263154074236 | 2273948316817424439 | 2268105471367840000 | others | karcher | 217.57 | 1515915625443148032 | 32 | 女 | 广东 | 2020-04-25 | 4 | 25 | 3 | Saturday |
3 | 2020-04-26 16:45:57 | 2295716521449619559 | 1515966223509261697 | 2268105442636858112 | furniture.kitchen.table | maestro | 39.33 | 1515915625450382848 | 20 | 男 | 重庆 | 2020-04-26 | 4 | 26 | 16 | Sunday |
4 | 2020-04-26 17:33:47 | 2295740594749702229 | 1515966223509104892 | 2268105428166508800 | electronics.smartphone | apple | 1387.01 | 1515915625448766464 | 21 | 男 | 北京 | 2020-04-26 | 4 | 26 | 17 | Sunday |
六、用户属性分析
# 1.性别分析
df.groupby('sex')['user_id'].nunique().plot(kind='pie', autopct='%.1f%%')
plt.title('性别分布')
# 男性与女性买家比例接近1:1
#性别&消费金额分析
order_amount = df.groupby('user_id')['price'].sum().to_frame().rename(columns={'price':'amount'})
sex = df.loc[:,['user_id','sex']].set_index('user_id')
duplicate_rows = sex.index.duplicated()
sex = sex[~duplicate_rows]
sex_amount = pd.merge(order_amount, sex, left_index=True, right_index=True)
sex_amount.groupby('sex')['amount'].describe()
#对比男性与女性的消费金额,发现各区段的消费金额都差不多,最大值男性稍高些;
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
sex | ||||||||
女 | 46790.0 | 1248.561009 | 4136.986434 | 0.02 | 141.180 | 462.46 | 1148.0475 | 160426.63 |
男 | 47014.0 | 1250.518169 | 4250.346041 | 0.02 | 147.855 | 453.10 | 1152.7225 | 165439.03 |
# 2.年龄分析
age_range = df.loc[:,['user_id','age']]
age_range.drop_duplicates(inplace=True)
pd.cut(age_range['age'],bins=[15,20,25,30,35,40,45,50]).value_counts().sort_index().plot(kind='bar')
plt.title('年龄分布')
plt.show()
#各年龄段的用户数量差距不大,40-45年龄段的用户数量稍多些,45-50年龄段的用户最少;
#性别&年龄组合分析
df['age_range'] = pd.cut(df['age'],bins=[15,20,25,30,35,40,45,50],
labels=['15-20','20-25','25-30', '30-35', '35-40', '40-45', '45-50'])
data = df.groupby('age_range')['sex'].value_counts().unstack().plot()
#对比男性与女性的不同年龄段的消费人数,发现30-45岁男性的人数多于女性,45-50岁的女性数量明显高于男性数量;
#图中出现了3个高峰,分别30-35岁男性数量、40-45岁男性数量以及45-50岁的女性数量,可以多向这类用户推送商品信息;
#年龄&消费金额分析
age_range = df.loc[:,['user_id','age_range']].set_index('user_id')
duplicate_rows = age_range.index.duplicated()
age_range = age_range[~duplicate_rows]
age_amount = pd.merge(order_amount, age_range, left_index=True, right_index=True)
age_amount.groupby('age_range')['amount'].describe()
#通过对不同年龄段的消费金额进行分析,发现15-20岁、40-45岁、45-50岁年龄段的用户消费金额较高,可多向这类户进行推送;
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
age_range | ||||||||
15-20 | 13326.0 | 1233.060742 | 4318.237739 | 0.02 | 148.1000 | 462.460 | 1148.0400 | 153588.55 |
20-25 | 13501.0 | 1248.641903 | 4190.348746 | 0.02 | 140.9200 | 460.550 | 1152.3500 | 160426.63 |
25-30 | 13448.0 | 1255.961840 | 4328.035290 | 0.02 | 145.7900 | 457.685 | 1148.0875 | 130853.08 |
30-35 | 13411.0 | 1303.689029 | 4630.996869 | 0.02 | 143.4250 | 443.220 | 1145.6900 | 165439.03 |
35-40 | 13409.0 | 1140.155669 | 3281.472455 | 0.02 | 138.8700 | 453.190 | 1145.8600 | 119062.38 |
40-45 | 13552.0 | 1278.547951 | 4224.424147 | 0.02 | 145.0825 | 462.460 | 1144.0300 | 117885.33 |
45-50 | 13157.0 | 1287.008547 | 4259.088814 | 0.02 | 150.4400 | 462.920 | 1163.9500 | 136738.03 |
# 3.地域分析--消费人数&订单数&销售额
#不同省份消费人数
fig,ax = plt.subplots(1, 3, figsize=(14, 8))
df.groupby('local')['user_id'].nunique().sort_values(ascending=True).plot(kind='barh',ax=ax[0])
ax[0].set_xlabel('消费人数')
ax[0].set_ylabel('省份')
#不同省份订单数量
df.groupby('local')['order_id'].nunique().sort_values(ascending=True).plot(kind='barh',ax=ax[1])
ax[1].set_xlabel('订单数')
ax[1].set_ylabel('省份')
#不同省份成交金额
df.groupby('local')['price'].sum().sort_values(ascending=True).plot(kind='barh',ax=ax[2])
ax[2].set_xlabel('销售额')
ax[2].set_ylabel('省份')
fig.suptitle('地域分布', fontsize=16, fontweight='bold')
plt.subplots_adjust(wspace=0.5)
plt.show()
# 广东、上海、北京的消费人数、订单数以及销售额均遥遥领先,此外虽然湖南消费人数最少,但订单数和销售额均排在第四,很有潜力;
#按喜好品牌分析
brand = df.groupby('brand')['user_id'].count().sort_values(ascending=False).head(20)
brand.plot(kind='bar')
plt.xlabel('品牌')
plt.ylabel('销售量')
plt.title('喜好品牌分布')
plt.show()
#销售量排名前五的品牌是samsung、apple、ava、tefal、lg;
#按电子产品类别分类
category = df.groupby('category_code')['user_id'].count().sort_values(ascending=False).head(20)
category.plot(kind='bar')
plt.xlabel('产品类别')
plt.ylabel('销售量')
plt.title('喜好的产品类别分布')
plt.show()
#销售量排名前五的产品类别是(除others):智能手机、笔记本、冰箱、耳机、电视机;
七、用户消费特征分析
1.用户整体消费分析
1)销售金额、订单数量、消费人数、客单价
2)订单数随星期的变化、订单数随小时的变化
fig, ax = plt.subplots(4, 1, figsize=(12,16))
#每月销售金额
df.groupby('date')['price'].sum().plot(ax=ax[0])
ax[0].set_ylabel('销售金额')
ax[0].set_title('销售金额')
#每月订单数量
df.groupby('date')['order_id'].nunique().plot(ax=ax[1])
ax[1].set_ylabel('订单数量')
ax[1].set_title('订单数量')
#每月消费人数
df.groupby('date')['user_id'].nunique().plot(ax=ax[2])
ax[2].set_ylabel('消费人数')
ax[2].set_title('消费人数')
#每月客单价(ATV:Average transaction value)
sale_amount = df.groupby('date')['price'].sum()
user_count = df.groupby('date')['user_id'].nunique()
sale_user = pd.merge(sale_amount,user_count,on='date')
sale_user.rename(columns={'price':'sale_amount', 'user_id':'user_count'}, inplace=True)
sale_user['ATV'] = round(sale_user['sale_amount']/sale_user['user_count'],1)
sale_user['ATV'].plot(ax=ax[3])
ax[3].set_xlabel('日期')
ax[3].set_ylabel('客单价')
ax[3].set_title('客单价')
fig.subplots_adjust(hspace=0.3)
plt.show()
#销售金额和订单数量的走势基本保持一致,1-4月销售额和单量较少且保持稳定,4月底开始上升,8月达到顶峰,9月开始下降,10月回升;
#消费人数有两个高峰,一个是在5月份,另一个是在7-9月,猜测是五一活动、暑期活动(6-8月份)和开学季(9月);
#其中8月份的消费人数远高于其他月份,是整年营收的关键时期,需提前准备好各类资源,运营计划、库存、物流、客服资源等;
#客单价4月-7月均处于较低水平,10-11月的客单价较高;
#订单数随星期分布
fig,ax = plt.subplots(1,2, figsize=(12,4))
custom_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
data = df.groupby('dayofweek')['order_id'].nunique()
weekday_categories = pd.Categorical(data.index, categories=custom_order, ordered=True)
week_order = pd.Series(data.values, index=weekday_categories)
week_order.sort_index().plot(ax=ax[0])
ax[0].set_xlabel('星期')
ax[0].set_ylabel('订单量')
ax[0].set_title('订单数随星期分布')
#订单数随小时分布
df.groupby('hour')['order_id'].nunique().plot(kind='bar', ax=ax[1],color='orange')
ax[1].set_xlabel('时')
ax[1].set_ylabel('订单量')
ax[1].set_title('订单数随小时分布')
fig.subplots_adjust(wspace=0.5)
plt.show()
#订单数随星期分布:周五的订单数量最低,周六最高,周日低于周六,但比工作日高;
#订单数随小时分布:大部分用户会在13-20时下单;
#结论:周末以及13-20点销量较高,需额外关注网站的运行稳定性;
2.用户个人消费分析
用户消费次数、用户消费金额、用户累计消费金额占比分析(用户贡献度)
#用户消费次数
user_consume_count = df.groupby('user_id')['order_id'].nunique()
user_consume_count.describe()
#用户消费次数的均值为4,最小值为1,75%为3次,最大值为666次;
count 93804.000000
mean 4.261311
std 18.140393
min 1.000000
25% 1.000000
50% 2.000000
75% 3.000000
max 666.000000
Name: order_id, dtype: float64
#用户消费金额
user_consume_amount = df.groupby('user_id')['price'].sum()
user_consume_amount.describe()
#用户消费金额的均值为1249.54,最小值为0.02,75%为1150.34,最大值为165439.03;
count 93804.000000
mean 1249.541926
std 4194.162345
min 0.020000
25% 145.120000
50% 458.910000
75% 1150.347500
max 165439.030000
Name: price, dtype: float64
#消费次数与消费金额关系
data = pd.merge(user_consume_count, user_consume_amount,on='user_id').rename(columns={'order_id':'count','price':'amount'})
correlation = data['count'].corr(data['amount'])
plt.figure(figsize=(10,8))
plt.scatter(data['count'], data['amount'])
plt.xlabel('消费次数')
plt.ylabel('消费金额')
plt.title('消费次数与消费金额的关系')
plt.text(300, 125000, 'Correlation: {:.3f}'.format(correlation))
plt.show()
#消费次数和消费金额之间的相关系数为0.940,高度相关,因此需通过各种方法提高用户的消费次数,以此来提高消费金额;
#用户累计消费金额占比分析(用户贡献度)
user_cumsum = user_consume_amount.sort_values().reset_index().rename(columns={'price':'amount'})
user_cumsum['cumsum'] = user_cumsum['amount'].cumsum().astype(int)
user_cumsum['prop'] = user_cumsum['cumsum'].apply(lambda x: x/user_cumsum['cumsum'].max())
user_cumsum.tail()
user_id | amount | cumsum | prop | |
---|---|---|---|---|
93799 | 1515915625514597888 | 135925.07 | 116595838 | 0.994743 |
93800 | 1515915625513577472 | 136738.03 | 116732576 | 0.995910 |
93801 | 1515915625512377088 | 153588.55 | 116886165 | 0.997220 |
93802 | 1515915625513695488 | 160426.63 | 117046591 | 0.998589 |
93803 | 1515915625512422912 | 165439.03 | 117212030 | 1.000000 |
user_cumsum['prop'].plot()
plt.show()
#共93804位用户,前80000名用户大约贡献了40%的销售额,后13804位用户则贡献了将近60%的销售额,符合二八定律,需对这部分用户重点维护;
3.用户消费周期分析
用户购买周期、用户生命周期
df.sort_values(by='event_time',ascending=True, inplace=True)
user_cycle = df.loc[:, ['event_time','order_id','user_id']]
user_cycle.drop_duplicates(inplace=True)
#用户购买周期
user_purchase_cycle = user_cycle.groupby('user_id').apply(lambda x: x['event_time']-x['event_time'].shift()).dt.days
print(user_purchase_cycle.describe())
print('\n')
user_purchase_cycle.hist(bins=20)
plt.grid(False)
plt.title('用户购买周期')
plt.show()
#用户购买周期的均值为9.6天,最大值为298天;
#大多数用户的购买周期小于6天;
#呈现典型的长尾分布,只有小部分用户的购买周期在50天以上,可在这批用户消费9天左右通过赠送优惠券等方式来刺激召回;
count 305924.000000
mean 9.646726
std 23.848902
min 0.000000
25% 0.000000
50% 0.000000
75% 6.000000
max 298.000000
Name: event_time, dtype: float64
#用户生命周期
user_life_cycle = user_cycle.groupby('user_id').apply(lambda x: x['event_time'].max()-x['event_time'].min()).dt.days
print(user_life_cycle.describe())
print('\n')
user_life_cycle.hist(bins=20)
plt.grid(False)
plt.title('用户生命周期')
plt.show()
#用户生命周期的均值为32天,中位数为0天(大多数用户只消费了一次),可在第30天左右对客户进行引导,促使其再次消费,延长生命周期;
#75%为42天,生命周期>42天的属于核心用户,需根据其特点推出针对性的营销活动,引导其持续消费;
count 93804.000000
mean 32.079965
std 57.461976
min 0.000000
25% 0.000000
50% 0.000000
75% 42.000000
max 319.000000
dtype: float64
八、用户分层分析
1.用户价值度分析—RFM模型
#RFM值处理
#R值:取date这列的最大值为2020年11月21日
#F值:取用户的购买次数
#M值:取用户的总消费金额
value = df.pivot_table(values=['date','order_id','price'], index='user_id',
aggfunc={'date':'max','order_id':'nunique','price':'sum'})
value.rename(columns={'date':'R', 'order_id':'F', 'price':'M'}, inplace=True)
value['R'] = (value['R'].max() - value['R']).dt.days
value.head()
R | F | M | |
---|---|---|---|
user_id | |||
1515915625439951872 | 135 | 1 | 416.64 |
1515915625440038400 | 24 | 2 | 56.43 |
1515915625440051712 | 5 | 5 | 7530.34 |
1515915625440099840 | 12 | 19 | 4935.60 |
1515915625440121600 | 130 | 2 | 182.83 |
#处理R值
value['R'] = pd.qcut(value['R'], q=4, labels=[4, 3, 2, 1], duplicates='drop').astype(int)
#处理F值
bins = [value['F'].quantile(i) for i in [0, 0.25, 0.5, 0.75, 1]]
bins = sorted(set(bins)) # 处理重复边界值
bins.insert(0,0)
value['F'] = pd.cut(value['F'], bins=bins, labels=[1, 2, 3, 4]).astype(int)
#处理M值
value['M'] = pd.qcut(value['M'], q=4, labels=[1, 2, 3, 4], duplicates='drop').astype(int)
value.head()
R | F | M | |
---|---|---|---|
user_id | |||
1515915625439951872 | 1 | 1 | 2 |
1515915625440038400 | 4 | 2 | 1 |
1515915625440051712 | 4 | 4 | 4 |
1515915625440099840 | 4 | 4 | 4 |
1515915625440121600 | 1 | 2 | 2 |
value['R_value'] = value['R'].apply(lambda x: 0 if x > value['R'].mean() else 1).astype(str)
value['F_value'] = value['F'].apply(lambda x: 0 if x < value['F'].mean() else 1).astype(str)
value['M_value'] = value['M'].apply(lambda x: 0 if x < value['M'].mean() else 1).astype(str)
value['RFM'] = value['R_value'] + value['F_value'] + value['M_value']
def RFM_func(data):
d = {
'000':'一般挽留用户',
'100':'一般发展用户',
'010':'一般保持用户',
'110':'一般价值用户',
'001':'重要挽留用户',
'101':'重要发展用户',
'011':'重要保持用户',
'111':'重要价值用户'
}
result = d[data]
return result
value['类别'] = value['RFM'].apply(RFM_func)
value.head()
R | F | M | R_value | F_value | M_value | RFM | 类别 | |
---|---|---|---|---|---|---|---|---|
user_id | ||||||||
1515915625439951872 | 1 | 1 | 2 | 1 | 0 | 0 | 100 | 一般发展用户 |
1515915625440038400 | 4 | 2 | 1 | 0 | 0 | 0 | 000 | 一般挽留用户 |
1515915625440051712 | 4 | 4 | 4 | 0 | 1 | 1 | 011 | 重要保持用户 |
1515915625440099840 | 4 | 4 | 4 | 0 | 1 | 1 | 011 | 重要保持用户 |
1515915625440121600 | 1 | 2 | 2 | 1 | 0 | 0 | 100 | 一般发展用户 |
value[['类别','RFM']].groupby('类别').count().sort_values(by='RFM', ascending=False).plot(kind='bar')
plt.legend(labels=[])
plt.xlabel('类别', loc='right')
plt.ylabel('用户数量', loc='top')
plt.show()
#一般发展用户数量最多,其次是重要保持用户和一般挽留用户;
2.用户活跃度分析
#未注册用户
#新用户:之前从未购买
#活跃用户:上月有购买且本月有购买
#不活跃用户:本月未购买
#回流用户:上月未购买但本月购买
user_status = df.pivot_table(values='order_id', index='user_id',columns='month', aggfunc='count', fill_value=0)
user_status = user_status.applymap(lambda x: 1 if x>0 else 0)
def status_judge(data):
status = []
for i in range(11):
if data.iloc[i] == 0:
if len(status) == 0:
status.append('unreg')
else:
if status[i-1] == 'unreg':
status.append('unreg')
else:
status.append('unactive')
else:
if len(status) == 0:
status.append('new')
else:
if status[i-1] == 'unreg':
status.append('new')
elif status[i-1] == 'unactive':
status.append('return')
else:
status.append('active')
return pd.Series(status,user_status.columns)
user_status = user_status.apply(status_judge,axis=1)
user_status = user_status.replace('unreg',np.NaN).apply(lambda x: pd.value_counts(x)).fillna(0)
plt.figure(figsize=(12,8))
user_status.T.plot.area(color=['red','orange','green','gray'])
plt.show()
#新用户从3月份开始逐渐增加,8月份的新用户增加数量最多,8月之后开始逐渐减少;
#活跃用户从4月份开始逐渐增加,8月份活跃用户增量最多,8月份之后开始减少;
#不活跃用户从6月份开始增加,且增加的速度也越来越快;
#回流用户从5月份开始增加,直至7月份开始保持稳定状态;
<Figure size 1200x800 with 0 Axes>
九、复购率和回购率分析(月维度)
#每月复购率(同个月内消费多次的用户占总用户的比例)
plt.figure(figsize=(6,4))
purchase_r = df.pivot_table(values='order_id', index='user_id',columns='month', aggfunc='nunique', fill_value=0)
purchase_r = purchase_r.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
(purchase_r.sum()/purchase_r.count()).plot()
plt.title('复购率')
plt.show()
#从6月开始到10月复购率一直保持增长,10月份复购率最高,达到48%;
#复购率有3个小高峰,分别为3月、5月和10月,猜测与清明、五一和十一假期有关;
#每月回购率(在一个时间窗口内进行了消费,在下一时间窗口内也进行了消费)
purchase_b = df.pivot_table(values='order_id', index='user_id',columns='month', aggfunc='count', fill_value=0)
purchase_b = purchase_b.applymap(lambda x:1 if x>0 else 0)
def return_judge(data):
status = []
for i in range(10):
if data.iloc[i] == 1:
if data.iloc[i+1] == 1:
status.append(1)
elif data.iloc[i+1] == 0:
status.append(0)
else:
status.append(np.NaN)
status.append(np.NaN)
return pd.Series(status,purchase_b.columns)
purchase_b = purchase_b.apply(return_judge, axis=1)
plt.figure(figsize=(6,4))
(purchase_b.sum()/purchase_b.count()).plot()
plt.title('回购率')
plt.show()
#回购率从1月-11月呈现下降趋势,其中3月、5月、8月和10月下降的最严重,估计也与清明、五一、暑假和十一假期有关;