O2O优惠券使用新人赛数据发掘工程
摘要:O2O优惠券使用新人赛数据发掘工程主要是介绍O2O优惠券使用新人赛数据是如何被挖掘的,本文对单一字段及组合字段从经验上做出实践及可视化分析,并在后面介绍一些突破瓶颈的一些方法和经验。
一、 背景
cctristan同学在<<O2O优惠券使用新人赛-初试>>notebook里作了很好的入门示范,本文会在它的基础上进行补充。新手在入门本章之前可以对cctristan同学的<<O2O优惠券使用新人赛-初试>>notebook进行了解。
二、数据挖掘工程准备
2.1 引入工程所需要的库
import pandas as pd import seaborn as sns import numpy as np from matplotlib import pyplot as plt import matplotlib from datetime import datetimeimport warnings warnings.filterwarnings("ignore")%matplotlib inline
我们需要对引用的库需要做一个基本的了解,比如说我们在进行可视化分析的时候,要知道seaborn有哪些样式的风格可以使用
print(plt.style.available) print('pandas version:',pd.__version__) print('seaborn version:',sns.__version__) print('matplotlib version:',matplotlib.__version__)
['seaborn-colorblind', 'classic', '_classic_test', 'seaborn-muted', 'seaborn-dark', 'seaborn-pastel', 'seaborn-ticks', 'seaborn-talk', 'Solarize_Light2', 'seaborn-notebook', 'dark_background', 'fivethirtyeight', 'seaborn-dark-palette', 'fast', 'bmh', 'seaborn-whitegrid', 'ggplot', 'seaborn-bright', 'grayscale', 'seaborn', 'seaborn-deep', 'seaborn-poster', 'tableau-colorblind10', 'seaborn-white', 'seaborn-paper', 'seaborn-darkgrid'] pandas version: 0.19.2 seaborn version: 0.7.1 matplotlib version: 2.2.2
2.2 读取数据
path ='datalab/Coupon Usage Data for O2O/'
train = pd.read_csv(path+'ccf_offline_stage1_train.csv')
train.head()
User_id | Merchant_id | Coupon_id | Discount_rate | Distance | Date_received | Date | |
---|---|---|---|---|---|---|---|
0 | 1439408 | 2632 | null | null | 0 | null | 20160217 |
1 | 1439408 | 4663 | 11002 | 150:20 | 1 | 20160528 | null |
2 | 1439408 | 2632 | 8591 | 20:1 | 0 | 20160217 | null |
3 | 1439408 | 2632 | 1078 | 20:1 | 0 | 20160319 | null |
4 | 1439408 | 2632 | 8591 | 20:1 | 0 | 20160613 | null |
train.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1754884 entries, 0 to 1754883 Data columns (total 7 columns): User_id int64 Merchant_id int64 Coupon_id object Discount_rate object Distance object Date_received object Date object dtypes: int64(2), object(5) memory usage: 93.7+ MB
train.isnull().sum()/len(train)
User_id 0.0 Merchant_id 0.0 Coupon_id 0.0 Discount_rate 0.0 Distance 0.0 Date_received 0.0 Date 0.0 dtype: float64
当我们读取数据,需要了解数据有7个列,有两个int64字段,5个object类型字段,没有缺失值。由此可知官方给出的数据已经帮助我们在缺失值做了一部分的工作,更细分的我们还需要知道5个object类型字段具体的内容是怎样。比如说Coupon_id字段有数字,也有字符串null,这个是在数据挖掘的工程里需要了解。
2.3 单一字段分析
(1) User_id 首先是一个用户编号,在工程里用户特征是非常重要的,因为它具有个性化及统计意义。这个工程属于用户画像的一部分,比如用户的年龄划分,性别划分,教育程度划分都是基础的工作,当然在这个工程里并没有年龄,性别及教育程度的特征,可以不作讨论。在<<O2O优惠券使用新人赛-初试>>提到,训练集及测试集的用户几乎是完全覆盖的。这样的特征字段其实对于线上线下同分布是及其有利的。
len(set(train['User_id']))
539438
train['sum'] = 1 user_id_count = train.groupby(['User_id'], as_index = False)['sum'].agg({'count':np.sum})
user_id_count.sort_values(['count'],ascending=0)[:20]
User_id | count | |
---|---|---|
370014 | 5054119 | 264 |
201029 | 2751537 | 155 |
215655 | 2949273 | 137 |
487797 | 6655171 | 136 |
354467 | 4840568 | 134 |
486773 | 6641735 | 132 |
290803 | 3977895 | 127 |
382156 | 5219700 | 126 |
395960 | 5409340 | 121 |
215879 | 2952204 | 119 |
7436 | 103066 | 117 |
422675 | 5770618 | 115 |
36551 | 501441 | 113 |
133130 | 1819999 | 112 |
183299 | 2507268 | 111 |
478645 | 6529542 | 111 |
90600 | 1243042 | 111 |
207508 | 2839484 | 110 |
252458 | 3450785 | 110 |
482795 | 6587445 | 109 |
我们发现了两个统计量,第一个是训练集有接近54万用户,第二是数据统计用户数据前20的用户编号和次数,这个可以帮助我们看到哪些用户操作是比较频繁的,这可以让我们联想到"重度用户"。我们当然可以对这些用户的操作数据进行可视化分析。
def user_count(data):if data > 10:return 3elif data > 5:return 2elif data > 1:return 1else:return 0
user_id_count['user_range'] = user_id_count['count'].map(user_count)
sns.set(font_scale=1.2,style="white") f,ax=plt.subplots(1,2,figsize=(17.5,8)) user_id_count['user_range'].value_counts().plot.pie(explode=[0,0.1,0.1,0.1],autopct='%1.1f%%',ax=ax[0]) plt.ylim([0, 300000]) ax[0].set_title('user_range_ratio') ax[0].set_ylabel('') sns.despine() sns.countplot('user_range',data=user_id_count,ax=ax[1]) plt.show()
从这个可视化结果,我们可以得到只有1次记录的用户占训练集的44.8%,有2到5次记录的用户占41.2%,6到10次记录占8.9%,超过10次记录占5.1%,这确实符合常规现象。
(2) Merchant_id 同理 User_id
Mer_id_count = train.groupby(['Merchant_id'], as_index = False)['sum'].agg({'count':np.sum})
Mer_id_count.sort_values(['count'],ascending=0)[:5]
Merchant_id | count | |
---|---|---|
3227 | 3381 | 142190 |
424 | 450 | 73866 |
5089 | 5341 | 66747 |
717 | 760 | 60280 |
2800 | 2934 | 40645 |
def Mer_count(data):if data > 1000:return 3elif data > 100:return 2elif data > 20:return 1else:return 0
Mer_id_count['mer_range'] = Mer_id_count['count'].map(Mer_count)
sns.set(font_scale=1.2,style="white") f,ax=plt.subplots(1,2,figsize=(17.5,8)) Mer_id_count['mer_range'].value_counts().plot.pie(explode=[0,0.1,0.1,0.1],autopct='%1.1f%%',ax=ax[0]) plt.ylim([0, 6000]) ax[0].set_title('Mer_range_ratio') ax[0].set_ylabel('') sns.despine() sns.countplot('mer_range',data=Mer_id_count,ax=ax[1]) plt.show()
从这个可视化结果,可以得到商家记录的数据小于等于20的占训练集的52.8%,大于20小于等于100次数占33.5%,大于100小于等于1000占12.0%,超过1000次记录占1.7%。统计次数最多的商家编号是3381,超过14万。
(3) Coupon_id 是一个非常重要的字段,所有问题的核心都会围绕这个字段去展开。这个字段与前两者不同的是它包含一些缺失值,先对优惠券的缺失值的数据进行统计。
print('有优惠券的数据为', train[(train['Coupon_id'] != 'null')].shape[0]) print('无优惠券的数据为', train[(train['Coupon_id'] == 'null')].shape[0])
有优惠券的数据为 1053282 无优惠券的数据为 701602
train1 = train[(train['Coupon_id'] != 'null')] Cou_id_count = train1.groupby(['Coupon_id'], as_index = False)['sum'].agg({'count':np.sum})
Cou_id_count.sort_values(['count'],ascending=0)[:5]
Coupon_id | count | |
---|---|---|
7883 | 7610 | 46729 |
3876 | 2418 | 29284 |
1518 | 11951 | 26035 |
8622 | 8555 | 26009 |
3175 | 1480 | 24815 |
def Cou_count(data):if data > 1000:return 3elif data > 100:return 2elif data > 10:return 1else:return 0
Cou_id_count['Cou_range'] = Cou_id_count['count'].map(Cou_count)
sns.set(font_scale=1.2,style="white") f,ax=plt.subplots(1,2,figsize=(17.5,8)) Cou_id_count['Cou_range'].value_counts().plot.pie(explode=[0,0.1,0.1,0.1],autopct='%1.1f%%',ax=ax[0]) plt.ylim([0, 7000]) ax[0].set_title('Cou_range_ratio') ax[0].set_ylabel('') sns.despine() sns.countplot('Cou_range',data=Cou_id_count,ax=ax[1]) plt.show()
我们可以得到有优惠券的数据占到总数据的60%,在优惠券的数据里,小于等于10的占训练集的53.9%,大于10小于等于100次数占39.6%,大于100小于等于1000占5.2%,超过1000次记录占1.4%。统计次数最多的优惠券编号是7610,超过4.6万。
(4)Discount_rate字段包含一些非结构化的字符串数据,比如'150:20' '20:1' '200:20' '30:5' '50:10' '10:5' '100:10' '200:30',像这种数据其实需要一定的数据转化,按照经验可以用1-20/150为折扣率,按照上述的方法也可以得到折扣率的统计及分布。
def convertRate(row):"""Convert discount to rate"""if row == 'null':return 1.0elif ':' in row:rows = row.split(':')return np.round(1.0 - float(rows[1])/float(rows[0]),2)else:return float(row)
train['discount_rate'] = train['Discount_rate'].apply(convertRate)
print('Discount_rate 类型:',train['discount_rate'].unique())
Discount_rate 类型: [ 1. 0.87 0.95 0.9 0.83 0.8 0.5 0.85 0.75 0.67 0.93 0.70.6 0.97 0.98 0.99 0.33 0.2 0.4 ]
Discount_rate字段跟其它字段不一样的是,这个字段有20种折扣率的类型,不想其它字段类型种类上万,其次20种类型不太适合饼图。
sns.set(style="white") sns.factorplot(x="discount_rate", data=train, kind="count", size=6, aspect=2,color='#ad9ee8')
<seaborn.axisgrid.FacetGrid at 0x7fb2f813fa90>
可以统计不打折的优惠券(就是没有优惠)占到40%,打折的优惠券的折扣率基本分布在0.5至0.97之间,9折优惠券最多,8.3折其次
(5)Distance字段分析同Discount_rate
sns.set(style="white") sns.factorplot(x="Distance", data=train, kind="count", size=6, aspect=2)
<seaborn.axisgrid.FacetGrid at 0x7fb305625438>
可以统计Distance为0占总数的40%以上,在图中可以看到有一个不是数字的类型,对于这种缺失值可以转化为数字比如-1,也可以把数字类型转化为字符串类型。
(6)Date_received和Date 都属于时间类型字段,也是非常重要的特征。借鉴cctristan同学<<O2O优惠券使用新人赛-初试>>notebook方法,可以得到比较明显的可视化分析。
couponbydate = train[train['Date_received'] != 'null'][['Date_received', 'Date']].groupby(['Date_received'], as_index=False).count() couponbydate.columns = ['Date_received','count'] buybydate = train[(train['Date'] != 'null') & (train['Date_received'] != 'null')][['Date_received', 'Date']].groupby(['Date_received'], as_index=False).count() buybydate.columns = ['Date_received','count']
date_buy = train['Date'].unique() date_buy = sorted(date_buy[date_buy != 'null'])date_received = train['Date_received'].unique() date_received = sorted(date_received[date_received != 'null'])sns.set_style('ticks') sns.set_context("notebook", font_scale= 1.4) plt.figure(figsize = (12,8)) date_received_dt = pd.to_datetime(date_received, format='%Y%m%d') plt.subplot(211) plt.bar(date_received_dt, couponbydate['count'], label = 'number of coupon received',color='#a675a1') plt.bar(date_received_dt, buybydate['count'], label = 'number of coupon used',color='#75a1a6') plt.yscale('log') plt.ylabel('Count') plt.legend()plt.subplot(212) plt.bar(date_received_dt, buybydate['count']/couponbydate['count'],color='#62a5de') plt.ylabel('Ratio(coupon used/coupon received)') plt.tight_layout()
我们可以从可视化的结果发现在春节期间优惠券发放数目是最多,但消费的比例是最低的,因此可以得出春节是一个比较明显但特征,而5月1日但表现却属于中规中矩,节日特征的选择需要慎重。其次在3月20日左右,消费的比列是最高的,这种现象其实需要深究,比如说3月20日左右是否优惠券的折扣力度是否提高,那么就要观察折扣率力度大的优惠券折扣率在3月20日左右分布如何,是否比平时折扣率的力度要大等等。这些会在复合字段去分析,其实包活上述优惠券每天领取,消费,消费的比例都属于复合特征的一部分。
2.4 复合字段分析
复合特征(组合特征)是整个数据挖掘工程的重中之重,用户喜欢哪种类型的商家,用户喜欢哪种类型的优惠券并消费哪种优优惠券,哪种类型的优惠券的商家消费的最多,在什么时间优惠券消费的最多都是很重要的特征,即便上述讲到的每天优惠券消费的比列也是不错的特征。在这个工程,特征的组合的重要性几乎占到了90%以上的比列。
2.4.1 以用户特征为主体进行组合,所描述的就是用户画像。
(1) UM(User_id and Merchant_id)组合特征
um_count = train.groupby(['User_id','Merchant_id'], as_index = False)['sum'].agg({'count':np.sum})
um_count.head(5)
User_id | Merchant_id | count | |
---|---|---|---|
0 | 4 | 1433 | 1 |
1 | 4 | 1469 | 1 |
2 | 35 | 3381 | 4 |
3 | 36 | 1041 | 1 |
4 | 36 | 5717 | 1 |
这个特征就可以告诉我们,每个用户所对应的商家的记录有多少条。按照这样的思路,我们还可以统计每个用户所领取商家的优惠券有多少条,没有领取该商家优惠券有多少条,消费该商家的优惠券有多少条,该商家优惠券消费的比例是多少等等这些特征都可以得到。
(2) UC(User_id and Coupon_id)组合特征
train1 = train[train['Coupon_id'] != 'null']
uc_count = train1.groupby(['User_id','Coupon_id'], as_index = False)['sum'].agg({'count':np.sum})
uc_count.head(5)
User_id | Coupon_id | count | |
---|---|---|---|
0 | 4 | 2902 | 1 |
1 | 4 | 8735 | 1 |
2 | 35 | 11951 | 2 |
3 | 35 | 1807 | 1 |
4 | 35 | 9776 | 1 |
同样可以统计得到每个用户所对应优惠券的数目,再根据上述UM统计可以知道编号为4,35分别有2次及4次记录,而这些记录都是有优惠券。
(3)UR(User_id and Discount_rate)组合特征
train['discount_rate'] = train['Discount_rate'].apply(convertRate)
折扣率在转换后发现有20种不同的数字,因为20并不是一个很大的数字,所以我们可以进行下面的组合
ur_count = train.groupby(['User_id','discount_rate'], as_index = False)['sum'].agg({'count':np.sum})
ur_count.head(5)
User_id | discount_rate | count | |
---|---|---|---|
0 | 4 | 0.833333 | 1 |
1 | 4 | 0.950000 | 1 |
2 | 35 | 0.500000 | 1 |
3 | 35 | 0.900000 | 3 |
4 | 36 | 0.750000 | 1 |
同理可以统计用户优惠券折扣率的分布情况,也可以统计到每个用户平均折扣率
(4)UD(User_id and Distance)组合特征
ud_count = train.groupby(['User_id','Distance'], as_index = False)['sum'].agg({'count':np.sum})
ud_count.head(5)
User_id | Distance | count | |
---|---|---|---|
0 | 4 | 10 | 2 |
1 | 35 | 0 | 4 |
2 | 36 | 4 | 1 |
3 | 36 | 8 | 1 |
4 | 64 | 2 | 1 |
同理可以统计用户在距离商家距离的分布情况。如果不太清楚其特征的统计意义,可以从业务上去理解,比如说用户为4在Distance为10领过两次,Distance为10字段有说明是距离商家超过5公里的地方,说明这个这个潜在的用户距离商家比较远。
(4)UT(User_id and Time)组合特征, 用户和时间的特征有两个,一个是用户领取优惠券的时间组合特征,二是用户消费优惠券的消费特征。
from datetime import date def getWeekday(row):if row == 'null':return rowelse:return date(int(row[0:4]), int(row[4:6]), int(row[6:8])).weekday() + 1
train['received_weekday'] = train['Date_received'].astype(str).apply(getWeekday) train['consume_weekday'] = train['Date'].astype(str).apply(getWeekday)# weekday_type : 周六和周日为1,其他为0 train['received_weekday_type'] = train['received_weekday'].apply(lambda x : 1 if x in [6,7] else 0 ) train['consume_weekday_type'] = train['consume_weekday'].apply(lambda x : 1 if x in [6,7] else 0 )
u_week_count = train.groupby(['User_id','received_weekday'], as_index = False)['sum'].agg({'count':np.sum}) u_week_type_count = train.groupby(['User_id','received_weekday_type'], as_index = False)['sum'].agg({'count':np.sum})
u_week_count.head(5)
User_id | received_weekday | count | |
---|---|---|---|
0 | 4 | 2 | 1 |
1 | 4 | 7 | 1 |
2 | 35 | 5 | 2 |
3 | 35 | 6 | 2 |
4 | 36 | 1 | 2 |
可以得到每个用户在什么日期领取到优惠券,每个用户在是否是周末领取优惠券的频率,比如说用户4,星期二和星期七各取一次,频率为1:1
train1 = train[train['Date'] != 'null']u_con_week_count = train1.groupby(['User_id','consume_weekday'], as_index = False)['sum'].agg({'count':np.sum}) u_con_week_type_count = train1.groupby(['User_id','consume_weekday_type'], as_index = False)['sum'].agg({'count':np.sum})
u_con_week_count.head(10)
User_id | consume_weekday | count | |
---|---|---|---|
0 | 165 | 1 | 5 |
1 | 165 | 2 | 2 |
2 | 165 | 3 | 4 |
3 | 165 | 4 | 2 |
4 | 165 | 5 | 1 |
5 | 184 | 7 | 1 |
6 | 209 | 7 | 1 |
7 | 215 | 4 | 1 |
8 | 239 | 2 | 1 |
9 | 285 | 5 | 1 |
同理也可以得到用户在什么日期消费优惠券及频率,比如165在非周末消费的比例为100%。而我们之前提到的用户4并没有消费。
按照这样的思路在做细一点,比如用户与节日组合特征分布情况,用户与日期小时组合分布情况都是必要的。为什么要做,因为它是用户画像工程的作业,你可以清晰的看到每个用户的行为习惯。接着再去做商家的画像,比如说这个商家潜在用户分布在什么位置,方圆1公里之内有多少潜在的用户,有多少消费的用户,什么时间用户领取的消费券最多,什么时间用户消费的优惠券最多等等诸如此类。
三、画龙点睛的想法
通常我们在中后期都会有一个经常出现的现象,这种现象我把它称之为台阶现象,我们大部分人经常会卡在某一个阶段,遇到瓶颈,很难突破,实验的结果经常是平稳的,当我们突破这一阶段,实验的结果就会出现新的台阶又回在这个曲线上保持相对平稳。
我们都希望能够突破瓶颈,并想拥有随时突破瓶颈的能力。当然我们也许可以在梦里实现,现实是非常难做的到。突破瓶颈的方法比如说有暴力搜索,还有很笨的一个个特征试。只要做足够多的实验,总会比之前的结果要好。这种方法在技术圈不少拿奖的方案里看过,比如说小数据的比赛。但这种方法其实在工业界上不太值得提倡的,作为一些尝试性的技巧是OK的。
更好的优秀的方法是从业务数据本身出发,找到它背后的规律,可以来源于你的生活,你的工作经验等等。比如说本作业,优惠券有一个非常重要的规律就是领取到消费券随着时间它消费的比例会越来越低。如果当从本身数据来看,很难发现这个规律,能够快速认可这个规律一个很大的原因是这种规律在生活中非常的熟悉。这就是画龙点晴的特征。
四、综述
1.本章从工程的角度进行数据发掘作业,多走一些正道。
2.遇到瓶颈更要放平心态,多探索。
O2O优惠券使用新人赛数据发掘工程相关推荐
- 天池O2O优惠券使用预测
参考文献: 题目简介: https://tianchi.aliyun.com/getStart/information.htm?raceId=231593 https://blog.csdn.net/ ...
- Sklearn:天池新人实战赛o2o优惠券使用预测 part1
日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) 阿里云官网:天池新人实战赛o2o优惠券使用预测 数据集下载链接 ...
- python项目--O2O优惠券线下使用情况数据分析
数据来源:天池大赛数据集:天池新人实战赛o2o优惠券使用预测:数据链接: https://tianchi.aliyun.com/competition/entrance/231593/introduc ...
- Sklearn:天池新人实战赛o2o优惠券使用预测 part2
日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) 阿里云官网:天池新人实战赛o2o优惠券使用预测 数据集下载链接 ...
- 天池 O2O 优惠券使用预测思路解析与代码实战
个人网站:redstonewill.com 前阵子因为机器学习训练营的任务安排,需要打一场 AI 比赛.然后就了解到最近热度很高且非常适合新人入门的一场比赛:天池新人实战赛o2o优惠券使用预测.今天, ...
- 天池比赛:o2o优惠券使用预测
一.比赛背景 O2O:全称Online To Offline,线上线下电子商务,是把线上的消费者带到现实的商店中去:在线支付线下商品.服务,再到线下去享受服务.通过打折(例如团购).提供信息.服务(例 ...
- O2O优惠券使用预测项目总结
O2O优惠券使用预测笔记 前言 项目介绍 数据 评价方式 赛题分析 基本思路 数据集划分 特征工程 模型选取 过程及代码 导入python库 导入与划分数据集 特征工程 模型训练与调参 预测测试集 总 ...
- 天池o2o优惠券使用预测(入门)
一.前言 近期学习了一下天池中o2o优惠券使用预测的学习赛,主要任务是通过分析建模,精准预测用户是否会在规定时间内使用相应优惠券.这次的参与主要是学习为主,牛刀小试. 二.解决方案 数据分析:对于给定 ...
- O2O优惠券发放与使用情况分析
一. 项目背景 随着互联网+行业的趋势不断加快,各行各业逐渐通过线上(online)与线下(offline)结合的方式来扩大营销面.与此同时,消费者也越来越愿意为便利以及更多更优的选择买单,也就促进了 ...
最新文章
- 当下火热的大数据视频,免费送(含源码)
- 三大测序平台芯片通量对比图
- 2016年第七届蓝桥杯决赛Java本科B组试题解析
- 弱电工程计算机网络系统基础知识
- 采用信号量机制实现消费者与生产者的线程同步_你还能聊聊常用的进程同步算法? 上篇[五]...
- GL 与 CV 管线 (pipeline) 比较与相互转换
- 中国首个开源学校教务管理系统、网站布局自动化、学生/成绩/教师、成绩查询
- Java 8 API Stream让List操作更便捷
- php7 编译 pdo mysql_php7 编译 pdo_mysql 问题, [mysql_driver.lo] Error 1
- 基于bootstrap模态框的日期选择器
- 密码库LibTomCrypt学习记录——目录
- 黑白风格android,颜色风格略不同 黑白华为Mate对比图赏
- Deepin更换仿Mac主题(附Mac壁纸)
- 小说APP源码的图片加载方式,懒加载和预加载的实现
- 利用C#实现的外挂式甲骨文拼音输入法
- 【05】制作第一个zblog模板第四期,完成首页logo、搜索、文章列表,友情链接等大部分首页自定义模块
- Android Q版本实现自动连接WiFi
- 《每日一题》——146. LRU 缓存|460. LFU 缓存
- 批量提取Word中的图片
- [YOLO专题-11]:YOLO V5 - ultralytics/train基于自定义图片数据集重新训练网络, 完成自己的目标检测