傾向スコア#

バランシングスコア#

バランシングスコア(balancing score)b(x)とは、b(x)が条件付けられた下での共変量xの分布が処置zと独立になる(処置群(z=1)と対照群(z=0)とでxの条件付き分布が等しくなる)ような関数である(Rosenbaum & Rubin, 1983)。

xz|b(x)

共変量x自身は最も細かい(finest)バランシングスコアであり、傾向スコアP(z=1|x)は最も粗い(coarsest)バランシングスコアである。

傾向スコア#

共変量xiの下でその対象iが処置される確率

ei=P(zi=1|xi)

傾向スコア(propensity score)という。

傾向スコアの使用方法#

1. マッチング

傾向スコアの値が同じ(厳密マッチング)か近い(最近傍マッチング)個体を比較する

2. 層別解析

傾向スコアの値によっていくつかのサブクラスに分け、各クラスで処置群と対照群の平均をとって全体のATEを推定する

3. 回帰分析・共分散分析

割当変数zと傾向スコアeを説明変数とした線形の回帰分析を行う

4. その他の推定量

IPW推定量やDR推定量など(後述)

傾向スコアの長所#

  1. 共変量を1次元に次元削減しているため、2つの群において共変量の値に重なりが少ない場合でも使える

  2. 共変量と結果変数のモデル設定を行わなくてもよい

    • 例えば回帰分析のような線形モデルを構築しなくてもよい → 共変量と結果変数の間に線形性を仮定できない場合でも使える可能性

  3. モデルの誤設定に強い

    • 共分散分析やロジスティック回帰によりE[y|x]を直接モデリングする方法と、層別による傾向スコア解析を比較したところ、傾向スコア解析のほうがモデルの誤設定から生じるバイアスがより少なかったという研究がある(Drake, 1993)

層別解析#

傾向スコアの値に応じて、分析対象をK個(例えば5つ)の部分集合に分け、各クラスで処置群と対照群の結果変数の平均を計算し、それらの加重平均を全体の推定量とする

τ^SA=k=1KNkN(y¯1ky¯0k)

IPW推定量#

逆確率重み付け(inverse probability weighting: IPW)による推定量(Rubin, 1985)

E^(y1)=i=1Nziyiei/i=1NzieiE^(y0)=i=1N(1zi)yi1ei/i=1N1zi1ei

によって各群の結果変数の期待値を推定でき、両者の差分でATEを推定できることが知られている。

なお、分子においてziyiのように処置変数ziを乗じているのは処置群を取り出すため。重みを

wi=ziei+1zi1ei

と考えればわかりやすいかもしれない。たとえば個体iが処置群ならzi=1となり第一項だけが残り、

yiwi=yi1ei

となる。

導出

真の傾向スコアの値が既知で、「強く無視できる割り当て」条件が成立するなら、

E(y1)=Ex[E(y1|x)]=Ex[E(ze|x)E(y1|x)](E(z/e)=E(z)/e=e/e=1)=Ex[E(zey1|x)]=E(zy1e)=E(z2y1+z(1z)y0e)(z2=z,z(1z)=0)=E(zye)

となり、E^(y1)y1の周辺平均の不偏推定量になる。E^(y0)も同様である。

真の傾向スコアeではなく推定値e^iを使ったとしても一致推定量となる。

Nを大きくすると

1Ni=1Nziei1,1Ni=1N1zi1ei1

に確率収束することが大数の法則により成立する。

E(z|x)=1p(z=1|x)+0p(z=0|x)=e
E(ze)=E(z)e=Ex[E(z|x)]e=ee=1

IPW推定のイメージ#

IPWはもともと標本抽出法におけるHorvitz–Thompson推定量からアイデアを得ている。Horvitz–Thompson推定量は層化抽出されたサンプルにおいて抽出確率の逆数で重みを付けることで、抽出確率が低いカテゴリの推定への影響度を高めて補正する。

同様にIPWも傾向スコアによって重み付けを行う。

Hide code cell source
import numpy as np
n = 100
np.random.seed(0)
x = np.random.normal(loc=10, size=n)

s = x - x.mean()
s = (s - s.min()) / (s.max() - s.min())

z = np.random.binomial(n=1, p=s)
tau = 1
y = z * tau + x + np.random.normal(loc=0, size=n)

import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=[4.5,3])
ax.scatter(x[z == 0], y[z == 0], label="z == 0", alpha=0.5)
ax.scatter(x[z == 1], y[z == 1], label="z == 1", alpha=0.5)
ax.set(xlabel="x", ylabel="y")
ax.legend()

import statsmodels.api as sm
logit_res = sm.Logit(z, sm.add_constant(x)).fit(disp=0)
x_range = np.linspace(x.min(), x.max(), 10)
p_hat = logit_res.predict(sm.add_constant(x_range))

ax2 = ax.twinx()
ax2.plot(x_range, p_hat)
ax2.set(ylabel="p_hat(z|x)")

fig.show()
../_images/943cf09be40978d0d01cff829ac251211ab6c9e8dff8cb85cfbfdee2bfa6e4d4.png
ate_naive = np.mean(y[z == 1]) - np.mean(y[z == 0])
print(f"{ate_naive=:.1f}")

p_hat = logit_res.predict(sm.add_constant(x))
y1_ipw = sum(z * y / p_hat) / sum(z / p_hat)
y0_ipw = sum( (1 - z) * y / (1 - p_hat) ) / sum( (1 - z) / (1 - p_hat) )
ate_ipw = y1_ipw - y0_ipw
print(f"{ate_ipw=:.1f}")
ate_naive=2.0
ate_ipw=1.4
Hide code cell source
# IPWとのつながり
# 単純な条件付き平均と同じになる
assert ( y[z==1].mean() - sum(y*z) / sum(z) ) < 10e-15
Hide code cell source
fig, axes = plt.subplots(nrows=2, figsize=[6, 6])
fig.subplots_adjust(hspace=0.4)

# 1: naive estimator
ax = axes[0]
ax.hist(y[z == 0], label="z == 0", alpha=.5)
ax.hist(y[z == 1], label="z == 1", alpha=.5)
ax.set(xlabel="y")
ax.legend()

y1 = np.mean(y[z == 1])
ax.axvline(y1, color="tomato")
ax.text(y1 * 1.01, 8, f"E[y|z=1]={y1:.1f}", color="tomato")
y0 = np.mean(y[z == 0])
ax.axvline(y0, color="steelblue")
ax.text(y0 * 1.01, 11, f"E[y|z=0]={y0:.1f}", color="steelblue")
ax.set(xlabel="y", title=f"naive estimate: E[y|z=1] - E[y|z=0] = {y1 - y0:.1f}")
ax.axhline(10, xmin=np.mean(y[z == 0]), xmax=np.mean(y[z == 1]), color="black")


# 2. IPW estimate
ax = axes[1]
ax.hist( y * (1-z) / (1-p_hat), label="z == 0", alpha=.5)
ax.hist( y * z / p_hat , label="z == 1", alpha=.5)
ax.set(xlabel="y (weighted)")
ax.legend()

y1 = sum(y*z / p_hat) / sum(z / p_hat)
ax.axvline(y1, color="tomato")
ax.text(y1 * 1.1, 40, f"E[y1]_IPW={y1:.1f}", color="tomato")
y0 = sum( y*(1-z) / (1-p_hat) ) / sum((1 - z) / (1-p_hat))
ax.axvline(y0, color="steelblue")
ax.text(y0 * 1.1, 30, f"E[y0]_IPW={y0:.1f}", color="steelblue")

ax.axhline(10, xmin=np.mean(y[z == 0]), xmax=np.mean(y[z == 1]), color="black")
ax.set(title=f"IPW Estimate: E[y1]_IPW - E[y0]_IPW = {y1 - y0:.1f}")

fig.show()
../_images/7fb232385f7b4e13914828655e4b2e34106836c3b843d7dcb3e0fbaf9da77a3f.png

Bayesian IPW#

DR推定量#

傾向スコア解析の改善余地が2点ある

  1. 傾向スコア推定後に結果変数の周辺分布を推定する際にz=0のグループの共変量の情報を用いておらず、データを無駄にしている

  2. 傾向スコアを計算する際のモデルが誤った場合、推定結果が誤ったものになる可能性

これらを解決するのがDR推定量。

二重にロバストな推定量(doubly robust estimator)は、傾向スコアと、共変量で結果変数を説明する回帰関数を両方用いる方法

傾向スコア算出のためのモデルp(z|x,α)=e(x,α)の母数αの一致推定量をα^、共変量で結果変数を説明する回帰関数E[y1|x]=g(x,β1)の母数の推定量をβ^1とすると

E^DR(y1)=1Ni=1N[ziyie(xi,α^)+(1zie(xi,α^))g(xi,β^1)]=1Ni=1N[ziyie(xi,α^)(zie(xi,α^)e(xi,α^))g(xi,β^1)]=1Ni=1N[yi1+(zie(xi,α^)e(xi,α^))(yi1g(xi,β^1))]

となる。

以下の条件のどちらかが成立していれば、DR推定量は結果変数の周辺平均の一致推定量となる

  • 条件A: 傾向スコアを計算する際に利用されるモデルが正しく指定されている

  • 条件B: 共変量で結果変数を説明する回帰関数のモデルが正しく指定されている

共変量釣り合い傾向スコア#

Imai, K., & Ratkovic, M. (2014). Covariate balancing propensity score.

**共変量釣り合い傾向スコア(covariate balancing propensity score:CBPS)**は、対照群・処置群の共変量の期待値が一致する(バランスする)という制約条件の下で傾向スコアを推定する方法

逆確率で重みを付けたとき、期待値がバランスしててほしい

E[DiXije(Xi)]=E[(1Di)Xij1e(Xi)]

推定はロジスティック回帰

log(e(Xi)1e(Xi))=β0+β1Xi1++βpXip

モーメント条件

E[DiXi1e(Xi)(1Di)Xi11e(Xi)]=0E[DiXije(Xi)(1Di)Xij1e(Xi)]=0

シンプルながらもモデルの誤特定に頑健らしい

モーメント法特有の過小識別の問題はどうクリアしてる?

causallib#

参考文献#