淘宝用户行为分析
数据来源
数据链接,我下载的是上传的905.80MB的UserBehavior.csv.zip。
本项目完整源码,提取码9a5v。
为减少篇幅,所以本篇的绘图部分代码没有附上,需要的小伙伴可以点开链接下载完整代码。
项目背景
UserBehavior是阿里巴巴提供的一个淘宝用户行为数据集,用于隐式反馈推荐问题的研究。
本数据集包含了11月25日至12月3日之间,有行为的约一百万随机用户的所有行为(行为包括点击、购买、加购、喜欢)。数据集的组织形式和MovieLens-20M类似,即数据集的每一行表示一条用户行为,由用户ID、商品ID、商品类目ID、行为类型和时间戳组成,并以逗号分隔。
分析目标
用户活跃时段,指标趋势用户对哪些产品、类目感兴趣,付费用户和非付费用户区别转化率,跳失率、复购率用户购买行为特点根据RFM模型对用户分类数据概览
数据准备
导包
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as snsfrom pyecharts import charts as pycfrom pyecharts import options as optsimport warnings%matplotlib inline# 忽略警告warnings.filterwarnings("ignore")# 修改matplotlib尺寸plt.rcParams['figure.figsize'] = (20, 8)# 解决matplotlib中文乱码plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']plt.rcParams['axes.unicode_minus'] = False
数据导入
原数据集有1亿多条,内存不够所以无法全部导入,故此次导入1000万条数据进行分析。
# 数据量过大,这里只选取前1000w行# 根据官网介绍给定列名columns = ["userid", 'itemid', 'categoryid', 'type', 'timestamp']# 使用iterator将数据分块,返回迭代器data = pd.read_csv("./UserBehavior.csv", iterator=True,chunksize=10000000, names=columns, parse_dates=['timestamp'])# 将前1000w行数据存放在df df=data.get_chunk(10000000)
查看一下数据信息,共1000万行,前三个字段都是int,后两个是object,我们需要将时间戳转换为日期格式。
数据清洗
将时间戳转为日期。数据集说明中写的是本数据集的日期范围是11月25日至12月3日,所以剔除这日期以外的数据。import timedef get_unixtime(timeStamp):formatStr = "%Y-%m-%d %H:%M:%S"tmObject = time.strptime(timeStamp, formatStr)return int(time.mktime(tmObject))startTime = get_unixtime("-11-25 00:00:00")endTime = get_unixtime("-12-03 00:00:00")# 保留11月25日至12月3日之间的数据(先减少数据量,再转换可以节省点时间)df = df[(df.timestamp >= startTime) & (df.timestamp <= endTime)]# 要注意时区问题,pd.to_datetime转换的默认时区不是中国df['datetime'] = pd.to_datetime(df.timestamp, unit='s', utc=True).dt.tz_convert("Asia/Shanghai")# 删除时间戳节约内存df.drop('timestamp', inplace=True, axis=1)# 将datetime进行拆分成date和hourdf['date'] = df.datetime.dt.datedf['hour'] = df.datetime.dt.hour
保留下来9994427条数据,最早的是-11-25 00:00:01,最晚的是-12-03 23:59:59。
数据分析
用户流量类指标
# 取消科学计数法,保留两位小数pd.set_option("float_format", lambda x: "%.2f" % x)# 9天总用户量,有操作记录的商品及类目count_users = df.userid.nunique()count_itemid = df.itemid.nunique()count_categoryid = df.categoryid.nunique()count_user_bought=df[df['type']=='buy'].userid.nunique()count_user_nobought = count_users-count_user_bought# 9天总操作记录数types = ["pv", "cart", "fav", "buy"]type_s = df.type.value_counts()# 拼接9天所有、日均、人均指标type_df = pd.DataFrame([type_s, type_s/9, type_s / count_users],columns=types, index=["all", "aver_day", "aver_user"])# 购买过的用户总共浏览了几次,购买了几次type_df.loc['user_bought']=df[df['userid'].isin(df[df['type']=='buy']['userid'].unique())].type.value_counts()# 没有购买过的用户总共浏览了几次购买了几次type_df.loc['user_nobought']=type_df.loc['all']-type_df.loc['user_bought']
print(f"uv:{count_users}")print(f"付费用户数:{count_user_bought}")print(f"付费用户占比:{count_user_bought/count_users*100:.2f}%")print(f"商品数:{count_itemid}")print(f"类目数:{count_categoryid}")# 非付费用户是否比付费用户贡献了更多的PV?print(f"付费用户人均贡献PV:{type_df.loc['user_bought'].pv/count_user_bought:.2f}")print(f"非付费用户人均贡献PV:{type_df.loc['user_nobought'].pv/count_user_nobought:.2f}")print(f"付费用户相较于非付费用户人均多贡献了{((type_df.loc['user_bought'].pv/count_user_bought)/(type_df.loc['user_nobought'].pv/count_user_nobought)-1)*100:.2f}%的PV")
-11-25至-12-03这9天内浏览量总计8944500次,添加购物车总计559127次,收藏产品总计291657次,购买总计199143次。平均每日浏览量993833次,添加购物车62125次,收藏产品32406次,购买22127次。平均每人浏览91次,添加购物车6次,收藏产品3次,购买2次。付费用户占总用户数的67.94%,付费用户相较于非付费用户人均多贡献了35.84%的PV,也就是说付费用户在淘宝app上的使用时间高于非付费用户,应该建立深度的用户画像,进行更精准的商品推荐。
跳失率/复购率
跳失率=只有点击行为的用户/总用户数
其实真正的跳失率应该是只浏览一个页面就离开的访问次数 / 该页面的全部访问次数,这边只是为了突出这些有待发展的客户。
复购率=购买2次及以上用户数/总购买用户数
复购率可以分为按客户计算和按交易计算,这里我采用的是按客户计算。
一定要确定统计周期,像这个数据的统计周期就是9天。
# 将数据按照userid进行分组groupby_userid = df.groupby(by=df.userid)# unstack()可以展开再成为一个dataframeuser_type = groupby_userid.type.value_counts().unstack()# 跳失率# user_type.sum(axis=1)对DataFrame进行横向相加,如果一个userid的pv值==横向相加的和,那就表明他只有点击行为,没有购买/加入购物车/收藏的行为only_pv_users = user_type[user_type['pv'] == user_type.sum(axis=1)]# shape返回的是元组(行数,列数),所以用[0]取出行数bounce_rate = only_pv_users.shape[0]/count_users# 复购率user_bought_twice = user_type[user_type['buy'] >= 2].shape[0]user_bought = user_type[user_type['buy'] >= 1].shape[0]repurchase_rate = user_bought_twice/user_boughtprint("跳失率:{:.2f}%".format(bounce_rate*100))print("复购率:{:.2f}%".format(repurchase_rate*100))print(f"总用户数:{count_users}")
跳失率为5.74%(只浏览不做其他操作的用户比例),这个数值处在可以接受的范围。复购率66.01%,这个数值高是好事情,但是得考虑是不是新客数量越来越少,或者单一渠道获取的顾客已经饱和,是否可以增加别的获客途径。
转化率
因为收藏和加入购物车没有必然关系,所以我们可以将用户的行为路径划分为两种来进行分析:
process1:浏览->收藏->购买process2:浏览->加入购物车->购买
因为每种type都有两种可能性,共有4种type,所以其实总共有16条路径。
# 分别将fav和buy的df提取出来pv_df = df[df['type'] == 'pv']fav_df = df[df['type'] == 'fav']buy_df = df[df['type'] == 'buy']cart_df = df[df['type'] == 'cart']# 将fav_df和buy_df合并,得到收藏后购买的用户人数# process1 浏览->收藏->购买pv_fav_df = pd.merge(left=pv_df, right=fav_df, how='inner', on=['userid', 'itemid', 'categoryid'], suffixes=('_pv', '_fav'))fav_buy_df = pd.merge(left=fav_df, right=buy_df, how='inner', on=['userid', 'itemid', 'categoryid'], suffixes=('_fav', '_buy'))count_user_pv_fav = len(pv_fav_df[pv_fav_df.datetime_pv < pv_fav_df.datetime_fav].userid.value_counts())count_user_fav_buy = len(fav_buy_df[fav_buy_df.datetime_fav < fav_buy_df.datetime_buy].userid.value_counts())# process2 浏览->添加购物车->购买pv_cart_df = pd.merge(left=pv_df, right=cart_df, how='inner', on=['userid', 'itemid', 'categoryid'], suffixes=('_pv', '_cart'))cart_buy_df = pd.merge(left=cart_df, right=buy_df, how='inner', on=['userid', 'itemid', 'categoryid'], suffixes=('_cart', '_buy'))count_user_pv_cart = len(pv_cart_df[pv_cart_df.datetime_pv < pv_cart_df.datetime_cart].userid.value_counts())count_user_cart_buy = len(cart_buy_df[cart_buy_df.datetime_cart < cart_buy_df.datetime_buy].userid.value_counts())# 将数据合并成元组的列表是为了后面用pyecharts画图,如果不画图可以不这样合并process1 = [("浏览人数", count_users), ("收藏人数", count_user_pv_fav),("购买人数", count_user_fav_buy)]process2 = [("浏览人数", count_users), ("添加购物车人数", count_user_pv_cart),("购买人数", count_user_cart_buy)]conversion = pd.DataFrame(data=[[user[1] for user in process1], [user[1] for user in process2]], index=['process1', 'process2'], columns=['pv', 'fav/cart', 'buy'])conversion['pv_fav/cart'] = conversion['fav/cart'].div(conversion['pv'])conversion['fav/cart_buy'] = conversion['buy'].div(conversion['fav/cart'])conversion['pv_buy'] = conversion['buy'].div(conversion['pv'])
process1:用户转化率为9%,18%的用户会在浏览后收藏产品,其中只有47%的用户会购买该商品。
process2:用户转化率为23%,41%的用户会在浏览后把商品添加到购物车,其中有55%的用户会购买该商品。
从整个用户行为来看,89%的操作都只是在浏览,只有2%进行购买操作。
无论是从柱状图的高矮、漏斗图的粗细和数据比较我们都可以发现,process2的转化率更高,相较于收藏商品,用户更喜欢在购买商品前将商品放入购物车。
我们从淘宝app的设计来看一下,进入淘宝app后最底下一行很明显就有购物车的选择图标,而要进入收藏必须通过“我的淘宝->收藏夹”进行查看,多一步操作就多一种客户流失的可能性。再从两个界面进行考虑,加入购物车的商品很大概率会是用户已经选择好参数的商品(例如衣服尺寸、食品口味、3C产品配置等),点开购物车用户即可看到当初已经选择好的商品的现价(双11前夕我相信剁手党不可能只把商品收藏而不提前加入购物车吧);而收藏页面显示的只是商品概览(图片、标题、价格等),并没有用户自定义的参数选择,如若客户从收藏页进入再购买,势必又要多几步操作。
为什么用户更喜欢在购买前先将商品放入购物车,推测原因:
同款产品不同店铺比价凑单满减先放入购物车,等有优惠活动再购买
可以在商品详情页、商品图品等醒目地方添加提示用户将商品加入购物车的标语,或者添加限时优惠券,让顾客产生购买的紧迫感,刺激消费可能。
用户购买间隔分布
# 用户加入购物车后/收藏产品后多久会购买buy_after_cart = pd.DataFrame(cart_buy_df.datetime_buy-cart_buy_df.datetime_cart, columns=['interval'])buy_after_fav = pd.DataFrame(fav_buy_df.datetime_buy-fav_buy_df.datetime_fav, columns=['interval'])# 将timedelta格式转换为小时buy_after_cart['hours'] = buy_after_cart['interval'].map(lambda x: x.total_seconds()//3600)buy_after_fav['hours'] = buy_after_fav['interval'].map(lambda x: x.total_seconds()//3600)# 有些用户会先购买,再收藏/加入购物车,再次购买(按照数据来看真的会有这种用户),所以我们剔除这种先购买再收藏/加入购物车的操作记录# 即删除负值buy_after_cart = buy_after_cart[buy_after_cart['hours'] >= 0]buy_after_fav = buy_after_fav[buy_after_fav['hours'] >= 0]# 因为会出现大量的0小时,就是购买行为和添加购物车/收藏行为不满一小时# 所以我们加个1,这样理解起来就是两种操作是间隔一小时buy_after_cart['hours'] = buy_after_cart['hours']+1buy_after_fav['hours'] = buy_after_fav['hours']+1
无论是哪一种路径,用户在1小时之内购买的可能性都较大,其中收藏后购买的用户中有45%的用户在收藏后1小时内就下单购买,而加入购物车后再购买的时间间隔明显较长,添加购物车后购买的用户中有超过60%的用户在加入购物车12小时之后再进行购买。如有用户收藏或加入购物车后可有立即的客服跟进,例如优惠放送、话术吸引,则则很可能提高用户快速购买的可能性,进一步提高购买转化率。
每日情况
# 首先按照date进行分组import datetimegroupby_date = df.groupby(df.date)# 将日期字符串先转为日期格式days = pd.to_datetime(['/11/25', '/11/26', '/11/27', '/11/28','/11/29', '/11/30', '/12/01', '/12/02', '/12/03'])# 将日期作为索引daily_df = pd.DataFrame(data=None, columns=types, index=days)# 通过循环获得每组的统计数据for day in days:daily_df.loc[day] = groupby_date.get_group(day).type.value_counts()# 我们认为当天使用淘宝app进行3次操作(无论是查看详情页、收藏、加入购物车还是购买都算)的用户就是活跃用户# 自定义活跃用户操作次数基准active_user_standard = 3# 添加每日UVdaily_df['uv'] = [groupby_date.get_group(day).userid.nunique() for day in daily_df.index]# 根据date和userid分组后计算当天操作次数达到基准或以上的用户数作为活跃用户数daily_df['active_user'] = [(groupby_date.get_group(day).groupby(by='userid').size() > active_user_standard).value_counts()[True] for day in daily_df.index]# 活跃用户比例daily_df['active_user_rate'] = daily_df['active_user']/daily_df['uv']# 交易用户数daily_df['buyer'] = df[df['type'] == 'buy'][['date', 'userid']].groupby(by=['date', 'userid']).size().count(level=0)# 交易用户比例daily_df['buyer_rate'] = daily_df['buyer']/daily_df['uv']# 判断当天是周几daily_df['weekday'] = [datetime.datetime.isoweekday(datetime.date(x.year, x.month, x.day)) for x in daily_df.index]# 用户活跃天数temp = df[['userid', 'date', 'type']].groupby(by=['date', 'userid']).count()user_active_days_df = temp[temp['type'] > active_user_standard].count(level=1)sorted_user_active_df = user_active_days_df['type'].value_counts().sort_index()
每日的活跃用户量基本在70%上下浮动,而成交客户基本维持在20%。
浏览量、收藏数、添加购物车次数、购买次数、用户量在/12/02当天急速上升。周末的各项指标都优于周内,周六周日用户尤为活跃,用户有更多的时间可以逛淘宝,所以应当在周五晚间、周六周日当天进行适当的营销活动。
这9天内,用户活跃天数服从近似正态分布,用户活跃天数主要集中在3-7天(用户当天操作记录达3条算作当天活跃)。
时段情况
# 根据时段进行分组groupby_hour = df.groupby(by=df.hour)# 创建一个空的时段dataframe# 列表生成式创建一个0-23的时间列表times = [x for x in range(0, 24)]time_df = pd.DataFrame(data=None, index=times, columns=types)for t in times:time_df.loc[t] = groupby_hour.get_group(t).type.value_counts()time_df['uv'] = [groupby_hour.get_group(hour).userid.nunique() for hour in times]# 交易用户活跃时段groupby_hour_userid = df[df['type']=='buy'][['userid','hour']].groupby(by=['hour','userid'])buyer_hour = groupby_hour_userid.size().count(level=0)
各时段指标走势大致一致5:00-10:00及18:00-21:00两段时间是用户活跃度迅速增长的时段10:00-18:00这个时间段的用户活跃度上下浮动,但基本保持着同一水平。13:00,15:00这两个时间段是用户活跃度稳定阶段的两个小高峰,我们可以猜测13:00是因为用户工作午休结束了拿起手机再摸一会儿鱼造成的,15:00是因为工作了一两个小时又想摸鱼。17:00开始一部分用户下班了或马上下班,可以开始正大光明摸鱼21:00达到用户活跃度峰值
产品/类目情况
# 按照itemid进行分组groupby_itemid = df.groupby(by='itemid')item_type_df = groupby_itemid.type.value_counts().unstack()# 按照categroyid进行分组groupby_categoryid = df.groupby(by='categoryid')cat_type_df = groupby_categoryid.type.value_counts().unstack()# 热卖商品top10item_type_df.sort_values(by='buy', ascending=False)[:10]# 热销类目top10cat_type_df.sort_values(by='buy', ascending=False)[:10]
itemid为3122135、3031354等一些商品在这9天内属于热销产品,总订单量都在50单以上。1464116、2735466等类目的是这9天内销售量较高的类目。
# 一件商品在被购买前会经过多少次操作(多少次操作才能换来一次购买)item_type_df['behavior_transform'] = item_type_df.sum(axis=1)//item_type_df.buy
左上角属于快消/高频/品牌选择少/寡头左下角属于低频/贵重/品牌选择少/寡头右上角属于快消/高频/品牌选择多右下角属于决策慎重/低频/品牌选择多
大部分购买前累计行为发生在0-100次,购买次数集中在0-20次,用户喜欢购买的产品分类是属于左下角,要让分布逐渐往右上靠,从平台方考虑可以增加商品大类的专区假设,减少用户搜索对比,提升用户体验。若想让分布先往左上角移动可以考虑建设高频网红专区,或者在用户搜索时首先展示用户购买过或收藏过的品牌/产品。
两次操作就达成购买行为的计数很高(浏览一次就买),大多数购买前累计操作次数分布在0-100次,累计100次以上操作才换来一次购买行为的产品很多,如果有更详细的数据,例如在网页中埋点记录详情页留存率及用户浏览时长,就可以判断是在哪一阶段造成的用户流失或跳失率高的原因。
用户操作多,但是就不买,无外乎价格、质量、评价、产品介绍等方面,虽然说数据不可以成为“皇帝的新衣”,但是让用户产生购买“皇帝新衣”的冲动还是可以考虑的(满足一下客户小小的虚荣心)。
RFM模型
RFM模型是衡量客户价值和客户创利能力的重要工具和手段。在众多的客户关系管理(CRM)的分析模式中,RFM模型是被广泛提到的。该机械模型通过一个客户的近期购买行为、购买的总体频率以及花了多少钱3项指标来描述该客户的价值状况。
百度百科
重要价值客户最近消费时间近,消费频次和消费金额高,必须重点关注并保持住,需要提高用户满意度以及粘性。重要保持客户最近消费时间远,但消费频次和消费金额高,可以认为是有段日子没有来的忠诚客户,要主动与其联系,关注社群运营。重要发展客户最近消费时间近,消费金额高,频次低,用户忠诚度不高,但很可能是潜力客户,需要着重发展。重要挽留客户最近消费时间远,消费金额高,频次低,很可能成为流失用户,应该采取挽留措施。
在这就不多具体介绍这个模型了,因为数据集缺少交易额,所以这里就只能实现RF模型。
# R:Recency最近一次交易日期;F:Frequency频率,交易次数buy_userid_group = df[df['type'] == 'buy'].groupby(by='userid')# 先创建一个空的DataFrameRF = pd.DataFrame(index=buy_userid_group.groups.keys(), columns=['R', 'F'])# 因为这边索引的顺序就是按照groupby之后的顺序,所以直接赋值即可RF['F'] = buy_userid_group.type.value_counts().valuesRF['last_buy_time'] = buy_userid_group.datetime.max().values# 我们假设获取数据集的第二天进行分析,所以选择/12/04为对照日期,保留天数RF['R'] = (pd.to_datetime('-12-04')-RF['last_buy_time']).dt.days
def R_score(x):if 0 <= x <= 1:return 4elif 2 <= x <= 3:return 3elif 4 <= x <= 6:return 2elif 7 <= x <= 9:return 1else:return 0def F_score(x):if x == 1:return 1elif x == 2:return 2elif x == 3:return 3elif x >= 4:return 4else:return 0# 根据R,F进行评分RF['R_score'] = RF.R.map(R_score)RF['F_score'] = RF.F.map(F_score)
def user_classfication(tup):R_score, F_score = tupif R_score >= 3 and F_score > 2:return "重要保持客户"elif R_score >= 3 and F_score <= 2:return "重要发展客户"elif R_score < 3 and F_score > 2:return "重要价值客户"elif R_score < 3 and F_score <= 2:return "重要挽留客户"else:return NoneRF['user_classification'] = RF[['R_score', 'F_score']].apply(user_classfication, axis=1)
用户类型主要分布在走红要保持客户和重要发展客户,重要价值客户偏少,仅有7%,应该着重关注重要发展客户,提高其交易频次,转换为重要价值客户。