【Python实战因果推断】2_因果效应异质性2

发布于:2024-06-27 ⋅ 阅读:(119) ⋅ 点赞:(0)

目录

CATE with Regression

Evaluating CATE Predictions


CATE with Regression

我想你可能已经预料到了:与应用因果推理中的大多数情况一样,答案往往从线性回归开始。但在走这条路之前,让我们把事情变得更具体一些。假设你在一家遍布全国的连锁餐厅工作。这项业务的一个关键组成部分是了解何时应该给顾客打折。为此,该公司在全国范围内进行了一项为期三年的实验,在连锁店中的六家不同餐厅随机提供折扣。数据存储在以下数据框中:

您的目标是了解何时是打折的最佳时机。在这些数据中,每家餐厅和每一天都有一行数据。这与本书中使用的大多数示例有些不同,以前的示例以顾客为单位。现在,单位是日-餐厅组合。即便如此,你仍然可以运用以前的推理方法,只是你要 "对待"(给予折扣)的不是顾客,而是 "对待 "日。您也可以在每一天的每家餐厅采用不同的价格,但我们还是把问题简化为保持各餐厅的价格一致。

您可以将这一业务问题视为 CATE 估算问题。如果您能创建一个模型,输出每一天的销售额对折扣的敏感度以及协变量,即 \frac\partial{\partial t}E[Sales(t)|X] ,那么,您就可以使用该模型来决定何时打折以及打多少折。

现在你有了更具体的东西可以利用,让我们来看看回归如何帮助你。回想一下,您当时的情况很复杂。您需要预测 \frac{\delta Y_{i}}{\delta T_{i}},但遗憾的是,它是不可观测的。所以你不能简单地使用多项式回归算法,然后将其作为目标。但也许你并不需要观察 \frac{\delta Y_{i}}{\delta T_{i}} 来预测它。

例如,假设您对数据拟合了以下线性模型:y_i=\beta_0+\beta_1t_i+\beta_2X_i+e_i

如果在治疗上加以区分,结果会如下:\frac{\delta y_i}{\delta t_i}=\beta_1  即 在随机治疗的情况下ATE。

由于可以通过估计前面的模型得到 \widehat{\beta}_{1} ,因此甚至可以说,即使无法观察到斜率,也可以预测斜率。在这个例子中,预测相当简单。您预测的是每个人的恒定值 \widehat{\beta}_{1}。这是件好事,但并不是您想要的。这是 ATE,而不是 CATE。这对您计算何时给予折扣没有任何帮助,因为每个单位(日和餐厅组合)得到的斜率预测都是一样的。

要改进它,可以做以下简单的改变:y_i=\beta_0+\beta_1t_i+\beta_2X_i+\beta_3t_iX_i+e_i,这样就可以预测出以下坡度:\frac{\widehat{\delta y_i}}{\delta t_i}=\widehat{\beta_1}+\widehat{\beta_3}X_i其中,\beta_3 是 X 特征的向量系数!

每个由不同 Xi 定义的实体都会有不同的斜率预测。换句话说,斜率预测会随着 X 的变化而变化。直观地说,包含治疗与协变因素之间的交互作用可以让模型了解效果如何随着这些协变因素的变化而变化。这就是回归如何为您提供估计 CATE 的方法,即使您无法直接预测它。
理论就讲到这里。让我们来看看如何编写代码。首先,您需要定义协变量。在本例中,协变量基本上是日期的特定特征,如月份、星期以及是否是节假日。我还加入了竞争对手的平均价格,因为这可能会影响顾客对每家餐厅折扣的反应。

一旦有了协变量,就需要将它们与治疗进行交互。* 运算符就可以做到这一点。它为左侧和右侧创建一个加法项,再加上一个交互项。例如,a*b 将在回归中包含 a、b 和 a * b 项。在你的例子中,结果如下:

sales_i=\beta_0+\beta_1discount_i+\beta_2X_i^\star discount_i+\beta_3X_i+e_i

import statsmodels.formula.api as smf
X = ["C(month)", "C(weekday)", "is_holiday", "competitors_price"]
regr_cate = smf.ols(f"sales ~ discounts*({'+'.join(X)})",data=data).fit()

估算出模型后,就可以从参数估算值中提取预测斜率:

\frac{\delta sales_i}{\delta discounts_i}=\widehat{\beta_1}+\widehat{\beta_3}X_i

其中,β1 是折扣系数,β3 是交互系数向量。您可以直接从拟合模型中提取这些参数,但获得斜率预测的更简便方法是使用导数的定义:\frac{\delta y}{\delta t}=\frac{y(t+\epsilon)-y(t)}{(t+\epsilon)-t}\epsilon趋近0, 您可以用 1 代替 \epsilon 来近似地理解这一定义:\frac{\delta y}{\delta t}\approx\hat{y}(t+1)-\hat{y}(t), 其中 y 由模型预测值给出。由于这是一个线性模型,因此近似值是精确的。

换句话说,您将用模型做出两个预测:一个通过原始数据,另一个通过原始数据,但处理量增加了一个单位。这两个预测之间的差异就是您的 CATE 预测。下面是一些代码:

ols_cate_pred = (
 regr_cate.predict(data.assign(discounts=data["discounts"]+1))
 -regr_cate.predict(data)
 )

好了,您有了 CATE 模型及其预测。但仍有一个潜伏的问题:它到底有多好?换句话说,如何评估这个模型?您可能已经知道,比较实际值和预测值在这里是行不通的,因为实际治疗效果并不是在单位水平上观察到的。

Evaluating CATE Predictions

如果您有传统数据科学的背景,您可能会发现这种 CATE 预测看起来很像常规的机器学习预测,但却有一个无法在单位层面观察到的隐秘目标。这意味着,传统机器学习中使用的大量模型评估技术(如交叉验证)在这里仍然适用,而其他技术则需要一些调整。

因此,为了保持传统,我们将数据分为训练集和测试集。既然有时间维度,那就用它吧。训练集将包含 2016 年和 2017 年的数据,测试集则包含 2018 年以后的数据:

train = data.query("day<'2018-01-01'")
test = data.query("day>='2018-01-01'")

现在,让我们重试之前的 CATE 回归模型,但只使用训练数据进行估计,并对测试集进行预测:

X = ["C(month)", "C(weekday)", "is_holiday", "competitors_price"]
 regr_model = smf.ols(f"sales ~ discounts*({'+'.join(X)})",
 data=train).fit()
 cate_pred = (
 regr_model.predict(test.assign(discounts=test["discounts"]+1))
 -regr_model.predict(test)
 )

为了增加趣味性,让我们用一个纯粹的预测性机器学习模型来衡量这个回归模型。这个机器学习模型只是试图预测结果 Y:

from sklearn.ensemble import GradientBoostingRegressor
 X = ["month", "weekday", "is_holiday", "competitors_price", "discounts"]
 y = "sales"
 np.random.seed(1)
 ml_model = GradientBoostingRegressor(n_estimators=50).fit(train[X],
 train[y])
 ml_pred = ml_model.predict(test[X])

最后,让我们把一个非常糟糕的模型也纳入比较范围。这个模型简单地输出-1 和 1 之间的随机数。这显然是无稽之谈,但也是一个值得关注的有趣基准。归根结底,你想知道用 CATE 模型分配治疗是否会比随机分配更好,这就是最后一个模型的作用。

为了方便起见,我将所有数据存储在一个新的数据帧 test_pred:

np.random.seed(123)
 test_pred = test.assign(
 ml_pred=ml_pred,
 cate_pred=cate_pred,
 rand_m_pred=np.random.uniform(-1, 1, len(test)),
 )

有了模型之后,就该想办法评估和比较它们了。也就是说,你必须面对ground truth是不可观测的这一事实。正如你很快就会看到的,诀窍在于认识到,尽管你无法测量单个个体的治疗效果,但你可以估计很小群体的治疗效果。因此,如果你想用 CATE 来评估你的模型,就必须依赖于群体层面的指标。