EconMLには SLearner、TLearner、XLearner、DRLearner……と手法が並んでいます。「結局どれを使えばいいのか」。いちばん多い質問です。
今回は事例を変えて、BtoB法人営業の「重点フォロー」を扱います。フォローを実施した企業が全体の8%しかないデータで、5つの手法を同じ物差しで並べます。
結論を先に3行で書きます。
- 同じデータ・同じ機械学習モデルで5手法を並べると、平均の効果は5つとも正解の近く(47〜56万円、正解47万円)です。差が出るのは「誰に効くか」の精度と、信頼区間を出せるかどうかです。
- 処置群8%で最も崩れたのは
TLearner(正解との相関0.72)。DRLearnerは0.93、LinearDMLは0.99でした。処置群を50%に増やしてもTLearnerは直りません。弱点は「2つのモデルの誤差が引き算で残る」ことです。 - 迷ったら
DRLearnerかLinearDML。ある4つの問いに答えれば決まります。
この記事を読み終えると、次のことができるようになります。
- 5つの手法を同じデータに当てて、同じ物差しで比べられる
- 各手法が「何をしているか」を1行で言える
- 処置群が少ないときに崩れる手法を避けられる
- 4つの問いで、自分の案件に合う手法を選べる
事例とデータ:重点フォローは契約金額を増やしたのか
法人営業6,000社のうち476社(8%)に、担当者が訪問頻度を上げる「重点フォロー」を実施しました。実施した企業の今年度契約金額は未実施の1.9倍。
ただし現場は大口・A担当の企業にフォローを集中させています。第4回で見た交絡そのものです。
データは btob_follow.csv。真の効果を仕込んであります。
以下からダウンロードできます。
| 列 | 意味 | 役割 |
|---|---|---|
size_class |
従業員規模(1〜4) | 効果修飾 X |
industry |
業種(製造/卸売/サービス/IT) | 効果修飾 X |
years |
取引年数 | 効果修飾 X |
rep_rank |
担当者ランク(1〜3) | 交絡 W |
last_year_sales |
前年度の契約金額(万円) | 交絡 W |
follow |
重点フォロー実施(0/1) | 施策 T |
sales |
今年度の契約金額(万円) | 成果 Y |
_true_cate |
仕込んだ真の効果(万円) | 答え合わせ用 |
まず、動かしてみる:5手法を同じ条件で並べる
中で使う機械学習モデルは、5手法とも同じ勾配ブースティングにそろえます。手法ごとに引数の名前が違うので、そこだけ注意してください。
以下、コードです。
import numpy as np
import pandas as pd
from sklearn.ensemble import (
GradientBoostingRegressor,
GradientBoostingClassifier
)
from econml.metalearners import (
SLearner,
TLearner,
XLearner
)
from econml.dr import DRLearner
from econml.dml import LinearDML
# データの読み込み
df = pd.read_csv("btob_follow.csv")
# 変数の定義
Y = df["sales"]
T = df["follow"]
X = pd.DataFrame({
"size_class":
df["size_class"],
"industry":
df["industry"].map({"製造":0,"卸売":1,"サービス":2,"IT":3}),
"years":
df["years"],
"rep_rank":
df["rep_rank"],
"last_year_sales":
df["last_year_sales"],
})
# フォロー実施率と、単純比較の差を計算
naive = Y[T == 1].mean() - Y[T == 0].mean()
print(f"フォロー実施率: {T.mean():.1%} 単純比較の差: {naive:.0f}万円")
# 5手法とも、中の機械学習モデルは同じ勾配ブースティングにそろえる
reg = lambda: GradientBoostingRegressor(random_state=0)
clf = lambda: GradientBoostingClassifier(random_state=0)
models = {
"SLearner":
SLearner(overall_model=reg()),
"TLearner":
TLearner(models=reg()),
"XLearner":
XLearner(
models=reg(),
propensity_model=clf()
),
"DRLearner":
DRLearner(
model_regression=reg(),
model_propensity=clf(),
cv=5, random_state=0
),
"LinearDML":
LinearDML(
model_y=reg(),
model_t=clf(),
discrete_treatment=True,
cv=5, random_state=0
),
}
# 各手法のATEを推定
for name, est in models.items():
est.fit(Y, T, X=X)
df[name] = est.effect(X)
print(f"{name:<10} 推定ATE {df[name].mean():5.1f}万円")
以下、実行結果です。
フォロー実施率: 7.9% 単純比較の差: 435万円 SLearner 推定ATE 46.7万円 TLearner 推定ATE 56.0万円 XLearner 推定ATE 53.1万円 DRLearner 推定ATE 51.7万円 LinearDML 推定ATE 51.5万円
単純比較の435万円が、どの手法でも47〜56万円まで落ちました。平均の効果だけなら、5手法の差は小さい。
1つ気づいてほしいのは、X に交絡(rep_rank、last_year_sales)も入れていることです。メタラーナー3種の fit には W の引数がありません。交絡も X に入れるのが、第4回の仕分けをメタラーナーで守る方法です。
答え合わせ:差が出るのは「誰に効くか」
6,000社分の効果を正解と突き合わせます。区間を出せるのは DRLearner と LinearDML だけです。
以下、コードです。
# 正解のATE
truth = df["_true_cate"]
# 正解のATEとの比較
print(
f"正解のATE {truth.mean():.1f}万円 "
f"正解のばらつき {truth.std():.1f}万円"
)
# 各手法の正解との相関と誤差
for name in models:
corr = df[name].corr(truth)
rmse = np.sqrt(((df[name] - truth) ** 2).mean())
print(
f"{name:<10} 正解との相関 {corr:.3f} 誤差 {rmse:5.1f}万円 "
f"ばらつき {df[name].std():5.1f}万円"
)
# 各手法のATEの95%区間
for name in ["DRLearner", "LinearDML"]:
lo, hi = models[name].ate_interval(X, alpha=0.05)
print(f"{name:<10} 推定ATEの95%区間 [{lo:.1f}, {hi:.1f}]")
以下、実行結果です。
正解のATE 47.4万円 正解のばらつき 33.8万円 SLearner 正解との相関 0.848 誤差 18.0万円 ばらつき 28.2万円 TLearner 正解との相関 0.717 誤差 31.6万円 ばらつき 43.5万円 XLearner 正解との相関 0.794 誤差 26.2万円 ばらつき 42.1万円 DRLearner 正解との相関 0.927 誤差 17.3万円 ばらつき 42.4万円 LinearDML 正解との相関 0.986 誤差 7.0万円 ばらつき 32.7万円 DRLearner 推定ATEの95%区間 [24.2, 79.2] LinearDML 推定ATEの95%区間 [43.3, 59.7]
順位は TLearner < XLearner < SLearner < DRLearner < LinearDML。SLearner は相関こそ悪くないものの、効果のばらつきが28万円と正解の34万円より小さく、効く企業と効かない企業の差を縮めています。DRLearner の区間は幅55万円と広く、処置群476社という薄さを正直に映しています。
処置群を増やすと、どの手法が直るか
同じ企業属性・同じ真の効果のまま、フォロー実施率だけを50%にしたデータでも動かしました。
XLearner は0.79から0.95へ、DRLearner は0.93から0.99へ大きく改善します。ところが TLearner は0.72から0.71で、処置群が6倍に増えても直りません。弱点は処置比率そのものではなく、次に説明する仕組みにあります。
裏で何が起きているのか:5手法を1行ずつ
SLearnerは、施策 T を特徴量の1つにして1本のモデルを作り、「T=1 の予測 − T=0 の予測」を効果とします。安定ですが、T はたくさんある特徴量の1つにすぎず、影響が薄まって効果が縮みます。
TLearnerは、処置群と対照群で別々のモデルを作り、予測の差を効果とします。2本のモデルはそれぞれ別の場所で近似を誤り、その誤差が引き算でそのまま残ります。試しに中のモデルを線形回帰にすると相関は0.98に上がります。このデータは売上も効果も直線だからで、メタラーナーの精度は、中のモデルの精度そのものです。
XLearnerは、T の2本のモデルに加えて「もし逆の群だったら」の代理値を作り、傾向スコア(第2回)で重み付けして合成します。処置群が少ないとき、対照群の情報を借りられます。
DRLearnerは、成果の回帰モデルと傾向スコアの2本を組み合わせ、どちらか一方が当たっていれば正しい答えに近づく設計です。二重頑健(Doubly Robust)の名前はここから来ています。たとえるなら命綱を2本かけている状態です。
LinearDMLは第2回のとおり、効果が X に対して直線と仮定する代わりに区間が狭い。仮定が正しいこのデータでは最強でした。
DRLearner と LinearDML が強い理由は共通しています。第2回の交差適合と直交化によって、中のモデルの誤差が効果の推定に入りにくい。メタラーナーにはこの仕掛けがありません。
手法を選ぶ4つの問い
ここまでを1枚にしたのが以下の図です。上から順に答えてください。
連続量の施策(訪問回数や値引き額)はメタラーナーでは扱えず、DML系になります(第10回)。迷ったら DRLearner か LinearDML から始めれば、大きく外しません。
ありがちな失敗
1つ目:手法を変えて、都合のいい数字を採用する。 今回の推定ATEは46.7〜56.0万円の幅がありました。5つ動かして最大の56.0万円を報告すれば、それは分析ではなく選択です。手法は動かす前に決める。複数出すなら全部見せる。これが第9回のレビューで最初に問われる点です。
2つ目:メタラーナーに効果修飾変数だけを渡す。 第3回・第5回の流れで X を「効く変数」だけにすると、交絡が消えます。
以下、コードです。
# 効果修飾変数だけ(交絡を外す)
X_only = X[["size_class", "industry", "years"]]
# 交絡を外したATEの推定
for name in ["SLearner", "TLearner", "XLearner"]:
models[name].fit(Y, T, X=X_only)
print(
f"{name:<10} 交絡を外した推定ATE "
f"{models[name].effect(X_only).mean():6.1f}万円"
)
以下、実行結果です。
SLearner 交絡を外した推定ATE 142.0万円 TLearner 交絡を外した推定ATE 136.0万円 XLearner 交絡を外した推定ATE 133.7万円
正解47万円の3倍です。エラーは出ません。DRLearner と LinearDML は X=修飾変数, W=交絡 と分けて渡しても51.7万円のままでした。メタラーナーは、X に交絡も入れる。これだけは忘れないでください。
まとめ
今回のポイントです。
- 平均の効果は5手法とも正解の近く。差が出るのは「誰に効くか」の精度と、区間を出せるか
TLearnerは2本のモデルの誤差が引き算で残る。処置群を増やしても直らない。メタラーナーの精度は中のモデルの精度DRLearnerとLinearDMLは交差適合と直交化で中のモデルの誤差に強く、区間も出る- 4つの問い:2値か/区間が要るか/処置群が少ないか/効果の形が単純か。迷ったら
DRLearnerかLinearDML - メタラーナーは W を分けられない。交絡も X に入れる。手法は動かす前に決める

