EconMLで実践する ビジネス因果推論
(第5回)
効果の差を生む変数はどれか
(CausalForestDMLに選ばせる)

EconMLで実践する ビジネス因果推論(第5回)効果の差を生む変数はどれか(CausalForestDMLに選ばせる)

第3回で「誰に効くか」を見るとき、X には購入頻度と休眠日数を入れました。なぜその2つだったのか。正直に言えば、「効きそうだから」という勘です。

実務では、その勘が外れます。今回は勘に頼らず、施策より前に決まっている変数を全部入れて、どれが効いているかを森に選ばせます。

結論を先に3行で書きます。

  1. CausalForestDML に5変数を渡すと、feature_importances_ が 購入頻度と休眠日数で全体の87% を占めると返してきました。第3回で人が選んだ2つと一致します。
  2. 森(CausalForestDML)は効果の形を仮定しません。直線(LinearDML)の答えと重なれば「直線で十分だった」と分かり、ずれれば直線では捉えられない形があると分かります。
  3. ただし森(CausalForestDML)は万能ではなく、信頼区間が広く、データの端では平均のほうへ縮みます。形が単純と分かったら、直線(LinearDML)に戻すのが実務の順番です。

この記事を読み終えると、次のことができるようになります。

  • CausalForestDML で、X を絞らずに効果を推定できる
  • feature_importances_ で「効果の差を生んでいる変数」を見つけられる
  • 通常のランダムフォレストと因果フォレストの違いを、人に説明できる
  • 森(CausalForestDML)と直線(LinearDML)を、どういう順番で使い分けるかが分かる

データは前回までと同じ ec_coupon.csv です。以下からダウンロードできます。

ec_coupon.csv

まず、動かしてみる:X を絞らずに全部入れる

第4回のフローチャートで「入れる」側に残った5列を、X と W に仕分けせず、まとめて X に渡します。

変数を入れる・入れない・XかWかを決めるフローチャート施策より前に決まっているか、施策と成果の両方に影響するか、効果の違いをその変数で見たいかの3つの問いを順にかけて、入れない・W・X・入れなくてよい、に仕分ける。この変数、入れる?入れない?X?W?候補の列を1つずつ、上から順に3つの問いにかける施策より前に決まっている変数か?はいいいえ入れない施策の後に動く変数。効果を消してしまう施策と成果の両方に影響するか?いいえはいW に入れる(交絡変数)入れ忘れると過大に。違いも見たいなら X 可効果の違いを、その変数で見たいか?いいえはいX に入れる(効果修飾変数)「誰に効くか」の切り口になる入れなくてよい迷ったら W に入れても、平均は壊れない順番が大事。1つ目の問いで「いいえ」なら、そこで終わり

以下、コードです。

import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingClassifier
from econml.dml import CausalForestDML

df = pd.read_csv("ec_coupon.csv")
Y = df["spend_30d"]
T = df["coupon"]

# 施策より前に決まっている5列を、仕分けせずに全部 X に入れる
X = pd.DataFrame({
    "past_freq":
        df["past_freq"],
    "days_since_last":
        df["days_since_last"],
    "member_rank":
        df["member_rank"].map({"ブロンズ": 0, "シルバー": 1, "ゴールド": 2}),
    "channel":
        df["channel"].map({"自然検索": 0, "広告": 1, "メルマガ": 2}),
    "tenure_months":
        df["tenure_months"],
})

est = CausalForestDML(
    model_y=GradientBoostingRegressor(random_state=0),
    model_t=GradientBoostingClassifier(random_state=0),
    discrete_treatment=True, n_estimators=1000, min_samples_leaf=100,
    cv=5, random_state=0
)
est.fit(Y, T, X=X)

lo, hi = est.ate_interval(X, alpha=0.05)
print(f"推定ATE: {est.ate(X):.0f}円  95%区間 [{lo:.0f}, {hi:.0f}]")

 

以下、実行結果です。

推定ATE: 381円  95%区間 [217, 545]

 

正解413円を区間に含んでいます。ただし区間の幅は328円。第2回の LinearDML は幅143円でしたから、2倍以上広い。理由は後半で扱います。

読みどころは2つ。n_estimators=1000 は木の本数。min_samples_leaf=100 は「葉(枝の末端)に最低100人は残す」指定で、小さくすると細かい違いを拾える代わりに区間が広がります。

 

効果の差を生んでいる変数はどれか

森(CausalForestDML)が学習の中で「どの変数で枝を分けると効果の差が大きくなったか」を数えたものが feature_importances_ です。

以下、コードです。

imp = pd.Series(
    est.feature_importances_, 
    index=X.columns
).sort_values(ascending=False)

print(imp.round(3))

 

以下、実行結果です。

past_freq          0.484
days_since_last    0.382
tenure_months      0.086
member_rank        0.029
channel            0.019
dtype: float64
因果フォレストの変数重要度購入回数0.484、休眠日数0.382で上位2つが全体の87%。在籍月数0.086、会員ランク0.029、流入経路0.019。効果の差を生んでいる変数はどれか:森が選んだ結果CausalForestDML の feature_importances_。施策より前に決まっている5変数を全部 X に入れて学習過去1年の購入回数past_freq0.484休眠日数days_since_last0.382在籍月数tenure_months0.086会員ランクmember_rank0.029流入経路channel0.019上2つで全体の87%第3回で人が「効きそう」と選んだ2変数を、森はデータだけから選び直した

購入頻度が0.484、休眠日数が0.382。この2つで全体の87%です。在籍月数がわずかに続き、会員ランクと流入経路はほぼゼロ。

第3回で「効きそう」と勘で選んだ2変数を、森はデータだけから選び直しました。逆に言えば、会員ランクは交絡変数としては重要でも(第4回)、「ランクによって効き方が変わる」わけではない、ということも分かります。

この図が本当に役に立つのは、勘が外れたときです。ノーマークだった列が上位に来たら、そこから施策の設計が変わります。

 

裏で何が起きているのか:同じ「木」でも分け方が違う

「ランダムフォレストなら知っている」という方ほど混乱するところです。名前は似ていますが、枝を分ける基準が違います。

通常のランダムフォレストと因果フォレストの分割基準の違い通常のランダムフォレストは購入金額が近い人をまとめる。因果フォレストはクーポンの効果の差が最も大きくなるように分ける。分割用と推定用のデータを分けるhonest splittingで区間が信用できる。同じ「木」でも、枝を分ける基準が違う通常のランダムフォレストY(購入金額)を当てにいくこの線で左右に分ける購入金額が近い人どうしを、同じ枝にまとめるクーポンの有無は見ていない購入金額 ↑高い人たち低い人たち因果フォレストT(クーポン)の効果を当てにいくこの線で左右に分ける効果の差が最も大きくなるように分ける左:効きが大きい人 / 右:効きが小さい人この高さの差=効果差が大きい差が小さい配布なし配布あり分割に使うデータと、葉で効果を計算するデータを分ける(honest)から、区間が信用できる

通常のランダムフォレストは、購入金額 Y が近い人どうしを同じ枝にまとめます。

「たくさん買う人」と「あまり買わない人」を分けていく。クーポンの有無は、数ある特徴量の1つにすぎません。

因果フォレストは、クーポンの効果が近い人どうしを同じ枝にまとめます。

枝の中で「配布あり」と「配布なし」の差を計算し、その差が左右で最も大きく違うところで分ける。だから葉に残るのは「効き方が似た人たち」で、葉の中の配布あり/なしの差が、そのままその人たちの効果になります。

たとえるなら、通常の森は「テストの点が近い子を集める」、因果の森は「同じ薬の効き方が近い子を集める」。集め方が違うので、できあがる枝もまったく違います。

因果フォレストは、データを2つに分け、片方で枝の分け方を決め、もう片方で葉の中の効果を計算します。

同じデータで両方やると「効果が大きく見える分け方」を探した上でそれを測ることになり、過大に出る。第2回の交差適合と同じ発想で、CausalForestDML では既定(honest=True)です。

 

森(CausalForestDML)の答えと直線(LinearDML)の答えを、正解と並べる

森は効果の形を仮定しません。第2回・第3回の LinearDML は「効果は X に対して直線的に変わる」と仮定していました。この2つを、正解と一緒に並べてみます。

以下、コードです。

df["cate_forest"] = est.effect(X)
freq = pd.cut(df["past_freq"], [0, 2, 4, 6, 9, 100],
              labels=["1-2回", "3-4回", "5-6回", "7-9回", "10回以上"])
print(df.groupby(freq)["cate_forest"].mean().round(0))

 

以下、実行結果です。

past_freq
1-2回     508.0
3-4回     425.0
5-6回     355.0
7-9回     124.0
10回以上     78.0
Name: cate_forest, dtype: float64
購入頻度別の平均効果:森・直線・正解の比較購入頻度3-4回から7-9回では3本がほぼ重なる。両端の1-2回(正解676円に対し森508円)と10回以上(正解-258円に対し森+78円)では、森だけが平均のほうへ縮んでいる。直線は端まで正解に沿う。森と直線の答えを、正解と並べる:購入頻度別の平均効果同じ人たちの効果を、CausalForestDML・LinearDML・仕込んだ正解の3通りで集計-2000+200+400+600+8001-2回1,567人3-4回1,596人5-6回921人7-9回692人10回以上224人森 +78直線 -219正解 -258森 +508正解 +676正解LinearDML(直線)CausalForestDML(森)縦軸:平均の効果(円)真ん中の3〜9回では3本が重なる。両端では、森だけが平均のほうへ縮んでいる

真ん中の3〜9回では、森・直線・正解の3本がほぼ重なります。ここで重なるということは、「効果が購入頻度に対して直線的に変わる」という第3回の仮定が、このデータでは正しかったということです。森は仮定なしでその形にたどり着いたので、直線の答えを裏付ける証拠になります。

いっぽう両端では、森だけがずれています。1-2回では正解676円に対して森は508円。10回以上では正解−258円に対して森は+78円で、マイナスの効果を捉えきれていません。

森は「近くの人の平均」で効果を出すので、データの端では近くの人がすべて内側にいて、平均が内側へ引き寄せられます。直線は端まで伸ばせますが、森は伸ばせません。

 

では、森(CausalForestDML)と直線(LinearDML)、どちらを使うか?

CausalForestDML(森) LinearDML(直線)
効果の形 仮定しない X に対して直線と仮定
X の選び方 全部入れて、森に選ばせる 人が選ぶ
信頼区間の幅(このデータ) 広い(ATE で328円) 狭い(143円)
データの端 平均のほうへ縮む 端まで伸ばせる(仮定が正しければ)
向いている場面 効果の形も、効く変数も分からない最初 形が単純と分かったあと

実務の順番はこうなります。最初に森(CausalForestDML)で当たりをつけ、効く変数と効果の形を見る。形が単純なら直線(LinearDML)に戻して区間を狭める。直線(LinearDML)で始めて仮定のずれに気づかないより、森(CausalForestDML)で始めて確かめるほうが安全です。

 

ありがちな失敗:重要度を「効果の大きさ」と読む

feature_importances_ は、予測モデルの特徴量重要度と同じ顔をしているので、同じように読みたくなります。ここに罠があります。

因果フォレストの重要度は、「その変数で分けると、効果の“違い”がどれだけ説明できるか」です。効果の大きさではありません。

会員ランクの重要度は0.029でした。これは「ランクによって効き方が変わらない」という意味であって、「ランクは効果に無関係」ではありません。第4回で見たとおり、ランクは交絡変数として必須です。重要度が低いからと W からも外すと、第4回の①(932円)に戻ります。

もう1つ。施策後の変数を入れてもエラーにはなりません。サイト訪問回数を X に足すと推定ATEは103円まで落ちます。森(CausalForestDML)であっても、変数の仕分けは第4回のルールのままです。

 

まとめ

今回のポイントです。

  • CausalForestDML は X を絞らずに全部渡せる。feature_importances_ が効果の差を生む変数を教えてくれる
  • このデータでは購入頻度と休眠日数で87%。第3回の勘と一致した
  • 通常の森(CausalForestDML)は「Y が近い人」を、因果の森は「効果が近い人」を集める。honest splitting で区間が信用できる
  • 森(CausalForestDML)は形を仮定しないが、区間が広く、端では平均に縮む。森(CausalForestDML)で当たりをつけ、形が単純なら直線(LinearDML)に戻す
  • 重要度は「効果の違いを説明する力」。低くても交絡なら W に入れる
Screenshot

【月1 特定テーマ講座(10月)】
Python で学ぶ 明日からできる「欠損値処理」超入門

【開催日時】 全2回(土)2026/10/17,10/31(13:30〜18:00)
【受講形式】 当日Zoom( or 復習用に後日動画視聴)
【参加費用】 2万2千円(税込み)/人