Hayami Kaito Web
~/tech

因果推論のしくみ:相関と因果の違い・A/B テスト・傾向スコア

published

「広告を見た人は、見なかった人より 3 倍以上よく買っている。だから広告の効果は大きい」。この結論は正しいでしょうか。もし、もともと買い物に関心の高い人ほど広告が表示されやすい仕組みだったら、広告がなくても、その人たちはよく買っていたはずです。

機械学習のモデルは、データの中の 相関を見つけるのが得意です。しかし、「広告を出すべきか」「この薬を使うべきか」のように、何かを変えたらどうなるかを判断するには、相関ではなく 因果を知る必要があります。そのための考え方と方法が 因果推論です。この記事では、相関と因果がずれる仕組みと、それを正す方法を、小さな計算例で確かめます。

結論を先に書くと、次のとおりです。

  • 原因と結果の両方に影響する要因(交絡因子)があると、単純な比較は効果を大きく見誤る。集計の仕方で結論が逆転することもある(シンプソンのパラドックス)
  • 因果の効果は、「同じ人が、した場合と、しなかった場合の結果の差」で定義される。片方しか観察できないので、集団の平均で推定する
  • いちばん確実なのは、ランダムに分けて比べること(A/B テスト・ランダム化比較試験)。交絡因子が両方のグループに均等に分かれる
  • ランダムに分けられない観察データでは、傾向スコアで重み付けして、交絡を取り除く。ただし、観察できていない交絡因子は取り除けない

説明の途中に出てくる計算例は、Python と NumPy で実際に動かした結果です。

環境

計算例を動かした環境です。

項目バージョン
Python3.13
NumPy2.5

相関と因果がずれるとき

シンプソンのパラドックス

まず、有名な実際のデータを見てみます。1986 年に発表された、腎臓結石の 2 つの治療法 A・B の成功率の比較です。結石の大きさで、患者を 2 つのグループに分けて集計しています。

import numpy as np

# 1. シンプソンのパラドックス:腎臓結石の 2 つの治療法(Charig ら 1986 年のデータ)
#            小さい結石(成功, 人数)  大きい結石(成功, 人数)
data = {"A": [(81, 87), (192, 263)], "B": [(234, 270), (55, 80)]}
for t, groups in data.items():
    rates = [round(s / n, 3) for s, n in groups]
    total = sum(s for s, _ in groups) / sum(n for _, n in groups)
    print(t, rates, round(total, 3))

実行結果:

A [0.931, 0.73] 0.78
B [0.867, 0.688] 0.826

左から、治療法、小さい結石と大きい結石それぞれでの成功率、全体の成功率です。

  • 小さい結石では、A の成功率 93.1% が B の 86.7% より高い
  • 大きい結石でも、A の成功率 73.0% が B の 68.8% より高い
  • ところが全体では、A の 78.0% より B の 82.6% のほうが高い

どちらのグループでも A が良いのに、合わせると B が良くなりました。これを シンプソンのパラドックスと呼びます。理由は、治療しにくい大きい結石の患者の多く(263 人)が A を受け、B を受けたのは主に治療しやすい小さい結石の患者(270 人)だったからです。結石の大きさが、どちらの治療を受けるかと成功するかの両方に影響していました。このような要因を 交絡因子と呼びます。

この場合、治療法の良し悪しを比べるには、結石の大きさをそろえたグループごとの比較を見るべきです。ただし、いつもグループごとに見ればよいわけではなく、どの変数でそろえるべきかは、何が何の原因になっているかという構造で決まります。

因果の効果の定義

因果推論では、因果の効果を次のように考えます(潜在的結果の考え方)。

  • ある人が広告を 見た場合の結果と、見なかった場合の結果の 2 つを考える
  • その差が、その人にとっての広告の効果
  • 全員について平均したものが 平均処置効果(ATE)

問題は、1 人の人について、2 つの結果の 片方しか観察できないことです。広告を見た人の「見なかった場合の結果」は、誰にも分かりません。これを 因果推論の根本問題と呼びます。そこで、「見た人たち」と「見なかった人たち」の平均を比べて推定するのですが、2 つのグループが、広告以外の点で似ていなければ、その差は広告の効果になりません。

交絡で効果を見誤る

広告と購入の例

10 万人のデータを作ります。各人には「買い物への関心の高さ」と「年齢」があり、関心が高いほど購入しやすく、広告も表示されやすいとします。広告の本当の効果は、購入の確率を少しだけ(ロジットで 0.3)上げることです。データを作った式を知っているので、本当の平均処置効果も計算できます。

import numpy as np

# 2. 広告の効果:買い物に関心の高い人ほど広告が表示されやすく、もともと買いやすい
rng = np.random.default_rng(0)
n = 100000
interest = rng.standard_normal(n)                      # 関心の高さ(交絡因子)
age = rng.uniform(20, 70, n)
def buy_prob(ad):
    # 本当の広告の効果は、購入の確率を(ロジットで)0.3 だけ上げる
    z = -2.5 + 1.2 * interest + 0.01 * (age - 45) + 0.3 * ad
    return 1 / (1 + np.exp(-z))
true_effect = (buy_prob(1) - buy_prob(0)).mean()        # 全員に見せた場合と、誰にも見せない場合の差
print("本当の効果", round(float(true_effect), 4))

# 観察データ:関心の高い人ほど広告が表示される
p_ad = 1 / (1 + np.exp(-(1.5 * interest + 0.02 * (age - 45))))
ad = rng.random(n) < p_ad
bought = rng.random(n) < buy_prob(ad)
naive = bought[ad].mean() - bought[~ad].mean()
print("単純な比較", round(float(bought[ad].mean()), 4), round(float(bought[~ad].mean()), 4), round(float(naive), 4))

実行結果:

本当の効果 0.0292
単純な比較 0.2143 0.0605 0.1538

本当の効果は、購入の確率を 2.92 ポイント上げることです。一方、広告を見た人の購入率は 21.43%、見なかった人は 6.05% で、単純に比べると 15.38 ポイントの差があり、本当の効果の 5 倍以上に見えてしまいました。広告を見た人は、もともと関心の高い人が多いので、広告がなくてもよく買っていたのです。

この観察データで「広告を見たかどうか」から購入を予測する機械学習のモデルを作れば、広告はとても良い予測の手がかりになります。予測としては正しいのですが、広告を増やせば購入がそれだけ増えるという意味ではありません。予測と因果の違いは、ここにあります。

A/B テスト:ランダムに分けて比べる

コイン投げで決めれば交絡は消える

交絡を取り除く、いちばん確実な方法は、広告を見せるかどうかを、コイン投げのようにランダムに決めることです。ランダムに分ければ、関心の高い人も低い人も、両方のグループに同じ割合で入ります。観察できる特徴だけでなく、観察できていない特徴も含めて、2 つのグループは平均的にそろうのです。医療では ランダム化比較試験(RCT)、ウェブサービスでは A/B テストと呼ばれます。

上のコードの続きで、同じ 10 万人を、広告を見せるグループと見せないグループにランダムに分けます。差の推定には、標本の大きさによるばらつきがあるので、95% 信頼区間も計算します。

# 3. A/B テスト:広告を見せるかどうかをコイン投げで決める
ad_rct = rng.random(n) < 0.5
bought_rct = rng.random(n) < buy_prob(ad_rct)
p1, p0 = bought_rct[ad_rct].mean(), bought_rct[~ad_rct].mean()
se = np.sqrt(p1 * (1 - p1) / ad_rct.sum() + p0 * (1 - p0) / (~ad_rct).sum())
print("A/B テスト", round(float(p1 - p0), 4), (round(float(p1 - p0 - 1.96 * se), 4), round(float(p1 - p0 + 1.96 * se), 4)))

実行結果:

A/B テスト 0.0257 (0.0215, 0.0299)

推定した効果は 2.57 ポイントで、95% 信頼区間は 2.15〜2.99 ポイントです。本当の効果 2.92 ポイントは、この区間に入っています。単純な比較の 15.38 ポイントとは大違いで、ランダムに分けるだけで、交絡がほぼ取り除かれました。

A/B テストでは、効果が小さいほど、見分けるのに多くの人数が必要になります。信頼区間の幅は人数の平方根に反比例するので、幅を半分にするには 4 倍の人数が必要です。また、何十もの指標を同時に調べると、どれかがたまたま差があるように見えてしまうので、何を主な指標にするかを事前に決めておくことが大切です。

傾向スコア:観察データから交絡を取り除く

ランダムに分けられないとき

いつも A/B テストができるとは限りません。過去のデータしかない場合や、喫煙の害を調べるために喫煙をランダムに割り当てるわけにはいかない場合のように、倫理的にできないこともあります。このような 観察データから因果の効果を推定するには、交絡因子を計算でそろえる必要があります。

1983 年に提案された 傾向スコアは、「その人の特徴から見て、その人が広告を見る確率」です。傾向スコアが同じ人どうしなら、広告を見たかどうかは、コイン投げで決まったのと同じように扱えます(交絡因子がすべて観察できていれば)。傾向スコアの使い方には、次のようなものがあります。

  • 逆確率重み付け(IPW):広告を見た人は「傾向スコアの逆数」、見なかった人は「1 ÷(1 − 傾向スコア)」で重み付けして平均する。見そうにないのに見た人は、似た人たちの代表として重く数える。こうすると、2 つのグループの特徴の分布が、全体の分布にそろう
  • 層別:傾向スコアが近い人どうしのグループに分け、グループの中で比べてから平均する。シンプソンのパラドックスで、結石の大きさで分けて比べたのと同じ考え方

観察データで推定し直す

上のコードの続きで、観察データから、関心と年齢を使ってロジスティック回帰で傾向スコアを推定し、2 つの方法で効果を推定します。比べるために、関心が観察できず、年齢だけで傾向スコアを推定した場合も計算します。

# 4. 傾向スコア:その人に広告が表示される確率を、観察できる特徴から推定する
def fit_logistic(F, t, steps=2000, lr=0.5):
    F = np.c_[np.ones(len(F)), (F - F.mean(axis=0)) / F.std(axis=0)]
    w = np.zeros(F.shape[1])
    for _ in range(steps):
        p = 1 / (1 + np.exp(-F @ w))
        w -= lr * F.T @ (p - t) / len(t)
    return 1 / (1 + np.exp(-F @ w))

def ipw(e):
    # 表示されにくい人が表示されたら重く、表示されやすい人が表示されなかったら重く数える
    return np.mean(ad * bought / e) - np.mean((~ad) * bought / (1 - e))

def stratify(e, k=10):
    # 傾向スコアが近い人どうしの 10 個のグループに分け、グループの中で比べてから平均する
    edges = np.quantile(e, np.linspace(0, 1, k + 1))
    g = np.clip(np.searchsorted(edges, e, side="right") - 1, 0, k - 1)
    return sum((g == j).mean() * (bought[(g == j) & ad].mean() - bought[(g == j) & ~ad].mean()) for j in range(k))

e = fit_logistic(np.c_[interest, age], ad)
print("傾向スコア(関心と年齢)", round(float(e.min()), 3), round(float(e.max()), 3), round(float(ipw(e)), 4), round(float(stratify(e)), 4))
e_age = fit_logistic(age[:, None], ad)
print("傾向スコア(年齢だけ)", round(float(ipw(e_age)), 4), round(float(stratify(e_age)), 4))

実行結果:

傾向スコア(関心と年齢) 0.002 0.999 0.0215 0.0346
傾向スコア(年齢だけ) 0.1519 0.152

1 行目の左の 2 つは、傾向スコアの最小値と最大値です。右の 2 つは、逆確率重み付けで 2.15 ポイント、層別で 3.46 ポイントと推定されました。単純な比較の 15.38 ポイントから、本当の効果 2.92 ポイントの近くまで戻っています。

ただし、A/B テストほど正確ではありません。理由は 2 つあります。

  • 傾向スコアが 0 や 1 に近い人がいる。この例では、関心がとても高い人はほぼ確実に広告を見ており、傾向スコアは 0.999 です。この人たちの「見なかった場合」を表す人はほとんどいないので、ごく少数の人に巨大な重み(1 ÷ 0.001 = 1,000)が付き、推定が不安定になります。比べる相手がいる範囲(重なり)が狭いと、観察データからの推定は難しくなります
  • 層別では、同じ層の中にも傾向スコアの違いが残る。10 個の層では分け方が粗く、交絡が少し残っています

2 行目は、関心を観察できず、年齢だけで傾向スコアを推定した場合です。逆確率重み付けでも層別でも約 15.2 ポイントと、単純な比較とほとんど変わりませんでした。観察できていない交絡因子は、傾向スコアでは取り除けないのです。観察データからの因果推論は、「交絡因子をすべて観察できている」という、データだけからは確かめられない前提に頼っています。ここが、ランダムに分ける A/B テストとの決定的な違いです。

そのほかの方法

ランダムに分けられず、交絡因子もすべては観察できないときのために、次のような方法も使われます。

  • 差の差分法:制度が変わった地域と変わらなかった地域で、変わる前と後の 変化の差を比べる。もともとの地域の違いを打ち消せる
  • 操作変数法:結果には直接影響せず、原因にだけ影響する要因(くじで決まった兵役など)を手がかりにする
  • 回帰不連続デザイン:「試験の点数が 60 点以上なら奨学金」のような境目のすぐ上とすぐ下の人を比べる。境目の近くでは、ほぼランダムに分かれたとみなせる
  • 因果グラフ:何が何の原因かを矢印の図で表し、どの変数でそろえれば交絡を取り除けるか(逆に、そろえてはいけない変数はどれか)を判断する

機械学習と組み合わせる研究も進んでいます。傾向スコアや結果の予測に機械学習のモデルを使う ダブル機械学習(2018 年)や、人によって違う効果を推定する 因果フォレスト(2018 年)などです。「広告を見せたら買う人」だけを選んで広告を出すような使い方(アップリフトモデリング)にもつながります。推薦システムのしくみ で触れた A/B テストや、判断を説明するしくみ で注意した「説明は因果ではない」という点も、この記事の内容とつながっています。

因果推論の手法の流れ

この記事で紹介した手法を年代順に並べます。

年手法・出来事ポイント
1923ネイマンの潜在的結果農業試験で、処置した場合としなかった場合の結果を考える
1935フィッシャーの『実験計画法』ランダムに割り当てることで、比較を公平にする
1951シンプソンのパラドックス集計の仕方で、関係の向きが逆転することを示す
1952ホルビッツ・トンプソン推定量選ばれる確率の逆数で重み付けして、全体を推定する
1974ルービンの因果モデル潜在的結果で、観察データの因果効果を定義する
1983傾向スコア処置を受ける確率で、交絡因子をまとめてそろえる
1986腎臓結石の治療の比較シンプソンのパラドックスの代表的な実例
1994最低賃金の差の差分法による研究制度の変化の前後と地域の違いで効果を推定する
2000パールの『Causality』因果グラフで、どの変数でそろえるべきかを判断する
2018ダブル機械学習・因果フォレスト機械学習を使って、因果効果やその人ごとの違いを推定する
2021ノーベル経済学賞(カード・アングリスト・インベンス)自然実験を使った因果推論の方法への貢献

まとめ

  • 原因と結果の両方に影響する交絡因子があると、単純な比較は効果を大きく見誤る。集計の仕方で結論が逆転することもある
  • 因果の効果は、同じ人がした場合としなかった場合の結果の差で定義される。片方しか観察できないので、集団の平均で推定する
  • 予測に役立つ特徴が、変えたら結果が変わる原因とは限らない
  • ランダムに分ける A/B テストは、観察できない特徴も含めて交絡を取り除く、いちばん確実な方法
  • 観察データでは、傾向スコアによる重み付けや層別で交絡を減らせる。ただし、傾向スコアが 0 や 1 に近いと不安定になり、観察できていない交絡因子は取り除けない
  • 差の差分法・操作変数法・回帰不連続デザイン・因果グラフなど、状況に合わせた方法がある

参考

  • J. Neyman, "On the Application of Probability Theory to Agricultural Experiments", 1923(英訳: Statistical Science 1990)
  • R. A. Fisher, "The Design of Experiments", Oliver and Boyd 1935
  • E. H. Simpson, "The Interpretation of Interaction in Contingency Tables", Journal of the Royal Statistical Society Series B 1951
  • D. G. Horvitz and D. J. Thompson, "A Generalization of Sampling Without Replacement From a Finite Universe", Journal of the American Statistical Association 1952
  • D. B. Rubin, "Estimating Causal Effects of Treatments in Randomized and Nonrandomized Studies", Journal of Educational Psychology 1974
  • P. R. Rosenbaum and D. B. Rubin, "The central role of the propensity score in observational studies for causal effects", Biometrika 1983
  • C. R. Charig et al., "Comparison of treatment of renal calculi by open surgery, percutaneous nephrolithotomy, and extracorporeal shockwave lithotripsy", British Medical Journal 1986
  • D. Card and A. B. Krueger, "Minimum Wages and Employment: A Case Study of the Fast-Food Industry in New Jersey and Pennsylvania", American Economic Review 1994
  • J. Pearl, "Causality: Models, Reasoning, and Inference", Cambridge University Press 2000
  • V. Chernozhukov et al., "Double/debiased machine learning for treatment and structural parameters", The Econometrics Journal 2018(arXiv:1608.00060)
  • S. Wager and S. Athey, "Estimation and Inference of Heterogeneous Treatment Effects using Random Forests", Journal of the American Statistical Association 2018(arXiv:1510.04342)