単純な線形回帰は本質的に因果モデルです
線形回帰モデルが因果関係にない場合に思いついた例を次に示します。薬物が時間0(t = 0)に服用され、t = 1 で心臓発作の速度に影響を与えないことをアプリオリとしましょう。で心臓発作トン= 1で心臓発作に影響= 2トンを(つまり、以前のダメージがダメージを受けやすく心になります)。t = 3での生存は、人々がt = 2で心臓発作を起こしたかどうかのみに依存します。t= 1での心臓発作は、t = 3での生存に現実的に影響しますが、矢印はありません。シンプル。
凡例は次のとおりです。
真の因果グラフは次のとおりです。
t = 1での心臓発作がt = 0での薬物摂取とは無関係であることを知らないふりをして、単純な線形回帰モデルを構築して、t = 0での心臓発作に対する薬物の効果を推定します。ここで、予測変数はDrug t = 0であり、結果変数はHeart Attack t = 1です。持っているデータはt = 3で生き残っている人だけなので、そのデータで回帰を実行します。
以下は、Drug t = 0の係数の95%ベイジアン信頼区間です。
確認できる確率の多くは0より大きいため、効果があるように見えます!ただし、効果が0であることをアプリオリに知っています。Judea Pearlなどによって開発された因果関係の数学により、この例には(コライダーの子孫の条件付けに起因する)バイアスがあることが容易にわかります。Judeaの仕事は、このような状況では、完全なデータセットを使用する必要があることを意味します(つまり、生き残った人だけを見る必要はありません)。
完全なデータセットを見たときの95%の信頼できる間隔を次に示します(つまり、生き残った人を条件にしない)。
。
これは0を中心に密に配置されており、本質的にはまったく関連付けられていません。
実際の例では、物事はそれほど単純ではないかもしれません。システマティックバイアスを引き起こす可能性のある変数はさらに多くあります(混乱、選択バイアスなど)。分析で調整するものは、パールによって数学化されています。アルゴリズムは、調整する変数を提案したり、調整が系統的バイアスを除去するのに十分ではない場合を教えてくれます。この正式な理論が設定されていれば、何を調整すべきか、何を調整すべきでないかについて議論するのにそれほど時間を費やす必要はありません。結果が適切かどうかについて、すぐに結論を出すことができます。実験をより良く設計でき、観測データをより簡単に分析できます。
ここだ、自由に利用可能なコースは、因果のDAGにオンラインミゲルエルナンによります。それには、教授/科学者/統計学者が目前の問題について反対の結論に達した実生活のケーススタディがたくさんあります。それらのいくつかはパラドックスのように見えるかもしれません。ただし、Judea Pearlのd-separationおよびbackdoor-criterionを使用して簡単に解決できます。
参考までに、データ生成プロセスのコードと、上記の信頼できる間隔のコードを以下に示します。
import numpy as np
import pandas as pd
import statsmodels as sm
import pymc3 as pm
from sklearn.linear_model import LinearRegression
%matplotlib inline
# notice that taking the drug is independent of heart attack at time 1.
# heart_attack_time_1 doesn't "listen" to take_drug_t_0
take_drug_t_0 = np.random.binomial(n=1, p=0.7, size=10000)
heart_attack_time_1 = np.random.binomial(n=1, p=0.4, size=10000)
proba_heart_attack_time_2 = []
# heart_attack_time_1 increases the probability of heart_attack_time_2. Let's say
# it's because it weakens the heart and makes it more susceptible to further
# injuries
#
# Yet, take_drug_t_0 decreases the probability of heart attacks happening at
# time 2
for drug_t_0, heart_attack_t_1 in zip(take_drug_t_0, heart_attack_time_1):
if drug_t_0 == 0 and heart_attack_t_1 == 0:
proba_heart_attack_time_2.append(0.1)
elif drug_t_0 == 1 and heart_attack_t_1 == 0:
proba_heart_attack_time_2.append(0.1)
elif drug_t_0 == 0 and heart_attack_t_1 == 1:
proba_heart_attack_time_2.append(0.5)
elif drug_t_0 == 1 and heart_attack_t_1 == 1:
proba_heart_attack_time_2.append(0.05)
heart_attack_time_2 = np.random.binomial(
n=2, p=proba_heart_attack_time_2, size=10000
)
# people who've had a heart attack at time 2 are more likely to die by time 3
proba_survive_t_3 = []
for heart_attack_t_2 in heart_attack_time_2:
if heart_attack_t_2 == 0:
proba_survive_t_3.append(0.95)
else:
proba_survive_t_3.append(0.6)
survive_t_3 = np.random.binomial(
n=1, p=proba_survive_t_3, size=10000
)
df = pd.DataFrame(
{
'survive_t_3': survive_t_3,
'take_drug_t_0': take_drug_t_0,
'heart_attack_time_1': heart_attack_time_1,
'heart_attack_time_2': heart_attack_time_2
}
)
# we only have access to data of the people who survived
survive_t_3_data = df[
df['survive_t_3'] == 1
]
survive_t_3_X = survive_t_3_data[['take_drug_t_0']]
lr = LinearRegression()
lr.fit(survive_t_3_X, survive_t_3_data['heart_attack_time_1'])
lr.coef_
with pm.Model() as collider_bias_model_normal:
alpha = pm.Normal(name='alpha', mu=0, sd=1)
take_drug_t_0 = pm.Normal(name='take_drug_t_0', mu=0, sd=1)
summation = alpha + take_drug_t_0 * survive_t_3_data['take_drug_t_0']
sigma = pm.Exponential('sigma', lam=1)
pm.Normal(
name='observed',
mu=summation,
sd=sigma,
observed=survive_t_3_data['heart_attack_time_1']
)
collider_bias_normal_trace = pm.sample(2000, tune=1000)
pm.plot_posterior(collider_bias_normal_trace['take_drug_t_0'])
with pm.Model() as no_collider_bias_model_normal:
alpha = pm.Normal(name='alpha', mu=0, sd=1)
take_drug_t_0 = pm.Normal(name='take_drug_t_0', mu=0, sd=1)
summation = alpha + take_drug_t_0 * df['take_drug_t_0']
sigma = pm.Exponential('sigma', lam=1)
pm.Normal(
name='observed',
mu=summation,
sd=sigma,
observed=df['heart_attack_time_1']
)
no_collider_bias_normal_trace = pm.sample(2000, tune=2000)
pm.plot_posterior(no_collider_bias_normal_trace['take_drug_t_0'])