目录
接触次数的效应
重新定义治疗变量和潜在混杂因素
更深入地审视干预情景
逆概率加权
标准化
总结及与非因果分析的比较
接触次数的效应
我们现在转而研究当前营销活动中接触次数的数量('campaign')对积极结果发生率的影响。具体来说,我们将估计如果所有客户都被接触多次与只被接触一次相比的成功率。这是一个相关的问题,因为接触客户需要资源(比如员工的时间)。结合其他输入,效果估计可以为员工提供是否在第一次尝试后继续联系客户,还是将时间更有利可图地用于其他任务的指导。
重新定义治疗变量和潜在混杂因素
相应地,我们将治疗变量a重新定义为:如果'campaign'大于1则为1,如果'campaign'等于1则为0。我们还需要重新定义y以恢复之前考虑接触方式时排除的非重叠群体。
y = pd.Series(le.fit_transform(data['y']))
a = (data['campaign'] > 1).astype(int)
print(y.mean())
print(a.mean())
0.11265417111780131
0.5716713605904632
至于混杂因素,我们继续使用之前包括的客户特征、之前的营销活动特点以及经济指标。
confounders = ['age', 'job', 'marital', 'education', 'default', 'housing', 'loan', 'pdays','previous', 'poutcome', 'emp.var.rate', 'cons.price.idx', 'cons.conf.idx', 'euribor3m', 'nr.employed']
以'campaign'作为治疗变量,我们设想在首次与客户接触后决定停止还是继续。在这种情况下,最后一次接触的特点('contact'、'day_of_week',当然还有'duration')可能还不知道,所以它们不应该被视为潜在的混杂因素。因此,我们只会添加'month'。
confounders += ['month']
重新提取混杂因素:
X = data[confounders]
X = pd.get_dummies(X, prefix_sep='=', drop_first=True)
更深入地审视干预情景
让我们停下来更仔细地思考一下决策情景:我们首先联系所有客户一次,然后决定是否继续联系还是停止。第一个观察是,通过继续联系,积极结果的发生率只能增加,因此治疗效果的正负符号是没有疑问的。相反,目标是估计在决定继续联系的情况下,积极结果的发生率有多大,以供成本效益分析参考。
第二,因果效应估计的主要挑战(也就是所谓的“因果推断的基本问题”)是我们通常不能观察到反事实结果:对于被给予a=0干预的个体,我们无法观察他们在a=1下的结果,反之亦然。然而,在当前的情景中,我们实际上可以推断出一些反事实结果。让我们定义Y0为客户仅被联系一次时可能出现的结果。对于实际上只被联系一次的客户(分配a=0),他们的观察结果y与潜在结果Y0相同。对于被联系多次的客户(分配a=1),如果我们选择在第一次联系后停止,则他们的潜在结果应该是Y0 = 0,否则银行员工不会持续联系他们。因此,对于所有客户,Y0可以根据以下方式推断:
y0 = y * (a == 0)
y0.mean()
0.05584150723511702
这个5.6%的成功率实际上是仅联系一次这一干预措施下的成功率。我们稍后会回到这个数值上来。
此外,潜在结果Y0在银行员工第一次联系后作出决策时是已知的。实际上,它可以被视为一种特殊类型的混杂因素,因为它具有确定性影响:如果Y0=1(成功),那么a=0是确定性的,因为继续联系没有益处,并且y=1。由于Y0=1的客户只接受a=0的处理,它们构成了一个完全非重叠的治疗群体。因此,应该将它们从因果分析中排除,就像我们之前在分析'contact'时所做的那样。所以我们把它们移除:
y = y.loc[y0 == 0]
a = a.loc[y0 == 0]
X = X.loc[y0 == 0]
X.shape
(38888, 46)
剩下的Y0=0的客户可以接受a=0或a=1的处理,因此它们不存在完全非重叠的情况。它们可能仍然存在部分非重叠的问题,但这可以通过使用逆概率加权(IPW)模型和Causal Inference 360的评估方法来进行检查,我们将在下一小节中这样做。
还需要记住的一点是,处理a=1,即'campaign' > 1,实际上是一个复合处理:它不是一个像“恰好联系两次”那样明确定义的干预措施,而是“根据数据中的'campaign'分布进行多次联系”(换句话说,遵循银行员工集体的做法)。实际上,人们可以想象在一定数量的联系之后,成功率可能会开始下降,因为客户可能会变得恼火甚至离开银行(这是我们在数据中没有观察到的结果)。在这个笔记本中,我们不会涉及这些额外的考虑因素,而是专注于二元化处理的简化案例。
逆概率加权
如同第2节中所述,我们将使用逻辑回归模型来实例化IPW,以预测治疗分配的概率。
lr = LogisticRegression(solver='lbfgs', max_iter=1000)
#lr = LogisticRegression(penalty='l1', solver='saga', max_iter=1000)
#lr = GradientBoostingClassifier()
ipw = IPW(lr)
ipw.fit(X, a)
IPW(clip_max=None, clip_min=None, use_stabilized=False, verbose=False,learner=LogisticRegression(max_iter=1000))
让我们通过评估图表来检查IPW模型:
eval_results = evaluate(ipw, X, a, y)
eval_results.plot_all()
eval_results.plot_covariate_balance(kind="love")
两个治疗组从一开始就相当平衡:
ROC曲线:倾向性AUC(蓝色曲线)仅为0.55,经过IPW后进一步降低到0.51。
倾向性分布:倾向性估计的分布是平衡的。
协变量平衡Love图:即使在IPW之前,标准化的平均差异也都低于0.1。
现在我们获得积极结果率和治疗效果的估计:
outcomes = ipw.estimate_population_outcome(X, a, y)
print(outcomes)
ipw.estimate_effect(outcomes[1], outcomes[0], effect_types=['diff'])
0 0.000000
1 0.100092
dtype: float64
diff 0.100092
dtype: float64
积极结果在a=0(停止)时为零,这符合该子人群的定义,即在一次接触后没有成功。在a=1下,估计的成功率为10%。接下来我们将使用标准化来分解这一比率。
标准化
回想一下,在标准化中,我们使用回归器从混杂因素X和治疗a来预测结果y。由于排除了在a=0下的积极结果Y0=1,因此现在在a=0的条件下y恒为零。因此,我们只需要为a=1拟合一个模型。为此,我们使用梯度提升分类树的StratifiedStandardization来为a=1建模,而对于a=0则使用线性回归。后者是一个“虚拟”的模型,只会返回一个全零的模型。
from sklearn.linear_model import LinearRegression
std = StratifiedStandardization({0: LinearRegression(), 1: GradientBoostingClassifier()}, predict_proba=True)
std.fit(X, a, y)
我们继续估计两种干预措施下的积极结果发生率。(最后两列的结果是预测的y=0和y=1的概率,是在a=1干预下的概率,因此加起来等于1。)
outcomes = std.estimate_population_outcome(X, a)
print(outcomes)
std.estimate_effect(outcomes[(1,1)], outcomes[0], effect_types=['diff'])
campaign
0 y 0.000000
1 0 0.9021711 0.097829
dtype: float64
diff 0.097829
dtype: float64
a=0干预(停止)下的估计结果为零,正如预期。a=1下的估计结果9.8%略低于IPW的结果。
使用标准化,我们还可以看到在继续联系所有客户的干预措施下,分配到的治疗组a=0和a=1的估计成功率是否有差异。这可以通过使用estimate_individual_outcome
方法(与上面的estimate_population_outcome
方法相反),提取a=1干预下的(1,1)列的成功概率,然后对两个治疗组的概率求平均来完成。
yPot = std.estimate_individual_outcome(X, a)#.xs(1, axis=1, level='y')
#print(yPot.head())
y1 = yPot[(1,1)]
print('Estimated success rates under continuing contact')
print('for the "untreated" group a=0: {}'.format(y1.loc[a == 0].mean()))
print('for the "treated" group a=1: {}'.format(y1.loc[a == 1].mean()))
Estimated success rates under continuing contact
for the "untreated" group a=0: 0.09543383555412627
for the "treated" group a=1: 0.09938921214795549
a=0组(未治疗组)的y1平均值略低于a=1组(治疗组),这解释了为什么总体的成功率9.8%略低。这意味着如果我们决定继续联系所有客户,我们可能在那些银行员工在数据中停止联系的客户(a=0)上的成功率略低于那些继续联系的客户(a=1)。这可能是由于银行员工在决定继续联系哪些客户时,有一定的能力预测哪些客户更有可能进行投资,尽管考虑到差异很小,我们不能肯定地说这一点。
我们也可以验证a=1组的估计成功率与观察到的成功率非常接近(因为这一组实际上接受了继续联系)。后者是在a=1条件下的发生率。再次说明,这可能是没有采用因果方法时所计算的结果。
print(y[a==1].mean())
0.09937993714431326
总结及与非因果分析的比较
回想一下,在因果分析中我们排除了第一次接触后就有积极结果的客户(Y0=1)。对于这些客户,我们可以假设(或者说定义)他们的反事实结果为Y1=1,如果我们继续联系他们的话。然后我们可以构建在a=1干预下的完整潜在结果集y1All:
y1All = y0.copy()
y1All[y1All == 0] = y1
print('Estimated success rate under one contact for all clients: {}'.format(y0.mean()))
print('Estimated success rate under more than one contact for all clients: {}'.format(y1All.mean()))
Estimated success rate under one contact for all clients: 0.05584150723511702
Estimated success rate under more than one contact for all clients: 0.1482073490897146
总结来说,从数据中我们观察到一次接触后的成功率为5.6%,而从因果分析中,我们预测如果所有客户都接受继续联系,成功率为14.8%。观察到的成功率11.3%介于两者之间,因为银行员工选择了一些客户继续联系,而停止了对其他客户的联系。
我们同样以与非因果分析的比较来结束,简单地查看基于治疗分配a=0、a=1的成功率。我们首先重新定义y和a,以带回被排除的Y0=1的人群。
y = pd.Series(le.fit_transform(data['y']))
a = (data['campaign'] > 1).astype(int)
y[a==0].mean(), y[a==1].mean()
(0.13037070626913047, 0.09937993714431326)
我们现在看到这些条件成功率是非常误导人的。它们似乎表明,如果客户被联系多次,成功率会更低,我们知道这并不是真的(即使是常识)。我们来分别看一下这两种情况:
- 对于a=0,13.0%的成功率仅来自于那些银行员工只联系了一次的客户。但如果他们对剩余的客户也只联系一次就停止,他们不会有额外的成功。因此,正好一次接触干预下的成功率要低得多,如上所述为5.6%。
- 对于a=1,9.9%的成功率接近于我们使用标准化估计的9.8%,针对那些第一次接触后不是成功的客户(Y0=0)。但是,我们必须记得加上那些第一次接触后就已经同意定期存款的客户(Y0=1),这样我们得到了在超过一次接触的干预下14.8%的成功率。