O2O优惠券数据分析
Posted Babyface Killer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了O2O优惠券数据分析相关的知识,希望对你有一定的参考价值。
写在前面的话
本文的数据分析任务是我在几个月前为了准备面试锻炼自己的数据分析思路做的,项目中没有涉及到机器学习建模内容,是一个单纯用数据得出结论的任务。因为准备时间仓促,所以项目里待完善的内容很多。最后机缘巧合也没有去应聘数据分析师的工作,因此想把这个项目分享出来,希望对想准备数据分析面试的读者有所帮助。本文的数据集来自天池中一个O2O优惠券核销率预测的比赛,感兴趣的读者可以自行下载相关数据集。
数据介绍
本文所使用的数据集是关于O2O营销活动优惠券发放的,提供的数据集包括线上和线下两部分。在线下数据集中包括用户id,商家id,优惠券id,折扣率,用户经常活动地点离最近商家距离,收到优惠券的日期以及核销优惠券的日期。线上数据集中除了线上数据集中提供的信息外还提供了用户的线上行为,包括点击,购买和领取,但线上数据集不提供距离信息。在本次数据分析任务中我分成了三步对优惠券的核销行为进行分析,分别是线下行为,线上行为和线上线下共同用户的行为。
线下行为
首先导入本次分析任务要用到的所有python library。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.formula.api as smf
import statsmodels.tsa.api as smt
import statsmodels.api as sm
import statsmodels.stats as sms
from statsmodels.tsa.arima_model import ARIMA
from statsmodels.tsa.stattools import adfuller
import plotly.express as px
from plotly import graph_objects as go
数据清洗
# 读取线下数据集
offline_df=pd.read_csv('/content/drive/MyDrive/o2o/ccf_offline_stage1_train.csv')
offline_df.head()
上面展示的就是线下数据集包含的字段。
print('线下消费行为数据行'.format(offline_df.shape[0]))
# 数据清洗
# 每个字段缺失值
offline_df.isnull().sum()
首先我们可以看到数据集里面缺失值还是非常多的,那么为什么会出现这些缺失值呢?从上面缺失字段的统计可以发现Coupon_id, Discount_rate, Date_received三者确实数量一致符合数据逻辑,即没有领取优惠券的消费记录中不含有这三条信息。而Distance出现缺失值说明不能提供用户的位置信息,可能用户关闭了定位功能或用户活动范围内没有最近的门店。Date出现缺失值就说明用户在产生领取行为后没有核销优惠券。
根据我们之前分析的缺失值出现原因,分别制定不同的填充缺失值策略。对于没有领取优惠券的消费,我们可以把Coupon_id, Discount_rate填补为0且填补值不与这两个字段中其他取值冲突。因为Distance的取值为0-10,所以对于Distance的缺失值我们可以填充为11。
offline_df['Coupon_id'].fillna(0,inplace=True)
offline_df['Discount_rate'].fillna(0,inplace=True)
offline_df['Distance'].fillna(11,inplace=True)
offline_df.dtypes
通过观察数据类型,我们会发现有些字段的数据类型不太合理,比如Coupon_id, Distance,Date_received 和 Date。我们可以手动来转换这些字段的数据类型:
# 转换逻辑:Coupon_id及Distance应为整数型取值数据,Date_received和Date应为日期型数据
convert_dict='Coupon_id':'int64','Distance':'int64'
offline_df=offline_df.astype(convert_dict)
offline_df['Date_received']=pd.to_datetime(offline_df['Date_received'],format='%Y%m%d')
offline_df['Date']=pd.to_datetime(offline_df['Date'],format='%Y%m%d')
接下来,我们把没有核销优惠券的日期填充为自定义的dummy date,在这里我使用的是2021-01-01,因为数据集中的数据都是2016年的所以当然不会出现2021年的日期,也可以使用其他的日期来填充。
# 将没有消费的日期和没有领取优惠券的日期填充为dummy date
offline_df['Date_received'].fillna('2021-01-01',inplace=True)
offline_df['Date'].fillna('2021-01-01',inplace=True)
offline_df['Date_received']=pd.to_datetime(offline_df['Date_received'])
offline_df['Date']=pd.to_datetime(offline_df['Date'])
我们再来观察一下清理后的数据集:
offline_df.head()
和之前比整齐了许多,但是数据集中的Discount_rate这个字段是object类型在后续做分析的时候不太好利用,所以我使用了自己定义的逻辑来把Discount_rate转换成了数值型。
# 拆分discount_rate
discount_rate_string=offline_df['Discount_rate']
purchase_amount=[]
discount_rate=[]
# 拆分逻辑:Discount rate有三种类型:0:无优惠券,浮点型:折扣率,比值型:满X减y
# 把Discount_rate统一为两种信息:满减金额(折扣率类型用999表示)和折扣率
for r in discount_rate_string:
r=str(r)
if '.' in r:
purchase_amount.append(999)
discount_rate.append(round(1-float(r),2))
elif ':' in r:
p=int(r.split(':')[0])
d=int(r.split(':')[1])
rate=d/p
purchase_amount.append(int(p))
discount_rate.append(round(rate,2))
elif float(r)==0.0:
purchase_amount.append(0)
discount_rate.append(0)
offline_df['Purchase_amount']=purchase_amount
offline_df['rate']=discount_rate
offline_df.head()
对Discount_rate处理过之后,我们的数据集中就多了表示折扣金额和折扣率的两个字段。下面我们就可以开始进行分析了。
线下消费行为记录中三种行为所占比例
在这个部分我主要分析了三种行为:1.没有使用优惠券进行消费 2.领取优惠券但没有使用 3.领取优惠券并且使用
# 探索性数据分析
# 提取所有使用优惠券的消费
useful_coupon_df=offline_df.loc[(offline_df['Date']<'2021-01-01') & (offline_df['Date_received']<'2021-01-01')]
# 提取所有没有使用优惠券的消费
no_coupon_df=offline_df.loc[offline_df['Coupon_id']==0]
# 提取所有领取优惠券但没有使用的记录
useless_coupon_df=offline_df.loc[(offline_df['Date_received']<'2021-01-01')&(offline_df['Date']=='2021-01-01')]
# 各种类型的记录占比
plt.figure(figsize=(8,5))
explode=[0.1,0.1,0.1]
plt.pie([len(useful_coupon_df),len(no_coupon_df),len(useless_coupon_df)],labels=['Used Coupon','No Coupon','Unused Coupon'],autopct='%1.1f%%',explode=explode,shadow=True)
plt.title('Coupon Usage')
55.7%的消费者行为是领取优惠券却不使用,40%的消费者行为是不使用优惠券直接购买,仅有4.3%的消费者行为是使用优惠券进行购买。由此可以衍生出几个需要探索的问题:1.影响消费者使用优惠券的因素是什么 2.所有购买过的消费者中从不使用优惠券,只使用优惠券,两者都有的比例。
# 线下领取优惠券的核销率
receive_count=len(offline_df[offline_df['Date_received']<'2021-01-01'])
use_count=len(offline_df[(offline_df['Date_received']<'2021-01-01')&(offline_df['Date']<'2021-01-01')])
plt.pie([use_count,receive_count-use_count],labels=['Used','Unused'],explode=[0.1,0.1],autopct='%1.1f%%')
可以看到所有被领取的优惠券只有7.2%被使用过,这个比例显然很低,造成优惠券核销率低的因素可能有很多,下面从满减额度(门槛),折扣率(使用优惠券的动力),以及门店距离(便利性)三个方面来分析影响优惠券核销率的因素。
影响线下优惠券核销率的因素分析
# 每个满减额度优惠券的核销率
useful_coupon_count=pd.DataFrame(useful_coupon_df['Purchase_amount'].value_counts())
useless_coupon_count=pd.DataFrame(useless_coupon_df['Purchase_amount'].value_counts())
coupon_count=pd.merge(useful_coupon_count,useless_coupon_count,how='inner',left_index=True,right_index=True)
coupon_count.sort_index(inplace=True)
label=coupon_count.index.astype(str)
value1=coupon_count['Purchase_amount_x']
value2=coupon_count['Purchase_amount_y']
plt.bar(x=label,height=value2,label='Unused Coupon',alpha=0.7)
plt.bar(x=label,height=value1,label='Used Coupon',bottom=value2,alpha=0.7)
plt.legend(loc='best')
plt.xlabel('Purchase Amount')
plt.ylabel('Amount')
axes2 = plt.twinx()
axes2.plot(label,value1/(value1+value2),color='red',marker='o')
axes2.set_ylabel('Usage rate')
从上图可以看出满减的门槛越低优惠券的核销率越高,而且没有门槛的无条件折扣券核销率也较高,由此可以得出结论对于线下门店优惠券使用门槛是影响优惠券核销率的关键因素。但由于考虑到成本问题,显然5元满减,10元满减或者无门槛满减的获客成本较高利润率较低
# 不同折扣率的核销率
useful_coupon_count=pd.DataFrame(useful_coupon_df['rate'].value_counts())
useless_coupon_count=pd.DataFrame(useless_coupon_df['rate'].value_counts())
coupon_count=pd.merge(useful_coupon_count,useless_coupon_count,how='inner',left_index=True,right_index=True)
coupon_count.sort_index(inplace=True)
label=coupon_count.index.astype(str)
value1=coupon_count['rate_x']
value2=coupon_count['rate_y']
plt.bar(x=label,height=value2,label='Unused Coupon',alpha=0.7)
plt.bar(x=label,height=value1,label='Used Coupon',bottom=value2,alpha=0.7)
plt.xticks(rotation='vertical')
plt.xlabel('Discount rate')
plt.ylabel('Amount')
plt.legend(loc='best')
axes2 = plt.twinx()
axes2.plot(label,value1/(value1+value2),color='red',marker='o')
axes2.set_ylabel('Usage rate')
从上图可以看出优惠券核销率和折扣额度显然没有线性关系,也就是说折扣额度不是影响优惠券核销率的关键因素。
# 不同距离的核销率
useful_coupon_count=pd.DataFrame(useful_coupon_df['Distance'].value_counts())
useless_coupon_count=pd.DataFrame(useless_coupon_df['Distance'].value_counts())
coupon_count=pd.merge(useful_coupon_count,useless_coupon_count,how='inner',left_index=True,right_index=True)
coupon_count.sort_index(inplace=True)
label=coupon_count.index.astype(str)
value1=coupon_count['Distance_x']
value2=coupon_count['Distance_y']
plt.bar(x=label,height=value2,label='Unused Coupon',alpha=0.7)
plt.bar(x=label,height=value1,label='Used Coupon',bottom=value2,alpha=0.7)
plt.xlabel('Distance')
plt.ylabel('Amount')
plt.legend(loc='best')
axes2 = plt.twinx()
axes2.plot(label,value1/(value1+value2),color='red',marker='o')
axes2.set_ylabel('Usage rate')
通过对核销优惠券的距离因素的分析可以看出在用户活动范围500米内的优惠券的核销率最高,显然门店距离对于线下优惠券核销率也是一个较为明显的影响因素。
购买过的用户中对优惠券的敏感程度
# 每个用户使用优惠券的次数
use_coupon_count=pd.DataFrame(offline_purchased_df[offline_purchased_df['Date_received']!='2021-01-01'].groupby('User_id')['Date'].count())
# 所有购买过的消费者的购买次数
purchase_count=pd.DataFrame(offline_purchased_df.groupby('User_id')['Date'].count())
coupon_analysis_df=pd.merge(purchase_count,use_coupon_count,how='outer',left_index=True,right_index=True)
coupon_analysis_df.fillna(0,inplace=True)
purchased_user_count=len(coupon_analysis_df)
no_coupon_only_count=len(coupon_analysis_df[coupon_analysis_df['Date_y']==0.0])
coupon_only_count=len(coupon_analysis_df[coupon_analysis_df['Date_x']==coupon_analysis_df['Date_y']])
plt.pie([no_coupon_only_count,coupon_only_count,purchased_user_count-no_coupon_only_count-coupon_only_count],labels=['No coupon only','Coupon only','Mixed'],autopct='%1.1f%%',explode=[0.1,0.1,0.1],shadow=True)
从上面的分析可以看出所有进行过购买的用户中78.7%是对优惠券不敏感的,而剩下21.4%的用户中有4.8%是对优惠券非常敏感的。
优惠券核销时间间隔
# 计算使用过的优惠券从领取到使用之间的时间间隔
useful_coupon_df['Time_interval']=(useful_coupon_df['Date']-useful_coupon_df['Date_received']).dt.days
# 优惠券核销时间分布
sns.distplot(useful_coupon_df['Time_interval'],kde=True,)
从上图可以看出大部分优惠券的核销时间间隔在两周之内,也就是说优惠券对销量提升的影响基本可以锁定在两周之内。
优惠券的拉新效果
此处的逻辑是:如果该用户首次购买时使用了优惠券则指定该用户为优惠券带来的新用户,若该用户首次购买时没有使用优惠券则指定该用户为老用户。
# 优惠券的拉新效果
offline_purchased_df=offline_df.loc[offline_df['Date']!='2021-01-01']
user_first_purchase_date=pd.DataFrame(offline_purchased_df.groupby('User_id')['Date'].min())
user_first_coupon_date=pd.DataFrame(useful_coupon_df.groupby('User_id')['Date'].min())
coupon_acquisition_df=pd.merge(user_first_purchase_date,user_first_coupon_date,how='inner',left_index=True,right_index=True)
coupon_acquisition_df=coupon_acquisition_df[coupon_acquisition_df['Date_x']==coupon_acquisition_df['Date_y']]
coupon_acquisition_user=coupon_acquisition_df.index
new_user=len(coupon_acquisition_user)
all_user=len(offline_df['User_id'].unique())
explode=[0.1,0.1]
plt.pie([new_user,all_user-new_user],labels=['New User with Coupon','Other'],autopct='%1.1f%%',explode=explode,shadow=True)
从上面的分析可以看出线下优惠券可以带来2.7%的新用户,对于优惠券拉新的效果需要和其他同类品牌的优惠券效果进行对比。但鉴于该品牌的用户基数较大,2.7%的新客户还是非常可观的。但是优惠券带来的新客户的质量还有待分析,如果新客户不进行重复购买则使用优惠券对业务并没有带来实质性的增长。
优惠券拉新质量
# 优惠券拉新的质量
new_user_coupon_df=offline_purchased_df[offline_purchased_df.User_id.isin(coupon_acquisition_user)]
all_user=offline_purchased_df['User_id'].unique()
other_user=[i for i in all_user if i not in coupon_acquisition_user]
other_user_df=offline_purchased_df[offline_purchased_df.User_id.isin(other_user)]
new_user_frequency=new_user_coupon_df['User_id'].value_counts()
other_user_frequency=other_user_df['User_id'].value_counts()
# 为什么使用箱线图而不使用密度图,因为购买频率为长尾分布,密度图不容易看出二者差别
plt.boxplot([new_user_frequency.values,other_user_frequency.values],showfliers=False,labels=['New user with coupon','Other user'],patch_artist=True)
plt.ylabel('Purchase Frequency')
通过对优惠券带来的新用户的购买频次和老用户的购买频次比较,可以发现优惠券带来的新用户购买频次普遍较低,说明优惠券带来的用户增长并没有转化为用户留存,需要对优惠券带来的新用户进行进一步的营销活动来保证新用户的留存。
优惠券活动时间序列分析
# 时间序列分析
ts_all=offline_purchased_df.groupby('Date')['User_id'].count()[:-1]
ts_coupon_usage=useful_coupon_df.groupby('Date')['User_id'].count()
receive_coupon_df=offline_df[offline_df['Date_received']!='2021-01-01']
ts_coupon_receive=receive_coupon_df.groupby('Date_received')['User_id'].count()
def tsplot(y, lags=None, figsize=(12, 7), style='bmh'):
"""
Plot time series, its ACF and PACF, calculate Dickey–Fuller test
y - timeseries
lags - how many lags to include in ACF, PACF calculation
"""
if not isinstance(y, pd.Series):
y = pd.Series(y)
with plt.style.context(style):
fig = plt.figure(figsize=figsize)
layout = (2, 2)
ts_ax = plt.subplot2grid(layout, (0, 0), colspan=2)
acf_ax = plt.subplot2grid(layout, (1, 0))
pacf_ax = plt.subplot2grid(layout, (1, 1))
y.plot(ax=ts_ax)
p_value = sm.tsa.stattools.adfuller(y)[1]
ts_ax.set_title('Time Series Analysis Plots\\n Dickey-Fuller: p=0:.5f'.format(p_value))
smt.graphics.plot_acf(y, lags=lags, ax=acf_ax)
smt.graphics.plot_pacf(y, lags=lags, ax=pacf_ax)
plt.tight_layout()
# 整体用户购买情况
tsplot(ts_all,lags=90)
# 优惠券发放情况
tsplot(ts_coupon_receive,lags=90)
# 优惠券使用情况
tsplot(ts_coupon_usage,lags=90)
通过对线下每日销量,每日优惠券领取次数,优惠券使用次数进行时间序列分析,发现在一月下旬用户大量领取优惠券但实际销量却没有明显增长甚至有所下降,而三月下旬及五月下旬却有明显增长。通过之前优惠券核销时间间隔的分析所获得的信息是优惠券通常在两周之内会被使用因此可以确定三月下旬及五月下旬的销量增长不是由一月下旬的优惠券领取带来的。但优惠券领取次数的时间序列表明三月下旬及五月下旬优惠券领取也有小幅增长,且三月下旬及五月下旬优惠券使用次数也有较大增长,基本可以确定三月下旬及五月下旬的优惠券发放带来了有效的销量提升。
不同月份优惠券发放分析
# 对每个发放优惠券的月份进行分析
jan_coupon=receive_coupon_df[(receive_coupon_df['Date_received']<'2016-02-01') & (receive_coupon_df['Date_received']>'2016-01-15')]
mar_coupon=receive_coupon_df[(receive_coupon_df['Date_received']<'2016-04-01') & (receive_coupon_df['Date_received']>'2016-03-15')]
may_coupon=receive_coupon_df[(receive_coupon_df['Date_received']<'2016-06-01') & (receive_coupon_df['Date_received']>'2016-05-15')]
jan_coupon_count=pd.DataFrame(jan_coupon['Purchase_amount'].value_counts())
mar_coupon_count=pd.DataFrame(mar_coupon['Purchase_amount'].value_counts())
may_coupon_count=pd.DataFrame(may_coupon['Purchase_amount'].value_counts())
coupon_months_df=pd.merge(jan_coupon_count,mar_coupon_count,how='inner',left_index=True,right_index=True)
coupon_months_df=pd.merge(coupon_months_df,may_coupon,how='inner',left_index=True,right_index=True)
fig,axs=plt.subplots(1,3,figsize=(15,15))
axs[0].pie(x=jan_coupon_count.values)
axs[0].legend(jan_coupon_count.index,loc='best')
axs[0].set_title('January Coupon Distribution')
axs[1].pie(x=mar_coupon_count.values)
axs[1].legend(mar_coupon_count.index,loc='best')
axs[1].set_title('March Coupon Distribution')
axs[2].pie(x=may_coupon_count.values)
axs[2].legend(may_coupon_count.index,loc='best')
axs[2].set_title('May Coupon Distribution')
结合之前的分析,首先小额满减的优惠券核销率较高,其次三月下旬及五月下旬的优惠券发放带来了销量提升。从上图可知,一月下旬大批量发放的优惠券主要集中在大额满减,而三月下旬及五月下旬策略调整为少量发放小额满减,该策略转换带来了有效的销量增长,证明了策略调整的有效性。
结论
结合之前对线下优惠券使用的分析得出几点有效结论:
1.线下优惠券核销率较低(7%)且大部分用户对优惠券不敏感(79%)
2.小额满减及无门槛优惠券核销率较高且距离用户活动范围500内的门店优惠券核销率较高
3.线下优惠券发放带来的用户增长有限(3%)且用户质量较低,可以考虑对用户进行后续营销活动增强用户对品牌认同感和忠诚度
4.发放优惠券确实能带来销量增长,但需要注意发放优惠券的类型(小额满减效果较好)。通过发放优惠券带来的销量增长需要结合ROI进行进一步考量。
那么我们第一步的线下行为数据分析就到此结束了。
写在最后的话
本文作者并非专业数据分析师,因此请以辩证的心态看待分析方法和结果
如有错误请指正
转载请注明出处
以上是关于O2O优惠券数据分析的主要内容,如果未能解决你的问题,请参考以下文章