0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

因果推論 Day 8/全30回 マッチング、LaLondeデータで「似た人」を探す

0
Last updated at Posted at 2026-08-18

この連載について

因果推論を「本を読んだ」で終わらせず、自分の言葉で説明でき、コードで再現できる状態まで落とす30日連載です。直前のDay 7では、傾向スコア $e(x)$ を推定し、処置を受ける逆確率の重みで観察データを「ならす」IPWを組みました。今日は同じ傾向スコアを、重み付けではなく**「似た人を探す」**ために使います。

データは、因果推論の歴史でいちばん有名なLaLonde(ラロンド)データです。NSW(National Supported Work、全米支援雇用実証) は1970年代の米国で、仕事に就きにくい人へ職業訓練を提供した実験で、参加者をくじ引きで訓練群と対照群に分けました。ランダムに割り付けたので訓練の効果という答えが分かっています。この実験の対照群を、まったく無関係な政府の労働力調査 CPS(Current Population Survey) の一般労働者にすり替えると、答え合わせのできる観察データが手に入ります。「実験の答えを知ったうえで、観察データの手法がどこまで迫れるか」を測れる、という設定そのものが教材です。


TL;DR(3行)

  • 同じ処置群でも、比べる相手を取り違えると効果は符号ごと崩れます。 職業訓練のRCT(NSW実験)の中で比べた答えは +1,794ドル。対照群だけを、実験とは無関係な全米調査CPSの一般労働者に差し替えると -8,498ドル になります。訓練の中身は何ひとつ変わっていません。
  • マッチング=処置された一人ひとりに「似た対照」を探してペアを組む。傾向スコア1次元の最近傍マッチング(共通サポートへのトリミング込み)で、ATTは +2,202ドル まで戻ります。
  • 戻った根拠はバランス診断(SMD、目安0.1)。人種ダミーのSMDが 2.43 → 0.08。ただしマッチングは実験の再現ではなく、非交絡性を信じたときの最善です。

今日の問い

職業訓練が年収を上げるか。実験の答えは +1,794ドル と分かっている。
手元にあるのが「訓練を受けた185人」と「無関係な調査から集めた15,992人」だけだったとしても、この答えに辿り着けるか?

Day 7 の IPW は、全員を残して重みで集団を「ならす」方法でした。今日はもう1つの道、処置を受けた一人ひとりに似た比較相手を「探す」マッチングをコードにします。そして観察データの推定を、実験の答えにぶつけて採点します。


概念① マッチング:重みで「ならす」か、ペアで「探す」か

IPWとマッチングは、同じ非交絡性の土台の上に立つ兄弟です。IPWは対照群全員を使い、処置群に似た人を重く、似ていない人を軽くして分布をならします。マッチングは逆に、処置ユニット $i$ ごとに共変量(実務では傾向スコア)が最も近い対照ユニット $m(i)$ を1人探してペアを組み、似ていない対照はそもそも使いません。機械学習の言葉にすると K=1 のK近傍法そのもので、実装も sklearn の NearestNeighbors で済みます。

ペアさえ組めれば、推定量はペア内の引き算の平均です。

$$
\hat{\tau}{\text{ATT}} = \frac{1}{N_t} \sum{i:,W_i = 1} \bigl( Y_i - Y_{m(i)} \bigr)
$$

この式が自然に答えるのは ATT(処置群における平均処置効果) $E[Y(1) - Y(0) \mid W = 1]$ です。比較相手を探してもらえるのは処置群の側だからです。今日の問いなら「訓練を受けた人たちにとって、訓練は効いたか」であって、CPSの平均的な人が訓練を受けたらどうなるか(ATE)ではありません。また今日は復元ありマッチングを使います。同じ対照が複数の処置ユニットのペアになることを許すと、マッチの質が上がりバイアスが減る代わりに、実質的に使われる対照が減って分散は増える、というトレードオフです。

前提はDay 6 の道具立てそのままです。非交絡性(この $X$ で条件付ければ割付は as-if random)とオーバーラップに加えて、Day 2 の SUTVA も暗黙に仮定しています。職業訓練は労働市場を介して干渉しうる、つまり訓練を受けた人が職を得た分だけ受けなかった人の職が減るなら、ペアの引き算は「訓練の効果」と「押しのけの効果」を混ぜてしまいます。今日はこれを仮定で封じ、崩れる世界は Day 28(空間干渉)で扱います。

概念② バランス診断:アウトカムを見る前に、SMDで設計を合格させる

マッチングが上手くいったかは、効果の数字ではなく共変量のバランスで判定します。定番の物差しが SMD(標準化平均差) です。

$$
\mathrm{SMD} = \frac{\bar{X}_t - \bar{X}_c}{\sqrt{(s_t^2 + s_c^2)/2}}
$$

平均差をプールした標準偏差で割るので、年齢もドルもダミー変数も同じ物差しで比べられます。経験的な目安は |SMD| < 0.1。全共変量についてマッチング前後のSMDを並べた図はラブプロットと呼ばれ、今日の fig1 がそれです。

大事なのは診断の順番です。Imbens & Rubin はマッチングを設計フェーズの道具と位置づけます。バランスが合格するまでは、アウトカム re78 を一切見ません。効果の数字を見ながら傾向スコアのスペックをいじり始めると、好みの答えが出るスペックを選ぶ誘惑に勝てないからです。


手を動かす①:実験ベンチマーク +1,794ドルを確定する

冒頭で触れたNSWを、もう少し詳しく見ておきます。LaLonde (1986) はこの実験の答えを金標準に据え、対照群だけをCPSやPSIDといった一般調査に差し替えて当時の計量経済学の手法を試し、どの手法も実験の答えを再現できないことを示しました。観察研究への警鐘として有名になった論文です。13年後、Dehejia & Wahba (1999) が同じデータに傾向スコア法で再挑戦し、共通サポートに注意すれば実験の答えの近くまで戻れることを示しました。今日使う nsw_mixtape はその Dehejia-Wahba サンプルです。

最初に金標準を確定します。実験の中の単純比較です。

from causaldata import nsw_mixtape

nsw = nsw_mixtape.load_pandas().data   # NSW職業訓練実験(Dehejia-Wahbaサンプル)
t = nsw.query("treat == 1")            # 職業訓練を受けた処置群
c_exp = nsw.query("treat == 0")        # 実験の対照群

bench = t["re78"].mean() - c_exp["re78"].mean()
print("実験ベンチマーク = %+.0f ドル(処置 %d人, 対照 %d人)" % (bench, len(t), len(c_exp)))
実験ベンチマーク = +1794 ドル(処置 185人, 対照 260人)

re78 は1978年(訓練後)の年収です。無作為割付なので、Day 4 で見たとおり引き算がそのまま因果効果になります。訓練は年収を平均 +1,794ドル 押し上げました。以降、この数字を答えとして持ち歩きます。


手を動かす②:素朴比較が -8,498ドルに崩壊する

ここからが観察研究の設定です。実験の対照群260人を捨て、代わりに一般人口の調査CPSから15,992人を対照群として連れてきます。まず何も考えずに引き算します。

import pandas as pd
from causaldata import nsw_mixtape, cps_mixtape

nsw = nsw_mixtape.load_pandas().data
cps = cps_mixtape.load_pandas().data   # CPS:一般人口の調査データ(観察対照群)
t = nsw.query("treat == 1")

naive = t["re78"].mean() - cps["re78"].mean()
print("素朴比較(NSW処置群 vs CPS対照群)= %+.0f ドル" % naive)

cols = ["age", "black", "marr", "re74"]
cmp = pd.DataFrame({
    "NSW処置群": t[cols].mean(),
    "CPS対照群": cps[cols].mean(),
}).round(2)
print(cmp)
素朴比較(NSW処置群 vs CPS対照群)= -8498 ドル
        NSW処置群    CPS対照群
age      25.82     33.23
black     0.84      0.07
marr      0.19      0.71
re74   2095.57  14016.80

訓練を受けると年収が 8,498ドル下がる、という数字が出ました。もちろん訓練のせいではありません。下の共変量を見れば理由は明白です。NSWの対象者は若く、84%が黒人で、既婚率19%、訓練前(1974年)の年収は平均2,096ドルの低所得層です。CPSは平均的な米国の労働者で、訓練前から年収が14,017ドルあります。そもそも年収水準の違う2つの集団を引き算しているだけで、Day 6 の言葉で言えば交絡因子 $X$ の分布が壊滅的にズレています。これが LaLonde が突きつけた出発点です。


手を動かす③:傾向スコアで「似た人」を探し、+2,202ドルまで戻す

ここから Dehejia & Wahba の再挑戦をなぞります。手順は概念②のフローのとおり、傾向スコア推定 → 共通サポートへのトリミング → 最近傍マッチング → バランス診断、の順です。マッチングの距離は傾向スコアそのものではなく、ロジット変換した線形化傾向スコア $\log(e/(1-e))$ で測ります。$e(x)$ は0と1の近くで非線形に圧縮されているので、端での「近さ」が歪むのを補正するためです(Imbens & Rubin 第15章)。

import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
from causaldata import nsw_mixtape, cps_mixtape
from sklearn.neighbors import NearestNeighbors

nsw = nsw_mixtape.load_pandas().data
cps = cps_mixtape.load_pandas().data
t = nsw.query("treat == 1")
bench = t["re78"].mean() - nsw.query("treat == 0")["re78"].mean()

df = pd.concat([t, cps], ignore_index=True).copy()
df["u74"] = (df["re74"] == 0).astype(int)   # 1974年に収入ゼロ
df["u75"] = (df["re75"] == 0).astype(int)   # 1975年に収入ゼロ

# 傾向スコア(Dehejia & Wahba 1999 流のスペック)
f = ("treat ~ age + I(age**2) + I(age**3) + educ + I(educ**2)"
     " + black + hisp + marr + nodegree + re74 + re75 + u74 + u75 + educ:re74")
df["ps"] = smf.logit(f, data=df).fit(disp=0).predict(df)

# 共通サポートへのトリミング:処置群の傾向スコア帯の外にいる対照は使わない
tr, co = df.query("treat == 1"), df.query("treat == 0")
co_in = co[co["ps"].between(tr["ps"].min(), tr["ps"].max())]

# 線形化傾向スコア logit e(x) の1次元で、1対1の最近傍マッチング(復元あり)
def lps(p):
    return np.log(p / (1 - p))

nn = NearestNeighbors(n_neighbors=1).fit(lps(co_in["ps"]).to_numpy().reshape(-1, 1))
_, idx = nn.kneighbors(lps(tr["ps"]).to_numpy().reshape(-1, 1))
matched = co_in.iloc[idx.ravel()]

att = (tr["re78"].values - matched["re78"].values).mean()
print("共通サポート内の対照 = %d / %d 人" % (len(co_in), len(co)))
print("マッチング後の ATT = %+.0f ドル(実験ベンチマーク %+.0f)" % (att, bench))

# バランス診断:マッチング前後のSMD(標準化平均差)
covs = ["age", "educ", "black", "hisp", "marr", "nodegree", "re74", "re75"]
def smd(a, b):
    return (a.mean() - b.mean()) / np.sqrt((a.var() + b.var()) / 2)

bal = pd.DataFrame({
    "マッチング前": [smd(tr[v], co[v]) for v in covs],
    "マッチング後": [smd(tr[v], matched[v]) for v in covs],
}, index=covs).round(2)
print(bal)
共通サポート内の対照 = 5355 / 15992 人
マッチング後の ATT = +2202 ドル(実験ベンチマーク +1794)
          マッチング前  マッチング後
age        -0.80    0.10
educ       -0.68   -0.18
black       2.43   -0.08
hisp       -0.05    0.07
marr       -1.23    0.07
nodegree    0.90    0.16
re74       -1.57    0.09
re75       -1.75   -0.00

まずトリミングの行を見てください。CPSの15,992人のうち、処置群の傾向スコア帯に入る対照は 5,355人 だけです。3人に2人は「NSWの対象者に似ている確率」が低すぎて、比較相手になりません。下の図がその様子です。左の網掛けが共通サポートの外で、CPSの大半がここに落ちます。さらに、サポート内の5,355人のうち実際にペアとして選ばれた対照は115人でした。復元ありマッチングでは、よく似た少数の対照が繰り返し使われます。

マッチングの模式(処置ユニットごとに傾向スコア最近傍の対照を探す。左の網掛け=共通サポート外のCPSは使わない)

次にバランス診断です。マッチング前は black のSMDが 2.43、訓練前年収 re75 が 1.75 と、目安0.1の20倍を超える壊滅的な偏りでした。マッチング後は8共変量中6つが0.1の内側に入り、black は 0.08 まで縮みます(educ と nodegree に 0.18、0.16 の偏りが残ります。1次元のスコアでのマッチングは万能ではありません)。ラブプロットで見ると一目瞭然です。

マッチング前後の共変量バランス(ラブプロット)。blackのSMD 2.43が0.08へ。赤破線が目安0.1

設計が概ね合格したので、ここで初めてアウトカムを見ます。ATTは +2,202ドル。素朴比較の -8,498ドルから、実験の答え +1,794ドルのすぐ近くまで戻りました。185ペアの差のばらつきから見積もると標準誤差は700ドル弱あるので(同じ対照の再利用を無視した目安です)、+2,202 と +1,794 の違いはノイズの範囲です。3つの推定を並べたのが下の図です。

推定値の比較(実験ベンチマーク+1794/素朴比較-8498/マッチング+2202。赤破線が実験の答え)


つまづき・誤解しやすい点

  • 「実験の答えに戻った=マッチングは実験の代わりになる」ではありません。 今日戻れたのは、割付を説明する共変量(特に訓練前2年分の収入)がたまたま観測されていたからです。マッチングがそろえられるのは観測した $X$ の分布だけで、隠れた交絡には無力です。Day 6 の「検証できない」はここでも生きていて、マッチングは非交絡性を信じたときの最善にすぎません。その信念がどれだけ崩れると結論が覆るかは、次回Day 9 の感度分析で定量化します。
  • 傾向スコアのスペックで答えが動きます。 上のコードから3乗項と交差項を落とした素直なスペックに変えると、ATTは +513ドル まで落ち、バランスも最大|SMD| 0.21 と悪化しました。バランスの良いスペックのほうがベンチマークに近かったのは偶然ではなく、これがバランス診断でスペックを選ぶ(ATTを見て選ばない)という規律の根拠です。効果の数字を見ながらスペックを探すと、+513 から +2,202 まで並んだ候補から好みの答えを選ぶ行為になってしまいます。
  • 答えているのはATTで、しかも共通サポート内の話です。 今日の +2,202ドルは「NSWの対象者のような低所得層が訓練を受けたときの効果」であって、CPSの平均的な労働者への効果ではありません。CPSの3人に2人を捨てた時点で、語れる対象は狭くなっています。語れる範囲が狭くなるのは手法の欠点ではなく、外挿で語らないという正直さです。

実務での使いどころ(空間データ/天地人の文脈)

  • 管路更新の効果検証はマッチングで設計できます。 更新を進めた自治体(処置)に対して、人口動態・管路延長・布設年代・財政力の似た未更新自治体を探してペアを組みます。素朴な「更新済み vs 未更新」の全体比較は、財政に余裕のある自治体ほど更新が早いという自己選択のせいで、今日の -8,498ドル型の事故を起こします。
  • 共通サポートの外は語らない、と最初に宣言します。 政令指定都市のように似た比較相手が存在しない自治体は推定から外し、外したことを明記します。CPSの3人に2人を捨てた今日の操作と同じで、「全自治体に効果があると言えます」より「この範囲の自治体なら言えます」のほうが強い報告です。
  • ラブプロットをレポートの1枚目に置きます。 効果の数字より先にバランス診断を見せ、「そもそも比較になっている」ことを示してから効果を語ります。SMDの目安0.1は、衛星データ由来の共変量(地表面温度・地盤変動・土地利用)でもそのまま使えます。
  • 隣の自治体に効果が漏れるなら、ペアの引き算では捕まえられません。 広域水道企業団や広域発注があると、処置の効果が対照側に波及してSUTVAが崩れます。ここは Day 28(空間×因果)の主戦場です。

参考(本棚)

  • Imbens & Rubin『Causal Inference for Statistics, Social, and Biomedical Sciences』第15章 マッチングによる共変量バランスの改善。設計フェーズとしてのマッチング・線形化傾向スコア・「アウトカムを見ない」原則。今日の下敷き
  • 同 第18章 マッチング推定量。ATT推定量・復元あり/なしのトレードオフ・バイアス修正。ペアの引き算の式の出どころ
  • Matheus Facure『Causal Inference for the Brave and True』第5章。KNNとしてのマッチングと、マッチング推定量のバイアスの議論
  • LaLonde (1986) "Evaluating the Econometric Evaluations of Training Programs with Experimental Data", American Economic Review。観察手法への警鐘となった原典
  • Dehejia & Wahba (1999) "Causal Effects in Nonexperimental Studies", JASA。傾向スコアでの再挑戦。今日のスペックの元
  • データ: causaldata パッケージの nsw_mixtape / cps_mixtape(Scott Cunningham『Causal Inference: The Mixtape』掲載データ、MITライセンス、2026-08-15取得)。本文の数値はすべて掲載コードの実行結果です

次回予告(Day 9)

マッチングもIPWも、そろえられるのは観測できた $X$ だけです。では、測れていない交絡が残っていたら結論はどうなるのか。Day 6 で「非交絡性は検証できない」と観念しましたが、諦める代わりに問いを変えます。「どれだけ強い隠れ交絡なら、この結論が覆るか」。この強さを1つの数字にするのが E-value で、次回Day 9 は感度分析を手組みします。今日の +2,202ドルがどれくらい打たれ強い数字なのかも、そこで採点します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?