分享一个 《pandas进阶宝典》 最新更新的实战项目,电商平台用户行为分析。以下是内容节选。
数据分析 1.行为概况 首先,我们要对用户的行为类型有一定的理解,了解每个行为所代表的含义。
浏览:作为用户与商品接触的第一个行为,它的数量级与其他行为类型相比而言是非常庞大的,因为: 用户购买之前需要货比三家,可能会浏览很多个商品最后只下单一个,此时就是多个浏览对应一个下单。 但大部分用户可能只是浏览了很多商品,但最终没下单的,此时就是多个浏览对应零个下单。 收藏:代表用户对商品有了一定程度的意向,但不一定有购买计划。 加购物车:代表用户对商品的购买意向进一步加强,但由于某种原因还在犹豫没最终决定,这个环节的成单率极高。 下单:代表交易达成,此时新用户已成为老用户,老用户继续产生价值。 需求1:对所有行为类型统计数量和占比 # 行为类型数量统计 ser_type_cnt = df['type_map' ].value_counts()# 行为类型数量占比 ser_type_pct = df['type_map' ].value_counts(normalize=True )# 合并 pd.concat([ser_type_cnt, ser_type_pct],axis=1 ).style.format("{:.2%}" ,subset=['proportion' ]).bar(subset='proportion' )
观察结果:
浏览行为占75.66%,印证了我们前面对的浏览行为的理解。 加购物车占14.65%,下单仅占了6.97%,可以推出一些权益活动,吸引客户下单,提高下单率。 此外用户评论和收藏的占比也较低,分别占2.46%和1.16%,应增强与用户之间的互动,提高评论数量和收藏率。 2.流量指标分析 指标设计可以很好的帮助我们了解业务状况、发现业务问题和异常。比如,xx企业将日活用户数作为北极星指标,针对该指标向下拆解各种细分指标,然后对各种细分指标进行优化提升,从而最终提升北极星指标,业绩大幅提升。
下面针对该项目数据中的行为类型设计一些常用的流量指标,用来监控用户行为的流量变化,比如浏览总量、浏览人数、日均访问量、人均访问数、消费用户人均访问量、消费用户人均消费次数、复购率 等等。
访问量指标
针对浏览行为可以统计出一段时间内的访问量大小,即流量有多少。下面对数据中全部用户计算访问量的指标。
pv(page view)总访问量:一段时间内全部访问量的总和 uv(user view)总访客数:一段时间内全部访客的总和,注意用户是单独的个体因此需要计算唯一值 pv_per_day 日均访问量:一段时间内平均每天的访问量 pv_per_user 人均访问量:每个用户的平均访问量 需求2:计算出以上所有用户的访问量指标 # 总访问量 pv = df[df['type_map' ] == '浏览' ]['user_id' ].count() print('总访问量:{}' .format(pv))# 总访客数 uv = df['user_id' ].nunique() print('总访客数:{}' .format(uv))# 日均访问量 pv_per_day = pv / df['date' ].nunique() print('日均访问量:%.0f' %pv_per_day)# 人均访问量=总访问量/总访客数 pv_per_user = pv / uv print('人均访问量:%.2f' %pv_per_user) ------ 总访问量:2757485 总访客数:440373 日均访问量:393926 人均访问量:6.26
消费用户流量指标
以上是对全部用户的流量指标,如果我们想分析消费用户群体,可以针对消费用户设计相关的流量指标,比如有:
user_pay 消费用户数量:一段时间内有下单行为的总用户数量 pv_pay 消费用户总访问量:一段时间内所有消费用户的浏览数量总和 user_pay_rate 消费用户数占比:消费用户数占总用户数的比例 pv_per_buy_user 消费用户人均访问量:一段时间内每个消费用户浏览数量的均值 需求3:计算出以上消费用户的流量指标 # 消费用户的唯一id user_pay = df[df['type_map' ] == '下单' ]['user_id' ].unique() print('消费用户数量:{}' .format(len(user_pay)))# 消费用户的总访问量 pv_pay = df[df['user_id' ].isin(user_pay)]['type_map' ].value_counts()['浏览' ] print('消费用户总访问量:{}' .format(pv_pay))# 消费用户数占比 user_pay_rate = len(user_pay) / uv print('消费用户数占比:%.2f%%' %(user_pay_rate*100 ))# 消费用户人均访问量 pv_per_buy_user = pv_pay / len(user_pay) print('消费用户人均访问量:%.2f' %pv_per_buy_user)# 消费用户人均消费次数 cnt_per_buy_user = df[df['type_map' ] == '下单' ].shape[0 ]/len(user_pay) print('消费用户人均消费次数:%.2f' %cnt_per_buy_user) ------ 消费用户数量:187549 消费用户总访问量:1400380 消费用户数占比:42.59 % 消费用户人均访问量:7.47 消费用户人均消费次数:1.18
复购率
复购行为指的是一个客户的历史下单次数至少一次以上,即再第一次购买后,过了一段时间又有了第二次、第三次购买行为。
需求5:计算出下单次数一次以上的客户占总下单人数比例 注意:这里的复购逻辑是以人为维度,可以包括不同sku商品的下单,只要该用户总下单次数>1就算复购。因此我们对下单的用户分组统计总下单次数,然后筛选出下单次数>1的客户占总下单客户数的比例。
# 每个用户消费总次数 df_pay_num = df[df['type_map' ]=='下单' ].groupby(['user_id' ])['type' ].count().to_frame(name='num' )# 消费次数>=2的客户数
repay_num = df_pay_num[df_pay_num['num' ]>1 ].shape[0 ]# 消费总客户数 total_pay_num = df_pay_num.shape[0 ]# 复购率 repay_rate =round(repay_num/total_pay_num,4 ) print('用户维度的复购率:%.2f%%' %(repay_rate*100 )) ------ 用户维度的复购率:11.55 %
对于用户维度的复购率而言,11.55%是比较低的,有很大的提升空间。可以联合运营部门设计一些权益活动吸引老客下单,提升复购率。
需求6:计算出每个商品下单次数一次以上的客户占该商品总下单人数比例 以上的复购率口径是人的维度,只要在电商平台下单次数大于1次就算复购。如果我们从商品的角度出发,也可以分析每个商品的复购率。
# 对用户和商品id分组统计下单的次数 df_sku_user_pay = df[df['type_map' ]=='下单' ].groupby(['sku_id' ,'user_id' ], as_index=False )['type' ].count() df_sku_user_pay.head()
# 筛选每个商品下用户下单次数大于1的 df_sku_user_repay = df_sku_user_pay[df_sku_user_pay['type' ]>1 ] df_sku_user_repay.head()
# 对商品分组统计下单次数大于1的客户数 df_sku_repay_cnt = df_sku_user_repay.groupby(['sku_id' ], as_index=False )['user_id' ].count().sort_values(by='user_id' ,ascending=False )# 对商品分组统计下单的总客户数 df_sku_pay_cnt = df.groupby(['sku_id' ], as_index=False )['type' ].count()# 商品无复购客户的填充为0,计算复购率,按复购数量倒序排序 df_sku_repay_rate = (pd.merge(df_sku_pay_cnt,df_sku_repay_cnt, on='sku_id' , how='left' ) .fillna(0 ) .rename(columns={'type' :'pay_cnt' ,'user_id' :'repay_cnt' }) .assign(repay_rate=lambda x:x['repay_cnt' ]/x['pay_cnt' ]) .sort_values(by='repay_cnt' ,ascending=False )) df_sku_repay_rate.head(10 )
以上就得到了每个sku商品的复购率。下面通过可视化看下复购率大于0的分布状况,是明显的有偏分布,大多数sku的复购率都在10%以内,极少数高比如50%是因为总下单数量小,没有统计意义。
plt.subplots(figsize=(15 , 5 )) df_sku_repay_rate.loc[df_sku_repay_rate['repay_rate' ]>0 ,'repay_rate' ].hist(bins=50 ) plt.show()
sku_repay_avg = df_sku_repay_rate.loc[df_sku_repay_rate['repay_rate' ]>0 ,'repay_rate' ].mean() sku_repay_med = df_sku_repay_rate.loc[df_sku_repay_rate['repay_rate' ]>0
,'repay_rate' ].median() sku_repay_max = df_sku_repay_rate.loc[df_sku_repay_rate['repay_cnt' ]>0 ,'repay_rate' ].max() print('sku商品复购率大于0的均值:%.2f%%' %(sku_repay_avg*100 )) print('sku商品复购率大于0的中位数:%.2f%%' %(sku_repay_med*100 )) print('sku商品复购率的最大值:%.2f%%' %(sku_repay_max*100 )) ------ sku商品复购率大于0 的均值:3.82 % sku商品复购率大于0 的中位数:1.61 % sku商品复购率的最大值:50.00 %
3.转化漏斗分析 浏览->收藏->加购物车->下单->评论,是一个用户从了解商品->交易产品->熟悉产品的过程。在这个过程中,由于每一个转化环节都会有一定流量的损失,因此流量最终会形成一个漏斗。
比如有10个用户浏览了商品,但最后加入购物车或者下单的只有7个,其他3个只是看看并没有后续行为,那么此时浏览到下单的转化率就是70%,其他环节的转化漏斗也是同理。
清楚地了解各环节转化率后,有助于我们发现薄弱环节并进行优化。同样地,各环节转化率也可以作为我们关注的指标,并按周期进行监测。
需求7:计算用户从浏览到下单的转化率及漏斗可视化 # 筛选用户的浏览数据 df_pv = df[df['type_map' ]=='浏览' ]# 筛选客户的下单数据 df_pay = df[df['type_map' ]=='下单' ] cols = ['user_id' ,'sku_id' ,'action_time' ,'type_map' ]# 将用户浏览和下单行为进行匹配 df_pv_pay = pd.merge(df_pv[cols], df_pay[cols], on=['user_id' ,'sku_id' ], how='inner' , suffixes=('_pv' , '_pay' )) df_pv_pay.head()
以上通过内连接将同用户同商品下存在浏览和下单行为的匹配出来。
但此时的数据还不能直接统计使用,仍需要两个关键的步骤。
第一个是,因为浏览到下单是有先后顺序的,我们的条件是下单时间一定要晚于浏览时间 ,因此这里我们对这个时间进一步筛选。
# 筛选下单时间在浏览时间之后的数据 t_0 = df_pv_pay.shape[0 ] df_pv_pay = df_pv_pay[df_pv_pay['action_time_pay' ] > df_pv_pay['action_time_pv' ]] t_1 = df_pv_pay.shape[0 ] print('时间筛选前数量 %.0f' %t_0) print('时间筛选后数量 %.0f' %t_1) print('时间筛选后减少数量 %.0f' %(t_0-t_1)) ------ 时间筛选前数量 300673 时间筛选后数量 215070 时间筛选后减少数量 85603
第二个是,用户在下单一个商品之前可能有不止一次的浏览,即多个浏览对应一个下单,同理也存在一个浏览对应多个下单记录的情况(如下图所示)。
要分析从浏览到下单的转化率,我们需要以用户+商品的维度考虑,即对于同一用户同一商品下的多重行为记录要进行去重处理 。举例说就是,同用户用商品下不论有多少次浏览行为,只要下单了那么都算作为一个转化,不应算作多个。
基于以上去重逻辑,我们使用 groupby
对同用户同商品的重复行为进行去重。
pv_pay_dict= {}# 去重后的浏览用户数 pv_cnt = df_pv.groupby(['user_id' ,'sku_id' ])['type_map' ].count().shape[0 ]
# 去重后的下单用户数 pay_cnt = df_pv_pay.groupby(['user_id' ,'sku_id' ])['type_map_pv' ].count().shape[0 ] pv_pay_rate = round(pay_cnt*100 /pv_cnt,4 ) pv_pay_dict['浏览' ] = 100 pv_pay_dict['下单' ] = pv_pay_rate print('浏览用户数 %.0f' %pv_cnt) print('浏览到下单的用户数 %.0f' %pay_cnt) print('浏览->下单转化率 %.2f%%' %pv_pay_rate) ------ 浏览用户数 2228233 浏览到下单的用户数 162772 浏览->下单转化率 7.30 %
从浏览到下单的转化率仅为7.3%,还有很大的优化空间。
接下来,我们使用 pyecharts
第三方包漏斗可视化。
from pyecharts import options as optsfrom pyecharts.charts import Funnelfrom pyecharts.globals import CurrentConfig, NotebookType CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB pv_pay=(Funnel(opts.InitOpts(width="600px" , height="300px" )) .add( series_name="" , data_pair=[[k,v] for k,v in pv_pay_dict.items()], tooltip_opts=opts.TooltipOpts(trigger="item" , formatter="{b} : {c}%" ), label_opts=opts.LabelOpts(is_show=True , position="inside" ), itemstyle_opts=opts.ItemStyleOpts(border_color="#fff" , border_width=1 ) )) pv_pay.load_javascript() pv_pay.render_notebook()
需求8:计算用户从浏览->加购物车->下单的转化率及漏斗可视化 多了一个环节,但分析过程与上面一样。
首先我们分别对浏览和加购物车、加购物车和下单做内连接的匹配。
这里注意一下:因为两次都是内连接,这意味着最终下单的客户一定是有浏览->加购物车->下单的行为轨迹的。但实际业务场景中,有的用户从浏览直接下单而不加入购物车,所以这种直接下单的用户群是没有包含在内的。
# 筛选用户的加购物车数据 df_cart = df[df['type_map' ]=='加购物车' ]# 浏览用户与加购物车匹配 df_pv_cart = pd.merge(df_pv[cols], df_cart[cols], on=['user_id' ,'sku_id' ], how='inner' , suffixes=('_pv' , '_cart' ))# 加购物车用户与下单匹配 df_cart_pay = pd.merge(df_cart[cols], df_pay[cols], on=['user_id' ,'sku_id' ], how='inner' , suffixes=('_cart' , '_pay' ))# 加购物车用户与下单匹配 df_pv_cart_pay = pd.merge(df_pv_cart, df_cart_pay, on=['user_id' ,'sku_id' ,'action_time_cart' ,'type_map_cart' ], how='inner' )
同样按照行为时间顺序进一步筛选。
# 加购物车时间大于浏览时间 t_0 = df_pv_cart.shape[0 ] df_pv_cart = df_pv_cart[df_pv_cart['action_time_cart' ] > df_pv_cart['action_time_pv' ]] t_1 = df_pv_cart.shape[0 ] print('时间筛选前数量 %.0f' %t_0) print('时间筛选后数量 %.0f' %t_1) print('时间筛选后减少数量 %.0f' %(t_0-t_1)) ------ 时间筛选前数量 895540 时间筛选后数量 569174 时间筛选后减少数量 326366 # 下单时间大于加购物车时间
t_0 = df_cart_pay.shape[0 ] df_cart_pay = df_cart_pay[df_cart_pay['action_time_pay' ] > df_cart_pay['action_time_cart' ]] t_1 = df_cart_pay.shape[0 ] print('时间筛选前数量 %.0f' %t_0) print('时间筛选后数量 %.0f' %t_1) print('时间筛选后减少数量 %.0f' %(t_0-t_1)) ------ 时间筛选前数量 196214 时间筛选后数量 182881 时间筛选后减少数量 13333
然后使用 groupby
对同用户同商品的重复行为进行去重。
pv_pay_cart_dict= {}# 去重后浏览客户数 pv_cnt = df_pv.groupby(['user_id' ,'sku_id' ])['type_map' ].count().shape[0 ]# 去重后加购物车用户数 cart_cnt = df_pv_cart.groupby(['user_id' ,'sku_id' ])['type_map_pv' ].count().shape[0 ]# 去重后下单用户数 pay_cnt = df_cart_pay.groupby(['user_id' ,'sku_id' ])['type_map_cart' ].count().shape[0 ]# 浏览到加购物车转化率 pv_cart_rate = round(cart_cnt*100 /pv_cnt,4 )# 浏览到加购物车到下单的转化率 pv_cart_pay_rate = round(pay_cnt*100 /pv_cnt,4 )# 加购物车到下单的转化率 cart_pay_rate = round(pay_cnt*100 /cart_cnt,4 ) pv_pay_cart_dict['浏览' ] = 100 pv_pay_cart_dict['加购物车' ] = pv_cart_rate pv_pay_cart_dict['下单' ] = cart_pay_rate print('浏览用户数 %.0f' %pv_cnt) print('加购物车用户数 %.0f' %cart_cnt) print('下单的用户数 %.0f' %pay_cnt) print('浏览->加购物车转化率 %.2f%%' %pv_cart_rate) print('浏览->加购物车->下单转化率 %.2f%%' %pv_cart_pay_rate) print('加购物车->下单转化率 %.2f%%' %cart_pay_rate) ------ 浏览用户数 2228233 加购物车用户数 354064 下单的用户数 137195 浏览->加购物车转化率 15.89 % 浏览->加购物车->下单转化率 6.16 % 加购物车->下单转化率 38.75 %
同样用 pyecharts
进行漏斗可视化。
pv_cart_pay=(Funnel(opts.InitOpts(width="600px" , height="400px" )) .add( series_name="" , sort_='none' , data_pair=[[k,v] for k,v in pv_pay_cart_dict.items()], tooltip_opts=opts.TooltipOpts(trigger="item" , formatter="{b} : {c}%" ) )) pv_cart_pay.render_notebook()
我们观察到,需求7中得到的转化率为7.3%,而需求8中得到的转化率为6.16%,同样都是从浏览到下单的转化,为什么结果却是不一样的呢?
这是因为,加购物车与下单并不是一种包含关系,而是一种相交的关系。二者的关系有以下三种情况(如下图)。
所以,需求7中的转化率更大,因为从浏览直接到下单,既包括了加购物车后下单也包括了直接下单;而需求8中的转化是从浏览,到加购物车,再到下单,是包含在需求7内的,所以转化率小。
4.消费时长分析 用户从浏览到下单的过程中,可能会有不同的路径,比如,浏览->下单,浏览->加购物车->下单,浏览->收藏->加购物车->下单。
不同的行为路径可能有不同的特性,比如消费时长。
为了更深入研究用户行为,下面我们分析不同路径用户的消费时长,即用户从浏览开始到下单之前所用的总时长,并统计时长的分布。
需求9:统计浏览->加购物车->下单的用户消费时长分布,并进行分布可视化 需求8中我们将浏览->加购物车->下单行为进行了内连接,下面对用户和商品进行分组统计浏览与下单的时间差。其中浏览时间筛选最早浏览时间,代表第一次接触商品,下单时间筛选最早的下单时间,代表浏览和加购物车后最近的下单。
pcp_interval = (df_pv_cart_pay.groupby(['user_id' , 'sku_id' ],as_index=False ) .apply(lambda x: (x.action_time_pay.min() - x.action_time_pv.min())) .rename(columns={None :'interval' }))
以上我们得到了下单与浏览行为的时间间隔,但此时的interval变量是 timedelta
时间差类型,我们需要进一步转化成数值类型。
# 获取时间差成分,筛选小时数 pcp_interval['interval' ] = pcp_interval['interval' ].dt.components['hours' ]
这是使用了 components
方法将时间差分解,并最终转换为0-24小时的小时数值。最后使用 seaborn
对时间间隔进行了分布可视化。
# 时间间隔分布可视化 fig, ax = plt.subplots(figsize=[16 ,6 ]) sns.countplot(x='interval' , data=pcp_interval, palette='Set1' )# 消费时长分布占比的注释 for p in ax.patches: ax.annotate('{:.2f}%' .format(100 *p.get_height()/len(pcp_interval['interval' ])), (p.get_x() + 0.1 , p.get_height() + 100 )) ax.set_yscale("log" ) plt.title('消费时间间隔' )
通过分布结果来看,大部分用户在一个小时内完成了商品下单,大部分用户的消费意愿比较明确。
以上是《pandas进阶宝典》中第四个实战项目的部分内容,目前仍在持续更新中,感兴趣的朋友可以戳 👉《pandas进阶宝典》 加入。
机器学习交流qq群955171419,加入微信群请 扫码