基于反事实逻辑的单位选择 - 李和珀尔 (2019)
Causal ML 包含了由 Li and Pearl (2019) 提出的反事实单元选择方法的实验版本。该方法尚未经过广泛测试或优化,因此用户应谨慎使用。本笔记本展示了反事实单元选择器的基本用法。
[2]:
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegressionCV
from sklearn.model_selection import train_test_split
from causalml.dataset import make_uplift_classification
from causalml.optimize import CounterfactualUnitSelector
from causalml.optimize import get_treatment_costs
from causalml.optimize import get_actual_value
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('white')
%matplotlib inline
生成数据
我们首先使用内置函数生成一些合成数据。
[3]:
df, X_names = make_uplift_classification(n_samples=5000,
treatment_name=['control', 'treatment'])
[4]:
# Lump all treatments together for this demo
df['treatment_numeric'] = df['treatment_group_key'].replace({'control': 0, 'treatment': 1})
[5]:
df['treatment_group_key'].value_counts()
[5]:
treatment 5000
control 5000
Name: treatment_group_key, dtype: int64
指定收益
在一个简单的双臂实验中,反事实单位选择方法考虑以下四个个体部分:
从不接受者:无论是否处于治疗中,都不会转换的人
总是接受者:无论是否处于治疗中,都会转化的那些人
编译器:那些在治疗组中会转化而在对照组中不会转化的人
反抗者:那些如果在控制组中会转化,而在治疗组中不会转化的人
如果我们假设转化的收益是$20,而处理的转化成本是$2.5,那么我们可以计算针对每种类型个体的收益如下。对于从不接受者,收益总是$0,因为他们不会转化或使用促销。对于总是接受者,收益是-$2.5,因为他们无论如何都会转化,但现在我们还额外给了他们价值$2.5的处理。对于顺从者,收益是转化收益减去处理成本,而对于反抗者,收益是-$20,因为如果我们不处理他们,他们会转化。
[6]:
nevertaker_payoff = 0
alwaystaker_payoff = -2.5
complier_payoff = 17.5
defier_payoff = -20
运行反事实单位选择器
在本节中,我们运行CounterfactualUnitSelector模型,并将其性能与随机分配和一种将所有单位分配到训练集中转化率最高的处理方案的方案进行比较。我们通过查看测试集中那些恰好处于每种方法推荐的处理组的单位的平均实际价值收益来衡量性能。
[7]:
# Specify the same costs as above but in a different form
tc_dict = {'control': 0, 'treatment': 2.5}
ic_dict = {'control': 0, 'treatment': 0}
conversion_value = np.full(df.shape[0], 20)
# Use the above information to get the cost of each treatment
cc_array, ic_array, conditions = get_treatment_costs(
treatment=df['treatment_group_key'], control_name='control',
cc_dict=tc_dict, ic_dict=ic_dict)
# Get the actual value of having a unit in their actual treatment
actual_value = get_actual_value(treatment=df['treatment_group_key'],
observed_outcome=df['conversion'],
conversion_value=conversion_value,
conditions=conditions,
conversion_cost=cc_array,
impression_cost=ic_array)
[8]:
df_train, df_test = train_test_split(df)
train_idx = df_train.index
test_idx = df_test.index
[9]:
# Get the outcome if treatments were allocated randomly
random_allocation_value = actual_value.loc[test_idx].mean()
# Get the actual value of those individuals who are in the best
# treatment group
best_ate = df_train.groupby(
'treatment_group_key')['conversion'].mean().idxmax()
actual_is_best_ate = df_test['treatment_group_key'] == best_ate
best_ate_value = actual_value.loc[test_idx][actual_is_best_ate].mean()
[10]:
cus = CounterfactualUnitSelector(learner=LogisticRegressionCV(),
nevertaker_payoff=nevertaker_payoff,
alwaystaker_payoff=alwaystaker_payoff,
complier_payoff=complier_payoff,
defier_payoff=defier_payoff)
cus.fit(data=df_train.drop('treatment_group_key', 1),
treatment='treatment_numeric',
outcome='conversion')
cus_pred = cus.predict(data=df_test.drop('treatment_group_key', 1),
treatment='treatment_numeric',
outcome='conversion')
best_cus = np.where(cus_pred > 0, 1, 0)
actual_is_cus = df_test['treatment_numeric'] == best_cus.ravel()
cus_value = actual_value.loc[test_idx][actual_is_cus].mean()
[11]:
labels = ['Random allocation', 'Best treatment assignment', 'CounterfactualUnitSelector']
values = [random_allocation_value, best_ate_value, cus_value]
plt.bar(labels, values)
plt.ylabel('Mean actual value in testing set')
plt.xticks(rotation=45)
plt.show()