本数据集由阿里巴巴提供的一亿多条的淘宝用户行为数据,包含了2017年11月25日至2017年12月3日之间,约一百万随机用户的所有行为(行为包括点击、购买、加购、收藏),数据集的每一行表示一条用户行为,针对的对象是商品详情页。
对淘宝用户行为数据进行分析,并给出改善建议。
日pv、日uv 整体呈现上升趋势,在12月2日达到最高峰、3日有所回落,PV日环比、UV日环比在12月2日增长接近25%
上述指标都在12月2日~ 3日有了大幅度提升,12月2日~ 3日虽然是周末,但是结合上个周末11月25日~ 26日的流量分析,可以排除周末原因;由于12月2日~3日距离双12较近,推测较大可能的原因是双12系列促销活动预热所带来的流量增加。
用户使用淘宝的常见过程是:浏览(PV),之后可以收藏(fav)、加入购物车(cart),再到购买(buy)。其中,fav、cart不是购买的必须过程。
收藏功能主要是帮助用户收藏自己喜欢但不立即购买的商品,而购物车既有类似收藏商品的功能,但主要还是方便用户批量购买商品,两者都是用于提升用户购物体验。但是商品收藏之后要购买还是面临着加入购物车和直接购买的选择,因此,本文的漏斗分析会将收藏排除在外,由于数据集的行为类型有限,没有下单、支付环节,这里只分析浏览、加购物车、购买的转化。
在9天内,有53.09%的用户在浏览后加入了购物车; 其中有59.22%的用户会在加入购物车进行购买行为; 由于数据集时间范围的限制,暂时无法知道这各环节的转化率是否正常,需要与历史数据对比才能作出最终的判断。
在9天内,有32.5%的用户浏览完便会进行购买; 由于数据集时间范围的限制,暂时无法知道这各环节的转化率是否正常,需要与历史数据对比才能作出最终的判断。
这9天中,购买的用户占比为63.94%,使用购物车功能用户占比53.09%,使用收藏功能的用户占比26.44%。
用户收藏/加购物车到购买的时间间隔分布,运营人员可以根据时间间隔分布,给用户设置提醒功能。
收藏且有购物意愿的用户中,接近77.13%的用户会在24h内完成购买,超过92.24%的用户会在3天内购买。
当用户收藏和加购物车3天后还未购买,可以大致推测用户的购买意愿并不强烈,此时可以通过适当的提示\优惠活动来引导用户购买,或者推荐对应的相似商品供用户选择。
由于原数据集有1亿多条记录,数据量过大,本文只对其中200万条数据进行分析。
字段 | 说明 |
---|---|
User ID | 用户ID(整数型) |
Item ID | 商品ID(整数型) |
Category ID | 商品类目ID(整数型) |
Behavior type | 行为类型(字符串型) |
Timestamp | 行为发生的时间戳(整数型) |
行为类型 | 说明 |
---|---|
pv | 商品详情页 pv,等价于点击 |
buy | 商品购买 |
cart | 将商品加入购物车 |
fav | 收藏商品 |
【注意:这里的行为都是针对商品详情页】
# 导入相关库
import numpy as np # 科学计算工具包
import pandas as pd # 数据分析工具包
import matplotlib.pyplot as plt # 图表绘制工具包
import seaborn as sns # 基于 matplot, 导入 seaborn 会修改默认的 matplotlib 配色方案和绘图样式,这会提高图表的可读性和美观性
import os,pymysql
from sqlalchemy import create_engine # 数据库
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体设置-黑体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
sns.set(font='SimHei') # 解决Seaborn中文显示问题
# 在 jupyter notebook 里面显示图表
%matplotlib inline
知识点
read_csv()
# 获取数据集
columns = ["user_id","item_id","category_id","behavior_type","timestamp"]
reader = pd.read_csv("/Users/xusanshan/Downloads/UserBehavior.csv", iterator=True,names=columns)
user_behavior = reader.get_chunk(2000000)
通过预览数据,了解所有字段和其含义。
user_behavior.head()
user_behavior.info()
# 统计缺失值数量和占比
def missing_info(data,num=5):
# func:统计缺失值数量和占比函数
# data: dataframe类型
# num: 数字类型,显示前几行数据
# return: 缺失值统计\占比
null_data = data.isnull().sum().sort_values(ascending=False)
percent_1 = data.isnull().sum()/data.isnull().count()
missing_data = pd.concat([null_data,percent_1.apply(lambda x: format(x, '.2%'))],axis=1,keys=['total missing','missing percentage'])
print(missing_data.head(num))
missing_info(user_behavior)
缺失情况:
# 查看每一行数据是否存在重复值
user_behavior.duplicated().sum()
重复情况:
# 时间戳 timestamp 转 datetime 格式的函数
def timestamp_datetime(data,column_list):
# func: 时间戳 timestamp 转 datetime 格式的函数
# data: dataframe类型
# num: 数字类型,显示前几行数据
# return: 日期时间类型数据
for i in column_list:
data[i] = pd.to_datetime(data[i], unit='s')
print('时间戳转换 datetime 格式完成')
data = user_behavior
column_list =['timestamp']
timestamp_datetime(data,column_list)
user_behavior.head()
user_behavior['date'] = user_behavior['timestamp'].dt.date
user_behavior['year_month'] = user_behavior['timestamp'].dt.to_period('M')
user_behavior['hour']= user_behavior['timestamp'].dt.hour
user_behavior.head()
# 观察数据异常情况
user_behavior.describe(include="all")
异常情况:
异常处理
因此需要筛选出在2017年11月25日~2017年12月3日范围的用户行为记录。
# 查看异常值的详细数据
user_behavior[(pd.to_datetime('2017-11-25')>user_behavior['date'])|(user_behavior['date']>pd.to_datetime('2017-12-03'))]
#第一种方法: 筛选出在范围内的记录
user_behavior = user_behavior[(user_behavior['date']>pd.to_datetime('2017-11-24'))&(user_behavior['date']<pd.to_datetime('2017-12-04'))]
user_behavior.info()
#第二种方法: 删除掉不在范围的记录
user_behavior=user_behavior.drop(index=user_behavior[(pd.to_datetime('2017-11-25')>user_behavior['date'])|(user_behavior['date']>pd.to_datetime('2017-12-03'))].index)
user_behavior.info()
数据清洗后
# 导出到 mysql 数据库的函数
def export_mysql(data,user,password,host_port,db,table_name):
# func: 导出到 mysql 数据库函数
# data: Dataframe 类型
# user: 用户名
# password: 密码
# host_port: 主机,端口
# db: 数据库名
# table_name: 表名
print('将清洗后的数据导出到 mysql')
engine = create_engine("mysql+pymysql://{}:{}@{}/{}".format(user, password, host_port, db))
con = engine.connect()#创建连接
data.to_sql(table_name,engine,if_exists='replace',index=False)
print('成功导出')
user = 'root'
password = '1234567890'
host_port = '127.0.0.1:3306'
db = 'test'
data = user_behavior[['user_id', 'item_id', 'category_id', 'behavior_type', 'timestamp','date', 'hour']]
table_name = 'user_behavior'
export_mysql(data,user,password,host_port,db,table_name)
导入数据库报错原因
将整个 user_behavior 数据集导入 mysql 会报错 ValueError: cannot infer type for <class 'NoneType'>
怀疑是 year_month 字段引起,它的格式是 period[M] ,会不会是 mysql 不能识别,所以报错。尝试去掉这个字段再导入,就成功了。
table = user_behavior.groupby(['behavior_type','date']).agg({"user_id":["count","nunique"],"item_id":"nunique"})
#对以上数据进行拆分,每个行为分一个表。
uv_pv_data = table.loc['pv',:].copy()
buy_data = table.loc['buy',:].copy()
cart_data = table.loc['cart',:].copy()
fav_data = table.loc['fav',:].copy()
# 对列名进行重命名
uv_pv_data.columns=['pv','uv','item_num']
buy_data.columns=['buy_times','buyers','buy_items']
cart_data.columns=['cart_times','cart_users','cart_items']
fav_data.columns=['fav_times','fav_users','fav_items']
# 构建基本信息表
basic=pd.concat([uv_pv_data,buy_data,cart_data,fav_data],axis=1)
# 人均浏览量
basic['pv/uv'] = basic['pv']/basic['uv']
# 人均浏览商品数
basic['avg_browse_item'] = basic['item_num']/basic['uv']
# 购买用户比例
basic['buyers_percent'] = basic['buyers'] /basic['uv']
# 人均购买次数
basic['avg_buytimes'] = basic['buy_times'] /basic['buyers']
# 人均购买商品数
basic['avg_buy_items'] = basic['buy_items'] /basic['buyers']
# 颜色越深,数值越高
basic.style.background_gradient()
通过上面的基础表,可以得知每日的基本情况:
uv_pv_data
# 计算 人均浏览量 PV
uv_pv_data['avg_pv'] = uv_pv_data['pv']/uv_pv_data['uv']
# 计算 PV 日环比
uv_pv_data['pv_dayonday'] = uv_pv_data["pv"].diff()/uv_pv_data["pv"].shift(1)*100
# 计算 UV 日环比
uv_pv_data['uv_dayonday'] = uv_pv_data["uv"].diff()/uv_pv_data["uv"].shift(1)*100
# 填充 NAN 值为0
uv_pv_data = uv_pv_data.fillna(0)
uv_pv_data.style.background_gradient()
fig,axes=plt.subplots(3,1,figsize=(12,18)) #创建一个一行两列的画布
uv_pv_data['pv'].plot(ax=axes[0],marker='o',color='r',label="PV")
uv_pv_data['uv'].plot(ax=axes[1],marker='o',color='b',label="UV")
uv_pv_data['pv_dayonday'].plot(ax=axes[2],marker='o',color='r',label="PV日环比")
uv_pv_data['uv_dayonday'].plot(ax=axes[2],marker='o',color='b',label="UV日环比")
axes[0].set_title('每日 PV 趋势',fontsize=14)
axes[1].set_title('每日 UV 趋势',fontsize=14)
axes[2].set_title('PV、UV日环比',fontsize=14)
axes[0].set_xlabel('日期')
axes[1].set_xlabel('日期')
axes[2].set_xlabel('日期')
axes[0].legend()
axes[1].legend()
axes[2].legend()
日pv、日uv 整体呈现上升趋势,在12月2日达到最高峰、3日有所回落,PV日环比、UV日环比在12月2日增长接近25%
上述指标都在12月2日~ 3日有了大幅度提升,12月2日~ 3日虽然是周末,但是结合上个周末11月25日~ 26日的流量分析,可以排除周末原因;由于12月2日~3日距离双12较近,推测较大可能的原因是双12系列促销活动预热所带来的流量增加。
用户使用淘宝的常见过程是:浏览(PV),之后可以收藏(fav)、加入购物车(cart),再到购买(buy)。其中,fav、cart不是购买的必须过程。
收藏功能主要是帮助用户收藏自己喜欢但不立即购买的商品,而购物车既有类似收藏商品的功能,但主要还是方便用户批量购买商品,两者都是用于提升用户购物体验。但是商品收藏之后要购买还是面临着加入购物车和直接购买的选择,因此,本文的漏斗分析会将收藏排除在外,由于数据集的行为类型有限,没有下单、支付环节,这里只分析浏览、加购物车、购买的转化。
分析的数据集中一共有19543名用户,其中有浏览行为的用户有19462名,说明有81名用户没有浏览行为。由于之前排除了2017年11月25日~2017年12月3日范围外的用户行为记录,所以很可能是,这81名用户的浏览行为是在2017年11月25日之前就发生了。所以这里排除这81名用户的数据。
# 总用户数
total_users = user_behavior['user_id'].nunique()
# UV数
pv_users= user_behavior[user_behavior['behavior_type']=='pv']['user_id'].nunique()
print('总用户数:',total_users,'UV数:',pv_users)
# 总用户
total_users_list = user_behavior['user_id'].unique()
# UV
pv_users_list = user_behavior[user_behavior['behavior_type']=='pv']['user_id'].unique()
# 找到两者的差集
np.setdiff1d(total_users_list,pv_users_list)
# 浏览个别用户行为数据
user_behavior[user_behavior['user_id']==1679]
# 构造漏斗分析的子集
funnel_data = user_behavior[["user_id","behavior_type"]]
# 去重处理:对每个用户去除相同行为,因为分析时只需要知道有没有对应的行为,而不需要知道行为的数量
funnel_data = funnel_data.drop_duplicates()
# 将用户路径组合起来
funnel_route = funnel_data.groupby('user_id')['behavior_type'].apply('-'.join).reset_index()
# 计算每种路径组合的用户数
funnel_route = funnel_route.groupby('behavior_type')['user_id'].nunique().reset_index()
# 为了助于理解,对字段进行更名处理
funnel_route.rename(columns={"behavior_type":"user_route","user_id":"user_amount"},inplace=True)
funnel_route
结果有60种组合路径,按照 pv 可以划分为三种情况:
可以看出,这印证了之前我们的想法,有些用户没有 pv 行为,漏斗分析中我们会排除掉这部分用户。同时也发现了,一些用户路径中 pv 不在开头,所以为了进行完整的漏斗分析,这里会对它进行拆分,分成pv前段和pv后段,仅分析pv后段的路径。
# 排除不含pv的数据
funnel_route = funnel_route[funnel_route['user_route'].str.contains("pv")]
funnel_route
#拆分不以 pv 开头的路径
# 按正则表达式来分隔,匹配pv前的-号
spliteDF = funnel_route['user_route'].str.split(r'-(?=pv)', 2, True)
spliteDF.columns = ['before-pv', 'after-pv']
spliteDF = pd.concat([funnel_route['user_amount'],spliteDF],axis=1)
spliteDF
def combine(series):
if series['after-pv'] == None:
result = series['before-pv']
else:
result = series['after-pv']
return result
spliteDF['route'] = spliteDF.apply(combine,axis=1)
combineDF = spliteDF[['user_amount','route']]
combineDF
#由于漏斗分析不分析收藏环节,删掉fav环节
route_data = combineDF.copy()
route_data['route'].replace(r'-fav','',regex=True,inplace=True)
route_data
# 将路径划分四类:包含 pv-cart-buy 为一类,包含 pv-cart为一类,包含 pv-buy为一类,最后归为 pv一类
def categorize(series):
if "pv-cart-buy" in series['route']:
result = "pv-cart-buy"
elif "pv-cart" in series['route']:
result = 'pv-cart'
elif "pv-buy" in series['route']:
result = 'pv-buy'
else:
result = 'only pv'
return result
route_data['route_type'] = route_data.apply(categorize,axis=1)
route_data
# 按照route_type汇总
route_data = route_data.groupby('route_type')['user_amount'].sum().reset_index()
route_data
# 构建两种漏斗表
# 浏览总用户数
pv_users= user_behavior[user_behavior['behavior_type']=='pv']['user_id'].nunique()
# 浏览后加入购物车的用户数
pv_cart_users = route_data[route_data['route_type']=='pv-cart'].user_amount.sum() + route_data[route_data['route_type']=='pv-cart-buy'].user_amount.sum()
# 浏览后加入购物车并购买的用户数
pv_cart_buy_users = route_data[route_data['route_type']=='pv-cart-buy'].user_amount.sum()
# 构建浏览-加入购物车-购买漏斗表
funnel_table_1 = pd.DataFrame({"node":["浏览","加入购物车","购买"],"users":[pv_users,pv_cart_users,pv_cart_buy_users]})
# 浏览后购买的用户数
pv_buy_users = route_data[route_data['route_type']=='pv-buy'].user_amount.sum()
# 构建浏览-加入购物车-购买漏斗表
funnel_table_2 = pd.DataFrame({"node":["浏览","购买"],"users":[pv_users,pv_buy_users]})
# 计算第一种漏斗的每一节点转化率
funnel_table_1['conversion_rate'] = round(funnel_table_1["users"]/funnel_table_1["users"].shift(1)*100,2)
# 填充 NAN 值为0
funnel_table_1 = funnel_table_1.fillna(100)
funnel_table_1
# 计算第二种漏斗的每一节点转化率
funnel_table_2['conversion_rate'] = round(funnel_table_2["users"]/funnel_table_2["users"].shift(1)*100,2)
# 填充 NAN 值为0
funnel_table_2 = funnel_table_2.fillna(100)
funnel_table_2
# 制作漏斗图所需的数据格式
def funnel_pairdata(data):
# func: 制作漏斗图所需的数据格式
# data: 数据集
rate_list = []
for i in range(len(data)):
x = data.loc[i,'node']
y = data.loc[i,'conversion_rate']
rate_list.append((x,y))
return rate_list
funnel_pairdata(funnel_table_1)
# 浏览-加入购物车-购买漏斗图
funnel = Funnel(init_opts=opts.InitOpts(width="800px", height="400px"))
funnel.add(
series_name='环节',
data_pair=funnel_pairdata(funnel_table_1),
sort_= "none",
tooltip_opts=opts.TooltipOpts(trigger="item", formatter="{a} <br/>{b} : {c}%"),
label_opts=opts.LabelOpts(is_show=True, position="inside",formatter="{b} {c}%"))
funnel.set_global_opts(
title_opts=opts.TitleOpts(title="浏览-加购物车-购买三大环节的转化漏斗", subtitle="转化率计算方式:每一步作为下一步的基线",pos_left="center"),
legend_opts=opts.LegendOpts(pos_left=0,pos_bottom="50%",orient="vertical",item_height=12))
funnel.render_notebook()
转化路径一:浏览-加购物车-购买
# 浏览-加入购物车-购买漏斗图
funnel = Funnel(init_opts=opts.InitOpts(width="800px", height="400px"))
funnel.add(
series_name='环节',
data_pair=funnel_pairdata(funnel_table_2),
tooltip_opts=opts.TooltipOpts(trigger="item", formatter="{a} <br/>{b} : {c}%"),
label_opts=opts.LabelOpts(is_show=True, position="inside",formatter="{b} {c}%"))
funnel.set_global_opts(
title_opts=opts.TitleOpts(title="浏览-购买两大环节的转化漏斗", subtitle="转化率计算方式:每一步作为下一步的基线",pos_left="center"),
legend_opts=opts.LegendOpts(pos_left=0,pos_bottom="50%",orient="vertical",item_height=12))
funnel.render_notebook()
转化路径二:浏览-购买
# 构建所需子集
route_list = combineDF.copy()
route_list = route_list.groupby('route')['user_amount'].sum().reset_index()
route_list
可以发现有部分用户购买后,还进行了加购和收藏的行为,说明除了商品页面外,购买后页面有加入购物车和收藏的入口。
为了了解完整的用户行为路径,这里将用户路径进行划分:以浏览为起点,购买为结尾。
#拆分以 buy 结尾的路径
# 按正则表达式来分隔,匹配buy后的-号
splitedata = route_list['route'].str.split(r'(?<=buy)-', 2, True)
splitedata.columns = ['before-buy', 'after-buy']
splitedata = pd.concat([route_list['user_amount'],splitedata['before-buy']],axis=1)
# 汇总
splitedata = splitedata.groupby('before-buy').user_amount.sum().reset_index()
# 为了助于理解,对字段进行更名处理
splitedata.rename(columns={"before-buy":"routes"},inplace=True)
splitedata
# 降序
categorized_route = splitedata.sort_values(by ="user_amount",ascending=False)
# 计算占比
categorized_route['percent'] = categorized_route['user_amount']/route_list['user_amount'].sum()
format_dict = {'percent': '{:.2%}'}
(categorized_route.style
.format(format_dict)
.bar(color='lightgreen', vmin=0, subset=['percent'], align='zero')
)
def usage(data):
fav_usage=0
cart_usage=0
buy_usage=0
for i in range(len(data)):
if "fav" in data.loc[i,'routes']:
fav_usage += data.loc[i,'user_amount']
if "cart" in data.loc[i,'routes']:
cart_usage +=data.loc[i,'user_amount']
if "buy" in data.loc[i,'routes']:
buy_usage +=data.loc[i,'user_amount']
usage_data= pd.DataFrame({"func":["收藏","加入购物车","购买"],"users":[fav_usage,cart_usage,buy_usage]})
return usage_data
func_usage = usage(categorized_route)
func_usage
# 计算和浏览用户数的占比
func_usage['percent'] = func_usage['users']/pv_users
# 降序
func_usage = func_usage.sort_values(by ="users",ascending=False)
format_dict = {'percent': '{:.2%}'}
(func_usage.style
.format(format_dict)
.bar(color='lightgreen', vmin=0, subset=['percent'], align='zero')
)
用户收藏/加购物车到购买的时间间隔分布,运营人员可以根据时间间隔分布,给用户设置提醒功能。
# 筛选数据集
df_fav = user_behavior[user_behavior["behavior_type"]=="fav"][["user_id","item_id","timestamp"]]
df_cart = user_behavior[user_behavior["behavior_type"]=="cart"][["user_id","item_id","timestamp"]]
df_buy = user_behavior[user_behavior["behavior_type"]=="buy"][["user_id","item_id","timestamp"]]
# 联结两个表
df_cart_buy = pd.merge(df_cart,df_buy,how="inner",on=["user_id","item_id"],suffixes=('_cart', '_buy'))
df_cart_buy["diff_time"] = df_cart_buy["timestamp_buy"]-df_cart_buy["timestamp_cart"]
df_fav_buy = pd.merge(df_fav,df_buy,how="inner",on=["user_id","item_id"],suffixes=('_fav', '_buy'))
df_fav_buy["diff_time"] = df_fav_buy["timestamp_buy"]-df_fav_buy["timestamp_fav"]
# 时间格式转化,单位为小时
df_cart_buy['diff_time'] = df_cart_buy['diff_time'].map(lambda x : x.days * 24 +x.seconds/3600)
df_fav_buy['diff_time'] = df_fav_buy['diff_time'].map(lambda x : x.days * 24 +x.seconds/3600)
# 筛选出时间差大于0的。时间差为负数,表示一个用户购买了一个商品后,再将同一个商品加入购物车的情况
df_cart_buy = df_cart_buy[df_cart_buy["diff_time"]>=0]
df_fav_buy = df_fav_buy[df_fav_buy["diff_time"]>=0]
df_cart_buy
# 分组,一共9天,分为9组
bins = [0,24,48,72,96,120,144,168,192,240]
df_cart_buy["time_bins"] = pd.cut(df_cart_buy["diff_time"],bins=bins,include_lowest=False) # include_lowest:是否包含左端点
bins = [0,24,48,72,96,120,144,168,192,240]
df_fav_buy["time_bins"] = pd.cut(df_fav_buy["diff_time"],bins=bins,include_lowest=False) # include_lowest:是否包含左端点
# 按组统计
cart_df = df_cart_buy.groupby("time_bins").user_id.count()
fav_df = df_fav_buy.groupby("time_bins").user_id.count()
cart_df
# 绘图 加购物车—购买时间间隔分布
plt.figure(figsize=(12,8))
rects = plt.bar(range(0,len(cart_df.index)),cart_df.values)
plt.xticks(range(0,len(cart_df.index)),cart_df.index)
plt.xlabel("单位:h",fontsize=10)
plt.title("加购物车—购买时间间隔分布(9天内)",fontsize=15)
# 设置数据标签
count=0
Sum=cart_df.sum()
for rect in rects:
height = rect.get_height()
rect_x = rect.get_x()
plt.text(rect.get_x() + rect.get_width()/2,height+50,str(height)+'人', ha='center',fontsize=11)
plt.text(rect.get_x() + rect.get_width()/2,height+140,str('{:.2f}'.format(cart_df.values[count]/Sum *100)) + "%",ha='center',fontsize=11)
count=count+1
# 绘图 收藏—购买时间间隔分布
plt.figure(figsize=(12,8))
rects = plt.bar(range(0,len(fav_df.index)),fav_df.values)
plt.xticks(range(0,len(fav_df.index)),fav_df.index)
plt.xlabel("单位:h",fontsize=10)
plt.title("收藏—购买时间间隔分布(9天内)",fontsize=15)
# 设置数据标签
count=0
Sum=fav_df.sum()
for rect in rects:
height = rect.get_height()
rect_x = rect.get_x()
plt.text(rect.get_x() + rect.get_width()/2,height+30,str(height)+'人', ha='center',fontsize=11)
plt.text(rect.get_x() + rect.get_width()/2,height+70,str('{:.2f}'.format(fav_df.values[count]/Sum *100)) + "%",ha='center',fontsize=11)
count=count+1
收藏且有购物意愿的用户中,接近77.13%的用户会在24h内完成购买,超过92.24%的用户会在3天内购买。
当用户收藏和加购物车3天后还未购买,可以大致推测用户的购买意愿并不强烈,此时可以通过适当的提示\优惠活动来引导用户购买,或者推荐对应的相似商品供用户选择。