O2O优惠券预测分析

前言

人啊,往往是在失去一些东西之后才懂得珍惜,才懂得害怕。但是还好她回来了。曲曲转转,还是那一份甜蜜。说不出太多的什么,因为那两天我觉得我的世界被摧毁了。不过现在,要打起精神努力奋斗了。因为我要还你一个美好的未来。You’ve always been important

这个五月给自己制定了很多的计划。有得忙的了。不过也结束了很多事情,包括我的不懂事。前两天,疯狂看视频,敲代码,总算是搞完了数据挖掘课程的结课项目。也是对自己的一个学习能力有点小骄傲,不过还是蛮拖,硬是拖到最后几天才写。就当做之前学生组织的事太忙了吧。现在清闲了,可以好好学习,好好陪我的小草莓了。

这次的数据挖掘项目是阿里云的天池大赛的一道新人赛的题,应该说是不难,很基础的一道题。感觉数据挖掘比较符合我的专业与我学的语言,所以做一下记录,毕竟以后是数据的时代。


题目分析

题目描述

随着移动设备的完善和普及,移动互联网+各行各业进入了高速发展阶段,这其中以O2O(Online to Offline)消费最为吸引眼球。据不完全统计,O2O行业估值上亿的创业公司至少有10家,也不乏百亿巨头的身影。O2O行业天然关联数亿消费者,各类APP每天记录了超过百亿条用户行为和位置记录,因而成为大数据科研和商业化运营的最佳结合点之一。 以优惠券盘活老用户或吸引新客户进店消费是O2O的一种重要营销方式。然而随机投放的优惠券对多数用户造成无意义的干扰。对商家而言,滥发的优惠券可能降低品牌声誉,同时难以估算营销成本。 个性化投放是提高优惠券核销率的重要技术,它可以让具有一定偏好的消费者得到真正的实惠,同时赋予商家更强的营销能力。本次大赛为参赛选手提供了O2O场景相关的丰富数据,希望参赛选手通过分析建模,精准预测用户是否会在规定时间内使用相应优惠券。

问题分析

题目给予了下面三个数据集

  • ccf_offline_stage1_test_revised.csv # 线下数据测试集
  • ccf_offline_stage1_train.csv # 线下训练集
  • ccf_online_stage1_train.csv # 线上训练集

    其中ccf_offline_stage1_train.csv为主要数据集,csv文件中包含以下数据:

  • Field Description

  • User_id 用户ID
  • Merchant_id 商户ID
  • Coupon_id 优惠券ID:null表示无优惠券消费,此时Discount_rate和Date_received字段无意义
  • Discount_rate 优惠率:x \in [0,1]代表折扣率;x:y表示满x减y。单位是元
  • Distance user经常活动的地点离该merchant的最近门店距离是x*500米(如果是连锁店,则取最近的一家门店),x\in[0,10];null表示无此信息,0表示低于500米,10表示大于5公里;
  • Date_received 领取优惠券日期
  • Date 消费日期:如果Date=null & Coupon_id != null,该记录表示领取优惠券但没有使用,即负样本;如果Date!=null & Coupon_id = null,则表示普通消费日期;如果Date!=null & Coupon_id != null,则表示用优惠券消费日期,即正样本;

读取数据集内容,统计部分数据如下:
import pandas as pd

name1 = ["User_id",'Merchant_id','Coupon_id','Discount_rate','Distance','Date_received','Date']
off_train = pd.read_csv('./data/ccf_offline_stage1_train.csv',names=name1)
#查看记录数
record_count = off_train.shape[0]
received_count = off_train['Date_received'].count()
coupon_count = len(off_train["Coupon_id"].value_counts())
user_count = len(off_train['User_id'].value_counts())
merchant_count = len(off_train['Merchant_id'].value_counts())
min_received = int(off_train["Date_received"].min())
max_received = int(off_train["Date_received"].max())
max_pay = int(off_train["Date"].max())
min_pay = int(off_train["Date"].min())
print('记录数为:',record_count)
print("优惠券领取记录:",received_count)
print("优惠券数量:",coupon_count)
print("用户数量:",user_count)
print("商家数量:",merchant_count)
print("最早领券时间;",min_received)
print("最晚领券时间;",max_received)
print("最早消费时间;",min_pay)
print("最晚消费时间;",max_pay)

有优惠卷,购买商品:75382
有优惠卷,未购商品:977900
无优惠卷,购买商品:701602
记录数为: 1754884
优惠券领取记录: 1053282
优惠券数量: 9738
用户数量: 539438
商家数量: 8415
最早领券时间; 20160101
最晚领券时间; 20160615
最早消费时间; 20160101
最晚消费时间; 20160630

因此可以知道本题的目的为训练一个模型进行预测某用户是否使用优惠券,故该问题为二分类问题。


数据预处理

缺失值检测:

查看缺省值

print(dfoff.isnull().any())

统计缺省值的比例

print(dfoff.isnull().sum() / len(dfoff))

User_id               False
Merchant_id           False
Coupon_id              True
Discount_rate          True
Distance               True
Date_received          True
Date                   True
is_manjian            False
min_pay_of_manjian    False
discount_rate          True
date_received          True
date                   True

折扣处理:

折扣是一条重要数据,不过数据集中的折扣有两种不同的类型,故要对两种类型进行统一,并添加其他与折扣有关的数据列,有助于后期的特征工程处理。

#判断是否为满减折扣
dfoff['is_manjian'] = dfoff['Discount_rate'].map(lambda x : 1 if ':' in str(x) else 0)
#找出优惠的最低消费
dfoff['min_pay_of_manjian'] = dfoff['Discount_rate'].map(lambda x : -1 if ":" not in str(x) else int(str(x).split(':')[0]))
#将满减的优惠也转换成折扣率
dfoff['discount_rate'] = dfoff['Discount_rate'].map(lambda x : float(x) if ':' not in str(x) else (float(str(x).split(':')[0]) - float(str(x).split(':')[1]))/float(str(x).split(':')[0]))

日期处理

导入数据后日期变为float类型,不变与处理,所以需要转换成date类型,并且日期如节假日对优惠券的使用影响很大,如周末的消费率会大大上涨

#设置一列为datel类型发日期数据领取优惠券日期数据
dfoff['date_received'] = pd.to_datetime(dfoff['Date_received'],format='%Y%m%d')
#设置一列为datel类型发日期数据的消费日期数据
dfoff['date'] = pd.to_datetime(dfoff['Date'],format='%Y%m%d')
#将日期转换成周几,便于分析
dfoff['weekday_Receive'] = dfoff['date_received'].apply(lambda  x :x.isoweekday())

距离处理

将距离为空的数据进行处理

dfoff[‘Distance’].fillna(-1,inplace= True)

标记距离是否为空

dfoff[‘null_Distance’] = dfoff[‘Distance’].map(lambda x : 1 if x == -1 else 0)

数据预处理后可以通过pyecharts进行绘图,更直观的感受数据的变化


特征工程

特征提取这一步,应该按照预处理所得的新的数据列进行特征的提取。主要有五大特征类:用户特征、商户特征、优惠券特征、用户商户组合特征、用户优惠券组合特征,不过由于能力有限,对pandas的一些方法没太弄懂,就提取了一些相对简单的特征。

  1. 用户领取优惠券的数量
  2. 用户当天领取特定的优惠券
  3. 用户当天领取特定的优惠券数量
  4. 用户当天重复领取特定的优惠券
  5. 用户当天领取的优惠券数量

即”User_id”, “Coupon_id”, ‘Date_received’
这三个数值的不同组合。

然后对数据集进行划分,以两个月为一个区间划分为训练集、验证集与预测集

训练集 : 2016-3-2 ~ 2016-5-3
验证集:2016-1-16 ~ 2016-3-17
预测集:2016-4-17 ~ 2016-6-18

从特征的提取来看在数据预处理中绘制的图表来看,许多重要的特征并没有被利用,如周末等,当然这主要是因为我的水平还不够,而且赶作业,不想提取太麻烦的特征

def get_feature(dfoff):
    # 优惠券类型转换,会把null也转换
    dfoff['Coupon_id'] = dfoff['Coupon_id'].map(lambda x: 0 if math.isnan(x) else int(x))
    dfoff['Date_received'] = dfoff['Date_received'].map(lambda x: 0 if math.isnan(x) else int(x))
    dfoff['cnt'] = 1  # 便于提取特征
    feature = dfoff.copy()
    # 用户领券数
    keys = ["User_id"]
    prefixs = 'simple_' + '_'.join(keys) + '_'
    pivot = pd.pivot_table(dfoff, index=keys, values='cnt', aggfunc=len)
    pivot = pd.DataFrame(pivot).rename(columns={'cnt': prefixs + 'received_cnt'}).reset_index()
    feature = pd.merge(feature, pivot, on=keys, how='left')

    # 用户领取某个优惠券
    keys = ["User_id", 'Coupon_id']
    prefixs = 'simple_' + '_'.join(keys) + '_'
    pivot = pd.pivot_table(dfoff, index=keys, values='cnt', aggfunc=len)
    pivot = pd.DataFrame(pivot).rename(columns={'cnt': prefixs + 'received_cnt'}).reset_index()
    feature = pd.merge(feature, pivot, on=keys, how='left')

    # 用户当天的领券数
    keys = ["User_id", 'Date_received']
    prefixs = 'simple_' + '_'.join(keys) + '_'
    pivot = pd.pivot_table(dfoff, index=keys, values='cnt', aggfunc=len)
    pivot = pd.DataFrame(pivot).rename(columns={'cnt': prefixs + 'received_cnt'}).reset_index()
    feature = pd.merge(feature, pivot, on=keys, how='left')

    # 用户当天领取某个特定的优惠券
    keys = ["User_id", "Coupon_id", 'Date_received']
    prefixs = 'simple_' + '_'.join(keys) + '_'
    pivot = pd.pivot_table(dfoff, index=keys, values='cnt', aggfunc=len)
    pivot = pd.DataFrame(pivot).rename(columns={'cnt': prefixs + 'received_cnt'}).reset_index()
    feature = pd.merge(feature, pivot, on=keys, how='left')

    # 用户是否当天多次领取某个特定的优惠券
    keys = ["User_id", "Coupon_id", 'Date_received']
    prefixs = 'simple_' + '_'.join(keys) + '_'
    pivot = pd.pivot_table(dfoff, index=keys, values='cnt', aggfunc=lambda x: 1 if len(x) > 1 else 0)
    pivot = pd.DataFrame(pivot).rename(columns={'cnt': prefixs + 'repeat_received'}).reset_index()
    feature = pd.merge(feature, pivot, on=keys, how='left')

    feature.drop(['cnt'], axis=1, inplace=True)
    return feature

构建模型与训练

构建训练集、验证集与预测集时只加入重要的部分数据,缩短程序运行时间,提高训练速度。

train = get_feature(train_field)[["User_id", "Coupon_id", "Date_received","is_manjian","discount_rate",                                "min_pay_of_manjian","null_Distance","label","simple_User_id_received_cnt",                                 "simple_User_id_Coupon_id_received_cnt","simple_User_id_Date_received_received_cnt",                                "simple_User_id_Coupon_id_Date_received_received_cnt",                            "simple_User_id_Coupon_id_Date_received_repeat_received"]]

validate = get_feature(validate_field)[["User_id", "Coupon_id", "Date_received","is_manjian","discount_rate",                              "min_pay_of_manjian","null_Distance","label","simple_User_id_received_cnt",                              "simple_User_id_Coupon_id_received_cnt","simple_User_id_Date_received_received_cnt",                               "simple_User_id_Coupon_id_Date_received_received_cnt",                              "simple_User_id_Coupon_id_Date_received_repeat_received"]]

test = get_feature(test_field)[["User_id", "Coupon_id", "Date_received","is_manjian","discount_rate",                              "min_pay_of_manjian","null_Distance","simple_User_id_received_cnt",                              "simple_User_id_Coupon_id_received_cnt","simple_User_id_Date_received_received_cnt",                              "simple_User_id_Coupon_id_Date_received_received_cnt",                             "simple_User_id_Coupon_id_Date_received_repeat_received"]]

模型是用xgboost给出的决策树,然后计入训练集进行拟合训练,XGBoost是以分类回归树(CART树)进行组合。而从运用来看xgboost的使用主要是训练集的构建与特征的提取,以及迭代的次数,从这些方面入手可以大大提高整个模型的预测准度。

dtrain = xgb.DMatrix(train.drop(["User_id", "Coupon_id","Date_received",'label'], axis= 1) ,label= train['label'])

最后加入数据集,让训练好的模型进行预测

pedict = model.predict(dtest)
result = pd.concat([test[["User_id", "Coupon_id","Date_received"]],pedict],axis= 1)

然后保存预测后的数据集为csv文件

总结

代码已上传github: https://github.com/chenpb-lu/Data_analyst
不过当然自己写的还是很基础的东西,多学一点没坏处,不过我没有把自己的预测集上传那个比赛,所以并不知道自己的预测准度是多少,应该不会太高。毕竟看了别人大佬的都是十几个特征起的。
好好加油,今天也是很开心呀!陈先生希望自己可以每天都很甜。每天吃一块超级好吃的曲奇~

赏瓶可乐吧(*^▽^*)