【因果推断】优惠券政策对不同店铺的影响

这次依然是用之前rossmann店铺竞赛的数据集。
之前的数据集探索处理在这里已经做过了,此处就不再赘述了CSDN链接
数据集地址:竞赛链接
这里探讨数据集中Promo2对于每家店铺销售额的影响。其中,Promo2是一个基于优惠券的邮寄活动,发送给参与商店的顾客。每封信里都有几张优惠券,大部分是所有产品的一般折扣,有效期为三个月。所以在这些优惠券到期之前,我们会给客户发新一轮的邮件。具体的变量解释可以看这里:Promo2释义
此处单纯是为了作因果推断的练习。由于笔者是因果推断的初学者,有些概念与处理可能会有疏漏错误,还请发现的读者不吝赐教。

一、数据可视化

首先,我们从单纯地数据可视化的角度来看总体均值以及店铺自身使用优惠券政策前后的比较。

import warnings
warnings.filterwarnings("ignore")import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
data = pd.read_csv("train.csv")
data = data[data["Open"]==1]
store_info = pd.read_csv("store.csv")#假设一年的第一周是包含1月4日的那周
data = data[data["Sales"]>0]
data["Date"]=pd.to_datetime(data["Date"])
data["Year"]=[i.year for i in data["Date"]]
data["Month"]=[i.month for i in data["Date"]]
def get_first_day_of_week(year, week):if np.isnan(year):return np.nanyear = int(year)week = int(week)first_thursday = datetime(year, 1, 4)  while first_thursday.weekday() != 3:  # weekday() returns 0 (Monday) through 6 (Sunday)  first_thursday += timedelta(days=1)  first_day_of_week = first_thursday - timedelta(days=first_thursday.weekday() - 1)  target_date = first_day_of_week + timedelta(weeks=week - 1)  return datetime.date(target_date)
res = []
for i in range(store_info.shape[0]):res.append(get_first_day_of_week(store_info.loc[i,"Promo2SinceYear"],store_info.loc[i,"Promo2SinceWeek"]))
store_info["Promo2StartTime"] = pd.to_datetime(res)
data = data.merge(store_info,on="Store")
data["Post"] = (data["Date"]>data["Promo2StartTime"]).astype("int8")
data["StateHoliday"] = (data["StateHoliday"]!="0").astype("int8")
## 先简单比较一下每天·店的均值
#均值比较
Promo2_date=data[data["Post"]==1].groupby(["Year","Month"])["Sales"].mean()
NoPromo2_date=data[data["Post"]==0].groupby(["Year","Month"])["Sales"].mean()
Promo2_date.index = ["-".join([str(j) for j in i]) for i in Promo2_date.index]
NoPromo2_date.index = ["-".join([str(j) for j in i]) for i in NoPromo2_date.index]
fig, ax = plt.subplots()
plt.plot(Promo2_date,label="has Promo2")
plt.plot(NoPromo2_date,label="does not have Promo2")
ax.xaxis.set_visible(False)
plt.legend()
plt.show()

在这里插入图片描述
图中蓝线代表实行了优惠券政策的店铺每个月的月均销量,而橙线则代表控制组的月均销量,可以看出控制组的销量明显高于优惠券政策的店铺。

## 去除那些没有“促销前”信息的店铺
tmp = data[~pd.isna(data["Promo2StartTime"])].groupby(["Store"])[["Promo2StartTime","Date"]].min()
drop_stores = list(tmp[tmp["Date"]>=tmp["Promo2StartTime"]].index)
for i in drop_stores:data = data[data["Store"]!=i]
data.index = range(data.shape[0])
data_promo2 = data[~pd.isna(data["Promo2StartTime"])].reset_index(drop=True)
isin_thirty_days=[abs(i).days<=30 for i in data_promo2["Date"]-data_promo2["Promo2StartTime"]]
data_promo2 = data_promo2[isin_thirty_days]
promo2_before=data_promo2[data_promo2["Date"]<data_promo2["Promo2StartTime"]].groupby(["Store"])["Sales"].mean()
promo2_after=data_promo2[data_promo2["Date"]>=data_promo2["Promo2StartTime"]].groupby(["Store"])["Sales"].mean()
fig, ax = plt.subplots(figsize=(10,10))
num_bars = 15
idx = range(num_bars)
plt.bar(idx,promo2_before[0:num_bars].values,width=0.35,label="before Promo2")
plt.bar([i+0.35 for i in idx],promo2_after[0:num_bars].values,width=0.35,label="after Promo2")
ax.xaxis.set_visible(False)
plt.legend()
plt.show()

在这里插入图片描述
选取部分店铺在发行优惠券前后的销售额均值对比,发现大多数情况下,销售额会随着降低,不过也有部分店铺在发行优惠券后销售额反而提升了。

## 作图平行趋势
promo2_bydate = data.groupby(["Promo2StartTime","Year","Month"])["Sales"].mean().reset_index()
nopromo2 = data[pd.isna(data["Promo2StartTime"])].groupby(["Year","Month"])["Sales"].mean().reset_index()
nopromo2.index = [str(i)+"-"+str(j) for i,j in zip(nopromo2["Year"],nopromo2["Month"])]
fig, ax = plt.subplots()
sns.lineplot(nopromo2.sort_values(["Year","Month"])["Sales"],label="no_promo")
for i in promo2_bydate["Promo2StartTime"].unique()[[0,10,20,15]]:tmp = promo2_bydate[promo2_bydate["Promo2StartTime"]==i].sort_values(["Year","Month"]).reset_index()tmp.index = [str(i)+"-"+str(j) for i,j in zip(tmp["Year"],tmp["Month"])]sns.lineplot(tmp["Sales"],label=str(i)[0:10])ax.vlines(x=str(i)[0:7].replace("-0","-"),ymin=50,ymax=9000,ls="dashed")ax.xaxis.set_visible(False)

在这里插入图片描述
选取几个不同时间开始发行优惠券的店铺的历史销售作图,可以发现大多数发行优惠券店铺的整体历史销售和不发行优惠券的那店铺趋势大致相同。

二、分组双重差分

从计量经济学的角度来看,本身我们获得的数据是一份面板数据(Panel Data),所以可以使用双重差分(DID)的方法来判断优惠券政策是否显著影响了店铺的销售额。但现在有一个问题,我们每一家店铺的优惠券政策(干预)实际上并非同时发生的。这并不符合传统DID的假设,因为对于不同的店铺而言,优惠券政策的影响效果是不同的。因此我们需要使用一个更为灵活的模型考虑。我们期望对于不同的时间而言,每个店铺有不同的效应。为了保证不出现梯度爆炸,我将时间分组为每年每周,而由于这一数据集中,干预或许并非是随机发生的,因此需要将其他的混淆因子也加入到模型中,能够使得模型解释性更好。

import statsmodels.formula.api as smf
## 按照群组分组进行OLS以作DIDdata["Promo2StartTime"] = data["Promo2StartTime"].fillna(pd.to_datetime("2100-01-01"))
data["Post"] = (data["Date"]>data["Promo2StartTime"]).astype("int8")
data["StateHoliday"] = (data["StateHoliday"]!="0").astype("int8")
data["WeekOfYear"] = [i.weekofyear for i in data["Date"]]
data_for_ols = data.groupby(["Store","Year","WeekOfYear"]).agg({"Sales":"sum","Promo":"sum","StateHoliday":"sum","SchoolHoliday":"sum","Promo2":"sum","Post":"sum"}).reset_index()
data_for_ols = data_for_ols.merge(store_info[["Store","StoreType","Assortment","CompetitionDistance","Promo2StartTime"]],on="Store")
boolize = ["Promo","StateHoliday","SchoolHoliday","Promo2","Post"]
for col in boolize:data_for_ols[col] = pd.Series([1 if i>0 else 0 for i in data_for_ols[col]]).astype("int8")data_for_ols["Sales"] = np.log(data_for_ols["Sales"])
formular = "Sales~Promo2:Post:C(Promo2StartTime):C(Year):C(WeekOfYear)+Promo+StateHoliday+SchoolHoliday+StoreType+Assortment+CompetitionDistance"
twfe_model = smf.ols(formular,data=data_for_ols).fit()
df_predict = data_for_ols[data_for_ols["Post"]*data_for_ols["Promo2"]==1].reset_index()
df_predict["Promo2"] = 0
df_predict["predict"] = twfe_model.predict(df_predict)
print("参数个数:",len(twfe_model.params))
print("估计的ATT:",(df_predict["Sales"]-df_predict["predict"]).mean())

在这里插入图片描述
最终我们使用OLS最小二乘建模,获得了一个有3754个参数的回归模型。对于销售额而言,平均干预效果为负数。

三、干预异质性检验

那么对于不同的店铺而言,优惠券政策是否有不同影响?为了研究这一问题,笔者使用了元机器学习(Meta-Learner)的技巧,构建了一个X-Learner:
在这里插入图片描述
X-Learner分为两个阶段:第一个阶段训练1个倾向得分模型(也就是图中最左侧,根据协变量X来预测T的模型),同时训练2个模型回应(respond)模型(也就是图中左侧2个并列的模型),分别将受到干预和没收到干预的部分分开使用协变量预测因变量的模型。到了第二阶段,我们使用第一阶段的回应模型预测出两个反事实结果:对于没有受到干预的店铺,如果当初受到干预的话会有多少销售额(图中的 Y ∗ ∣ T = 0 Y^*|T=0 YT=0)以及对于受到了干预的店铺如果当初没有受到干预的话会有多少销售额(图中的 Y ∗ ∣ T = 1 Y^*|T=1 YT=1)。将 Y 1 Y1 Y1 Y 0 Y0 Y0相见,我们就获得了条件平均处理效果(CATE)。我们将CATE作为应变量,再使用协变量X训练2个模型(也就是图中带有绿色CATE的部分)。使用这2个模型按照倾向得分倒数的权重预测最终的CATE。(因为倾向得分越高表示越有可能受到干预,倒数反而代表对应样本更稀有,对于非随机实验而言参考价值更大)

## X-learner## PS Model
Y_PS = "treatment"
train = pd.concat([train_T1,train_T0],axis=0)
train_ps = pd.get_dummies(train)
X_PS = [i for i in train_ps.columns if i != "Sales" and i != "treatment"]
ps_model = LogisticRegression(penalty='none')
ps_model.fit(train_ps[X_PS],train_ps[Y_PS])## Predict_Model
X_lgbm = [i for i in data_t1.columns if i != "Sales" and i != "treatment"]
Y = "Sales"
model_t1 = LGBMRegressor()
model_t0 = LGBMRegressor()model_t0.fit(train_T0[X_lgbm],train_T0[Y],sample_weight=1/ps_model.predict_proba(pd.get_dummies(train_T0)[X_PS])[:, 0])
model_t1.fit(train_T1[X_lgbm],train_T1[Y],sample_weight=1/ps_model.predict_proba(pd.get_dummies(train_T1)[X_PS])[:, 1])## second_stage
tau_hat_0 = model_t1.predict(train_T0[X_lgbm])-train_T0[Y]
tau_hat_1 = train_T1[Y]-model_t0.predict(train_T1[X_lgbm])model_tau_t1 = LGBMRegressor()
model_tau_t0 = LGBMRegressor()model_tau_t0.fit(train_T0[X_lgbm],tau_hat_0)
model_tau_t1.fit(train_T1[X_lgbm],tau_hat_1)
# estimate the CATE
valid = pd.concat([valid_T1,valid_T0],axis=0)ps_valid = ps_model.predict_proba(pd.get_dummies(valid)[X_PS])[:, 1]cate_valid = valid.assign(cate=(ps_valid*model_tau_t0.predict(valid[X_lgbm]) +(1-ps_valid)*model_tau_t1.predict(valid[X_lgbm]))
)
sns.displot(cate_valid["cate"])

在这里插入图片描述
可以看出,使用X-Learner预测的结果中,在验证集大部分的店铺在受到了干预之后,销售量明显是负数;只有小部分的店铺销售额会受到正面影响。
接下来将验证集中各个CATE排序,求出累计的干预效果,以此做出QINI曲线。

sorted_cate = cate_valid["cate"].values[np.argsort(-cate_valid["cate"].values)]
cumulative_cate = np.cumsum(sorted_cate)/np.arange(1,len(sorted_cate)+1)
cumulative_fractions = np.arange(1, len(cumulative_cate) + 1) / len(cumulative_cate)
plt.plot(cumulative_fractions,cumulative_cate,"r",label="QINI Curve")
plt.plot([0,1],[cumulative_cate[0],cumulative_cate[-1]],"k--",label="Random Assignment")
plt.title("QINI Curve")
plt.legend()
plt.show()

在这里插入图片描述
可以看到,在一开始,的确有些店铺的销售额是受到了优惠券政策的正向影响;但是到了之后,优惠券政策依然是负向影响。

四、总结

本文使用了竞赛用的销售数据进行了销售额与优惠券政策的因果推断。实际上更多的是因果推断方法论的学习。对于优惠券政策而言,销售额很有可能并非是真正的干预目标,而且店铺是否要发现优惠券,也需要考量除了数据集以外的其他因素。而对于销售额的干预影响,很多公司都会做Uplift Model来衡量,笔者有空也会对此进行学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/41527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server 2022 中的 Tempdb 性能改进非常显著

无论是在我的会话中还是在我写的博客中&#xff0c;Tempdb 始终是我的话题。然而&#xff0c;当谈到 SQL Server 2022 中引入的重大性能变化时&#xff0c;我从未如此兴奋过。他们解决了我们最大的性能瓶颈之一&#xff0c;即系统页面闩锁并发。 在 SQL Server 2019 中&#x…

Go语言如何入门,有哪些书推荐?

Go 语言之所以如此受欢迎&#xff0c;其编译器功不可没。Go 语言的发展也得益于其编译速度够快。 对开发者来说&#xff0c;更快的编译速度意味着更短的反馈周期。大型的 Go 应用程序总是能在几秒钟之 内完成编译。而当使用 go run编译和执行小型的 Go 应用程序时&#xff0c;其…

如何利用Github Action实现自动Merge PR

我是蚂蚁背大象(Apache EventMesh PMC&Committer)&#xff0c;文章对你有帮助给项目rocketmq-rust star,关注我GitHub:mxsm&#xff0c;文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsmapache.com 1. 引言 GitHub Actions 是 GitHub 提供的一种强大而灵活的自…

SSM中小学生信息管理系统 -计算机毕业设计源码02677

摘要 随着社会的发展和教育的进步&#xff0c;中小学生信息管理系统成为学校管理的重要工具。本论文旨在基于SSM框架&#xff0c;采用Java编程语言和MySQL数据库&#xff0c;设计和开发一套高效、可靠的中小学生信息管理系统。中小学生信息管理系统以学生为中心&#xff0c;通过…

赤壁之战的烽火台 - 观察者模式

“当烽火连三月&#xff0c;家书抵万金&#xff1b;设计模式得其法&#xff0c;千军如一心。” 在波澜壮阔的三国历史长河中&#xff0c;赤壁之战无疑是一场改变乾坤的重要战役。而在这场战役中&#xff0c;一个看似简单却至关重要的系统发挥了巨大作用——烽火台。这个古老的…

OpenAI的崛起:从梦想到现实

OpenAI的崛起不仅是人工智能领域的重大事件&#xff0c;也是科技史上一个引人注目的篇章。本文将深入探讨OpenAI从创立到如今的演变过程&#xff0c;分析其成功的关键因素&#xff0c;以及未来的发展方向。 一、OpenAI的初创期&#xff1a;理想主义与混乱并存 OpenAI成立于20…

插入排序——C语言

假设我们现在有一个数组&#xff0c;对它进行排序&#xff0c;插入排序的算法如同它的名字一样&#xff0c;就是将元素一个一个插入到合适的位置&#xff0c;那么&#xff0c;该如何做呢&#xff1f; 如果我们要从小到大进行排序的话&#xff0c;步骤如下&#xff1a; 1.对于…

区间最值问题-RQM(ST表,线段树)

1.ST表求解 ST表的实质其实是动态规划&#xff0c;下面是区间最小的递归公式&#xff0c;最大只需将min改成max即可 f[i][j] min(f[i][j - 1], f[i (1 << j - 1)][j - 1]); 二维数组的f[i][j]表示从i开始连续2*j个数的最小/大值。 例如&#xff1a;我们给出一个数组…

uniapp启动安卓模拟器mumu

mumu模拟器下载 ADB&#xff1a; android debug bridge &#xff0c; 安卓调试桥&#xff0c;是一个多功能的命令行工具&#xff0c;他使你能够与连接的安卓设备进行交互 # adb连接安卓模拟器 adb connect 127.0.0.1:port # 查看adb设备 adb deviceshubuilderx 有内置的adb&a…

MSPM0G3507——滴答定时器和普通定时

滴答定时器定时&#xff1a;&#xff08;放在主函数即可&#xff09; volatile unsigned int delay_times 0;//搭配滴答定时器实现的精确ms延时 void delay_ms(unsigned int ms) {delay_times ms;while( delay_times ! 0 ); } //滴答定时器中断 void SysTick_Handler(…

Python28-7.4 独立成分分析ICA分离混合音频

独立成分分析&#xff08;Independent Component Analysis&#xff0c;ICA&#xff09;是一种统计与计算技术&#xff0c;主要用于信号分离&#xff0c;即从多种混合信号中提取出独立的信号源。ICA在处理盲源分离&#xff08;Blind Source Separation&#xff0c;BSS&#xff0…

【机器学习】(基础篇一) —— 什么是机器学习

什么是机器学习 本系列博客为你从机器学习的介绍开始&#xff0c;使用大量的代码实战和验证&#xff0c;最终帮助你完全掌握什么是机器学习 人工智能、机器学习和深度学习的关系 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;&#xff1a;是一门研…

Java多线程不会?一文解决——

方法一 新建类如MyThread继承Thread类重写run()方法再通过new MyThread类来新建线程通过start方法启动新线程 案例&#xff1a; class MyThread extends Thread {public MyThread(String name) {super(name);}Overridepublic void run() {for(int i0;i<10;i){System.out.…

react dangerouslySetInnerHTML将html字符串以变量方式插入页面,点击后出现编辑状态

1.插入变量 出现以下编辑状态 2.解决 给展示富文本的标签添加css样式 pointerEvents: none

那些年背过的面试题——MySQL篇

本文是技术人面试系列 MySQL 篇&#xff0c;面试中关于 MySQL 都需要了解哪些基础&#xff1f;一文带你详细了解&#xff0c;欢迎收藏&#xff01; WhyMysql&#xff1f; NoSQL 数据库四大家族 列存储 Hbase K-V 存储 Redis 图像存储 Neo4j 文档存储 MongoDB 云存储 OSS …

AI大模型的智能心脏:向量数据库的崛起

在人工智能的飞速发展中,一个关键技术正悄然成为AI大模型的智能心脏——向量数据库。它不仅是数据存储和管理的革命性工具,更是AI技术突破的核心。随着AI大模型在各个领域的广泛应用,向量数据库的重要性日益凸显。 01 技术突破:向量数据库的内在力量 向量数据库以其快速检索…

RNN、LSTM与GRU循环神经网络的深度探索与实战

循环神经网络RNN、LSTM、GRU 一、引言1.1 序列数据的迷宫探索者&#xff1a;循环神经网络&#xff08;RNN&#xff09;概览1.2 深度探索的阶梯&#xff1a;LSTM与GRU的崛起1.3 撰写本博客的目的与意义 二、循环神经网络&#xff08;RNN&#xff09;基础2.1 定义与原理2.1.1 RNN…

【Python】组合数据类型:序列,列表,元组,字典,集合

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言组合数据类型序列类型序列常见的操作符列表列表操作len()append()insert()remove()index()sort()reverse()count() 元组三种序列类型的区别 集合类型四种操作符集合setfrozens…

【CSS in Depth 2精译】2.5 无单位的数值与行高

当前内容所在位置 第一章 层叠、优先级与继承第二章 相对单位 2.1 相对单位的威力2.2 em 与 rem2.3 告别像素思维2.4 视口的相对单位2.5 无单位的数值与行高 ✔️2.6 自定义属性2.7 本章小结 2.5 无单位的数值与行高 有些属性允许使用无单位的数值&#xff08;unitless value…