傾向スコア(propensity score)は、ある個体が処置を受ける確率を共変量から推定した値です。たとえば「70歳・重症度が高い患者が手術を受ける確率は0.6」のように、年齢や重症度といった多数の背景を1つの確率にまとめます。この値が近い人どうしを比べれば、無作為割り付けができない観察データでも、処置群と対照群を背景のそろった状態で比較できます。
ただし「傾向スコアを使えば交絡が消える」という理解は誤りです。この記事では、定義と前提から推定・マッチング・IPTW・SMDの計算・Love plotの描き方までを、原典の論文と、scikit-learn 1.9.1で実行して出力を確認したPythonコードで追いかけます。
まとめ:傾向スコアで調整できるもの、できないもの
傾向スコアが調整できるのは、モデルの説明変数に入れた測定済みの共変量だけです。未測定の交絡因子は調整されません。実務の判断は次の表のとおりです。
| 論点 | 結論 | 根拠 |
|---|---|---|
| 推定モデル | ロジスティック回帰などで推定 | 共変量バランスで適否を評価 |
| scikit-learn | C=np.inf を指定(1.8以降) | 1.8 changelog |
| マッチング幅 | ロジット変換PSの標準偏差×0.2 | Austin 2009 |
| バランス指標 | p値でなくSMD | 標本サイズに依存しない |
| SMDの目安 | 0.1は「提案」で合意ではない | Austin 2009 |
| 重み付け後のSMD | 分母の定義を必ず書く | ツールで値が変わる |
| 極端な重み | 推定対象を確認し、ATOならoverlap weightを検討 | Li et al. 2018 |
傾向スコアによる調整後も、未測定交絡が残り得る点はRCTとの違いです。記事の後半で、傾向スコア分析を疑似RCTと呼ぶことへの批判とあわせて扱います。
傾向スコアの定義と、回帰分析で共変量を入れる方法との違い
Rosenbaum and Rubin(1983)が定めた「処置を受ける確率」
傾向スコアは Rosenbaum PR, Rubin DB「The central role of the propensity score in observational studies for causal effects」Biometrika 1983;70(1):41-55 で提案されました。共変量の集合 x を持つ個体が処置群に入る確率 e(x) が傾向スコアです。
この定義の要点は、多次元の共変量を1次元に落とすところにあります。共変量が20個あれば、20個すべてが一致するペアを探すのは事実上不可能です。傾向スコアは20次元を0から1の値へ圧縮し、「同じ状態」という条件を「処置を受ける確率が同じ」へ緩めます。日本語では傾向値、カタカナではプロペンシティスコアとも呼ばれ、いずれも同じものを指します。
強く無視可能な割り付けの構成条件:無交絡性と正値性
同論文は、傾向スコアで交絡を除けるための条件を「強く無視可能な処置割り付け(strongly ignorable treatment assignment)」として定めています。中身は2つです。
- 無交絡性:共変量 x を条件付けると、処置の割り付けが潜在的なアウトカムと独立になる。言い換えると、処置とアウトカムの両方に効く変数がすべて x に入っている
- 正値性(positivity):どの x についても 0 < e(x) < 1。処置を必ず受ける人・絶対に受けない人がいない
無交絡性はデータから検証できません。モデルに入れなかった変数が交絡していないことは、統計量では示せないからです。推定した傾向スコアの分布は重なり不足の診断に使えますが、正値性の成立を証明するものではありません。処置群でスコアが0に近い場合、または対照群で1に近い場合に、IPTWの重みが極端に大きくなります。
共変量・交絡因子・処置後変数の切り分け
共変量は処置とアウトカムの関係を見るときに考慮する背景変数の総称で、交絡因子はそのうち処置とアウトカムの両方に影響するものを指します。Austin and Stuart(2015)は、処置の選択にだけ効く変数よりも、アウトカムに関係する変数(予後因子)や交絡因子をモデルに入れる方が望ましいとし、変数は因果ダイアグラムと先行研究・専門家の知見で選び、分析データでの仮説検定で選ばないよう勧めています。処置にだけ効く変数(操作変数)を入れると、効果推定のバイアスと分散がかえって増えることがあります(同論文が引く Myers et al. 2011)。
入れてはいけないのが、処置の後に決まる変数(処置後変数)です。手術の有無を処置とするとき、術後合併症の有無をモデルに入れると、処置の効果そのものを説明変数として打ち消してしまいます。共変量の候補に欠測が多い場合の扱いは欠測値とは?欠損値との違い・種類と補完・削除の対処法で整理しています。
アウトカム回帰と傾向スコア調整における外挿リスクの違い
共変量をアウトカムの回帰モデルに全部入れる方法(多変量解析)とは、失敗の出方が違います。回帰は、共変量の重なりがない領域でもモデルが外挿して推定値を返します。「処置群にしか存在しない年齢層」があっても、式の上では答えが出ます。
傾向スコアは、アウトカムを見る前に群の比較可能性を確認する設計です。マッチングや重み付けの段階で重なりのない個体が落ちるか、重みが極端になるため、外挿が起きていることに気づけます。回帰係数の読み方そのものは重回帰分析とは?単回帰との違い・式の立て方と結果の見方を実例で解説で扱っています。
傾向スコアの推定手順と、scikit-learnの既定値が起こす問題
ロジスティック回帰による推定と共変量の選び方
推定は、処置の有無を目的変数、共変量を説明変数としたロジスティック回帰が既定です。予測精度(AUC)を上げることは目的ではありません。目的はバランスであり、AUCが高すぎるモデルは両群の重なりが小さいことを意味するので、むしろ警告として読みます。本記事ではロジスティック回帰を出発点にしますが、非線形性や高次元性が強い場合は勾配ブースティングなどの機械学習による推定も候補になります。採用するモデルは予測精度だけでなく、調整後の共変量バランスで評価します。線形の判別モデルとの関係は判別分析とは?線形判別・マハラノビス距離・正準判別分析の違いとExcel・Pythonでの手順が参考になります。
scikit-learn 1.8以降の正則化解除:C=np.infの指定
Pythonで実装するとき最初に踏む落とし穴がここです。LogisticRegression は既定で L2 正則化(C=1.0)がかかっており、Rの glm(family = binomial) のような最尤推定ではありません。後述する overlap weighting の厳密バランスのように、傾向スコアの性質の一部は最尤推定を前提にしています。
正則化を外す書き方は、scikit-learn 1.8で変わりました。1.8の変更履歴は penalty 引数を非推奨にし、1.10で削除すると告知しています。代わりに「use C=np.inf instead of penalty=None」とあります。1.9.1で penalty=None を渡すと FutureWarning が出ます。これから書くコードは LogisticRegression(C=np.inf) にしてください。多くの解説記事に載っている penalty=None は、1.10で動かなくなります。
傾向スコアマッチングの種類とキャリパー幅0.2の根拠
最近傍マッチングとキャリパー制約
最も使われるのが最近傍マッチングです。処置群の各個体について、傾向スコアが最も近い対照群の個体を選びます。ただし「最も近い」だけでは、遠く離れた相手でもペアになります。そこで許容差の上限を決めるのがキャリパーです。
Austin(2009)は解析で「calipers of width equal to 0.2 of the standard deviation of the logit of the propensity score」を採用しています。傾向スコアそのものではなくロジット変換した値の標準偏差に0.2を掛ける、という点が実装で間違えやすいところです。
復元の有無・1対kマッチング・CEM
非復元(一度使った対照は再利用しない)が既定です。復元ありにすると対照群の同じ個体が何度も使われ、実効サンプルサイズが見た目より小さくなります。1対2、1対3にすると利用する対照数を増やせますが、追加する対照は近さの点で劣ることがあり、バランスが悪化する可能性があります。比率ごとにバランスと推定精度を比較します。
傾向スコアを介さない選択肢がCEM(coarsened exact matching)です。年齢を10歳刻みにするなど共変量を粗く区切り、同じ区画どうしを完全一致でマッチさせます。共変量が少なく、区切り方に臨床的な根拠があるときはこちらの方が説明しやすくなります。
「傾向スコアでマッチングするな」という批判
King G, Nielsen R「Why Propensity Scores Should Not Be Used for Matching」Political Analysis 2019;27(4):435-454 は、傾向スコアマッチングが「often accomplishes the opposite of its intended goal—thus increasing imbalance, inefficiency, model dependence, and bias」と主張しました。傾向スコアマッチングは完全無作為化実験を近似するだけで、CEMやマハラノビス距離マッチングのように層別(ブロック化)した実験を近似しないため、取り除けるはずの不均衡を見逃す、という理屈です。
特に、すでにバランスが良いデータでキャリパーを狭めて刈り込み続けると、傾向スコアがほぼ同じ個体どうしの組み合わせはランダムに近くなり、元データより不均衡が増えることがあります。実務の対応は単純で、刈り込みの前後でSMDを毎回確認し、悪化したらそれ以上狭めないことです。同論文も傾向スコア自体には「other productive uses」があるとしており、重み付けでの利用まで否定してはいません。
Pythonでの実装と実行結果
次のコードは2,000件の人工データで傾向スコアを推定し、キャリパー付き最近傍マッチングを行い、マッチング前後とIPTWのSMDを比較します。
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
rng = np.random.default_rng(20260906)
n = 2000
age = rng.normal(60, 12, n)
severity = rng.normal(0, 1, n)
female = rng.binomial(1, 0.45, n)
logit_t = -6.0 + 0.06 * age + 0.8 * severity + 0.3 * female
treat = rng.binomial(1, 1 / (1 + np.exp(-logit_t)))
X = pd.DataFrame({"age": age, "severity": severity, "female": female})
model = LogisticRegression(C=np.inf, max_iter=1000).fit(X, treat)
ps = model.predict_proba(X)[:, 1]
def smd(x, t, w=None):
if w is None:
w = np.ones_like(x, dtype=float)
a, b = w[t == 1], w[t == 0]
xa, xb = x[t == 1], x[t == 0]
ma = np.average(xa, weights=a)
mb = np.average(xb, weights=b)
va = np.average((xa - ma) ** 2, weights=a)
vb = np.average((xb - mb) ** 2, weights=b)
return (ma - mb) / np.sqrt((va + vb) / 2)
logit_ps = np.log(ps / (1 - ps))
caliper = 0.2 * logit_ps.std(ddof=1)
idx_t = np.flatnonzero(treat == 1)
pool = list(np.flatnonzero(treat == 0))
pairs = []
for i in idx_t:
if not pool:
break
d = np.abs(logit_ps[pool] - logit_ps[i])
j = int(np.argmin(d))
if d[j] <= caliper:
pairs.append((i, pool.pop(j)))
m_idx = np.array([k for p in pairs for k in p])
w_iptw = np.where(treat == 1, 1 / ps, 1 / (1 - ps))
print(f"treated={int(treat.sum())} control={int((treat==0).sum())}")
print(f"caliper(0.2*SD of logit PS)={caliper:.4f} matched pairs={len(pairs)}")
print(f"{'covariate':<10}{'SMD before':>12}{'SMD matched':>13}{'SMD IPTW':>11}")
for c in X.columns:
v = X[c].to_numpy()
print(f"{c:<10}{smd(v, treat):>12.4f}"
f"{smd(v[m_idx], treat[m_idx]):>13.4f}"
f"{smd(v, treat, w_iptw):>11.4f}")
Python 3.12.13/numpy 2.5.4/pandas 3.0.6/scikit-learn 1.9.1 での出力は次のとおりです。
treated=256 control=1744
caliper(0.2*SD of logit PS)=0.1840 matched pairs=254
covariate SMD before SMD matched SMD IPTW
age 0.5559 -0.0034 -0.2227
severity 0.6174 -0.0008 -0.0886
female 0.1383 0.0315 -0.0592
処置群256件のうち254件がペアになり、マッチング前に0.5559だったageのSMDが-0.0034まで下がりました。同じデータを既定のL2正則化のまま推定すると、マッチング後のSMDはageが0.0293、femaleが-0.0709で、正則化を外した方がバランスが良くなります。もう一つ注目すべきは右端の列です。同じ傾向スコアを使っているのに、IPTWではageが-0.2227とマッチングより明らかに悪い。重み付けはマッチングの上位互換ではありません。
IPTW・安定化重み・overlap weightingの使い分け
IPTW(逆確率重み付け)の定義とATE・ATTの重み
IPTW(inverse probability of treatment weighting、IPWとも書きます)は、処置群に 1/e(x)、対照群に 1/(1-e(x)) の重みを与えます。処置を受けにくかったのに受けた個体を重く数えることで、母集団全体に処置した場合と全体に対照を適用した場合を比較する形にします。推定対象はATE(平均処置効果)です。
処置群における効果(ATT)を重み付けで推定するときは、処置群の重みを1、対照群の重みを e(x)/(1-e(x)) にします。Austin and Stuart(2015)はこの重みを「multiplying the conventional weights by e, so that treated subjects receive a weight of one」と説明しています。マッチングが推定するのも通常はATTです。「処置を受けた患者にとって有効だったか」を知りたいならATT、「母集団全体に適用したらどうなるか」ならATEを選びます。
極端な重みと、安定化重みが実際に直すもの
IPTWの弱点は、傾向スコアが0や1に近い個体で重みが発散することです。安定化重み(stabilized weight)は分子に処置割合を掛けてこれを抑えます。ただし、安定化がバランスを改善するという説明は誤りです。4種類の重みを並べると分かります。有効サンプルサイズ(ESS)はKishの式(重みの和の2乗÷重みの2乗の和)で、群ごとに計算しています。
p_t = treat.mean()
w_iptw = np.where(treat == 1, 1 / ps, 1 / (1 - ps))
w_stab = np.where(treat == 1, p_t / ps, (1 - p_t) / (1 - ps))
w_over = np.where(treat == 1, 1 - ps, ps)
w_att = np.where(treat == 1, 1.0, ps / (1 - ps))
def ess(w):
return w.sum() ** 2 / (w ** 2).sum()
weights = {"IPTW": w_iptw, "stabilized": w_stab, "overlap": w_over, "ATT": w_att}
print(f"{'':<10}" + "".join(f"{k:>12}" for k in weights))
print(f"{'max w':<10}" + "".join(f"{w.max():>12.2f}" for w in weights.values()))
print(f"{'ESS treat':<10}" + "".join(f"{ess(w[treat==1]):>12.1f}" for w in weights.values()))
print(f"{'ESS ctrl':<10}" + "".join(f"{ess(w[treat==0]):>12.1f}" for w in weights.values()))
for c in X.columns:
v = X[c].to_numpy()
print(f"{c:<10}" + "".join(f"{smd(v, treat, w):>12.4f}" for w in weights.values()))
IPTW stabilized overlap ATT
max w 120.68 15.45 0.99 1.70
ESS treat 92.3 92.3 249.5 256.0
ESS ctrl 1714.1 1714.1 1103.4 842.3
age -0.2227 -0.2227 0.0000 0.0207
severity -0.0886 -0.0886 -0.0000 -0.0149
female -0.0592 -0.0592 0.0000 -0.0043
最大重みは120.68から15.45へ下がりました。一方でSMDは3変数とも小数第4位まで完全に同じで、処置群のESSも92.3のままです。安定化は各群の重みを定数倍するだけで群内の相対的な重みは変わらないため、加重平均やESSを計算する段階で定数が約分されて消えます。安定化だけで分散推定が正しくなるわけではなく、効果推定法に応じたロバスト分散推定などが別途必要です。
処置群256件に対してIPTWのESSは92.3しかなく、少数の個体が推定値を支配しています。Austin and Stuart(2015)が2014年に出版されたIPTWの論文29本を調べたところ、重みの分布を何らかの形で確認していたのは3本(10.3%)でした。最大重みとESSは、SMDと一緒に必ず報告してください。
overlap weightingが持つ厳密バランス性
上の表の overlap 列は、処置群に 1-e(x)、対照群に e(x) という「反対の群に割り付けられる確率」を重みにする手法です。Li F, Morgan KL, Zaslavsky AM「Balancing Covariates via Propensity Score Weighting」JASA 2018;113(521):390-400 が提案しました。重みが0から1に収まるため発散しません(実測で最大0.99)。推定対象は、両群が最も重なる集団での効果(ATO)です。
同論文は、傾向スコアを正則化なしのロジスティック回帰(最尤推定)で推定すると、この重みはモデルに含めた共変量の加重平均を有限標本でも両群で一致させると示しています。分布全体やモデルに含めていない高次項の一致まで保証するものではありません。上の出力でSMDが0.0000になっているのはそのためです。ここで先ほどの正則化の話が効いてきます。既定のまま推定した場合と C=np.inf を指定した場合で比べます。
for label, kw in [("default (C=1.0, L2)", dict()),
("C=np.inf (no penalty)", dict(C=np.inf))]:
ps_ = LogisticRegression(max_iter=1000, **kw).fit(X, treat).predict_proba(X)[:, 1]
w = np.where(treat == 1, 1 - ps_, ps_)
print(label)
for c in X.columns:
print(f" overlap weight SMD {c:<9}{smd(X[c].to_numpy(), treat, w):>10.6f}")
default (C=1.0, L2)
overlap weight SMD age 0.000025
overlap weight SMD severity 0.003443
overlap weight SMD female 0.002910
C=np.inf (no penalty)
overlap weight SMD age 0.000002
overlap weight SMD severity -0.000001
overlap weight SMD female 0.000001
C=np.inf ではSMDの絶対値が10のマイナス6乗台まで下がりました。この残差は収束許容値(既定の tol=1e-4)によるもので、tol=1e-8 にすると10のマイナス12乗台まで小さくなります。論文が述べる厳密バランスが数値として再現されています。既定のL2正則化を残すとseverityは0.003443で、3桁悪化します。実害が出る大きさではありませんが、理論どおりに動いていないことの証拠です。
SMD(標準化平均差)の定義と計算方法
連続変数と二値変数の定義式
Austin PC「Balance diagnostics for comparing the distribution of baseline covariates between treatment groups in propensity-score matched samples」Statistics in Medicine 2009;28(25):3083-3107 が定義式を与えています。連続変数では、処置群と対照群の平均の差を、両群の分散の単純平均の平方根で割ります。
SMD = (処置群の平均 − 対照群の平均) ÷ √((処置群の分散 + 対照群の分散) / 2)
二値変数では平均の代わりに割合 p を使い、分母は p(1-p) の単純平均の平方根になります。分母が標本サイズで加重した合併分散ではなく単純平均である点が、実装上の注意点です。記事中のコードの smd 関数は、分散を自由度補正なし(重み付きなら重みの和で割る)で計算する簡略実装です。二値変数ではこれが p(1-p) と一致しますが、連続変数では標本分散と係数がわずかに違います。Austin and Stuart(2015)の補正係数つき重み付き分散で計算し直すと、調整前のageは0.5559から0.5552、IPTW後は-0.2227から-0.2220になりました。判定が変わる差ではありませんが、論文に載せる値は計算式まで書いてください。分散と標準偏差の計算そのものは標準偏差の求め方|計算表でたどる5ステップとエクセル関数・分散との違いで確認できます。
p値ではなくSMDを使う理由
同論文は「Unlike t-tests and other statistical tests of hypothesis, the standardized difference is not influenced by sample size.」と述べています。バランス評価にt検定やカイ二乗検定を使うと、標本サイズが大きいだけで有意差が出て、小さければ実質的な偏りがあっても有意になりません。マッチングでサンプルが減れば、それだけでp値は大きくなります。検定とp値の関係そのものは有意差検定とは?有意差・p値の意味と検定の種類・やり方をわかりやすく解説で整理しています。
重み付け後のSMD:調整前SDと重み付きSDによる数値の違い
IPTWやoverlap weightを使った後のSMDには、分母の定義が2通りあります。Austin and Stuart(2015)は平均と分散の両方を重み付きで計算する方法を示しています。一方、Rの標準であるcobaltは、cobalt 5.0.0のリファレンスで「The computed spread bal.tab() uses is always that of the full, unadjusted sample (i.e., before matching, weighting, or subclassification), as recommended by Stuart (2010).」と明記し、調整前の標本の標準偏差で割ります。分母には、ATTなら処置群のSD、ATEなら2群の分散の単純平均を自動で選びます。
同じIPTWの重みで、分母だけを入れ替えて計算した結果が次です。
def smd_fixed_sd(x, t, w):
# 分母を調整前(重みなし)の2群の分散の単純平均に固定する
sd0 = np.sqrt((x[t == 1].var(ddof=1) + x[t == 0].var(ddof=1)) / 2)
ma = np.average(x[t == 1], weights=w[t == 1])
mb = np.average(x[t == 0], weights=w[t == 0])
return (ma - mb) / sd0
print(f"{'covariate':<10}{'weighted SD':>13}{'unadjusted SD':>15}")
for c in X.columns:
v = X[c].to_numpy()
print(f"{c:<10}{smd(v, treat, w_iptw):>13.4f}{smd_fixed_sd(v, treat, w_iptw):>15.4f}")
covariate weighted SD unadjusted SD
age -0.2227 -0.2381
severity -0.0886 -0.0947
female -0.0592 -0.0587
ageは-0.2227と-0.2381で、0.015ずれました。重みで分散が変わるほど差は大きくなり、0.1の前後にいる共変量では判定が入れ替わり得ます。Pythonで自作した値とRのcobaltの値が合わないときは、まずこの分母を疑ってください。論文やレポートには、どちらの定義で計算したかを書く必要があります。
メタ分析のSMD(Hedges’ g)とは別物
「SMD」で検索すると、メタ分析の効果量としてのSMDが出てきます。Cochrane Handbook 第6章(6.5.1.2)は、Cochraneレビューで使うSMDを「the effect size known in social science as Hedges’ (adjusted) g」とし、分母には両群のアウトカムから推定した合併SDを使うと説明しています。
違いは3つあります。対象がアウトカム(効果の大きさ)か共変量(背景の偏り)か、分母が標本サイズで加重した合併SDか2群の分散の単純平均か、そして小標本の補正があるかです。同じ略語でも計算式と用途が異なるため、数値をそのまま同一視しないでください。特に、共変量バランスの0.1という目安を、介入効果の実質的な大きさの判定基準にはできません。メタ分析での使い方はメタ分析(メタアナリシス)とは?やり方5ステップと異質性・出版バイアスの判断基準で扱っています。
Love plotの描き方と読み方
Love plotとは何か、名前の由来
Love plotは、各共変量の絶対SMDを縦に並べ、調整前と調整後を同じ図の上に重ねるドットプロットです。共変量が20個あっても1枚で調整の効き方が読めるため、論文の図として定着しました。
名前の由来は Austin(2009)に記録されています。同論文は「graphical displays that first appear to have been developed by Love and colleagues」と述べ、参考文献[24]として Ahmed A, Perry GJ, Fleg JL, Love TE, Goff Jr DC, Kitzman DW, American Heart Journal 2006;152:956-966 を挙げています。この Love TE が Thomas E. Love で、彼の名がそのままプロット名になりました。
Python(matplotlib)でLove plotを描くコード
Pythonには cobalt に相当する定番パッケージがないため、matplotlib で描くのが確実です。上のコードの続きで、調整前・マッチング後・overlap weight後の絶対SMDを1枚に重ねます。
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
covs = list(X.columns)
before = [abs(smd(X[c].to_numpy(), treat)) for c in covs]
matched = [abs(smd(X[c].to_numpy()[m_idx], treat[m_idx])) for c in covs]
overlap = [abs(smd(X[c].to_numpy(), treat, w_over)) for c in covs]
order = np.argsort(before)
y = np.arange(len(covs))
fig, ax = plt.subplots(figsize=(6, 2.8))
ax.scatter(np.array(before)[order], y, marker="o", label="before")
ax.scatter(np.array(matched)[order], y, marker="^", label="matched")
ax.scatter(np.array(overlap)[order], y, marker="s", label="overlap weight")
ax.axvline(0.1, linestyle="--", color="gray")
ax.set_yticks(y, [covs[i] for i in order])
ax.set_xlabel("absolute standardized mean difference")
ax.legend(loc="lower right")
fig.tight_layout()
fig.savefig("love_plot.png", dpi=150)
print("saved love_plot.png")
出力される love_plot.png では、調整前(丸)のseverityとageが0.6前後の右端にあり、マッチング後(三角)とoverlap weight後(四角)は0.1の破線より左の0付近に集まります。femaleだけマッチング後が0.03ほど右に残ります。読み方は単純で、調整後の点がすべて破線の左にあるか、そして調整前に右にあった共変量ほど大きく左へ動いているかを見ます。共変量は調整前のSMDの大きさ順に並べると、偏りの大きかった変数が上に来て読みやすくなります。
R(cobalt)の love.plot() で描くコード
Rでは cobalt の love.plot() が標準です。MatchItの結果をそのまま渡せます。次はMatchItに同梱の lalonde データで、ロジット変換した傾向スコアの0.2SDをキャリパーにした最近傍マッチングを行う例です。引数は MatchIt 4.8.1 と cobalt 5.0.0 のリファレンスマニュアルに沿っています。
library(MatchIt)
library(cobalt)
data("lalonde", package = "MatchIt")
m.out <- matchit(treat ~ age + educ + race + married + nodegree + re74 + re75,
data = lalonde, method = "nearest", distance = "glm",
caliper = 0.2, link = "linear.logit")
bal.tab(m.out, thresholds = c(m = .1), un = TRUE)
love.plot(m.out, thresholds = c(m = .1), abs = TRUE,
var.order = "unadjusted", stars = "raw")
link = "linear.logit" でロジット変換した傾向スコアを距離にし、caliper = 0.2 は既定の std.caliper = TRUE によって標準偏差単位で解釈されます。love.plot() の既定の統計量について、リファレンスは「For binary and multi-category treatments, "mean.diffs" (i.e., mean differences) is the default.」と書いています。連続値の処置では相関係数に切り替わるので、何が描かれているかを確認してください。cobaltは既定で二値変数を標準化せず割合の差のまま描くため、stars = "raw" でそれらの行に印(*)を付けて区別します。
「SMD 0.1未満なら合格」の出典と限界
0.1という数字は広く使われていますが、Austin(2009)の記述は慎重です。「While there is no clear consensus on this issue, some researchers have proposed that a standardized difference of 0.1 (10 per cent) denotes meaningful imbalance in the baseline covariate」とあり、参考文献[23]の Normand SLT et al., Journal of Clinical Epidemiology 2001;54:387-398 による提案として紹介されています。合意された基準ではありません。
同論文は0.1の実質的な意味も示しています。2群が等分散の正規分布に従い大きさも同じという条件のもとで、SMDが0.10のときCohenの非重複率(U1)は7.7%です。この条件では、0.1は2群の分布がほぼ重なっている水準です。分布が歪んでいる共変量では平均の差だけで重なりは判断できないため、分散比や分布の重ね描きも併せて見ます。
小標本の注意もあります。マッチング後のペアが100組程度しかない場合、傾向スコアモデルが正しく特定されていてもSMDは広い範囲でばらつきます。数十ペアの解析で「SMDが0.12だから失格」と切るのは統計的に無理があります。判断は、その共変量がアウトカムにとってどれだけ重要かと合わせて行います。
バランスが取れないときの手順
SMDが下がらないときは、手法を変える前にモデルを疑います。順番は次のとおりです。
- 偏りの残った共変量の二乗項・交互作用項を傾向スコアモデルに追加する
- 両群の傾向スコア分布を重ねて描き、重なり(common support)の外にいる個体を確認する
- 重なりの外を除外するか、キャリパーを狭める(0.2から0.1へ)。狭めた後にSMDが悪化したら戻す
- それでも極端な重みが残る場合は推定対象を再検討し、重なり集団の効果(ATO)が研究目的に合うならoverlap weightingを検討する
共通サポートを外れた個体を除外すると、推定できる集団が狭まります。「残った集団は誰か」を論文やレポートに書かないと、結果の適用範囲が読者に伝わりません。
R・Python・SPSSの対応パッケージと実行手順
RとPythonのパッケージと版
| 環境 | 推定 | マッチング | 重み付け | Love plot |
|---|---|---|---|---|
| R | glm | MatchIt 4.8.1 | WeightIt 2.1.0 | cobalt 5.0.0 |
| Python | scikit-learn 1.9.1 | psmpy 0.3.16 | causallib 0.10.0 | matplotlib |
| SPSS | 拡張ダイアログ | FUZZY拡張 | 重み変数を自作 | グラフで自作 |
版は2026年10月17日時点のCRANとPyPIの公開版です。Rが最も整っています。MatchIt でマッチング、WeightIt で重み付け、cobalt でバランス評価という分担で、3つとも同じ作者(Noah Greifer)が保守し、オブジェクトをそのまま受け渡せます。MatchIt 4.8.1(2026年9月26日公開)では、サブクラス分類後に summary(subclass = TRUE) でサブクラス別の集計を表示するとき、サブクラス内のSMDが全標本ではなくサブクラス内のSDで標準化されていた不具合が修正されました。4.8.0以前でサブクラスごとのバランス表を作っていた場合は、再計算した方が安全です。
Pythonには決定版がありません。psmpy 0.3.16 はマッチングとプロットを一通り備えていますが、キャリパーの指定方法がRのMatchItと異なります。causalml 0.18.0 と dowhy 0.14 は因果推論のフレームワーク寄りで、傾向スコアは構成要素の一つという位置づけです。本記事の自作コードでは、キャリパーやSMDの定義をコード上で確認できます。既存パッケージを使う場合も、既定値と計算式を確認し、使用した設定を論文に記載してください。なお scikit-learn 1.9系は Python 3.11以上が必要です。
SPSSで傾向スコアマッチングを実行する手順
SPSS Statisticsでは[データ]メニューの傾向スコアマッチングのダイアログを使います(英語版の Propensity Score Matching。IBMサポートの技術文書ではSPSS 27の日本語名を「傾向スコアによる一致」と表記)。中身はIBMが公開している拡張ダイアログで、同梱のヘルプには「This command requires the Python Essentials」とあり、FUZZY拡張コマンドの1.3.0以上も必要です。手順は次のとおりです。
- [拡張機能]→[拡張機能ハブ]から FUZZY 拡張コマンドを導入または更新する(インストールに管理者権限での起動が必要な場合がある)
- [データ]→ 傾向スコアマッチングのダイアログを開き、群指標(処置=1、対照=0の変数)と予測変数(共変量)を指定する
- 傾向スコアを保存する変数名、許容差(Match Tolerance)、ケースID、マッチIDの変数名、出力データセット名を指定する
- [オプション]で非復元抽出を選び、ケース順の無作為化(Randomize case order)にチェックを入れて実行する
- 出力されたデータセットで、マッチング前後の共変量の平均と分散からSMDを計算する
注意点は2つあります。1つ目は許容差の尺度です。ヘルプは許容差を「the absolute value of the difference in the propensity scores」と定義しており、傾向スコアそのものの差です。Austin(2009)の「ロジット変換した傾向スコアの標準偏差×0.2」とは尺度が違うので、同じ0.2を入れても同じキャリパーにはなりません。2つ目はケースの順番です。無作為化しないとファイルの並び順にマッチングが進み、ヘルプも「If the case records have a systematic order, processing in file order could introduce some bias.」と注意しています。
FUZZYが入っていないと、IBMの技術文書にあるとおり「This procedure requires the FUZZY extension command which is not installed.」というエラーで止まります。IBMコミュニティでの回答(2025年7月)によると、このダイアログは1対1しか作れず、1対Nにしたいときは同じ[データ]メニューの Case Control Matching を使います。どちらもマッチング本体は FUZZY に依存し、FUZZYは2023年にマッチングのアルゴリズムが更新されたため、古い版が入っている環境では拡張機能ハブから更新するよう案内されています。
SPSSには重み付けやLove plotを作る専用の機能がないため、IPTWは傾向スコアを保存したあと計算式で重み変数を作り、Love plotはグラフ機能で自作します。重み付けやバランス評価まで行うなら、RかPythonに移る方が早く済みます。
傾向スコア分析を「疑似RCT」と呼べない理由と、論文に書くべきこと
個体の完全一致と共変量分布のバランスの違い
傾向スコアマッチングを疑似RCTと呼ぶ説明は日本語の解説記事でも見かけますが、この呼び方には正面からの批判があります。三澤晴雄・三澤朱実「傾向スコア分析は疑似RCTではない」(第32回バイオメディカル・ファジィ・システム学会年次大会講演論文集、2019年11月、山梨)は次のように述べています。
「傾向スコアは2個体が同じ状態であるための必要条件を十分条件であるかのように扱っている。また傾向スコアは被験者の状態を直接表すものではない。」
同じ状態なら傾向スコアは同じになります。しかし傾向スコアが同じでも、状態が同じとは限りません。年齢が高く重症度が低い患者と、年齢が低く重症度が高い患者が、同じスコアでペアになることがあるからです。
ただし、これは個々のペアの話です。Rosenbaum and Rubin(1983)が示したのは、傾向スコアが同じ個体の集まりでは、モデルに入れた共変量の分布が処置群と対照群で等しくなるという群単位の性質(バランシングスコア)で、ペアごとの背景の一致は保証していません。ペア単位の比較から個人の効果を語らないことが、この指摘から引き出せる実務上の教訓です。
RHC研究の例と、予後因子の選定・残余交絡
同論文はConnorsらの右心カテーテル検査(RHC)研究を例に、この問題を具体化しています。「傾向スコアを用いると2群のRHC必要度は合わせているが重症度は合わせていないという大変おかしなことになる。『RHCは必要ないが重症』という患者は少なくない。」
傾向スコアがそろえるのは「処置を受ける確率」であって「アウトカムのリスク」ではありません。前の節のとおり、モデルに入れた共変量は群単位でそろうので、処置確率とリスクが違うこと自体は比較を否定する理由になりません。問題は、重症度のようにアウトカムに強く効く変数がモデルに入っていない、あるいは測定されていない場合で、そのときはSMDが全て0.1未満でも結論は歪みます。著者らの結論は明快です。「傾向スコアは無作為割つけに基づくRCTの代替や疑似RCTではなく、処置決定者(医師)の判断まで含めた治療の総合的評価と考えるべきである。」
傾向スコアを使うべきでない場面
次の条件では、傾向スコア分析を採用しない方が正しい判断です。
- 研究目的に合うRCTを倫理的・実務的に実施できる場合。三澤らは「処置と対照のどちらが良いか不明の場合はRCTを実施すべきである」と明言しています
- 処置の決定要因が記録されていない場合。カルテに残らない判断要因が処置とアウトカムの双方に関係し、測定済み共変量でも調整できないなら、無交絡性を仮定する根拠が不足します
- 両群の重なりがほとんどない場合。正値性が満たされず、傾向スコアの分布が分離しているなら、比較可能な個体が存在しないという事実の方が結論です
論文・レポートに必ず書く項目
三澤らは傾向スコア研究の実務上の欠落も指摘しています。「RCTの論文には『処置と対照は無作為に割り付けた』と書かなければならないのと同様、傾向スコアの論文では処置を選択した方針を明記すべきであろう。(現状では多くの論文に書かれていない)」
この指摘を踏まえると、報告すべき項目は次のようになります。処置の選択方針、傾向スコアモデルに入れた共変量の一覧と選定理由、推定に使ったソフトと正則化の有無、マッチングの方法とキャリパー幅、重み付けなら最大重みとESS、マッチング前後の各共変量のSMD(分母の定義を明記)を載せた表とLove plot、そして除外された件数と除外後の集団の特徴です。除外件数を書かない報告は、適用範囲が読者に判定できません。因果関係を主張する分析全般の落とし穴は因果分析とは?相関関係との違い・手法の使い分けと落とし穴を解説にまとめています。
よくある質問
傾向スコアと回帰分析(多変量解析)はどちらを使うべきですか?
アウトカムの発生件数が少ないときは傾向スコアが有利です。回帰では、アウトカムのイベント数に対して共変量が多すぎるとモデルが不安定になります。傾向スコアは処置の有無を目的変数にするため、処置を受けた人数が十分あれば共変量を多く入れられます。イベントが潤沢にあり共変量も少ないなら、回帰で問題ありません。両方を実施して推定値が大きく食い違うなら、それ自体がモデルを見直すサインです。
共変量と交絡因子の違いは何ですか?
共変量は解析で考慮する背景変数の総称、交絡因子はそのうち処置とアウトカムの両方に影響し、両者の関係を歪める変数です。すべての交絡因子は共変量ですが、逆は成り立ちません。アウトカムにだけ影響する変数(予後因子)は交絡因子ではありませんが、傾向スコアモデルに入れると推定の精度が上がります。処置にだけ影響してアウトカムに関係しない変数は、入れると分散が増えるため入れません。
傾向スコアマッチングとIPTWはどちらを選ぶべきですか?
推定したい対象で決めます。処置を受けた集団での効果(ATT)を知りたいならマッチングかATT重み、母集団全体に適用した場合の効果(ATE)ならIPTWです。実務上の分岐点は重みの大きさで、本記事の実測では最大重みが120.68、処置群のESSは256件中92.3でした。この水準では少数の個体が推定値を支配します。重みの分布、群別ESS、共変量バランス、推定値の感度を併せて確認してください。overlap weightingや対象者の除外を検討する場合は、変更後の推定対象が研究目的に合うかも確認します。
SMDが0.1未満なら必ずバランスが取れていると言えますか?
言えません。0.1はNormandら(2001)の提案であり、Austin(2009)は「no clear consensus」と明記しています。マッチング後のペアが100組程度の小標本では、モデルが正しくてもSMDは広くばらつきます。重み付け後は分母の定義によって値自体が変わります。またSMDは平均の差しか見ないため、分散比や分布の重なりも併せて確認してください。
傾向スコアは日本語で何と呼びますか?
傾向スコアが定訳で、傾向値やプロペンシティスコアと表記されることもあります。いずれも propensity score の訳語で、指すものは同じです。分析全体は傾向スコア分析または傾向スコア解析と呼びます。英語論文では propensity score matching を PSM、inverse probability of treatment weighting を IPTW と略記します。