数据分析项目五:O2O优惠券分析

导入数据

        seaborn是用来后面画热力图的

        首先我们观察一下数据,把每一列的含义解释一下:

User_id:用户id

Merchant_id:商家id

Coupon_id:优惠券id(若为空表示无优惠券消费,此时Discount_rate和Date_received字段无意义,后需要验证的)

Discount_rate:优惠率,代表满多少减多少,需要规整一下数据

Distance:User经常活动的地点离该merchant的最近距离,0表示低于500米,10表示大于5公里

Date_received:领取优惠券日期

Date:消费日期,但不知是否使用了消费券

        导入数据时使用parse_dates参数,这个是在导入数据时就直接对时间数据进行数据类型转换

        NaT代表时间类型的空值

# 1.导入数据
# 参数parse_dates可以将列转化为datetime数据类型,就不用像以前一样自己改了
offline = pd.read_csv('ccf_offline_stage1_train.csv',parse_dates=['Date_received','Date'])
offline.head()

        观察一下数据

offline.info()
# 175万+条数据,这里也可以看到最后两列已经成功转换了数据类型

数据规整

        数据规整的第一步肯定是要观察、处理空值

        这里的空值很多,肯定是不能删除的,保留就行,只是需要验证我们之前的猜想:折扣率、领券日期的空值是否跟优惠券id的空值有关联

# 2.数据规整
#判断每一列有多少空值
offline.isnull().sum() # isnull返回的结果是:空为True,非空为False。而由于True代表1,sum求和结果就是空值的数量

# 我们观察到优惠券id、折扣率、领券日期三列空值数量相等,可以考虑是不是存在三者同时==0的情况,我们后续验证

把Discount_rate列的满减政策转换成折扣率

        这里按照视频来写会报错,首先我们传入折扣率的数据,先确保数据是字符串类型,如果不是字符串我们返回NaN,中间的步骤看注释很好理解

# 2.1把Discount_rate列的满减政策转换成折扣率
def discount_rate_opt(s):
    if isinstance(s, str):  # 确保 s 是字符串
        if ':' in s:
            split_1 = s.split(':')
            rate = (int(split_1[0]) - int(split_1[1])) / int(split_1[0])  # 计算折扣率
            return round(rate, 2)  # 四舍五入,保留两位小数
        elif s.lower() == 'null':  # 处理字符串 'null'
            return np.NaN
        else:
            return float(s)  # 处理已经是浮点数的情况
    else:
        return np.NaN  # 如果不是字符串,则返回 NaN
offline['Discount_rate'] = offline['Discount_rate'].map(discount_rate_opt)

判断Coupon_id、Discount_rate、Date_received的空值是否一一对应

        先学习一个新函数np.all(),很好理解

# 学习新函数np.all():判断一个迭代数据中是否都为True,如果是返回True,否则返回False
np.all([True,False,True])

        

        这里先使用isnull判断这些列中是否有空值,返回值是True or False组成的数组,然后使用np.all函数判断这两列是否在每一行都是相同的布尔值

nan1 = offline['Coupon_id'].isnull() # 判断优惠券是否为空
nan2 = offline['Discount_rate'].isnull() # 判断折扣率是否为空
np.all(nan1 == nan2) # 如果为True说明这两列中空值位置是对应的

nan3 = offline['Date_received'].isnull() # 判断领券日期是否为空
np.all(nan1 == nan3)

根据Date和Coupon_id对用户进行区分

        我们先判断有几种用户:

        如果Date = null & Coupon_id != null,有券未消费(cpon_no_consume)
        如果Date = null & Coupon_id = null,无券未消费(no_cpon_no_consume)
        如果Date != null & Coupon_id = null,无券消费(no_cpon_consume)
        如果Date != null & Coupon_id != null,有券消费(cpon_consume)

cpon_no_consume = offline[(offline['Date'].isnull() & offline['Coupon_id'].notnull())]
no_cpon_no_consume = offline[(offline['Date'].isnull() & offline['Coupon_id'].isnull())]
no_cpon_consume = offline[(offline['Date'].notnull() & offline['Coupon_id'].isnull())]
cpon_consume = offline[(offline['Date'].notnull() & offline['Coupon_id'].notnull())]

        我们发现无券未消费的人数为0,则不需要分析,接下来,我们着重分析其余三种用户

print('有券未消费:{}'.format(len(cpon_no_consume)))
print('无券未消费:{}'.format(len(no_cpon_no_consume))) # 无意义,不需分析
print('无券消费:{}'.format(len(no_cpon_consume)))
print('有券消费:{}'.format(len(cpon_consume)))
# 用券消费用户7万,相对其他用户来说,占比较少

数据分析

        我们这里选择绘制饼图来看占比,先把数据放入一个字典中,然后直接用pd.series转化为series对象,接着绘制饼图即可

# 3.数据分析
#绘制饼图占比
consume_status_dict = {'cpon_no_consume':len(cpon_no_consume),'no_cpon_consume':len(no_cpon_consume),'cpon_consume':len(cpon_consume)}
consume_status = pd.Series(consume_status_dict)
# 这里在上个项目说series对象要转换成list才能画饼图,其实不是的,series可以直接画
consume_status

        消费方式构成的饼图:

# 消费方式构成的饼图
fig,ax = plt.subplots(1,1,figsize=(8,10))
consume_status.plot.pie(ax = ax,
                      autopct = '%1.1f%%',
                       shadow = True, # 阴影
                       explode = [0.02,0.05,0.2], # 间隔
                       textprops = {'fontsize':15,'color':'blue'}, # 设置字体、颜色
                       wedgeprops = {'linewidth':1,'edgecolor':'black'}, # 设置边界
                       labels = ['有券未消费 \n({})'.format(len(cpon_no_consume)),
                                '无券消费 \n({})'.format(len(no_cpon_consume)),
                                '有券消费 \n({})'.format(len(cpon_consume))]
                      )
ax.set_ylabel('') # 去除ylabel
ax.set_title('消费占比情况')
plt.legend(labels = ['有券未消费','无券消费','有券消费'])
# 有券未消费占比55.7%最大,说明大多数人拿到券后未使用
# 无券消费用户占比40%,可能是优惠券吸引力不大,或者新用户较多不知道优惠券
# 用券消费用户占比很小,使用率不高,可以考虑加大优惠券力度

在有券消费人群中,分析平均到店距离和平均优惠折扣

        有券消费人群我们求出来过:cpon_consume

        根据商家id分组求平均

# 各商家对应的顾客到店平均距离
Merchant_distance = cpon_consume.groupby('Merchant_id')['Distance'].mean()
Merchant_distance
# 共有4076个商家
Merchant_distance[Merchant_distance == 0]
# 其中有1431个商家的用券消费用户平均范围在500米以内

        操作同上

# 各商家对应的顾客到店消费平均折扣力度
Merchant_discount_rate = cpon_consume.groupby('Merchant_id')['Discount_rate'].mean()
Merchant_discount_rate.sort_values(ascending=False)
Merchant_discount_rate.hist()
Merchant_discount_rate.mean()
# 所有商家平均折扣力度为0.88

持券到店消费人数最多的商家

        我们使用的表仍然是cpon_consume,对商家id分组,对用户id去重求和

        去重求和可以len(x.unique)一步到位

        我们专门分析消费人数>=500的商家,一共16家

# 3.2持券到店消费人数最多的商家
# 对商家id进行分组,对用户id进行去重统计
popular_merchant = cpon_consume.groupby('Merchant_id')['User_id'].apply(lambda x:len(x.unique())).sort_values(ascending=False)
# 找出持券消费人数大于等于500的商家
popular_merchant500 = popular_merchant[popular_merchant>=500]
popular_merchant500.name = 'customer_count' # 指定列名为消费者数量(持券消费者)
print(len(popular_merchant500))
print(popular_merchant500)
# 持券消费人数在500以上的商家有16个
# 人数最多的商家是5341,人数在2800
# 这批商家对优惠券使用方法得当,可以适当借鉴其推广方式

对持券消费人数在500以上的商家,分析顾客达到店平均距离和平均折扣率的关系

        这三项我们已经分别求出来过了,只需要连接到一张表中就能分析了,merge函数

# 3.3对持券消费人数在500以上的商家,分析顾客达到店平均距离和平均折扣率的关系
# 这里我们已经找出消费人数500+的商家,也统计出了平均距离和平均折扣力度,需要做的是用merge函数联合三张表
merchant_pop_dis = pd.merge(left = popular_merchant500,right = Merchant_distance,on = 'Merchant_id')
merchant_pop_dis_rate = pd.merge(left = merchant_pop_dis,right = Merchant_discount_rate,on = 'Merchant_id')
merchant_pop_dis_rate

计算到店消费人数与平均距离和折扣力度的相关系数

        介绍新函数corr(),专门求相关系数的函数,一般与热力图连用

# 3.4计算到店消费人数与平均距离和折扣力度的相关系数
# 学习新函数corr(correlation:相关系数),用来计算df数据中列与列的相关性(皮尔逊相关系数)
# 1:完全正相关   -1:完全负相关
# 正相关:随着变量增大而增大,反之同理
# 负相关:随着变量增大而减小,反之同理
merchant_pop_dis_rate.corr()
# 持券消费人数,与距离和折扣率都呈现负相关,属于生活中的正常现象

        

        这里使用了热力图,那么前期导入库的时候要加上seaborn就行

# 用热力图展示相关系数
sns.heatmap(data = merchant_pop_dis_rate.corr(),annot = True,cmap = 'Accent',vmax = 1,vmin = -1)
# 到店消费人数与顾客到店铺距离呈现负相关,相关系数为0.31,在0.3-0.5之间,为低度相关
# 到店消费人数与折扣力度呈现负相关,相关系数为0.2,在0-0.3之间,相关程度极弱
# 综上所述,这家店之所以火爆,应该是物美价廉导致的,与距离和折扣力度相关性都不高

分析每天中优惠券的总体发放量与使用量情况

        这一部分视频丢失,我说一下自己的理解,也还比较好懂

        业务分析:日期(优惠券发放日期:Date_received,使用日期:date)用作图表的x轴。需要统计每天优惠券发放数量和使用数量(y轴)

        因为我们要将优惠券发放日期作为x轴,那么肯定要将Date_received中的数据先去除空值,然后去重

# 取出存在领券日期的记录,升序、去重
# 这个是作为x轴
date_received_sort = offline[offline['Date_received'].notnull()]['Date_received'].sort_values().unique()
date_received_sort[:5]

        接下来是统计每天优惠券的使用量,仍然是在cpon_consume中统计,只有这里面的人才是用券消费的,这里的做法是取出用户id、接收日期,根据日期分组然后求和人数,最后为了便于理解改了列名

# 每天优惠券的使用量(即持券消费人群)
# 使用量这里是在cpon_consume表中查询的,里面的数据全是用券消费的人
consume_num_everyday = cpon_consume[['User_id','Date_received']]
consume_num_everyday = consume_num_everyday.groupby('Date_received').count()
consume_num_everyday = consume_num_everyday.rename(columns={'User_id':'count'}) # 把user_id列名改为count
consume_num_everyday

        

        操作同上,只不过是在总表offline中提取这两列,因为有人拿到了优惠券却没用,这些也是要算在发放量中的

# 每天发放优惠券的量(取出所有的领券日期!=null的数据,按照天进行分组,计数)
# 发放优惠券是在总表中查找的
consume_sendout_everyday = offline[offline['Date_received'].notnull()][['Date_received','User_id']]
consume_sendout_everyday = consume_sendout_everyday.groupby('Date_received').count()
consume_sendout_everyday = consume_sendout_everyday.rename(columns={'User_id':'count'}) # 把user_id列名改为count
consume_sendout_everyday

        

        这里的知识点就是plt.yscale('log'),进行对数缩放,这样能放大橙色区域

# 绘制每天发券量和每天用券量柱形图
plt.figure(figsize = (18,6))
plt.bar(x = date_received_sort,height = consume_sendout_everyday['count'],label = '每天发券量')
plt.bar(x = date_received_sort,height = consume_num_everyday['count'],label = '每天用券量')
plt.legend()
# 由于在一幅图上量级不同,导致看不清楚,我们可以对y轴进行对数缩放
plt.yscale('log')

# 以16年2月为例,用券量级在1000,发券量量级在10万,在100倍左右,优惠券使用率很低

        由图可知,优惠券使用率最高在16年3月底,30%,最低在16年1月底,3%。整体来看,优惠券使用率波动较大

# 计算每天优惠券与发券量占比
plt.figure(figsize = (18,6))
plt.bar(x = date_received_sort,height = consume_num_everyday['count']/consume_sendout_everyday['count'])

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值